1 -- Minetest: builtin/misc_helpers.lua
3 --------------------------------------------------------------------------------
8 elseif tp == "string" then
9 return string.format("%q", o)
10 elseif tp == "boolean" then
12 elseif tp == "nil" then
14 -- Uncomment for full function dumping support.
15 -- Not currently enabled because bytecode isn't very human-readable and
16 -- dump's output is intended for humans.
17 --elseif tp == "function" then
18 -- return string.format("loadstring(%q)", string.dump(o))
20 return string.format("<%s>", tp)
24 --------------------------------------------------------------------------------
25 -- Dumps values in a line-per-value format.
26 -- For example, {test = {"Testing..."}} becomes:
28 -- _["test"][1] = "Testing..."
29 -- This handles tables as keys and circular references properly.
30 -- It also handles multiple references well, writing the table only once.
31 -- The dumped argument is internal-only.
33 function dump2(o, name, dumped)
35 -- "dumped" is used to keep track of serialized tables to handle
36 -- multiple references and circular tables properly.
37 -- It only contains tables as keys. The value is the name that
38 -- the table has in the dump, eg:
39 -- {x = {"y"}} -> dumped[{"y"}] = '_["x"]'
41 if type(o) ~= "table" then
42 return string.format("%s = %s\n", name, basic_dump(o))
45 return string.format("%s = %s\n", name, dumped[o])
48 -- This contains a list of strings to be concatenated later (because
49 -- Lua is slow at individual concatenation).
51 for k, v in pairs(o) do
53 if type(k) == "table" then
57 -- Key tables don't have a name, so use one of
58 -- the form _G["table: 0xFFFFFFF"]
59 keyStr = string.format("_G[%q]", tostring(k))
61 table.insert(t, dump2(k, keyStr, dumped))
64 keyStr = basic_dump(k)
66 local vname = string.format("%s[%s]", name, keyStr)
67 table.insert(t, dump2(v, vname, dumped))
69 return string.format("%s = {}\n%s", name, table.concat(t))
72 --------------------------------------------------------------------------------
73 -- This dumps values in a one-line format, like serialize().
74 -- For example, {test = {"Testing..."}} becomes {["test"] = {[1] = "Testing..."}}
75 -- This supports tables as keys, but not circular references.
76 -- It performs poorly with multiple references as it writes out the full
78 -- The dumped argument is internal-only.
80 function dump(o, dumped)
81 -- Same as "dumped" in dump2. The difference is that here it can only
82 -- contain boolean (and nil) values since multiple references aren't
85 if type(o) == "table" then
87 return "<circular reference>"
91 for k, v in pairs(o) do
94 table.insert(t, string.format("[%s] = %s", k, v))
96 return string.format("{%s}", table.concat(t, ", "))
102 --------------------------------------------------------------------------------
103 function string:split(sep)
104 local sep, fields = sep or ",", {}
105 local pattern = string.format("([^%s]+)", sep)
106 self:gsub(pattern, function(c) fields[#fields+1] = c end)
110 --------------------------------------------------------------------------------
111 function file_exists(filename)
112 local f = io.open(filename, "r")
121 --------------------------------------------------------------------------------
122 function string:trim()
123 return (self:gsub("^%s*(.-)%s*$", "%1"))
126 assert(string.trim("\n \t\tfoo bar\t ") == "foo bar")
128 --------------------------------------------------------------------------------
129 function math.hypot(x, y)
135 if x == 0 then return 0 end
137 return x * math.sqrt(1 + t * t)
140 --------------------------------------------------------------------------------
141 function get_last_folder(text,count)
142 local parts = text:split(DIR_DELIM)
150 retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
156 --------------------------------------------------------------------------------
157 function cleanup_path(temppath)
159 local parts = temppath:split("-")
162 if temppath ~= "" then
163 temppath = temppath .. "_"
165 temppath = temppath .. parts[i]
168 parts = temppath:split(".")
171 if temppath ~= "" then
172 temppath = temppath .. "_"
174 temppath = temppath .. parts[i]
177 parts = temppath:split("'")
180 if temppath ~= "" then
181 temppath = temppath .. ""
183 temppath = temppath .. parts[i]
186 parts = temppath:split(" ")
189 if temppath ~= "" then
192 temppath = temppath .. parts[i]
198 function core.formspec_escape(text)
200 text = string.gsub(text,"\\","\\\\")
201 text = string.gsub(text,"%]","\\]")
202 text = string.gsub(text,"%[","\\[")
203 text = string.gsub(text,";","\\;")
204 text = string.gsub(text,",","\\,")
210 function core.splittext(text,charlimit)
213 local current_idx = 1
215 local start,stop = string.find(text," ",current_idx)
216 local nl_start,nl_stop = string.find(text,"\n",current_idx)
217 local gotnewline = false
218 if nl_start ~= nil and (start == nil or nl_start < start) then
224 while start ~= nil do
225 if string.len(last_line) + (stop-start) > charlimit then
226 table.insert(retval,last_line)
230 if last_line ~= "" then
231 last_line = last_line .. " "
234 last_line = last_line .. string.sub(text,current_idx,stop -1)
237 table.insert(retval,last_line)
243 start,stop = string.find(text," ",current_idx)
244 nl_start,nl_stop = string.find(text,"\n",current_idx)
246 if nl_start ~= nil and (start == nil or nl_start < start) then
253 --add last part of text
254 if string.len(last_line) + (string.len(text) - current_idx) > charlimit then
255 table.insert(retval,last_line)
256 table.insert(retval,string.sub(text,current_idx))
258 last_line = last_line .. " " .. string.sub(text,current_idx)
259 table.insert(retval,last_line)
265 --------------------------------------------------------------------------------
267 if INIT == "game" then
268 local dirs1 = {9, 18, 7, 12}
269 local dirs2 = {20, 23, 22, 21}
271 function core.rotate_and_place(itemstack, placer, pointed_thing,
272 infinitestacks, orient_flags)
273 orient_flags = orient_flags or {}
275 local unode = core.get_node_or_nil(pointed_thing.under)
279 local undef = core.registered_nodes[unode.name]
280 if undef and undef.on_rightclick then
281 undef.on_rightclick(pointed_thing.under, unode, placer,
282 itemstack, pointed_thing)
285 local pitch = placer:get_look_pitch()
286 local fdir = core.dir_to_facedir(placer:get_look_dir())
287 local wield_name = itemstack:get_name()
289 local above = pointed_thing.above
290 local under = pointed_thing.under
291 local iswall = (above.y == under.y)
292 local isceiling = not iswall and (above.y < under.y)
293 local anode = core.get_node_or_nil(above)
297 local pos = pointed_thing.above
300 if undef and undef.buildable_to then
301 pos = pointed_thing.under
306 if core.is_protected(pos, placer:get_player_name()) then
307 core.record_protection_violation(pos,
308 placer:get_player_name())
312 local ndef = core.registered_nodes[node.name]
313 if not ndef or not ndef.buildable_to then
317 if orient_flags.force_floor then
320 elseif orient_flags.force_ceiling then
323 elseif orient_flags.force_wall then
326 elseif orient_flags.invert_wall then
331 core.set_node(pos, {name = wield_name,
332 param2 = dirs1[fdir+1]})
333 elseif isceiling then
334 if orient_flags.force_facedir then
335 core.set_node(pos, {name = wield_name,
338 core.set_node(pos, {name = wield_name,
339 param2 = dirs2[fdir+1]})
341 else -- place right side up
342 if orient_flags.force_facedir then
343 core.set_node(pos, {name = wield_name,
346 core.set_node(pos, {name = wield_name,
351 if not infinitestacks then
352 itemstack:take_item()
358 --------------------------------------------------------------------------------
359 --Wrapper for rotate_and_place() to check for sneak and assume Creative mode
360 --implies infinite stacks when performing a 6d rotation.
361 --------------------------------------------------------------------------------
364 core.rotate_node = function(itemstack, placer, pointed_thing)
365 core.rotate_and_place(itemstack, placer, pointed_thing,
366 core.setting_getbool("creative_mode"),
367 {invert_wall = placer:get_player_control().sneak})
372 --------------------------------------------------------------------------------
373 function core.explode_table_event(evt)
375 local parts = evt:split(":")
377 local t = parts[1]:trim()
378 local r = tonumber(parts[2]:trim())
379 local c = tonumber(parts[3]:trim())
380 if type(r) == "number" and type(c) == "number" and t ~= "INV" then
381 return {type=t, row=r, column=c}
385 return {type="INV", row=0, column=0}
388 --------------------------------------------------------------------------------
389 function core.explode_textlist_event(evt)
391 local parts = evt:split(":")
393 local t = parts[1]:trim()
394 local r = tonumber(parts[2]:trim())
395 if type(r) == "number" and t ~= "INV" then
396 return {type=t, index=r}
400 return {type="INV", index=0}
403 --------------------------------------------------------------------------------
404 function core.explode_scrollbar_event(evt)
405 local retval = core.explode_textlist_event(evt)
407 retval.value = retval.index
413 --------------------------------------------------------------------------------
414 function core.pos_to_string(pos)
415 return "(" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")"
418 --------------------------------------------------------------------------------
419 -- mainmenu only functions
420 --------------------------------------------------------------------------------
421 if INIT == "mainmenu" then
422 function core.get_game(index)
423 local games = game.get_games()
425 if index > 0 and index <= #games then
432 function fgettext(text, ...)
433 text = core.gettext(text)
434 local arg = {n=select('#', ...), ...}
436 -- Insert positional parameters ($1, $2, ...)
439 while pos <= text:len() do
440 newpos = text:find('[$]', pos)
441 if newpos == nil then
442 result = result .. text:sub(pos)
445 paramindex = tonumber(text:sub(newpos+1, newpos+1))
446 result = result .. text:sub(pos, newpos-1) .. tostring(arg[paramindex])
452 return core.formspec_escape(text)