diff options
Diffstat (limited to 'builtin/game')
-rw-r--r-- | builtin/game/auth.lua | 2 | ||||
-rw-r--r-- | builtin/game/chatcommands.lua | 166 | ||||
-rw-r--r-- | builtin/game/constants.lua | 17 | ||||
-rw-r--r-- | builtin/game/deprecated.lua | 16 | ||||
-rw-r--r-- | builtin/game/falling.lua | 176 | ||||
-rw-r--r-- | builtin/game/features.lua | 7 | ||||
-rw-r--r-- | builtin/game/init.lua | 4 | ||||
-rw-r--r-- | builtin/game/item.lua | 89 | ||||
-rw-r--r-- | builtin/game/item_entity.lua | 24 | ||||
-rw-r--r-- | builtin/game/misc.lua | 157 | ||||
-rw-r--r-- | builtin/game/privileges.lua | 1 | ||||
-rw-r--r-- | builtin/game/register.lua | 34 | ||||
-rw-r--r-- | builtin/game/static_spawn.lua | 22 |
13 files changed, 461 insertions, 254 deletions
diff --git a/builtin/game/auth.lua b/builtin/game/auth.lua index 423eb3134..deb811b14 100644 --- a/builtin/game/auth.lua +++ b/builtin/game/auth.lua @@ -20,7 +20,7 @@ function core.privs_to_string(privs, delim) local list = {} for priv, bool in pairs(privs) do if bool then - table.insert(list, priv) + list[#list + 1] = priv end end return table.concat(list, delim) diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 5d317de4b..3350140ee 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -51,6 +51,27 @@ core.register_on_chat_message(function(name, message) return true -- Handled chat message end) +-- Parses a "range" string in the format of "here (number)" or +-- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors +local function parse_range_str(player_name, str) + local p1, p2 + local args = str:split(" ") + + if args[1] == "here" then + p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2])) + if p1 == nil then + return false, "Unable to get player " .. player_name .. " position" + end + else + p1, p2 = core.string_to_area(str) + if p1 == nil then + return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" + end + end + + return p1, p2 +end + -- -- Chat commands -- @@ -63,6 +84,18 @@ core.register_chatcommand("me", { end, }) +core.register_chatcommand("admin", { + description = "Show the name of the server owner", + func = function(name) + local admin = minetest.setting_get("name") + if admin then + return true, "The administrator of this server is "..admin.."." + else + return false, "There's no administrator named in the config file." + end + end, +}) + core.register_chatcommand("help", { privs = {}, params = "[all/privs/<cmd>]", @@ -83,7 +116,7 @@ core.register_chatcommand("help", { local cmds = {} for cmd, def in pairs(core.chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, cmd) + cmds[#cmds + 1] = cmd end end table.sort(cmds) @@ -94,7 +127,7 @@ core.register_chatcommand("help", { local cmds = {} for cmd, def in pairs(core.chatcommands) do if core.check_player_privs(name, def.privs) then - table.insert(cmds, format_help_line(cmd, def)) + cmds[#cmds + 1] = format_help_line(cmd, def) end end table.sort(cmds) @@ -102,7 +135,7 @@ core.register_chatcommand("help", { elseif param == "privs" then local privs = {} for priv, def in pairs(core.registered_privileges) do - table.insert(privs, priv .. ": " .. def.description) + privs[#privs + 1] = priv .. ": " .. def.description end table.sort(privs) return true, "Available privileges:\n"..table.concat(privs, "\n") @@ -148,8 +181,10 @@ core.register_chatcommand("grant", { end local privs = core.get_player_privs(grantname) local privs_unknown = "" + local basic_privs = + core.string_to_privs(core.setting_get("basic_privs") or "interact,shout") for priv, _ in pairs(grantprivs) do - if priv ~= "interact" and priv ~= "shout" and + if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then return false, "Your privileges are insufficient." end @@ -190,8 +225,10 @@ core.register_chatcommand("revoke", { end local revoke_privs = core.string_to_privs(revoke_priv_str) local privs = core.get_player_privs(revoke_name) + local basic_privs = + core.string_to_privs(core.setting_get("basic_privs") or "interact,shout") for priv, _ in pairs(revoke_privs) do - if priv ~= "interact" and priv ~= "shout" and + if not basic_privs[priv] and not core.check_player_privs(name, {privs=true}) then return false, "Your privileges are insufficient." end @@ -315,10 +352,16 @@ core.register_chatcommand("teleport", { p.x = tonumber(p.x) p.y = tonumber(p.y) p.z = tonumber(p.z) - teleportee = core.get_player_by_name(name) - if teleportee and p.x and p.y and p.z then - teleportee:setpos(p) - return true, "Teleporting to "..core.pos_to_string(p) + if p.x and p.y and p.z then + local lm = tonumber(minetest.setting_get("map_generation_limit") or 31000) + if p.x < -lm or p.x > lm or p.y < -lm or p.y > lm or p.z < -lm or p.z > lm then + return false, "Cannot teleport out of map bounds!" + end + teleportee = core.get_player_by_name(name) + if teleportee then + teleportee:setpos(p) + return true, "Teleporting to "..core.pos_to_string(p) + end end local teleportee = nil @@ -415,40 +458,65 @@ core.register_chatcommand("set", { end, }) -core.register_chatcommand("deleteblocks", { +local function emergeblocks_callback(pos, action, num_calls_remaining, ctx) + if ctx.total_blocks == 0 then + ctx.total_blocks = num_calls_remaining + 1 + ctx.current_blocks = 0 + end + ctx.current_blocks = ctx.current_blocks + 1 + + if ctx.current_blocks == ctx.total_blocks then + core.chat_send_player(ctx.requestor_name, + string.format("Finished emerging %d blocks in %.2fms.", + ctx.total_blocks, (os.clock() - ctx.start_time) * 1000)) + end +end + +local function emergeblocks_progress_update(ctx) + if ctx.current_blocks ~= ctx.total_blocks then + core.chat_send_player(ctx.requestor_name, + string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)", + ctx.current_blocks, ctx.total_blocks, + (ctx.current_blocks / ctx.total_blocks) * 100)) + + core.after(2, emergeblocks_progress_update, ctx) + end +end + +core.register_chatcommand("emergeblocks", { params = "(here [radius]) | (<pos1> <pos2>)", - description = "delete map blocks contained in area pos1 to pos2", + description = "starts loading (or generating, if inexistent) map blocks " + .. "contained in area pos1 to pos2", privs = {server=true}, func = function(name, param) - local p1 = {} - local p2 = {} - local args = param:split(" ") - if args[1] == "here" then - local player = core.get_player_by_name(name) - if player == nil then - core.log("error", "player is nil") - return false, "Unable to get current position; player is nil" - end - p1 = player:getpos() - p2 = p1 + local p1, p2 = parse_range_str(name, param) + if p1 == false then + return false, p2 + end - if #args >= 2 then - local radius = tonumber(args[2]) or 0 - p1 = vector.add(p1, radius) - p2 = vector.subtract(p2, radius) - end - else - local pos1, pos2 = unpack(param:split(") (")) - if pos1 == nil or pos2 == nil then - return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" - end + local context = { + current_blocks = 0, + total_blocks = 0, + start_time = os.clock(), + requestor_name = name + } - p1 = core.string_to_pos(pos1 .. ")") - p2 = core.string_to_pos("(" .. pos2) + core.emerge_area(p1, p2, emergeblocks_callback, context) + core.after(2, emergeblocks_progress_update, context) - if p1 == nil or p2 == nil then - return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)" - end + return true, "Started emerge of area ranging from " .. + core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) + end, +}) + +core.register_chatcommand("deleteblocks", { + params = "(here [radius]) | (<pos1> <pos2>)", + description = "delete map blocks contained in area pos1 to pos2", + privs = {server=true}, + func = function(name, param) + local p1, p2 = parse_range_str(name, param) + if p1 == false then + return false, p2 end if core.delete_area(p1, p2) then @@ -698,7 +766,7 @@ core.register_chatcommand("time", { local hour = (current_time - minutes) / 60 return true, ("Current time is %d:%02d"):format(hour, minutes) end - local player_privs = minetest.get_player_privs(name) + local player_privs = core.get_player_privs(name) if not player_privs.settime then return false, "You don't have permission to run this command " .. "(missing privilege: settime)." @@ -727,6 +795,13 @@ core.register_chatcommand("time", { end, }) +core.register_chatcommand("days", { + description = "Display day count", + func = function(name, param) + return true, "Current day is " .. core.get_day_count() + end +}) + core.register_chatcommand("shutdown", { description = "shutdown server", privs = {server=true}, @@ -790,14 +865,25 @@ core.register_chatcommand("kick", { }) core.register_chatcommand("clearobjects", { + params = "[full|quick]", description = "clear all objects in world", privs = {server=true}, func = function(name, param) - core.log("action", name .. " clears all objects.") + local options = {} + if param == "" or param == "full" then + options.mode = "full" + elseif param == "quick" then + options.mode = "quick" + else + return false, "Invalid usage, see /help clearobjects." + end + + core.log("action", name .. " clears all objects (" + .. options.mode .. " mode).") core.chat_send_all("Clearing all objects. This may take long." .. " You may experience a timeout. (by " .. name .. ")") - core.clear_objects() + core.clear_objects(options) core.log("action", "Object clearing done.") core.chat_send_all("*** Cleared all objects.") end, diff --git a/builtin/game/constants.lua b/builtin/game/constants.lua new file mode 100644 index 000000000..d0b7c753c --- /dev/null +++ b/builtin/game/constants.lua @@ -0,0 +1,17 @@ +-- Minetest: builtin/constants.lua + +-- +-- Constants values for use with the Lua API +-- + +-- Built-in Content IDs (for use with VoxelManip API) +core.CONTENT_UNKNOWN = 125 +core.CONTENT_AIR = 126 +core.CONTENT_IGNORE = 127 + +-- Block emerge status constants (for use with core.emerge_area) +core.EMERGE_CANCELLED = 0 +core.EMERGE_ERRORED = 1 +core.EMERGE_FROM_MEMORY = 2 +core.EMERGE_FROM_DISK = 3 +core.EMERGE_GENERATED = 4 diff --git a/builtin/game/deprecated.lua b/builtin/game/deprecated.lua index bbe68be3e..cd1cf5e2d 100644 --- a/builtin/game/deprecated.lua +++ b/builtin/game/deprecated.lua @@ -3,9 +3,8 @@ -- -- Default material types -- -function digprop_err() - core.log("info", debug.traceback()) - core.log("info", "WARNING: The core.digprop_* functions are obsolete and need to be replaced by item groups.") +local function digprop_err() + core.log("deprecated", "The core.digprop_* functions are obsolete and need to be replaced by item groups.") end core.digprop_constanttime = digprop_err @@ -16,12 +15,12 @@ core.digprop_woodlike = digprop_err core.digprop_leaveslike = digprop_err core.digprop_glasslike = digprop_err -core.node_metadata_inventory_move_allow_all = function() - core.log("info", "WARNING: core.node_metadata_inventory_move_allow_all is obsolete and does nothing.") +function core.node_metadata_inventory_move_allow_all() + core.log("deprecated", "core.node_metadata_inventory_move_allow_all is obsolete and does nothing.") end -core.add_to_creative_inventory = function(itemstring) - core.log('info', "WARNING: core.add_to_creative_inventory: This function is deprecated and does nothing.") +function core.add_to_creative_inventory(itemstring) + core.log("deprecated", "core.add_to_creative_inventory: This function is deprecated and does nothing.") end -- @@ -32,7 +31,7 @@ local envref_deprecation_message_printed = false setmetatable(core.env, { __index = function(table, key) if not envref_deprecation_message_printed then - core.log("info", "WARNING: core.env:[...] is deprecated and should be replaced with core.[...]") + core.log("deprecated", "core.env:[...] is deprecated and should be replaced with core.[...]") envref_deprecation_message_printed = true end local func = core[key] @@ -50,4 +49,3 @@ setmetatable(core.env, { function core.rollback_get_last_node_actor(pos, range, seconds) return core.rollback_get_node_actions(pos, range, seconds, 1)[1] end - diff --git a/builtin/game/falling.lua b/builtin/game/falling.lua index 58f68fc56..57bb98cfd 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -6,52 +6,49 @@ core.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", + visual_size = {x = 0.667, y = 0.667}, textures = {}, - visual_size = {x=0.667, y=0.667}, + physical = true, + is_visible = false, + collide_with_objects = false, + collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, }, 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 core.registered_items[itemname] then - item_texture = core.registered_items[itemname].inventory_image - item_type = core.registered_items[itemname].type - end - local prop = { + self.object:set_properties({ is_visible = true, textures = {node.name}, - } - self.object:set_properties(prop) + }) end, get_staticdata = function(self) - return self.node.name + return core.serialize(self.node) end, on_activate = function(self, staticdata) - self.object:set_armor_groups({immortal=1}) - self:set_node({name=staticdata}) + self.object:set_armor_groups({immortal = 1}) + + local node = core.deserialize(staticdata) + if node then + self:set_node(node) + elseif staticdata ~= "" then + self:set_node({name = staticdata}) + end end, on_step = function(self, dtime) - -- Set gravity - self.object:setacceleration({x=0, y=-10, z=0}) + -- Set gravity + local acceleration = self.object:getacceleration() + if not vector.equals(acceleration, {x = 0, y = -10, z = 0}) then + self.object:setacceleration({x = 0, y = -10, z = 0}) + end -- 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 bcp = {x = pos.x, y = pos.y - 0.7, z = pos.z} -- Position of bottom center point local bcn = core.get_node(bcp) local bcd = core.registered_nodes[bcn.name] -- Note: walkable is in the node definition, not in item groups @@ -62,7 +59,7 @@ core.register_entity(":__builtin:falling_node", { if bcd and bcd.leveled and bcn.name == self.node.name then local addlevel = self.node.level - if addlevel == nil or addlevel <= 0 then + if not addlevel or addlevel <= 0 then addlevel = bcd.leveled end if core.add_node_level(bcp, addlevel) == 0 then @@ -75,7 +72,7 @@ core.register_entity(":__builtin:falling_node", { core.remove_node(bcp) return end - local np = {x=bcp.x, y=bcp.y+1, z=bcp.z} + local np = {x = bcp.x, y = bcp.y + 1, z = bcp.z} -- Check what's here local n2 = core.get_node(np) -- If it's not air or liquid, remove node and replace it with @@ -86,25 +83,25 @@ core.register_entity(":__builtin:falling_node", { if core.registered_nodes[n2.name].buildable_to == false then -- Add dropped items local drops = core.get_node_drops(n2.name, "") - local _, dropped_item for _, dropped_item in ipairs(drops) do core.add_item(np, dropped_item) end end -- Run script hook - local _, callback for _, callback in ipairs(core.registered_on_dignodes) do - callback(np, n2, nil) + callback(np, n2) end end -- Create node and remove entity - core.add_node(np, self.node) + if core.registered_nodes[self.node.name] then + core.add_node(np, self.node) + end self.object:remove() nodeupdate(np) return end local vel = self.object:getvelocity() - if vector.equals(vel, {x=0,y=0,z=0}) then + if vector.equals(vel, {x = 0, y = 0, z = 0}) then local npos = self.object:getpos() self.object:setpos(vector.round(npos)) end @@ -119,7 +116,7 @@ end function drop_attached_node(p) local nn = core.get_node(p).name core.remove_node(p) - for _,item in ipairs(core.get_node_drops(nn, "")) do + for _, item in ipairs(core.get_node_drops(nn, "")) do local pos = { x = p.x + math.random()/2 - 0.25, y = p.y + math.random()/2 - 0.25, @@ -131,25 +128,13 @@ end function check_attached_node(p, n) local def = core.registered_nodes[n.name] - local d = {x=0, y=0, z=0} + 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 + d = core.wallmounted_to_dir(n.param2) else d.y = -1 end - local p2 = {x=p.x+d.x, y=p.y+d.y, z=p.z+d.z} + local p2 = vector.add(p, d) local nn = core.get_node(p2).name local def2 = core.registered_nodes[nn] if def2 and not def2.walkable then @@ -162,10 +147,10 @@ end -- Some common functions -- -function nodeupdate_single(p, delay) +function nodeupdate_single(p) local n = core.get_node(p) if core.get_item_group(n.name, "falling_node") ~= 0 then - local p_bottom = {x=p.x, y=p.y-1, z=p.z} + local p_bottom = {x = p.x, y = p.y - 1, z = p.z} local n_bottom = core.get_node(p_bottom) -- Note: walkable is in the node definition, not in item groups if core.registered_nodes[n_bottom.name] and @@ -175,37 +160,88 @@ function nodeupdate_single(p, delay) core.get_node_level(p_bottom) < core.get_node_max_level(p_bottom))) and (not core.registered_nodes[n_bottom.name].walkable or core.registered_nodes[n_bottom.name].buildable_to) then - if delay then - core.after(0.1, nodeupdate_single, {x=p.x, y=p.y, z=p.z}, false) - else - n.level = core.get_node_level(p) - core.remove_node(p) - spawn_falling_node(p, n) - nodeupdate(p) - end + n.level = core.get_node_level(p) + core.remove_node(p) + spawn_falling_node(p, n) + return true end end if core.get_item_group(n.name, "attached_node") ~= 0 then if not check_attached_node(p, n) then drop_attached_node(p) - nodeupdate(p) + return true end end + + return false 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) +-- This table is specifically ordered. +-- We don't walk diagonals, only our direct neighbors, and self. +-- Down first as likely case, but always before self. The same with sides. +-- Up must come last, so that things above self will also fall all at once. +local nodeupdate_neighbors = { + {x = -1, y = -1, z = 0}, + {x = 1, y = -1, z = 0}, + {x = 0, y = -1, z = -1}, + {x = 0, y = -1, z = 1}, + {x = 0, y = -1, z = 0}, + {x = -1, y = 0, z = 0}, + {x = 1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = 0, y = 0, z = -1}, + {x = 0, y = 0, z = 0}, + {x = 0, y = 1, z = 0}, +} - 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 +function nodeupdate(p) + -- Round p to prevent falling entities to get stuck. + p = vector.round(p) + + -- We make a stack, and manually maintain size for performance. + -- Stored in the stack, we will maintain tables with pos, and + -- last neighbor visited. This way, when we get back to each + -- node, we know which directions we have already walked, and + -- which direction is the next to walk. + local s = {} + local n = 0 + -- The neighbor order we will visit from our table. + local v = 1 + + while true do + -- Push current pos onto the stack. + n = n + 1 + s[n] = {p = p, v = v} + -- Select next node from neighbor list. + p = vector.add(p, nodeupdate_neighbors[v]) + -- Now we check out the node. If it is in need of an update, + -- it will let us know in the return value (true = updated). + if not nodeupdate_single(p) then + -- If we don't need to "recurse" (walk) to it then pop + -- our previous pos off the stack and continue from there, + -- with the v value we were at when we last were at that + -- node + repeat + local pop = s[n] + p = pop.p + v = pop.v + s[n] = nil + n = n - 1 + -- If there's nothing left on the stack, and no + -- more sides to walk to, we're done and can exit + if n == 0 and v == 11 then + return + end + until v < 11 + -- The next round walk the next neighbor in list. + v = v + 1 + else + -- If we did need to walk the neighbor, then + -- start walking it from the walk order start (1), + -- and not the order we just pushed up the stack. + v = 1 + end end end diff --git a/builtin/game/features.lua b/builtin/game/features.lua index f082b0db8..2aad458da 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -8,13 +8,14 @@ core.features = { use_texture_alpha = true, no_legacy_abms = true, texture_names_parens = true, + area_store_custom_ids = true, } function core.has_feature(arg) if type(arg) == "table" then - missing_features = {} - result = true - for ft, _ in pairs(arg) do + local missing_features = {} + local result = true + for ftr in pairs(arg) do if not core.features[ftr] then missing_features[ftr] = true result = false diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 3f82f85c7..a6cfa3bf8 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -1,10 +1,11 @@ -local scriptpath = minetest.get_builtin_path()..DIR_DELIM +local scriptpath = core.get_builtin_path()..DIR_DELIM local commonpath = scriptpath.."common"..DIR_DELIM local gamepath = scriptpath.."game"..DIR_DELIM dofile(commonpath.."vector.lua") +dofile(gamepath.."constants.lua") dofile(gamepath.."item.lua") dofile(gamepath.."register.lua") @@ -25,4 +26,3 @@ 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 index 6628a4081..36c2c1a68 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -27,19 +27,11 @@ function core.get_pointed_thing_position(pointed_thing, above) 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 + -- The position where a node would be dug + return pointed_thing.under elseif pointed_thing.type == "object" then - obj = pointed_thing.ref - if obj ~= nil then - return obj:getpos() - else - return nil - end - else - return nil + return pointed_thing.ref and pointed_thing.ref:getpos() end end @@ -96,25 +88,26 @@ function core.dir_to_facedir(dir, is6d) end end +-- Table of possible dirs +local facedir_to_dir = { + {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}, +} +-- Mapping from facedir value to index in facedir_to_dir. +local facedir_to_dir_map = { + [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, +} function core.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]] + return facedir_to_dir[facedir_to_dir_map[facedir]] end function core.dir_to_wallmounted(dir) @@ -139,6 +132,19 @@ function core.dir_to_wallmounted(dir) end end +-- table of dirs in wallmounted order +local wallmounted_to_dir = { + [0] = {x = 0, y = 1, z = 0}, + {x = 0, y = -1, z = 0}, + {x = 1, y = 0, z = 0}, + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = 0, y = 0, z = -1}, +} +function core.wallmounted_to_dir(wallmounted) + return wallmounted_to_dir[wallmounted] +end + function core.get_node_drops(nodename, toolname) local drop = ItemStack({name=nodename}):get_definition().drop if drop == nil then @@ -227,7 +233,8 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2) place_to = {x = under.x, y = under.y, z = under.z} end - if core.is_protected(place_to, placer:get_player_name()) then + if core.is_protected(place_to, placer:get_player_name()) and + not minetest.check_player_privs(placer, "protection_bypass") then core.log("action", placer:get_player_name() .. " tried to place " .. def.name .. " at protected position " @@ -334,8 +341,12 @@ function core.item_place(itemstack, placer, pointed_thing, param2) return itemstack end +function core.item_secondary_use(itemstack, placer) + return itemstack +end + function core.item_drop(itemstack, dropper, pos) - if dropper.is_player then + if dropper and dropper:is_player() then local v = dropper:get_look_dir() local p = {x=pos.x, y=pos.y+1.2, z=pos.z} local cs = itemstack:get_count() @@ -349,12 +360,17 @@ function core.item_drop(itemstack, dropper, pos) v.y = v.y*2 + 2 v.z = v.z*2 obj:setvelocity(v) + obj:get_luaentity().dropped_by = dropper:get_player_name() + return itemstack end else - core.add_item(pos, itemstack) + if core.add_item(pos, itemstack) then + return itemstack + end end - return itemstack + -- If we reach this, adding the object to the + -- environment failed end function core.do_item_eat(hp_change, replace_with_item, itemstack, user, pointed_thing) @@ -429,7 +445,8 @@ function core.node_dig(pos, node, digger) return end - if core.is_protected(pos, digger:get_player_name()) then + if core.is_protected(pos, digger:get_player_name()) and + not minetest.check_player_privs(digger, "protection_bypass") then core.log("action", digger:get_player_name() .. " tried to dig " .. node.name .. " at protected position " @@ -560,6 +577,7 @@ core.nodedef_default = { diggable = true, climbable = false, buildable_to = false, + floodable = false, liquidtype = "none", liquid_alternative_flowing = "", liquid_alternative_source = "", @@ -587,6 +605,7 @@ core.craftitemdef_default = { -- Interaction callbacks on_place = redef_wrapper(core, 'item_place'), -- core.item_place on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop + on_secondary_use = redef_wrapper(core, 'item_secondary_use'), on_use = nil, } @@ -604,6 +623,7 @@ core.tooldef_default = { -- Interaction callbacks on_place = redef_wrapper(core, 'item_place'), -- core.item_place + on_secondary_use = redef_wrapper(core, 'item_secondary_use'), on_drop = redef_wrapper(core, 'item_drop'), -- core.item_drop on_use = nil, } @@ -622,6 +642,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items -- Interaction callbacks on_place = redef_wrapper(core, 'item_place'), + on_secondary_use = redef_wrapper(core, 'item_secondary_use'), on_drop = nil, on_use = nil, } diff --git a/builtin/game/item_entity.lua b/builtin/game/item_entity.lua index 6425a10aa..a66bf33d0 100644 --- a/builtin/game/item_entity.lua +++ b/builtin/game/item_entity.lua @@ -4,11 +4,14 @@ function core.spawn_item(pos, item) -- Take item in any format local stack = ItemStack(item) local obj = core.add_entity(pos, "__builtin:item") - obj:get_luaentity():set_item(stack:to_string()) + -- Don't use obj if it couldn't be added to the map. + if obj then + obj:get_luaentity():set_item(stack:to_string()) + end return obj end --- If item_entity_ttl is not set, enity will have default life time +-- If item_entity_ttl is not set, enity will have default life time -- Setting it to -1 disables the feature local time_to_live = tonumber(core.setting_get("item_entity_ttl")) @@ -28,6 +31,7 @@ core.register_entity(":__builtin:item", { spritediv = {x = 1, y = 1}, initial_sprite_basepos = {x = 0, y = 0}, is_visible = false, + infotext = "", }, itemstring = '', @@ -47,6 +51,7 @@ core.register_entity(":__builtin:item", { local c = s local itemtable = stack:to_table() local itemname = nil + local description = "" if itemtable then itemname = stack:to_table().name end @@ -55,6 +60,7 @@ core.register_entity(":__builtin:item", { if core.registered_items[itemname] then item_texture = core.registered_items[itemname].inventory_image item_type = core.registered_items[itemname].type + description = core.registered_items[itemname].description end local prop = { is_visible = true, @@ -63,6 +69,7 @@ core.register_entity(":__builtin:item", { visual_size = {x = s, y = s}, collisionbox = {-c, -c, -c, c, c, c}, automatic_rotate = math.pi * 0.5, + infotext = description, } self.object:set_properties(prop) end, @@ -71,7 +78,8 @@ core.register_entity(":__builtin:item", { return core.serialize({ itemstring = self.itemstring, always_collect = self.always_collect, - age = self.age + age = self.age, + dropped_by = self.dropped_by }) end, @@ -81,11 +89,12 @@ core.register_entity(":__builtin:item", { if data and type(data) == "table" then self.itemstring = data.itemstring self.always_collect = data.always_collect - if data.age then + if data.age then self.age = data.age + dtime_s else self.age = dtime_s end + self.dropped_by = data.dropped_by end else self.itemstring = staticdata @@ -197,9 +206,10 @@ core.register_entity(":__builtin:item", { 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 + local inv = hitter:get_inventory() + if inv and self.itemstring ~= '' then + local left = inv:add_item("main", self.itemstring) + if left and not left:is_empty() then self.itemstring = left:to_string() return end diff --git a/builtin/game/misc.lua b/builtin/game/misc.lua index e3b7d82bc..de41cfc91 100644 --- a/builtin/game/misc.lua +++ b/builtin/game/misc.lua @@ -4,88 +4,81 @@ -- Misc. API functions -- -local timers = {} -local mintime -local function update_timers(delay) - mintime = false - local sub = 0 - for index = 1, #timers do - index = index - sub - local timer = timers[index] - timer.time = timer.time - delay - if timer.time <= 0 then - core.set_last_run_mod(timer.mod_origin) - timer.func(unpack(timer.args or {})) - table.remove(timers, index) - sub = sub + 1 - elseif mintime then - mintime = math.min(mintime, timer.time) - else - mintime = timer.time - end - end -end +local jobs = {} +local time = 0.0 +local last = core.get_us_time() / 1000000 -local timers_to_add -local function add_timers() - for _, timer in ipairs(timers_to_add) do - table.insert(timers, timer) +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 - timers_to_add = false -end + last = new -local delay = 0 -core.register_globalstep(function(dtime) - if not mintime then - -- abort if no timers are running + if #jobs < 1 then return end - if timers_to_add then - add_timers() - end - delay = delay + dtime - if delay < mintime then - return + + -- 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 - update_timers(delay) - delay = 0 end) -function core.after(time, func, ...) +function core.after(after, func, ...) assert(tonumber(time) and type(func) == "function", "Invalid core.after invocation") - if not mintime then - mintime = time - timers_to_add = {{ - time = time+delay, - func = func, - args = {...}, - mod_origin = core.get_last_run_mod(), - }} - return - end - mintime = math.min(mintime, time) - timers_to_add = timers_to_add or {} - timers_to_add[#timers_to_add+1] = { - time = time+delay, - func = func, - args = {...}, - mod_origin = core.get_last_run_mod(), + jobs[#jobs + 1] = { + func = func, + expire = time + after, + arg = {...}, + mod_origin = core.get_last_run_mod() } end -function core.check_player_privs(name, privs) +function core.check_player_privs(player_or_name, ...) + local name = player_or_name + -- Check if we have been provided with a Player object. + if type(name) ~= "string" then + name = name:get_player_name() + end + + local requested_privs = {...} local player_privs = core.get_player_privs(name) local missing_privileges = {} - for priv, val in pairs(privs) do - if val - and not player_privs[priv] then - table.insert(missing_privileges, priv) + + if type(requested_privs[1]) == "table" then + -- We were provided with a table like { privA = true, privB = true }. + for priv, value in pairs(requested_privs[1]) do + if value and not player_privs[priv] then + missing_privileges[#missing_privileges + 1] = priv + end + end + else + -- Only a list, we can process it directly. + for key, priv in pairs(requested_privs) do + if not player_privs[priv] then + missing_privileges[#missing_privileges + 1] = priv + end end end + if #missing_privileges > 0 then return false, missing_privileges end + return true, "" end @@ -103,12 +96,31 @@ function core.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) + temp_table[#temp_table + 1] = value end end return temp_table end +-- Returns two position vectors representing a box of `radius` in each +-- direction centered around the player corresponding to `player_name` +function core.get_player_radius_area(player_name, radius) + local player = core.get_player_by_name(player_name) + if player == nil then + return nil + end + + local p1 = player:getpos() + local p2 = p1 + + if radius then + p1 = vector.subtract(p1, radius) + p2 = vector.add(p2, radius) + end + + return p1, p2 +end + function core.hash_node_position(pos) return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768 end @@ -166,3 +178,22 @@ function core.raillike_group(name) end return id end + +-- HTTP callback interface +function core.http_add_fetch(httpenv) + httpenv.fetch = function(req, callback) + local handle = httpenv.fetch_async(req) + + local function update_http_status() + local res = httpenv.fetch_async_get(handle) + if res.completed then + callback(res) + else + core.after(0, update_http_status) + end + end + core.after(0, update_http_status) + end + + return httpenv +end diff --git a/builtin/game/privileges.lua b/builtin/game/privileges.lua index 7e6387c72..bd5ead624 100644 --- a/builtin/game/privileges.lua +++ b/builtin/game/privileges.lua @@ -32,6 +32,7 @@ core.register_privilege("settime", "Can use /time") core.register_privilege("privs", "Can modify privileges") core.register_privilege("basic_privs", "Can modify 'shout' and 'interact' privileges") core.register_privilege("server", "Can do server maintenance stuff") +core.register_privilege("protection_bypass", "Can bypass node protection in the world") core.register_privilege("shout", "Can speak in chat") core.register_privilege("ban", "Can ban and unban players") core.register_privilege("kick", "Can kick players") diff --git a/builtin/game/register.lua b/builtin/game/register.lua index d0e04bfc3..f330491a2 100644 --- a/builtin/game/register.lua +++ b/builtin/game/register.lua @@ -11,10 +11,11 @@ local register_alias_raw = core.register_alias_raw core.register_alias_raw = nil -- --- Item / entity / ABM registration functions +-- Item / entity / ABM / LBM registration functions -- core.registered_abms = {} +core.registered_lbms = {} core.registered_entities = {} core.registered_items = {} core.registered_nodes = {} @@ -51,27 +52,38 @@ local forbidden_item_names = { local function check_modname_prefix(name) if name:sub(1,1) == ":" then - -- Escape the modname prefix enforcement mechanism + -- If the name starts with a colon, we can skip the modname prefix + -- mechanism. return name:sub(2) else - -- Modname prefix enforcement + -- Enforce that the name starts with the correct mod name. local expected_prefix = core.get_current_modname() .. ":" if name:sub(1, #expected_prefix) ~= expected_prefix then error("Name " .. name .. " does not follow naming conventions: " .. - "\"modname:\" or \":\" prefix required") + "\"" .. expected_prefix .. "\" or \":\" prefix required") end + + -- Enforce that the name only contains letters, numbers and underscores. local subname = name:sub(#expected_prefix+1) - if subname:find("[^abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_]") then + if subname:find("[^%w_]") then error("Name " .. name .. " does not follow naming conventions: " .. "contains unallowed characters") end + return name end end function core.register_abm(spec) -- Add to core.registered_abms - core.registered_abms[#core.registered_abms+1] = spec + core.registered_abms[#core.registered_abms + 1] = spec + spec.mod_origin = core.get_current_modname() or "??" +end + +function core.register_lbm(spec) + -- Add to core.registered_lbms + check_modname_prefix(spec.name) + core.registered_lbms[#core.registered_lbms + 1] = spec spec.mod_origin = core.get_current_modname() or "??" end @@ -221,7 +233,7 @@ function core.register_alias(name, convert_to) error("Unable to register alias: Name is forbidden: " .. name) end if core.registered_items[name] ~= nil then - core.log("WARNING: Not registering alias, item with same name" .. + core.log("warning", "Not registering alias, item with same name" .. " is already defined: " .. name .. " -> " .. convert_to) else --core.log("Registering alias: " .. name .. " -> " .. convert_to) @@ -268,6 +280,7 @@ core.register_item(":unknown", { description = "Unknown Item", inventory_image = "unknown_item.png", on_place = core.item_place, + on_secondary_use = core.item_secondary_use, on_drop = core.item_drop, groups = {not_in_creative_inventory=1}, diggable = true, @@ -284,6 +297,7 @@ core.register_node(":air", { pointable = false, diggable = false, buildable_to = true, + floodable = true, air_equivalent = true, drop = "", groups = {not_in_creative_inventory=1}, @@ -385,7 +399,7 @@ end local function make_registration() local t = {} local registerfunc = function(func) - table.insert(t, func) + t[#t + 1] = func core.callback_origins[func] = { mod = core.get_current_modname() or "??", name = debug.getinfo(1, "n").name or "??" @@ -461,9 +475,9 @@ end function core.register_on_player_hpchange(func, modifier) if modifier then - table.insert(core.registered_on_player_hpchanges.modifiers, func) + core.registered_on_player_hpchanges.modifiers[#core.registered_on_player_hpchanges.modifiers + 1] = func else - table.insert(core.registered_on_player_hpchanges.loggers, func) + core.registered_on_player_hpchanges.loggers[#core.registered_on_player_hpchanges.loggers + 1] = func end core.callback_origins[func] = { mod = core.get_current_modname() or "??", diff --git a/builtin/game/static_spawn.lua b/builtin/game/static_spawn.lua index 492ab6ca6..100334226 100644 --- a/builtin/game/static_spawn.lua +++ b/builtin/game/static_spawn.lua @@ -3,31 +3,23 @@ local function warn_invalid_static_spawnpoint() if core.setting_get("static_spawnpoint") and not core.setting_get_pos("static_spawnpoint") then - core.log('error', "The static_spawnpoint setting is invalid: \"".. + core.log("error", "The static_spawnpoint setting is invalid: \"".. core.setting_get("static_spawnpoint").."\"") end end warn_invalid_static_spawnpoint() -local function put_player_in_spawn(obj) - warn_invalid_static_spawnpoint() +local function put_player_in_spawn(player_obj) local static_spawnpoint = core.setting_get_pos("static_spawnpoint") if not static_spawnpoint then return false end - core.log('action', "Moving "..obj:get_player_name().. - " to static spawnpoint at ".. - core.pos_to_string(static_spawnpoint)) - obj:setpos(static_spawnpoint) + core.log("action", "Moving " .. player_obj:get_player_name() .. + " to static spawnpoint at " .. core.pos_to_string(static_spawnpoint)) + player_obj:setpos(static_spawnpoint) return true end -core.register_on_newplayer(function(obj) - put_player_in_spawn(obj) -end) - -core.register_on_respawnplayer(function(obj) - return put_player_in_spawn(obj) -end) - +core.register_on_newplayer(put_player_in_spawn) +core.register_on_respawnplayer(put_player_in_spawn) |