From 1cd512913e4d4ad1fb43d4b6e3d7971bb6c67528 Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Sun, 27 Apr 2014 17:55:49 -0400 Subject: Organize builtin into subdirectories --- builtin/game/auth.lua | 188 ++++++++++ builtin/game/chatcommands.lua | 725 ++++++++++++++++++++++++++++++++++++ builtin/game/deprecated.lua | 53 +++ builtin/game/detached_inventory.lua | 19 + builtin/game/falling.lua | 217 +++++++++++ builtin/game/features.lua | 29 ++ builtin/game/forceloading.lua | 79 ++++ builtin/game/init.lua | 23 ++ builtin/game/item.lua | 589 +++++++++++++++++++++++++++++ builtin/game/item_entity.lua | 123 ++++++ builtin/game/misc.lua | 134 +++++++ builtin/game/privileges.lua | 53 +++ builtin/game/register.lua | 410 ++++++++++++++++++++ builtin/game/statbars.lua | 160 ++++++++ builtin/game/static_spawn.lua | 33 ++ builtin/game/voxelarea.lua | 103 +++++ 16 files changed, 2938 insertions(+) create mode 100644 builtin/game/auth.lua create mode 100644 builtin/game/chatcommands.lua create mode 100644 builtin/game/deprecated.lua create mode 100644 builtin/game/detached_inventory.lua create mode 100644 builtin/game/falling.lua create mode 100644 builtin/game/features.lua create mode 100644 builtin/game/forceloading.lua create mode 100644 builtin/game/init.lua create mode 100644 builtin/game/item.lua create mode 100644 builtin/game/item_entity.lua create mode 100644 builtin/game/misc.lua create mode 100644 builtin/game/privileges.lua create mode 100644 builtin/game/register.lua create mode 100644 builtin/game/statbars.lua create mode 100644 builtin/game/static_spawn.lua create mode 100644 builtin/game/voxelarea.lua (limited to 'builtin/game') diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua new file mode 100644 index 000000000..b6cca609c --- /dev/null +++ b/builtin/game/auth.lua @@ -0,0 +1,188 @@ +-- Minetest: builtin/auth.lua + +-- +-- Authentication handler +-- + +function minetest.string_to_privs(str, delim) + assert(type(str) == "string") + delim = delim or ',' + privs = {} + for _, priv in pairs(string.split(str, delim)) do + privs[priv:trim()] = true + end + return privs +end + +function minetest.privs_to_string(privs, delim) + assert(type(privs) == "table") + delim = delim or ',' + list = {} + for priv, bool in pairs(privs) do + if bool then + table.insert(list, priv) + end + end + return table.concat(list, delim) +end + +assert(minetest.string_to_privs("a,b").b == true) +assert(minetest.privs_to_string({a=true,b=true}) == "a,b") + +minetest.auth_file_path = minetest.get_worldpath().."/auth.txt" +minetest.auth_table = {} + +local function read_auth_file() + local newtable = {} + local file, errmsg = io.open(minetest.auth_file_path, 'rb') + if not file then + minetest.log("info", minetest.auth_file_path.." could not be opened for reading ("..errmsg.."); assuming new world") + return + end + for line in file:lines() do + if line ~= "" then + local name, password, privilegestring = string.match(line, "([^:]*):([^:]*):([^:]*)") + if not name or not password or not privilegestring then + error("Invalid line in auth.txt: "..dump(line)) + end + local privileges = minetest.string_to_privs(privilegestring) + newtable[name] = {password=password, privileges=privileges} + end + end + io.close(file) + minetest.auth_table = newtable + minetest.notify_authentication_modified() +end + +local function save_auth_file() + local newtable = {} + -- Check table for validness before attempting to save + for name, stuff in pairs(minetest.auth_table) do + assert(type(name) == "string") + assert(name ~= "") + assert(type(stuff) == "table") + assert(type(stuff.password) == "string") + assert(type(stuff.privileges) == "table") + end + local file, errmsg = io.open(minetest.auth_file_path, 'w+b') + if not file then + error(minetest.auth_file_path.." could not be opened for writing: "..errmsg) + end + for name, stuff in pairs(minetest.auth_table) do + local privstring = minetest.privs_to_string(stuff.privileges) + file:write(name..":"..stuff.password..":"..privstring..'\n') + end + io.close(file) +end + +read_auth_file() + +minetest.builtin_auth_handler = { + get_auth = function(name) + assert(type(name) == "string") + -- Figure out what password to use for a new player (singleplayer + -- always has an empty password, otherwise use default, which is + -- usually empty too) + local new_password_hash = "" + -- If not in authentication table, return nil + if not minetest.auth_table[name] then + return nil + end + -- Figure out what privileges the player should have. + -- Take a copy of the privilege table + local privileges = {} + for priv, _ in pairs(minetest.auth_table[name].privileges) do + privileges[priv] = true + end + -- If singleplayer, give all privileges except those marked as give_to_singleplayer = false + if minetest.is_singleplayer() then + for priv, def in pairs(minetest.registered_privileges) do + if def.give_to_singleplayer then + privileges[priv] = true + end + end + -- For the admin, give everything + elseif name == minetest.setting_get("name") then + for priv, def in pairs(minetest.registered_privileges) do + privileges[priv] = true + end + end + -- All done + return { + password = minetest.auth_table[name].password, + privileges = privileges, + } + end, + create_auth = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + minetest.log('info', "Built-in authentication handler adding player '"..name.."'") + minetest.auth_table[name] = { + password = password, + privileges = minetest.string_to_privs(minetest.setting_get("default_privs")), + } + save_auth_file() + end, + set_password = function(name, password) + assert(type(name) == "string") + assert(type(password) == "string") + if not minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, password) + else + minetest.log('info', "Built-in authentication handler setting password of player '"..name.."'") + minetest.auth_table[name].password = password + save_auth_file() + end + return true + end, + set_privileges = function(name, privileges) + assert(type(name) == "string") + assert(type(privileges) == "table") + if not minetest.auth_table[name] then + minetest.builtin_auth_handler.create_auth(name, minetest.get_password_hash(name, minetest.setting_get("default_password"))) + end + minetest.auth_table[name].privileges = privileges + minetest.notify_authentication_modified(name) + save_auth_file() + end, + reload = function() + read_auth_file() + return true + end, +} + +function minetest.register_authentication_handler(handler) + if minetest.registered_auth_handler then + error("Add-on authentication handler already registered by "..minetest.registered_auth_handler_modname) + end + minetest.registered_auth_handler = handler + minetest.registered_auth_handler_modname = minetest.get_current_modname() +end + +function minetest.get_auth_handler() + if minetest.registered_auth_handler then + return minetest.registered_auth_handler + end + return minetest.builtin_auth_handler +end + +function minetest.set_player_password(name, password) + if minetest.get_auth_handler().set_password then + minetest.get_auth_handler().set_password(name, password) + end +end + +function minetest.set_player_privs(name, privs) + if minetest.get_auth_handler().set_privileges then + minetest.get_auth_handler().set_privileges(name, privs) + end +end + +function minetest.auth_reload() + if minetest.get_auth_handler().reload then + return minetest.get_auth_handler().reload() + end + return false +end + + diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua new file mode 100644 index 000000000..f8df83d8e --- /dev/null +++ b/builtin/game/chatcommands.lua @@ -0,0 +1,725 @@ +-- Minetest: builtin/chatcommands.lua + +-- +-- Chat command handler +-- + +minetest.chatcommands = {} +function minetest.register_chatcommand(cmd, def) + def = def or {} + def.params = def.params or "" + def.description = def.description or "" + def.privs = def.privs or {} + minetest.chatcommands[cmd] = def +end + +minetest.register_on_chat_message(function(name, message) + local cmd, param = string.match(message, "^/([^ ]+) *(.*)") + if not param then + param = "" + end + local cmd_def = minetest.chatcommands[cmd] + if cmd_def then + local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs) + if has_privs then + cmd_def.func(name, param) + else + minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")") + end + return true -- handled chat message + end + return false +end) + +-- +-- Chat commands +-- +minetest.register_chatcommand("me", { + params = "", + description = "chat action (eg. /me orders a pizza)", + privs = {shout=true}, + func = function(name, param) + minetest.chat_send_all("* " .. name .. " " .. param) + end, +}) + +minetest.register_chatcommand("help", { + privs = {}, + params = "(nothing)/all/privs/", + description = "Get help for commands or list privileges", + func = function(name, param) + local format_help_line = function(cmd, def) + local msg = "/"..cmd + if def.params and def.params ~= "" then msg = msg .. " " .. def.params end + if def.description and def.description ~= "" then msg = msg .. ": " .. def.description end + return msg + end + if param == "" then + local msg = "" + cmds = {} + for cmd, def in pairs(minetest.chatcommands) do + if minetest.check_player_privs(name, def.privs) then + table.insert(cmds, cmd) + end + end + minetest.chat_send_player(name, "Available commands: "..table.concat(cmds, " ")) + minetest.chat_send_player(name, "Use '/help ' to get more information, or '/help all' to list everything.") + elseif param == "all" then + minetest.chat_send_player(name, "Available commands:") + for cmd, def in pairs(minetest.chatcommands) do + if minetest.check_player_privs(name, def.privs) then + minetest.chat_send_player(name, format_help_line(cmd, def)) + end + end + elseif param == "privs" then + minetest.chat_send_player(name, "Available privileges:") + for priv, def in pairs(minetest.registered_privileges) do + minetest.chat_send_player(name, priv..": "..def.description) + end + else + local cmd = param + def = minetest.chatcommands[cmd] + if not def then + minetest.chat_send_player(name, "Command not available: "..cmd) + else + minetest.chat_send_player(name, format_help_line(cmd, def)) + end + end + end, +}) +minetest.register_chatcommand("privs", { + params = "", + description = "print out privileges of player", + func = function(name, param) + if param == "" then + param = name + else + --[[if not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Privileges of "..param.." are hidden from you.") + return + end]] + end + minetest.chat_send_player(name, "Privileges of "..param..": "..minetest.privs_to_string(minetest.get_player_privs(param), ' ')) + end, +}) +minetest.register_chatcommand("grant", { + params = " |all", + description = "Give privilege to player", + privs = {}, + func = function(name, param) + if not minetest.check_player_privs(name, {privs=true}) and + not minetest.check_player_privs(name, {basic_privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + local grantname, grantprivstr = string.match(param, "([^ ]+) (.+)") + if not grantname or not grantprivstr then + minetest.chat_send_player(name, "Invalid parameters (see /help grant)") + return + elseif not minetest.auth_table[grantname] then + minetest.chat_send_player(name, "Player "..grantname.." does not exist.") + return + end + local grantprivs = minetest.string_to_privs(grantprivstr) + if grantprivstr == "all" then + grantprivs = minetest.registered_privileges + end + local privs = minetest.get_player_privs(grantname) + local privs_known = true + for priv, _ in pairs(grantprivs) do + if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + if not minetest.registered_privileges[priv] then + minetest.chat_send_player(name, "Unknown privilege: "..priv) + privs_known = false + end + privs[priv] = true + end + if not privs_known then + return + end + minetest.set_player_privs(grantname, privs) + minetest.log(name..' granted ('..minetest.privs_to_string(grantprivs, ', ')..') privileges to '..grantname) + minetest.chat_send_player(name, "Privileges of "..grantname..": "..minetest.privs_to_string(minetest.get_player_privs(grantname), ' ')) + if grantname ~= name then + minetest.chat_send_player(grantname, name.." granted you privileges: "..minetest.privs_to_string(grantprivs, ' ')) + end + end, +}) +minetest.register_chatcommand("revoke", { + params = " |all", + description = "Remove privilege from player", + privs = {}, + func = function(name, param) + if not minetest.check_player_privs(name, {privs=true}) and + not minetest.check_player_privs(name, {basic_privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + local revokename, revokeprivstr = string.match(param, "([^ ]+) (.+)") + if not revokename or not revokeprivstr then + minetest.chat_send_player(name, "Invalid parameters (see /help revoke)") + return + elseif not minetest.auth_table[revokename] then + minetest.chat_send_player(name, "Player "..revokename.." does not exist.") + return + end + local revokeprivs = minetest.string_to_privs(revokeprivstr) + local privs = minetest.get_player_privs(revokename) + for priv, _ in pairs(revokeprivs) do + if priv ~= "interact" and priv ~= "shout" and priv ~= "interact_extra" and not minetest.check_player_privs(name, {privs=true}) then + minetest.chat_send_player(name, "Your privileges are insufficient.") + return + end + end + if revokeprivstr == "all" then + privs = {} + else + for priv, _ in pairs(revokeprivs) do + privs[priv] = nil + end + end + minetest.set_player_privs(revokename, privs) + minetest.log(name..' revoked ('..minetest.privs_to_string(revokeprivs, ', ')..') privileges from '..revokename) + minetest.chat_send_player(name, "Privileges of "..revokename..": "..minetest.privs_to_string(minetest.get_player_privs(revokename), ' ')) + if revokename ~= name then + minetest.chat_send_player(revokename, name.." revoked privileges from you: "..minetest.privs_to_string(revokeprivs, ' ')) + end + end, +}) +minetest.register_chatcommand("setpassword", { + params = " ", + description = "set given password", + privs = {password=true}, + func = function(name, param) + local toname, raw_password = string.match(param, "^([^ ]+) +(.+)$") + if not toname then + toname = string.match(param, "^([^ ]+) *$") + raw_password = nil + end + if not toname then + minetest.chat_send_player(name, "Name field required") + return + end + local actstr = "?" + if not raw_password then + minetest.set_player_password(toname, "") + actstr = "cleared" + else + minetest.set_player_password(toname, minetest.get_password_hash(toname, raw_password)) + actstr = "set" + end + minetest.chat_send_player(name, "Password of player \""..toname.."\" "..actstr) + if toname ~= name then + minetest.chat_send_player(toname, "Your password was "..actstr.." by "..name) + end + end, +}) +minetest.register_chatcommand("clearpassword", { + params = "", + description = "set empty password", + privs = {password=true}, + func = function(name, param) + toname = param + if toname == "" then + minetest.chat_send_player(name, "Name field required") + return + end + minetest.set_player_password(toname, '') + minetest.chat_send_player(name, "Password of player \""..toname.."\" cleared") + end, +}) + +minetest.register_chatcommand("auth_reload", { + params = "", + description = "reload authentication data", + privs = {server=true}, + func = function(name, param) + local done = minetest.auth_reload() + if done then + minetest.chat_send_player(name, "Done.") + else + minetest.chat_send_player(name, "Failed.") + end + end, +}) + +minetest.register_chatcommand("teleport", { + params = ",, | | ,, | ", + description = "teleport to given position", + privs = {teleport=true}, + func = function(name, param) + -- Returns (pos, true) if found, otherwise (pos, false) + local function find_free_position_near(pos) + local tries = { + {x=1,y=0,z=0}, + {x=-1,y=0,z=0}, + {x=0,y=0,z=1}, + {x=0,y=0,z=-1}, + } + for _, d in ipairs(tries) do + local p = {x = pos.x+d.x, y = pos.y+d.y, z = pos.z+d.z} + local n = minetest.get_node_or_nil(p) + if n and n.name then + local def = minetest.registered_nodes[n.name] + if def and not def.walkable then + return p, true + end + end + end + return pos, false + end + + local teleportee = nil + local p = {} + p.x, p.y, p.z = string.match(param, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + teleportee = minetest.get_player_by_name(name) + if teleportee and p.x and p.y and p.z then + minetest.chat_send_player(name, "Teleporting to ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + + local teleportee = nil + local p = nil + local target_name = nil + target_name = string.match(param, "^([^ ]+)$") + teleportee = minetest.get_player_by_name(name) + if target_name then + local target = minetest.get_player_by_name(target_name) + if target then + p = target:getpos() + end + end + if teleportee and p then + p = find_free_position_near(p) + minetest.chat_send_player(name, "Teleporting to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + + if minetest.check_player_privs(name, {bring=true}) then + local teleportee = nil + local p = {} + local teleportee_name = nil + teleportee_name, p.x, p.y, p.z = string.match(param, "^([^ ]+) +([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + if teleportee_name then + teleportee = minetest.get_player_by_name(teleportee_name) + end + if teleportee and p.x and p.y and p.z then + minetest.chat_send_player(name, "Teleporting "..teleportee_name.." to ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + + local teleportee = nil + local p = nil + local teleportee_name = nil + local target_name = nil + teleportee_name, target_name = string.match(param, "^([^ ]+) +([^ ]+)$") + if teleportee_name then + teleportee = minetest.get_player_by_name(teleportee_name) + end + if target_name then + local target = minetest.get_player_by_name(target_name) + if target then + p = target:getpos() + end + end + if teleportee and p then + p = find_free_position_near(p) + minetest.chat_send_player(name, "Teleporting "..teleportee_name.." to "..target_name.." at ("..p.x..", "..p.y..", "..p.z..")") + teleportee:setpos(p) + return + end + end + + minetest.chat_send_player(name, "Invalid parameters (\""..param.."\") or player not found (see /help teleport)") + return + end, +}) + +minetest.register_chatcommand("set", { + params = "[-n] | ", + description = "set or read server configuration setting", + privs = {server=true}, + func = function(name, param) + local arg, setname, setvalue = string.match(param, "(-[n]) ([^ ]+) (.+)") + if arg and arg == "-n" and setname and setvalue then + minetest.setting_set(setname, setvalue) + minetest.chat_send_player(name, setname.." = "..setvalue) + return + end + local setname, setvalue = string.match(param, "([^ ]+) (.+)") + if setname and setvalue then + if not minetest.setting_get(setname) then + minetest.chat_send_player(name, "Failed. Use '/set -n ' to create a new setting.") + return + end + minetest.setting_set(setname, setvalue) + minetest.chat_send_player(name, setname.." = "..setvalue) + return + end + local setname = string.match(param, "([^ ]+)") + if setname then + local setvalue = minetest.setting_get(setname) + if not setvalue then + setvalue = "" + end + minetest.chat_send_player(name, setname.." = "..setvalue) + return + end + minetest.chat_send_player(name, "Invalid parameters (see /help set)") + end, +}) + +minetest.register_chatcommand("mods", { + params = "", + description = "lists mods installed on the server", + privs = {}, + func = function(name, param) + local response = "" + local modnames = minetest.get_modnames() + for i, mod in ipairs(modnames) do + response = response .. mod + -- Add space if not at the end + if i ~= #modnames then + response = response .. " " + end + end + minetest.chat_send_player(name, response) + end, +}) + +local function handle_give_command(cmd, giver, receiver, stackstring) + minetest.log("action", giver.." invoked "..cmd..', stackstring="' + ..stackstring..'"') + minetest.log(cmd..' invoked, stackstring="'..stackstring..'"') + local itemstack = ItemStack(stackstring) + if itemstack:is_empty() then + minetest.chat_send_player(giver, 'error: cannot give an empty item') + return + elseif not itemstack:is_known() then + minetest.chat_send_player(giver, 'error: cannot give an unknown item') + return + end + local receiverref = minetest.get_player_by_name(receiver) + if receiverref == nil then + minetest.chat_send_player(giver, receiver..' is not a known player') + return + end + local leftover = receiverref:get_inventory():add_item("main", itemstack) + if leftover:is_empty() then + partiality = "" + elseif leftover:get_count() == itemstack:get_count() then + partiality = "could not be " + else + partiality = "partially " + end + -- The actual item stack string may be different from what the "giver" + -- entered (e.g. big numbers are always interpreted as 2^16-1). + stackstring = itemstack:to_string() + if giver == receiver then + minetest.chat_send_player(giver, '"'..stackstring + ..'" '..partiality..'added to inventory.'); + else + minetest.chat_send_player(giver, '"'..stackstring + ..'" '..partiality..'added to '..receiver..'\'s inventory.'); + minetest.chat_send_player(receiver, '"'..stackstring + ..'" '..partiality..'added to inventory.'); + end +end + +minetest.register_chatcommand("give", { + params = " ", + description = "give item to player", + privs = {give=true}, + func = function(name, param) + local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$") + if not toname or not itemstring then + minetest.chat_send_player(name, "name and itemstring required") + return + end + handle_give_command("/give", name, toname, itemstring) + end, +}) +minetest.register_chatcommand("giveme", { + params = "", + description = "give item to yourself", + privs = {give=true}, + func = function(name, param) + local itemstring = string.match(param, "(.+)$") + if not itemstring then + minetest.chat_send_player(name, "itemstring required") + return + end + handle_give_command("/giveme", name, name, itemstring) + end, +}) +minetest.register_chatcommand("spawnentity", { + params = "", + description = "spawn entity at your position", + privs = {give=true, interact=true}, + func = function(name, param) + local entityname = string.match(param, "(.+)$") + if not entityname then + minetest.chat_send_player(name, "entityname required") + return + end + minetest.log("action", '/spawnentity invoked, entityname="'..entityname..'"') + local player = minetest.get_player_by_name(name) + if player == nil then + minetest.log("error", "Unable to spawn entity, player is nil") + return true -- Handled chat message + end + local p = player:getpos() + p.y = p.y + 1 + minetest.add_entity(p, entityname) + minetest.chat_send_player(name, '"'..entityname + ..'" spawned.'); + end, +}) +minetest.register_chatcommand("pulverize", { + params = "", + description = "delete item in hand", + privs = {}, + func = function(name, param) + local player = minetest.get_player_by_name(name) + if player == nil then + minetest.log("error", "Unable to pulverize, player is nil") + return true -- Handled chat message + end + if player:get_wielded_item():is_empty() then + minetest.chat_send_player(name, 'Unable to pulverize, no item in hand.') + else + player:set_wielded_item(nil) + minetest.chat_send_player(name, 'An item was pulverized.') + end + end, +}) + +-- Key = player name +minetest.rollback_punch_callbacks = {} + +minetest.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if minetest.rollback_punch_callbacks[name] then + minetest.rollback_punch_callbacks[name](pos, node, puncher) + minetest.rollback_punch_callbacks[name] = nil + end +end) + +minetest.register_chatcommand("rollback_check", { + params = "[] [] [limit]", + description = "check who has last touched a node or near it, ".. + "max. ago (default range=0, seconds=86400=24h, limit=5)", + privs = {rollback=true}, + func = function(name, param) + local range, seconds, limit = + param:match("(%d+) *(%d*) *(%d*)") + range = tonumber(range) or 0 + seconds = tonumber(seconds) or 86400 + limit = tonumber(limit) or 5 + if limit > 100 then + minetest.chat_send_player(name, "That limit is too high!") + return + end + minetest.chat_send_player(name, "Punch a node (range=".. + range..", seconds="..seconds.."s, limit="..limit..")") + + minetest.rollback_punch_callbacks[name] = function(pos, node, puncher) + local name = puncher:get_player_name() + minetest.chat_send_player(name, "Checking "..minetest.pos_to_string(pos).."...") + local actions = minetest.rollback_get_node_actions(pos, range, seconds, limit) + local num_actions = #actions + if num_actions == 0 then + minetest.chat_send_player(name, "Nobody has touched the ".. + "specified location in "..seconds.." seconds") + return + end + local time = os.time() + for i = num_actions, 1, -1 do + local action = actions[i] + minetest.chat_send_player(name, + ("%s %s %s -> %s %d seconds ago.") + :format( + minetest.pos_to_string(action.pos), + action.actor, + action.oldnode.name, + action.newnode.name, + time - action.time)) + end + end + end, +}) + +minetest.register_chatcommand("rollback", { + params = " [] | : []", + description = "revert actions of a player; default for is 60", + privs = {rollback=true}, + func = function(name, param) + local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)") + if not target_name then + local player_name = nil + player_name, seconds = string.match(param, "([^ ]+) *(%d*)") + if not player_name then + minetest.chat_send_player(name, "Invalid parameters. See /help rollback and /help rollback_check") + return + end + target_name = "player:"..player_name + end + seconds = tonumber(seconds) or 60 + minetest.chat_send_player(name, "Reverting actions of ".. + target_name.." since "..seconds.." seconds.") + local success, log = minetest.rollback_revert_actions_by( + target_name, seconds) + if #log > 100 then + minetest.chat_send_player(name, "(log is too long to show)") + else + for _, line in pairs(log) do + minetest.chat_send_player(name, line) + end + end + if success then + minetest.chat_send_player(name, "Reverting actions succeeded.") + else + minetest.chat_send_player(name, "Reverting actions FAILED.") + end + end, +}) + +minetest.register_chatcommand("status", { + params = "", + description = "print server status line", + privs = {}, + func = function(name, param) + minetest.chat_send_player(name, minetest.get_server_status()) + end, +}) + +minetest.register_chatcommand("time", { + params = "<0...24000>", + description = "set time of day", + privs = {settime=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Missing parameter") + return + end + local newtime = tonumber(param) + if newtime == nil then + minetest.chat_send_player(name, "Invalid time") + else + minetest.set_timeofday((newtime % 24000) / 24000) + minetest.chat_send_player(name, "Time of day changed.") + minetest.log("action", name .. " sets time " .. newtime) + end + end, +}) + +minetest.register_chatcommand("shutdown", { + params = "", + description = "shutdown server", + privs = {server=true}, + func = function(name, param) + minetest.log("action", name .. " shuts down server") + minetest.request_shutdown() + minetest.chat_send_all("*** Server shutting down (operator request).") + end, +}) + +minetest.register_chatcommand("ban", { + params = "", + description = "ban IP of player", + privs = {ban=true}, + func = function(name, param) + if param == "" then + minetest.chat_send_player(name, "Ban list: " .. minetest.get_ban_list()) + return + end + if not minetest.get_player_by_name(param) then + minetest.chat_send_player(name, "No such player") + return + end + if not minetest.ban_player(param) then + minetest.chat_send_player(name, "Failed to ban player") + else + local desc = minetest.get_ban_description(param) + minetest.chat_send_player(name, "Banned " .. desc .. ".") + minetest.log("action", name .. " bans " .. desc .. ".") + end + end, +}) + +minetest.register_chatcommand("unban", { + params = "", + description = "remove IP ban", + privs = {ban=true}, + func = function(name, param) + if not minetest.unban_player_or_ip(param) then + minetest.chat_send_player(name, "Failed to unban player/IP") + else + minetest.chat_send_player(name, "Unbanned " .. param) + minetest.log("action", name .. " unbans " .. param) + end + end, +}) + +minetest.register_chatcommand("kick", { + params = " [reason]", + description = "kick a player", + privs = {kick=true}, + func = function(name, param) + local tokick, reason = string.match(param, "([^ ]+) (.+)") + if not tokick then + tokick = param + end + if not minetest.kick_player(tokick, reason) then + minetest.chat_send_player(name, "Failed to kick player " .. tokick) + else + minetest.chat_send_player(name, "kicked " .. tokick) + minetest.log("action", name .. " kicked " .. tokick) + end + end, +}) + +minetest.register_chatcommand("clearobjects", { + params = "", + description = "clear all objects in world", + privs = {server=true}, + func = function(name, param) + minetest.log("action", name .. " clears all objects") + minetest.chat_send_all("Clearing all objects. This may take long. You may experience a timeout. (by " .. name .. ")") + minetest.clear_objects() + minetest.log("action", "object clearing done") + minetest.chat_send_all("*** Cleared all objects.") + end, +}) + +minetest.register_chatcommand("msg", { + params = " ", + description = "Send a private message", + privs = {shout=true}, + func = function(name, param) + local found, _, sendto, message = param:find("^([^%s]+)%s(.+)$") + if found then + if minetest.get_player_by_name(sendto) then + minetest.log("action", "PM from "..name.." to "..sendto..": "..message) + minetest.chat_send_player(sendto, "PM from "..name..": "..message) + minetest.chat_send_player(name, "Message sent") + else + minetest.chat_send_player(name, "The player "..sendto.." is not online") + end + else + minetest.chat_send_player(name, "Invalid usage, see /help msg") + end + end, +}) diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua new file mode 100644 index 000000000..d8b578d48 --- /dev/null +++ b/builtin/game/deprecated.lua @@ -0,0 +1,53 @@ +-- Minetest: builtin/deprecated.lua + +-- +-- Default material types +-- +function digprop_err() + minetest.log("info", debug.traceback()) + minetest.log("info", "WARNING: The minetest.digprop_* functions are obsolete and need to be replaced by item groups.") +end + +minetest.digprop_constanttime = digprop_err +minetest.digprop_stonelike = digprop_err +minetest.digprop_dirtlike = digprop_err +minetest.digprop_gravellike = digprop_err +minetest.digprop_woodlike = digprop_err +minetest.digprop_leaveslike = digprop_err +minetest.digprop_glasslike = digprop_err + +minetest.node_metadata_inventory_move_allow_all = function() + minetest.log("info", "WARNING: minetest.node_metadata_inventory_move_allow_all is obsolete and does nothing.") +end + +minetest.add_to_creative_inventory = function(itemstring) + minetest.log('info', "WARNING: minetest.add_to_creative_inventory: This function is deprecated and does nothing.") +end + +-- +-- EnvRef +-- +minetest.env = {} +local envref_deprecation_message_printed = false +setmetatable(minetest.env, { + __index = function(table, key) + if not envref_deprecation_message_printed then + minetest.log("info", "WARNING: minetest.env:[...] is deprecated and should be replaced with minetest.[...]") + envref_deprecation_message_printed = true + end + local func = minetest[key] + if type(func) == "function" then + rawset(table, key, function(self, ...) + return func(...) + end) + else + rawset(table, key, nil) + end + return rawget(table, key) + end +}) + +function minetest.rollback_get_last_node_actor(pos, range, seconds) + return minetest.rollback_get_node_actions(pos, range, seconds, 1)[1] +end + diff --git a/builtin/game/detached_inventory.lua b/builtin/game/detached_inventory.lua new file mode 100644 index 000000000..3757f1387 --- /dev/null +++ b/builtin/game/detached_inventory.lua @@ -0,0 +1,19 @@ +-- Minetest: builtin/detached_inventory.lua + +minetest.detached_inventories = {} + +function minetest.create_detached_inventory(name, callbacks) + local stuff = {} + stuff.name = name + if callbacks then + stuff.allow_move = callbacks.allow_move + stuff.allow_put = callbacks.allow_put + stuff.allow_take = callbacks.allow_take + stuff.on_move = callbacks.on_move + stuff.on_put = callbacks.on_put + stuff.on_take = callbacks.on_take + end + minetest.detached_inventories[name] = stuff + return minetest.create_detached_inventory_raw(name) +end + diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua new file mode 100644 index 000000000..93d17221a --- /dev/null +++ b/builtin/game/falling.lua @@ -0,0 +1,217 @@ +-- Minetest: builtin/item.lua + +-- +-- Falling stuff +-- + +minetest.register_entity(":__builtin:falling_node", { + initial_properties = { + physical = true, + collide_with_objects = false, + collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5}, + visual = "wielditem", + textures = {}, + visual_size = {x=0.667, y=0.667}, + }, + + node = {}, + + set_node = function(self, node) + self.node = node + local stack = ItemStack(node.name) + local itemtable = stack:to_table() + local itemname = nil + if itemtable then + itemname = stack:to_table().name + end + local item_texture = nil + local item_type = "" + if minetest.registered_items[itemname] then + item_texture = minetest.registered_items[itemname].inventory_image + item_type = minetest.registered_items[itemname].type + end + prop = { + is_visible = true, + textures = {node.name}, + } + self.object:set_properties(prop) + end, + + get_staticdata = function(self) + return self.node.name + end, + + on_activate = function(self, staticdata) + self.object:set_armor_groups({immortal=1}) + --self.object:setacceleration({x=0, y=-10, z=0}) + self:set_node({name=staticdata}) + end, + + on_step = function(self, dtime) + -- Set gravity + self.object:setacceleration({x=0, y=-10, z=0}) + -- Turn to actual sand when collides to ground or just move + local pos = self.object:getpos() + local bcp = {x=pos.x, y=pos.y-0.7, z=pos.z} -- Position of bottom center point + local bcn = minetest.get_node(bcp) + local bcd = minetest.registered_nodes[bcn.name] + -- Note: walkable is in the node definition, not in item groups + if not bcd or + (bcd.walkable or + (minetest.get_item_group(self.node.name, "float") ~= 0 and + bcd.liquidtype ~= "none")) then + if bcd and bcd.leveled and + bcn.name == self.node.name then + local addlevel = self.node.level + if addlevel == nil or addlevel <= 0 then + addlevel = bcd.leveled + end + if minetest.add_node_level(bcp, addlevel) == 0 then + self.object:remove() + return + end + elseif bcd and bcd.buildable_to and + (minetest.get_item_group(self.node.name, "float") == 0 or + bcd.liquidtype == "none") then + minetest.remove_node(bcp) + return + end + local np = {x=bcp.x, y=bcp.y+1, z=bcp.z} + -- Check what's here + local n2 = minetest.get_node(np) + -- If it's not air or liquid, remove node and replace it with + -- it's drops + if n2.name ~= "air" and (not minetest.registered_nodes[n2.name] or + minetest.registered_nodes[n2.name].liquidtype == "none") then + local drops = minetest.get_node_drops(n2.name, "") + minetest.remove_node(np) + -- Add dropped items + local _, dropped_item + for _, dropped_item in ipairs(drops) do + minetest.add_item(np, dropped_item) + end + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_dignodes) do + callback(np, n2, nil) + end + end + -- Create node and remove entity + minetest.add_node(np, self.node) + self.object:remove() + nodeupdate(np) + else + -- Do nothing + end + end +}) + +function spawn_falling_node(p, node) + obj = minetest.add_entity(p, "__builtin:falling_node") + obj:get_luaentity():set_node(node) +end + +function drop_attached_node(p) + local nn = minetest.get_node(p).name + minetest.remove_node(p) + for _,item in ipairs(minetest.get_node_drops(nn, "")) do + local pos = { + x = p.x + math.random()/2 - 0.25, + y = p.y + math.random()/2 - 0.25, + z = p.z + math.random()/2 - 0.25, + } + minetest.add_item(pos, item) + end +end + +function check_attached_node(p, n) + local def = minetest.registered_nodes[n.name] + local d = {x=0, y=0, z=0} + if def.paramtype2 == "wallmounted" then + if n.param2 == 0 then + d.y = 1 + elseif n.param2 == 1 then + d.y = -1 + elseif n.param2 == 2 then + d.x = 1 + elseif n.param2 == 3 then + d.x = -1 + elseif n.param2 == 4 then + d.z = 1 + elseif n.param2 == 5 then + d.z = -1 + end + else + d.y = -1 + end + local p2 = {x=p.x+d.x, y=p.y+d.y, z=p.z+d.z} + local nn = minetest.get_node(p2).name + local def2 = minetest.registered_nodes[nn] + if def2 and not def2.walkable then + return false + end + return true +end + +-- +-- Some common functions +-- + +function nodeupdate_single(p, delay) + n = minetest.get_node(p) + if minetest.get_item_group(n.name, "falling_node") ~= 0 then + p_bottom = {x=p.x, y=p.y-1, z=p.z} + n_bottom = minetest.get_node(p_bottom) + -- Note: walkable is in the node definition, not in item groups + if minetest.registered_nodes[n_bottom.name] and + (minetest.get_item_group(n.name, "float") == 0 or minetest.registered_nodes[n_bottom.name].liquidtype == "none") and + (n.name ~= n_bottom.name or (minetest.registered_nodes[n_bottom.name].leveled and minetest.env:get_node_level(p_bottom) < minetest.env:get_node_max_level(p_bottom))) and + (not minetest.registered_nodes[n_bottom.name].walkable or + minetest.registered_nodes[n_bottom.name].buildable_to) then + if delay then + minetest.after(0.1, nodeupdate_single, {x=p.x, y=p.y, z=p.z}, false) + else + n.level = minetest.env:get_node_level(p) + minetest.remove_node(p) + spawn_falling_node(p, n) + nodeupdate(p) + end + end + end + + if minetest.get_item_group(n.name, "attached_node") ~= 0 then + if not check_attached_node(p, n) then + drop_attached_node(p) + nodeupdate(p) + end + end +end + +function nodeupdate(p, delay) + -- Round p to prevent falling entities to get stuck + p.x = math.floor(p.x+0.5) + p.y = math.floor(p.y+0.5) + p.z = math.floor(p.z+0.5) + + for x = -1,1 do + for y = -1,1 do + for z = -1,1 do + nodeupdate_single({x=p.x+x, y=p.y+y, z=p.z+z}, delay or not (x==0 and y==0 and z==0)) + end + end + end +end + +-- +-- Global callbacks +-- + +function on_placenode(p, node) + nodeupdate(p) +end +minetest.register_on_placenode(on_placenode) + +function on_dignode(p, node) + nodeupdate(p) +end +minetest.register_on_dignode(on_dignode) diff --git a/builtin/game/features.lua b/builtin/game/features.lua new file mode 100644 index 000000000..f3de3ba21 --- /dev/null +++ b/builtin/game/features.lua @@ -0,0 +1,29 @@ +-- Minetest: builtin/features.lua + +minetest.features = { + glasslike_framed = true, + nodebox_as_selectionbox = true, + chat_send_player_param3 = true, + get_all_craft_recipes_works = true, + use_texture_alpha = true, + no_legacy_abms = true, +} + +function minetest.has_feature(arg) + if type(arg) == "table" then + missing_features = {} + result = true + for ft, _ in pairs(arg) do + if not minetest.features[ftr] then + missing_features[ftr] = true + result = false + end + end + return result, missing_features + elseif type(arg) == "string" then + if not minetest.features[arg] then + return false, {[arg]=true} + end + return true, {} + end +end diff --git a/builtin/game/forceloading.lua b/builtin/game/forceloading.lua new file mode 100644 index 000000000..84895792b --- /dev/null +++ b/builtin/game/forceloading.lua @@ -0,0 +1,79 @@ +-- Prevent anyone else accessing those functions +local forceload_block = minetest.forceload_block +local forceload_free_block = minetest.forceload_free_block +minetest.forceload_block = nil +minetest.forceload_free_block = nil + +local blocks_forceloaded +local total_forceloaded = 0 + +local BLOCKSIZE = 16 +local function get_blockpos(pos) + return { + x = math.floor(pos.x/BLOCKSIZE), + y = math.floor(pos.y/BLOCKSIZE), + z = math.floor(pos.z/BLOCKSIZE)} +end + +function minetest.forceload_block(pos) + local blockpos = get_blockpos(pos) + local hash = minetest.hash_node_position(blockpos) + if blocks_forceloaded[hash] ~= nil then + blocks_forceloaded[hash] = blocks_forceloaded[hash] + 1 + return true + else + if total_forceloaded >= (tonumber(minetest.setting_get("max_forceloaded_blocks")) or 16) then + return false + end + total_forceloaded = total_forceloaded+1 + blocks_forceloaded[hash] = 1 + forceload_block(blockpos) + return true + end +end + +function minetest.forceload_free_block(pos) + local blockpos = get_blockpos(pos) + local hash = minetest.hash_node_position(blockpos) + if blocks_forceloaded[hash] == nil then return end + if blocks_forceloaded[hash] > 1 then + blocks_forceloaded[hash] = blocks_forceloaded[hash] - 1 + else + total_forceloaded = total_forceloaded-1 + blocks_forceloaded[hash] = nil + forceload_free_block(blockpos) + end +end + +-- Keep the forceloaded areas after restart +local wpath = minetest.get_worldpath() +local function read_file(filename) + local f = io.open(filename, "r") + if f==nil then return {} end + local t = f:read("*all") + f:close() + if t=="" or t==nil then return {} end + return minetest.deserialize(t) +end + +local function write_file(filename, table) + local f = io.open(filename, "w") + f:write(minetest.serialize(table)) + f:close() +end + +blocks_forceloaded = read_file(wpath.."/force_loaded.txt") +for _, __ in pairs(blocks_forceloaded) do + total_forceloaded = total_forceloaded + 1 +end + +minetest.after(5, function() + for hash, _ in pairs(blocks_forceloaded) do + local blockpos = minetest.get_position_from_hash(hash) + forceload_block(blockpos) + end +end) + +minetest.register_on_shutdown(function() + write_file(wpath.."/force_loaded.txt", blocks_forceloaded) +end) diff --git a/builtin/game/init.lua b/builtin/game/init.lua new file mode 100644 index 000000000..b878a2664 --- /dev/null +++ b/builtin/game/init.lua @@ -0,0 +1,23 @@ + +local scriptpath = minetest.get_builtin_path()..DIR_DELIM +local commonpath = scriptpath.."common"..DIR_DELIM +local gamepath = scriptpath.."game"..DIR_DELIM + +dofile(commonpath.."vector.lua") + +dofile(gamepath.."item.lua") +dofile(gamepath.."register.lua") +dofile(gamepath.."item_entity.lua") +dofile(gamepath.."deprecated.lua") +dofile(gamepath.."misc.lua") +dofile(gamepath.."privileges.lua") +dofile(gamepath.."auth.lua") +dofile(gamepath.."chatcommands.lua") +dofile(gamepath.."static_spawn.lua") +dofile(gamepath.."detached_inventory.lua") +dofile(gamepath.."falling.lua") +dofile(gamepath.."features.lua") +dofile(gamepath.."voxelarea.lua") +dofile(gamepath.."forceloading.lua") +dofile(gamepath.."statbars.lua") + diff --git a/builtin/game/item.lua b/builtin/game/item.lua new file mode 100644 index 000000000..002c14f5e --- /dev/null +++ b/builtin/game/item.lua @@ -0,0 +1,589 @@ +-- Minetest: builtin/item.lua + +local function copy_pointed_thing(pointed_thing) + return { + type = pointed_thing.type, + above = vector.new(pointed_thing.above), + under = vector.new(pointed_thing.under), + ref = pointed_thing.ref, + } +end + +-- +-- Item definition helpers +-- + +function minetest.inventorycube(img1, img2, img3) + img2 = img2 or img1 + img3 = img3 or img1 + return "[inventorycube" + .. "{" .. img1:gsub("%^", "&") + .. "{" .. img2:gsub("%^", "&") + .. "{" .. img3:gsub("%^", "&") +end + +function minetest.get_pointed_thing_position(pointed_thing, above) + if pointed_thing.type == "node" then + if above then + -- The position where a node would be placed + return pointed_thing.above + else + -- The position where a node would be dug + return pointed_thing.under + end + elseif pointed_thing.type == "object" then + obj = pointed_thing.ref + if obj ~= nil then + return obj:getpos() + else + return nil + end + else + return nil + end +end + +function minetest.dir_to_facedir(dir, is6d) + --account for y if requested + if is6d and math.abs(dir.y) > math.abs(dir.x) and math.abs(dir.y) > math.abs(dir.z) then + + --from above + if dir.y < 0 then + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 19 + else + return 13 + end + else + if dir.z < 0 then + return 10 + else + return 4 + end + end + + --from below + else + if math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 15 + else + return 17 + end + else + if dir.z < 0 then + return 6 + else + return 8 + end + end + end + + --otherwise, place horizontally + elseif math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 1 + end + else + if dir.z < 0 then + return 2 + else + return 0 + end + end +end + +function minetest.facedir_to_dir(facedir) + --a table of possible dirs + return ({{x=0, y=0, z=1}, + {x=1, y=0, z=0}, + {x=0, y=0, z=-1}, + {x=-1, y=0, z=0}, + {x=0, y=-1, z=0}, + {x=0, y=1, z=0}}) + + --indexed into by a table of correlating facedirs + [({[0]=1, 2, 3, 4, + 5, 2, 6, 4, + 6, 2, 5, 4, + 1, 5, 3, 6, + 1, 6, 3, 5, + 1, 4, 3, 2}) + + --indexed into by the facedir in question + [facedir]] +end + +function minetest.dir_to_wallmounted(dir) + if math.abs(dir.y) > math.max(math.abs(dir.x), math.abs(dir.z)) then + if dir.y < 0 then + return 1 + else + return 0 + end + elseif math.abs(dir.x) > math.abs(dir.z) then + if dir.x < 0 then + return 3 + else + return 2 + end + else + if dir.z < 0 then + return 5 + else + return 4 + end + end +end + +function minetest.get_node_drops(nodename, toolname) + local drop = ItemStack({name=nodename}):get_definition().drop + if drop == nil then + -- default drop + return {nodename} + elseif type(drop) == "string" then + -- itemstring drop + return {drop} + elseif drop.items == nil then + -- drop = {} to disable default drop + return {} + end + + -- Extended drop table + local got_items = {} + local got_count = 0 + local _, item, tool + for _, item in ipairs(drop.items) do + local good_rarity = true + local good_tool = true + if item.rarity ~= nil then + good_rarity = item.rarity < 1 or math.random(item.rarity) == 1 + end + if item.tools ~= nil then + good_tool = false + for _, tool in ipairs(item.tools) do + if tool:sub(1, 1) == '~' then + good_tool = toolname:find(tool:sub(2)) ~= nil + else + good_tool = toolname == tool + end + if good_tool then + break + end + end + end + if good_rarity and good_tool then + got_count = got_count + 1 + for _, add_item in ipairs(item.items) do + got_items[#got_items+1] = add_item + end + if drop.max_items ~= nil and got_count == drop.max_items then + break + end + end + end + return got_items +end + +function minetest.item_place_node(itemstack, placer, pointed_thing, param2) + local item = itemstack:peek_item() + local def = itemstack:get_definition() + if def.type ~= "node" or pointed_thing.type ~= "node" then + return itemstack, false + end + + local under = pointed_thing.under + local oldnode_under = minetest.get_node_or_nil(under) + local above = pointed_thing.above + local oldnode_above = minetest.get_node_or_nil(above) + + if not oldnode_under or not oldnode_above then + minetest.log("info", placer:get_player_name() .. " tried to place" + .. " node in unloaded position " .. minetest.pos_to_string(above)) + return itemstack, false + end + + local olddef_under = ItemStack({name=oldnode_under.name}):get_definition() + olddef_under = olddef_under or minetest.nodedef_default + local olddef_above = ItemStack({name=oldnode_above.name}):get_definition() + olddef_above = olddef_above or minetest.nodedef_default + + if not olddef_above.buildable_to and not olddef_under.buildable_to then + minetest.log("info", placer:get_player_name() .. " tried to place" + .. " node in invalid position " .. minetest.pos_to_string(above) + .. ", replacing " .. oldnode_above.name) + return itemstack, false + end + + -- Place above pointed node + local place_to = {x = above.x, y = above.y, z = above.z} + + -- If node under is buildable_to, place into it instead (eg. snow) + if olddef_under.buildable_to then + minetest.log("info", "node under is buildable to") + place_to = {x = under.x, y = under.y, z = under.z} + end + + if minetest.is_protected(place_to, placer:get_player_name()) then + minetest.log("action", placer:get_player_name() + .. " tried to place " .. def.name + .. " at protected position " + .. minetest.pos_to_string(place_to)) + minetest.record_protection_violation(place_to, placer:get_player_name()) + return itemstack + end + + minetest.log("action", placer:get_player_name() .. " places node " + .. def.name .. " at " .. minetest.pos_to_string(place_to)) + + local oldnode = minetest.get_node(place_to) + local newnode = {name = def.name, param1 = 0, param2 = param2} + + -- Calculate direction for wall mounted stuff like torches and signs + if def.paramtype2 == 'wallmounted' and not param2 then + local dir = { + x = under.x - above.x, + y = under.y - above.y, + z = under.z - above.z + } + newnode.param2 = minetest.dir_to_wallmounted(dir) + -- Calculate the direction for furnaces and chests and stuff + elseif def.paramtype2 == 'facedir' and not param2 then + local placer_pos = placer:getpos() + if placer_pos then + local dir = { + x = above.x - placer_pos.x, + y = above.y - placer_pos.y, + z = above.z - placer_pos.z + } + newnode.param2 = minetest.dir_to_facedir(dir) + minetest.log("action", "facedir: " .. newnode.param2) + end + end + + -- Check if the node is attached and if it can be placed there + if minetest.get_item_group(def.name, "attached_node") ~= 0 and + not check_attached_node(place_to, newnode) then + minetest.log("action", "attached node " .. def.name .. + " can not be placed at " .. minetest.pos_to_string(place_to)) + return itemstack, false + end + + -- Add node and update + minetest.add_node(place_to, newnode) + + local take_item = true + + -- Run callback + if def.after_place_node then + -- Deepcopy place_to and pointed_thing because callback can modify it + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if def.after_place_node(place_to_copy, placer, itemstack, + pointed_thing_copy) then + take_item = false + end + end + + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_placenodes) do + -- Deepcopy pos, node and pointed_thing because callback can modify them + local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z} + local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2} + local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2} + local pointed_thing_copy = copy_pointed_thing(pointed_thing) + if callback(place_to_copy, newnode_copy, placer, oldnode_copy, itemstack, pointed_thing_copy) then + take_item = false + end + end + + if take_item then + itemstack:take_item() + end + return itemstack, true +end + +function minetest.item_place_object(itemstack, placer, pointed_thing) + local pos = minetest.get_pointed_thing_position(pointed_thing, true) + if pos ~= nil then + local item = itemstack:take_item() + minetest.add_item(pos, item) + end + return itemstack +end + +function minetest.item_place(itemstack, placer, pointed_thing, param2) + -- Call on_rightclick if the pointed node defines it + if pointed_thing.type == "node" and placer and + not placer:get_player_control().sneak then + local n = minetest.get_node(pointed_thing.under) + local nn = n.name + if minetest.registered_nodes[nn] and minetest.registered_nodes[nn].on_rightclick then + return minetest.registered_nodes[nn].on_rightclick(pointed_thing.under, n, + placer, itemstack, pointed_thing) or itemstack, false + end + end + + if itemstack:get_definition().type == "node" then + return minetest.item_place_node(itemstack, placer, pointed_thing, param2) + end + return itemstack +end + +function minetest.item_drop(itemstack, dropper, pos) + if dropper.get_player_name then + local v = dropper:get_look_dir() + local p = {x=pos.x+v.x, y=pos.y+1.5+v.y, z=pos.z+v.z} + local obj = minetest.add_item(p, itemstack) + if obj then + v.x = v.x*2 + v.y = v.y*2 + 1 + v.z = v.z*2 + obj:setvelocity(v) + end + else + minetest.add_item(pos, itemstack) + end + return ItemStack("") +end + +function minetest.item_eat(hp_change, replace_with_item) + return function(itemstack, user, pointed_thing) -- closure + if itemstack:take_item() ~= nil then + user:set_hp(user:get_hp() + hp_change) + itemstack:add_item(replace_with_item) -- note: replace_with_item is optional + end + return itemstack + end +end + +function minetest.node_punch(pos, node, puncher, pointed_thing) + -- Run script hook + for _, callback in ipairs(minetest.registered_on_punchnodes) do + -- Copy pos and node because callback can modify them + local pos_copy = vector.new(pos) + local node_copy = {name=node.name, param1=node.param1, param2=node.param2} + local pointed_thing_copy = pointed_thing and copy_pointed_thing(pointed_thing) or nil + callback(pos_copy, node_copy, puncher, pointed_thing_copy) + end +end + +function minetest.handle_node_drops(pos, drops, digger) + -- Add dropped items to object's inventory + if digger:get_inventory() then + local _, dropped_item + for _, dropped_item in ipairs(drops) do + local left = digger:get_inventory():add_item("main", dropped_item) + if not left:is_empty() then + local p = { + x = pos.x + math.random()/2-0.25, + y = pos.y + math.random()/2-0.25, + z = pos.z + math.random()/2-0.25, + } + minetest.add_item(p, left) + end + end + end +end + +function minetest.node_dig(pos, node, digger) + local def = ItemStack({name=node.name}):get_definition() + if not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then + minetest.log("info", digger:get_player_name() .. " tried to dig " + .. node.name .. " which is not diggable " + .. minetest.pos_to_string(pos)) + return + end + + if minetest.is_protected(pos, digger:get_player_name()) then + minetest.log("action", digger:get_player_name() + .. " tried to dig " .. node.name + .. " at protected position " + .. minetest.pos_to_string(pos)) + minetest.record_protection_violation(pos, digger:get_player_name()) + return + end + + minetest.log('action', digger:get_player_name() .. " digs " + .. node.name .. " at " .. minetest.pos_to_string(pos)) + + local wielded = digger:get_wielded_item() + local drops = minetest.get_node_drops(node.name, wielded:get_name()) + + local wdef = wielded:get_definition() + local tp = wielded:get_tool_capabilities() + local dp = minetest.get_dig_params(def.groups, tp) + if wdef and wdef.after_use then + wielded = wdef.after_use(wielded, digger, node, dp) or wielded + else + -- Wear out tool + if not minetest.setting_getbool("creative_mode") then + wielded:add_wear(dp.wear) + end + end + digger:set_wielded_item(wielded) + + -- Handle drops + minetest.handle_node_drops(pos, drops, digger) + + local oldmetadata = nil + if def.after_dig_node then + oldmetadata = minetest.get_meta(pos):to_table() + end + + -- Remove node and update + minetest.remove_node(pos) + + -- Run callback + if def.after_dig_node then + -- Copy pos and node because callback can modify them + local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local node_copy = {name=node.name, param1=node.param1, param2=node.param2} + def.after_dig_node(pos_copy, node_copy, oldmetadata, digger) + end + + -- Run script hook + local _, callback + for _, callback in ipairs(minetest.registered_on_dignodes) do + -- Copy pos and node because callback can modify them + local pos_copy = {x=pos.x, y=pos.y, z=pos.z} + local node_copy = {name=node.name, param1=node.param1, param2=node.param2} + callback(pos_copy, node_copy, digger) + end +end + +-- This is used to allow mods to redefine minetest.item_place and so on +-- NOTE: This is not the preferred way. Preferred way is to provide enough +-- callbacks to not require redefining global functions. -celeron55 +local function redef_wrapper(table, name) + return function(...) + return table[name](...) + end +end + +-- +-- Item definition defaults +-- + +minetest.nodedef_default = { + -- Item properties + type="node", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 99, + usable = false, + liquids_pointable = false, + tool_capabilities = nil, + node_placement_prediction = nil, + + -- Interaction callbacks + on_place = redef_wrapper(minetest, 'item_place'), -- minetest.item_place + on_drop = redef_wrapper(minetest, 'item_drop'), -- minetest.item_drop + on_use = nil, + can_dig = nil, + + on_punch = redef_wrapper(minetest, 'node_punch'), -- minetest.node_punch + on_rightclick = nil, + on_dig = redef_wrapper(minetest, 'node_dig'), -- minetest.node_dig + + on_receive_fields = nil, + + on_metadata_inventory_move = minetest.node_metadata_inventory_move_allow_all, + on_metadata_inventory_offer = minetest.node_metadata_inventory_offer_allow_all, + on_metadata_inventory_take = minetest.node_metadata_inventory_take_allow_all, + + -- Node properties + drawtype = "normal", + visual_scale = 1.0, + -- Don't define these because otherwise the old tile_images and + -- special_materials wouldn't be read + --tiles ={""}, + --special_tiles = { + -- {name="", backface_culling=true}, + -- {name="", backface_culling=true}, + --}, + alpha = 255, + post_effect_color = {a=0, r=0, g=0, b=0}, + paramtype = "none", + paramtype2 = "none", + is_ground_content = true, + sunlight_propagates = false, + walkable = true, + pointable = true, + diggable = true, + climbable = false, + buildable_to = false, + liquidtype = "none", + liquid_alternative_flowing = "", + liquid_alternative_source = "", + liquid_viscosity = 0, + drowning = 0, + light_source = 0, + damage_per_second = 0, + selection_box = {type="regular"}, + legacy_facedir_simple = false, + legacy_wallmounted = false, +} + +minetest.craftitemdef_default = { + type="craft", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 99, + liquids_pointable = false, + tool_capabilities = nil, + + -- Interaction callbacks + on_place = redef_wrapper(minetest, 'item_place'), -- minetest.item_place + on_drop = redef_wrapper(minetest, 'item_drop'), -- minetest.item_drop + on_use = nil, +} + +minetest.tooldef_default = { + type="tool", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 1, + liquids_pointable = false, + tool_capabilities = nil, + + -- Interaction callbacks + on_place = redef_wrapper(minetest, 'item_place'), -- minetest.item_place + on_drop = redef_wrapper(minetest, 'item_drop'), -- minetest.item_drop + on_use = nil, +} + +minetest.noneitemdef_default = { -- This is used for the hand and unknown items + type="none", + -- name intentionally not defined here + description = "", + groups = {}, + inventory_image = "", + wield_image = "", + wield_scale = {x=1,y=1,z=1}, + stack_max = 99, + liquids_pointable = false, + tool_capabilities = nil, + + -- Interaction callbacks + on_place = redef_wrapper(minetest, 'item_place'), + on_drop = nil, + on_use = nil, +} + diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua new file mode 100644 index 000000000..8150e6da1 --- /dev/null +++ b/builtin/game/item_entity.lua @@ -0,0 +1,123 @@ +-- Minetest: builtin/item_entity.lua + +function minetest.spawn_item(pos, item) + -- Take item in any format + local stack = ItemStack(item) + local obj = minetest.add_entity(pos, "__builtin:item") + obj:get_luaentity():set_item(stack:to_string()) + return obj +end + +minetest.register_entity(":__builtin:item", { + initial_properties = { + hp_max = 1, + physical = true, + collide_with_objects = false, + collisionbox = {-0.17,-0.17,-0.17, 0.17,0.17,0.17}, + visual = "sprite", + visual_size = {x=0.5, y=0.5}, + textures = {""}, + spritediv = {x=1, y=1}, + initial_sprite_basepos = {x=0, y=0}, + is_visible = false, + }, + + itemstring = '', + physical_state = true, + + set_item = function(self, itemstring) + self.itemstring = itemstring + local stack = ItemStack(itemstring) + local itemtable = stack:to_table() + local itemname = nil + if itemtable then + itemname = stack:to_table().name + end + local item_texture = nil + local item_type = "" + if minetest.registered_items[itemname] then + item_texture = minetest.registered_items[itemname].inventory_image + item_type = minetest.registered_items[itemname].type + end + prop = { + is_visible = true, + visual = "sprite", + textures = {"unknown_item.png"} + } + if item_texture and item_texture ~= "" then + prop.visual = "sprite" + prop.textures = {item_texture} + prop.visual_size = {x=0.50, y=0.50} + else + prop.visual = "wielditem" + prop.textures = {itemname} + prop.visual_size = {x=0.20, y=0.20} + prop.automatic_rotate = math.pi * 0.25 + end + self.object:set_properties(prop) + end, + + get_staticdata = function(self) + --return self.itemstring + return minetest.serialize({ + itemstring = self.itemstring, + always_collect = self.always_collect, + }) + end, + + on_activate = function(self, staticdata) + if string.sub(staticdata, 1, string.len("return")) == "return" then + local data = minetest.deserialize(staticdata) + if data and type(data) == "table" then + self.itemstring = data.itemstring + self.always_collect = data.always_collect + end + else + self.itemstring = staticdata + end + self.object:set_armor_groups({immortal=1}) + self.object:setvelocity({x=0, y=2, z=0}) + self.object:setacceleration({x=0, y=-10, z=0}) + self:set_item(self.itemstring) + end, + + on_step = function(self, dtime) + local p = self.object:getpos() + p.y = p.y - 0.3 + local nn = minetest.get_node(p).name + -- If node is not registered or node is walkably solid and resting on nodebox + local v = self.object:getvelocity() + if not minetest.registered_nodes[nn] or minetest.registered_nodes[nn].walkable and v.y == 0 then + if self.physical_state then + self.object:setvelocity({x=0,y=0,z=0}) + self.object:setacceleration({x=0, y=0, z=0}) + self.physical_state = false + self.object:set_properties({ + physical = false + }) + end + else + if not self.physical_state then + self.object:setvelocity({x=0,y=0,z=0}) + self.object:setacceleration({x=0, y=-10, z=0}) + self.physical_state = true + self.object:set_properties({ + physical = true + }) + end + end + end, + + on_punch = function(self, hitter) + if self.itemstring ~= '' then + local left = hitter:get_inventory():add_item("main", self.itemstring) + if not left:is_empty() then + self.itemstring = left:to_string() + return + end + end + self.itemstring = '' + self.object:remove() + end, +}) + diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua new file mode 100644 index 000000000..82cc527cd --- /dev/null +++ b/builtin/game/misc.lua @@ -0,0 +1,134 @@ +-- Minetest: builtin/misc.lua + +-- +-- Misc. API functions +-- + +minetest.timers_to_add = {} +minetest.timers = {} +minetest.register_globalstep(function(dtime) + for _, timer in ipairs(minetest.timers_to_add) do + table.insert(minetest.timers, timer) + end + minetest.timers_to_add = {} + for index, timer in ipairs(minetest.timers) do + timer.time = timer.time - dtime + if timer.time <= 0 then + timer.func(unpack(timer.args or {})) + table.remove(minetest.timers,index) + end + end +end) + +function minetest.after(time, func, ...) + assert(tonumber(time) and type(func) == "function", + "Invalid minetest.after invocation") + table.insert(minetest.timers_to_add, {time=time, func=func, args={...}}) +end + +function minetest.check_player_privs(name, privs) + local player_privs = minetest.get_player_privs(name) + local missing_privileges = {} + for priv, val in pairs(privs) do + if val then + if not player_privs[priv] then + table.insert(missing_privileges, priv) + end + end + end + if #missing_privileges > 0 then + return false, missing_privileges + end + return true, "" +end + +local player_list = {} + +minetest.register_on_joinplayer(function(player) + player_list[player:get_player_name()] = player +end) + +minetest.register_on_leaveplayer(function(player) + player_list[player:get_player_name()] = nil +end) + +function minetest.get_connected_players() + local temp_table = {} + for index, value in pairs(player_list) do + if value:is_player_connected() then + table.insert(temp_table, value) + end + end + return temp_table +end + +function minetest.hash_node_position(pos) + return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768 +end + +function minetest.get_position_from_hash(hash) + local pos = {} + pos.x = (hash%65536) - 32768 + hash = math.floor(hash/65536) + pos.y = (hash%65536) - 32768 + hash = math.floor(hash/65536) + pos.z = (hash%65536) - 32768 + return pos +end + +function minetest.get_item_group(name, group) + if not minetest.registered_items[name] or not + minetest.registered_items[name].groups[group] then + return 0 + end + return minetest.registered_items[name].groups[group] +end + +function minetest.get_node_group(name, group) + minetest.log("deprecated", "Deprecated usage of get_node_group, use get_item_group instead") + return minetest.get_item_group(name, group) +end + +function minetest.string_to_pos(value) + local p = {} + p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + local p = {} + p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$") + if p.x and p.y and p.z then + p.x = tonumber(p.x) + p.y = tonumber(p.y) + p.z = tonumber(p.z) + return p + end + return nil +end + +assert(minetest.string_to_pos("10.0, 5, -2").x == 10) +assert(minetest.string_to_pos("( 10.0, 5, -2)").z == -2) +assert(minetest.string_to_pos("asd, 5, -2)") == nil) + +function minetest.setting_get_pos(name) + local value = minetest.setting_get(name) + if not value then + return nil + end + return minetest.string_to_pos(value) +end + +-- To be overriden by protection mods +function minetest.is_protected(pos, name) + return false +end + +function minetest.record_protection_violation(pos, name) + for _, func in pairs(minetest.registered_on_protection_violation) do + func(pos, name) + end +end + diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua new file mode 100644 index 000000000..244aa453c --- /dev/null +++ b/builtin/game/privileges.lua @@ -0,0 +1,53 @@ +-- Minetest: builtin/privileges.lua + +-- +-- Privileges +-- + +minetest.registered_privileges = {} + +function minetest.register_privilege(name, param) + local function fill_defaults(def) + if def.give_to_singleplayer == nil then + def.give_to_singleplayer = true + end + if def.description == nil then + def.description = "(no description)" + end + end + local def = {} + if type(param) == "table" then + def = param + else + def = {description = param} + end + fill_defaults(def) + minetest.registered_privileges[name] = def +end + +minetest.register_privilege("interact", "Can interact with things and modify the world") +minetest.register_privilege("teleport", "Can use /teleport command") +minetest.register_privilege("bring", "Can teleport other players") +minetest.register_privilege("settime", "Can use /time") +minetest.register_privilege("privs", "Can modify privileges") +minetest.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges") +minetest.register_privilege("server", "Can do server maintenance stuff") +minetest.register_privilege("shout", "Can speak in chat") +minetest.register_privilege("ban", "Can ban and unban players") +minetest.register_privilege("kick", "Can kick players") +minetest.register_privilege("give", "Can use /give and /giveme") +minetest.register_privilege("password", "Can use /setpassword and /clearpassword") +minetest.register_privilege("fly", { + description = "Can fly using the free_move mode", + give_to_singleplayer = false, +}) +minetest.register_privilege("fast", { + description = "Can walk fast using the fast_move mode", + give_to_singleplayer = false, +}) +minetest.register_privilege("noclip", { + description = "Can fly through walls", + give_to_singleplayer = false, +}) +minetest.register_privilege("rollback", "Can use the rollback functionality") + diff --git a/builtin/game/register.lua b/builtin/game/register.lua new file mode 100644 index 000000000..99c5115c4 --- /dev/null +++ b/builtin/game/register.lua @@ -0,0 +1,410 @@ +-- Minetest: builtin/misc_register.lua + +-- +-- Make raw registration functions inaccessible to anyone except this file +-- + +local register_item_raw = minetest.register_item_raw +minetest.register_item_raw = nil + +local register_alias_raw = minetest.register_alias_raw +minetest.register_item_raw = nil + +-- +-- Item / entity / ABM registration functions +-- + +minetest.registered_abms = {} +minetest.registered_entities = {} +minetest.registered_items = {} +minetest.registered_nodes = {} +minetest.registered_craftitems = {} +minetest.registered_tools = {} +minetest.registered_aliases = {} + +-- For tables that are indexed by item name: +-- If table[X] does not exist, default to table[minetest.registered_aliases[X]] +local alias_metatable = { + __index = function(t, name) + return rawget(t, minetest.registered_aliases[name]) + end +} +setmetatable(minetest.registered_items, alias_metatable) +setmetatable(minetest.registered_nodes, alias_metatable) +setmetatable(minetest.registered_craftitems, alias_metatable) +setmetatable(minetest.registered_tools, alias_metatable) + +-- These item names may not be used because they would interfere +-- with legacy itemstrings +local forbidden_item_names = { + MaterialItem = true, + MaterialItem2 = true, + MaterialItem3 = true, + NodeItem = true, + node = true, + CraftItem = true, + craft = true, + MBOItem = true, + ToolItem = true, + tool = true, +} + +local function check_modname_prefix(name) + if name:sub(1,1) == ":" then + -- Escape the modname prefix enforcement mechanism + return name:sub(2) + else + -- Modname prefix enforcement + local expected_prefix = minetest.get_current_modname() .. ":" + if name:sub(1, #expected_prefix) ~= expected_prefix then + error("Name " .. name .. " does not follow naming conventions: " .. + "\"modname:\" or \":\" prefix required") + end + local subname = name:sub(#expected_prefix+1) + if subname:find("[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]") then + error("Name " .. name .. " does not follow naming conventions: " .. + "contains unallowed characters") + end + return name + end +end + +function minetest.register_abm(spec) + -- Add to minetest.registered_abms + minetest.registered_abms[#minetest.registered_abms+1] = spec +end + +function minetest.register_entity(name, prototype) + -- Check name + if name == nil then + error("Unable to register entity: Name is nil") + end + name = check_modname_prefix(tostring(name)) + + prototype.name = name + prototype.__index = prototype -- so that it can be used as a metatable + + -- Add to minetest.registered_entities + minetest.registered_entities[name] = prototype +end + +function minetest.register_item(name, itemdef) + -- Check name + if name == nil then + error("Unable to register item: Name is nil") + end + name = check_modname_prefix(tostring(name)) + if forbidden_item_names[name] then + error("Unable to register item: Name is forbidden: " .. name) + end + itemdef.name = name + + -- Apply defaults and add to registered_* table + if itemdef.type == "node" then + -- Use the nodebox as selection box if it's not set manually + if itemdef.drawtype == "nodebox" and not itemdef.selection_box then + itemdef.selection_box = itemdef.node_box + elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then + itemdef.selection_box = { + type = "fixed", + fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8}, + } + end + setmetatable(itemdef, {__index = minetest.nodedef_default}) + minetest.registered_nodes[itemdef.name] = itemdef + elseif itemdef.type == "craft" then + setmetatable(itemdef, {__index = minetest.craftitemdef_default}) + minetest.registered_craftitems[itemdef.name] = itemdef + elseif itemdef.type == "tool" then + setmetatable(itemdef, {__index = minetest.tooldef_default}) + minetest.registered_tools[itemdef.name] = itemdef + elseif itemdef.type == "none" then + setmetatable(itemdef, {__index = minetest.noneitemdef_default}) + else + error("Unable to register item: Type is invalid: " .. dump(itemdef)) + end + + -- Flowing liquid uses param2 + if itemdef.type == "node" and itemdef.liquidtype == "flowing" then + itemdef.paramtype2 = "flowingliquid" + end + + -- BEGIN Legacy stuff + if itemdef.cookresult_itemstring ~= nil and itemdef.cookresult_itemstring ~= "" then + minetest.register_craft({ + type="cooking", + output=itemdef.cookresult_itemstring, + recipe=itemdef.name, + cooktime=itemdef.furnace_cooktime + }) + end + if itemdef.furnace_burntime ~= nil and itemdef.furnace_burntime >= 0 then + minetest.register_craft({ + type="fuel", + recipe=itemdef.name, + burntime=itemdef.furnace_burntime + }) + end + -- END Legacy stuff + + -- Disable all further modifications + getmetatable(itemdef).__newindex = {} + + --minetest.log("Registering item: " .. itemdef.name) + minetest.registered_items[itemdef.name] = itemdef + minetest.registered_aliases[itemdef.name] = nil + register_item_raw(itemdef) +end + +function minetest.register_node(name, nodedef) + nodedef.type = "node" + minetest.register_item(name, nodedef) +end + +function minetest.register_craftitem(name, craftitemdef) + craftitemdef.type = "craft" + + -- BEGIN Legacy stuff + if craftitemdef.inventory_image == nil and craftitemdef.image ~= nil then + craftitemdef.inventory_image = craftitemdef.image + end + -- END Legacy stuff + + minetest.register_item(name, craftitemdef) +end + +function minetest.register_tool(name, tooldef) + tooldef.type = "tool" + tooldef.stack_max = 1 + + -- BEGIN Legacy stuff + if tooldef.inventory_image == nil and tooldef.image ~= nil then + tooldef.inventory_image = tooldef.image + end + if tooldef.tool_capabilities == nil and + (tooldef.full_punch_interval ~= nil or + tooldef.basetime ~= nil or + tooldef.dt_weight ~= nil or + tooldef.dt_crackiness ~= nil or + tooldef.dt_crumbliness ~= nil or + tooldef.dt_cuttability ~= nil or + tooldef.basedurability ~= nil or + tooldef.dd_weight ~= nil or + tooldef.dd_crackiness ~= nil or + tooldef.dd_crumbliness ~= nil or + tooldef.dd_cuttability ~= nil) then + tooldef.tool_capabilities = { + full_punch_interval = tooldef.full_punch_interval, + basetime = tooldef.basetime, + dt_weight = tooldef.dt_weight, + dt_crackiness = tooldef.dt_crackiness, + dt_crumbliness = tooldef.dt_crumbliness, + dt_cuttability = tooldef.dt_cuttability, + basedurability = tooldef.basedurability, + dd_weight = tooldef.dd_weight, + dd_crackiness = tooldef.dd_crackiness, + dd_crumbliness = tooldef.dd_crumbliness, + dd_cuttability = tooldef.dd_cuttability, + } + end + -- END Legacy stuff + + minetest.register_item(name, tooldef) +end + +function minetest.register_alias(name, convert_to) + if forbidden_item_names[name] then + error("Unable to register alias: Name is forbidden: " .. name) + end + if minetest.registered_items[name] ~= nil then + minetest.log("WARNING: Not registering alias, item with same name" .. + " is already defined: " .. name .. " -> " .. convert_to) + else + --minetest.log("Registering alias: " .. name .. " -> " .. convert_to) + minetest.registered_aliases[name] = convert_to + register_alias_raw(name, convert_to) + end +end + +local register_biome_raw = minetest.register_biome +minetest.registered_biomes = {} +function minetest.register_biome(biome) + minetest.registered_biomes[biome.name] = biome + register_biome_raw(biome) +end + +function minetest.on_craft(itemstack, player, old_craft_list, craft_inv) + for _, func in ipairs(minetest.registered_on_crafts) do + itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack + end + return itemstack +end + +function minetest.craft_predict(itemstack, player, old_craft_list, craft_inv) + for _, func in ipairs(minetest.registered_craft_predicts) do + itemstack = func(itemstack, player, old_craft_list, craft_inv) or itemstack + end + return itemstack +end + +-- Alias the forbidden item names to "" so they can't be +-- created via itemstrings (e.g. /give) +local name +for name in pairs(forbidden_item_names) do + minetest.registered_aliases[name] = "" + register_alias_raw(name, "") +end + + +-- Deprecated: +-- Aliases for minetest.register_alias (how ironic...) +--minetest.alias_node = minetest.register_alias +--minetest.alias_tool = minetest.register_alias +--minetest.alias_craftitem = minetest.register_alias + +-- +-- Built-in node definitions. Also defined in C. +-- + +minetest.register_item(":unknown", { + type = "none", + description = "Unknown Item", + inventory_image = "unknown_item.png", + on_place = minetest.item_place, + on_drop = minetest.item_drop, + groups = {not_in_creative_inventory=1}, + diggable = true, +}) + +minetest.register_node(":air", { + description = "Air (you hacker you!)", + inventory_image = "unknown_node.png", + wield_image = "unknown_node.png", + drawtype = "airlike", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, + air_equivalent = true, + drop = "", + groups = {not_in_creative_inventory=1}, +}) + +minetest.register_node(":ignore", { + description = "Ignore (you hacker you!)", + inventory_image = "unknown_node.png", + wield_image = "unknown_node.png", + drawtype = "airlike", + paramtype = "none", + sunlight_propagates = false, + walkable = false, + pointable = false, + diggable = false, + buildable_to = true, -- A way to remove accidentally placed ignores + air_equivalent = true, + drop = "", + groups = {not_in_creative_inventory=1}, +}) + +-- The hand (bare definition) +minetest.register_item(":", { + type = "none", + groups = {not_in_creative_inventory=1}, +}) + + +function minetest.override_item(name, redefinition) + if redefinition.name ~= nil then + error("Attempt to redefine name of "..name.." to "..dump(redefinition.name), 2) + end + if redefinition.type ~= nil then + error("Attempt to redefine type of "..name.." to "..dump(redefinition.type), 2) + end + local item = minetest.registered_items[name] + if not item then + error("Attempt to override non-existent item "..name, 2) + end + for k, v in pairs(redefinition) do + rawset(item, k, v) + end + register_item_raw(item) +end + + +function minetest.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 = nil + for i = 1, cb_len do + local cb_ret = callbacks[i](...) + + if mode == 0 and i == 1 then + ret = cb_ret + elseif 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) table.insert(t, func) end + return t, registerfunc +end + +local function make_registration_reverse() + local t = {} + local registerfunc = function(func) table.insert(t, 1, func) end + return t, registerfunc +end + +minetest.registered_on_chat_messages, minetest.register_on_chat_message = make_registration() +minetest.registered_globalsteps, minetest.register_globalstep = make_registration() +minetest.registered_playerevents, minetest.register_playerevent = make_registration() +minetest.registered_on_mapgen_inits, minetest.register_on_mapgen_init = make_registration() +minetest.registered_on_shutdown, minetest.register_on_shutdown = make_registration() +minetest.registered_on_punchnodes, minetest.register_on_punchnode = make_registration() +minetest.registered_on_placenodes, minetest.register_on_placenode = make_registration() +minetest.registered_on_dignodes, minetest.register_on_dignode = make_registration() +minetest.registered_on_generateds, minetest.register_on_generated = make_registration() +minetest.registered_on_newplayers, minetest.register_on_newplayer = make_registration() +minetest.registered_on_dieplayers, minetest.register_on_dieplayer = make_registration() +minetest.registered_on_respawnplayers, minetest.register_on_respawnplayer = make_registration() +minetest.registered_on_prejoinplayers, minetest.register_on_prejoinplayer = make_registration() +minetest.registered_on_joinplayers, minetest.register_on_joinplayer = make_registration() +minetest.registered_on_leaveplayers, minetest.register_on_leaveplayer = make_registration() +minetest.registered_on_player_receive_fields, minetest.register_on_player_receive_fields = make_registration_reverse() +minetest.registered_on_cheats, minetest.register_on_cheat = make_registration() +minetest.registered_on_crafts, minetest.register_on_craft = make_registration() +minetest.registered_craft_predicts, minetest.register_craft_predict = make_registration() +minetest.registered_on_protection_violation, minetest.register_on_protection_violation = make_registration() + diff --git a/builtin/game/statbars.lua b/builtin/game/statbars.lua new file mode 100644 index 000000000..ca656a974 --- /dev/null +++ b/builtin/game/statbars.lua @@ -0,0 +1,160 @@ + +local health_bar_definition = +{ + hud_elem_type = "statbar", + position = { x=0.5, y=1 }, + text = "heart.png", + number = 20, + direction = 0, + size = { x=24, y=24 }, + offset = { x=(-10*24)-25, y=-(48+24+10)}, +} + +local breath_bar_definition = +{ + hud_elem_type = "statbar", + position = { x=0.5, y=1 }, + text = "bubble.png", + number = 20, + direction = 0, + size = { x=24, y=24 }, + offset = {x=25,y=-(48+24+10)}, +} + +local hud_ids = {} + +local function initialize_builtin_statbars(player) + + if not player:is_player() then + return + end + + local name = player:get_player_name() + + if name == "" then + return + end + + if (hud_ids[name] == nil) then + hud_ids[name] = {} + end + + if player:hud_get_flags().healthbar then + if hud_ids[name].id_healthbar == nil then + health_bar_definition.number = player:get_hp() + hud_ids[name].id_healthbar = player:hud_add(health_bar_definition) + end + else + if hud_ids[name].id_healthbar ~= nil then + player:hud_remove(hud_ids[name].id_healthbar) + hud_ids[name].id_healthbar = nil + end + end + + if (player:get_breath() < 11) then + if player:hud_get_flags().breathbar then + if hud_ids[name].id_breathbar == nil then + hud_ids[name].id_breathbar = player:hud_add(breath_bar_definition) + end + else + if hud_ids[name].id_breathbar ~= nil then + player:hud_remove(hud_ids[name].id_breathbar) + hud_ids[name].id_breathbar = nil + end + end + elseif hud_ids[name].id_breathbar ~= nil then + player:hud_remove(hud_ids[name].id_breathbar) + hud_ids[name].id_breathbar = nil + end +end + +local function cleanup_builtin_statbars(player) + + if not player:is_player() then + return + end + + local name = player:get_player_name() + + if name == "" then + return + end + + hud_ids[name] = nil +end + +local function player_event_handler(player,eventname) + assert(player:is_player()) + + local name = player:get_player_name() + + if name == "" then + return + end + + if eventname == "health_changed" then + initialize_builtin_statbars(player) + + if hud_ids[name].id_healthbar ~= nil then + player:hud_change(hud_ids[name].id_healthbar,"number",player:get_hp()) + return true + end + end + + if eventname == "breath_changed" then + initialize_builtin_statbars(player) + + if hud_ids[name].id_breathbar ~= nil then + player:hud_change(hud_ids[name].id_breathbar,"number",player:get_breath()*2) + return true + end + end + + if eventname == "hud_changed" then + initialize_builtin_statbars(player) + return true + end + + return false +end + +function minetest.hud_replace_builtin(name, definition) + + if definition == nil or + type(definition) ~= "table" or + definition.hud_elem_type ~= "statbar" then + return false + end + + if name == "health" then + health_bar_definition = definition + + for name,ids in pairs(hud_ids) do + local player = minetest.get_player_by_name(name) + if player and hud_ids[name].id_healthbar then + player:hud_remove(hud_ids[name].id_healthbar) + initialize_builtin_statbars(player) + end + end + return true + end + + if name == "breath" then + breath_bar_definition = definition + + for name,ids in pairs(hud_ids) do + local player = minetest.get_player_by_name(name) + if player and hud_ids[name].id_breathbar then + player:hud_remove(hud_ids[name].id_breathbar) + initialize_builtin_statbars(player) + end + end + return true + end + + return false +end + +minetest.register_on_joinplayer(initialize_builtin_statbars) +minetest.register_on_leaveplayer(cleanup_builtin_statbars) +minetest.register_playerevent(player_event_handler) diff --git a/builtin/game/static_spawn.lua b/builtin/game/static_spawn.lua new file mode 100644 index 000000000..e8c107d86 --- /dev/null +++ b/builtin/game/static_spawn.lua @@ -0,0 +1,33 @@ +-- Minetest: builtin/static_spawn.lua + +local function warn_invalid_static_spawnpoint() + if minetest.setting_get("static_spawnpoint") and + not minetest.setting_get_pos("static_spawnpoint") then + minetest.log('error', "The static_spawnpoint setting is invalid: \"".. + minetest.setting_get("static_spawnpoint").."\"") + end +end + +warn_invalid_static_spawnpoint() + +local function put_player_in_spawn(obj) + warn_invalid_static_spawnpoint() + local static_spawnpoint = minetest.setting_get_pos("static_spawnpoint") + if not static_spawnpoint then + return false + end + minetest.log('action', "Moving "..obj:get_player_name().. + " to static spawnpoint at ".. + minetest.pos_to_string(static_spawnpoint)) + obj:setpos(static_spawnpoint) + return true +end + +minetest.register_on_newplayer(function(obj) + put_player_in_spawn(obj) +end) + +minetest.register_on_respawnplayer(function(obj) + return put_player_in_spawn(obj) +end) + diff --git a/builtin/game/voxelarea.lua b/builtin/game/voxelarea.lua new file mode 100644 index 000000000..93bbf73a8 --- /dev/null +++ b/builtin/game/voxelarea.lua @@ -0,0 +1,103 @@ +VoxelArea = { + MinEdge = {x=1, y=1, z=1}, + MaxEdge = {x=0, y=0, z=0}, + ystride = 0, + zstride = 0, +} + +function VoxelArea:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + + local e = o:getExtent() + o.ystride = e.x + o.zstride = e.x * e.y + + return o +end + +function VoxelArea:getExtent() + return { + x = self.MaxEdge.x - self.MinEdge.x + 1, + y = self.MaxEdge.y - self.MinEdge.y + 1, + z = self.MaxEdge.z - self.MinEdge.z + 1, + } +end + +function VoxelArea:getVolume() + local e = self:getExtent() + return e.x * e.y * e.z +end + +function VoxelArea:index(x, y, z) + local i = (z - self.MinEdge.z) * self.zstride + + (y - self.MinEdge.y) * self.ystride + + (x - self.MinEdge.x) + 1 + return math.floor(i) +end + +function VoxelArea:indexp(p) + local i = (p.z - self.MinEdge.z) * self.zstride + + (p.y - self.MinEdge.y) * self.ystride + + (p.x - self.MinEdge.x) + 1 + return math.floor(i) +end + +function VoxelArea:position(i) + local p = {} + + i = i - 1 + + p.z = math.floor(i / self.zstride) + self.MinEdge.z + i = i % self.zstride + + p.y = math.floor(i / self.ystride) + self.MinEdge.y + i = i % self.ystride + + p.x = math.floor(i) + self.MinEdge.x + + return p +end + +function VoxelArea:contains(x, y, z) + return (x >= self.MinEdge.x) and (x <= self.MaxEdge.x) and + (y >= self.MinEdge.y) and (y <= self.MaxEdge.y) and + (z >= self.MinEdge.z) and (z <= self.MaxEdge.z) +end + +function VoxelArea:containsp(p) + return (p.x >= self.MinEdge.x) and (p.x <= self.MaxEdge.x) and + (p.y >= self.MinEdge.y) and (p.y <= self.MaxEdge.y) and + (p.z >= self.MinEdge.z) and (p.z <= self.MaxEdge.z) +end + +function VoxelArea:containsi(i) + return (i >= 1) and (i <= self:getVolume()) +end + +function VoxelArea:iter(minx, miny, minz, maxx, maxy, maxz) + local i = self:index(minx, miny, minz) - 1 + local last = self:index(maxx, maxy, maxz) + local ystride = self.ystride + local zstride = self.zstride + local yoff = (last+1) % ystride + local zoff = (last+1) % zstride + local ystridediff = (i - last) % ystride + local zstridediff = (i - last) % zstride + return function() + i = i + 1 + if i % zstride == zoff then + i = i + zstridediff + elseif i % ystride == yoff then + i = i + ystridediff + end + if i <= last then + return i + end + end +end + +function VoxelArea:iterp(minp, maxp) + return self:iter(minp.x, minp.y, minp.z, maxp.x, maxp.y, maxp.z) +end -- cgit v1.2.3