Organize builtin into subdirectories
[oweals/minetest.git] / builtin / common / misc_helpers.lua
1 -- Minetest: builtin/misc_helpers.lua
2
3 --------------------------------------------------------------------------------
4 function basic_dump2(o)
5         if type(o) == "number" then
6                 return tostring(o)
7         elseif type(o) == "string" then
8                 return string.format("%q", o)
9         elseif type(o) == "boolean" then
10                 return tostring(o)
11         elseif type(o) == "function" then
12                 return "<function>"
13         elseif type(o) == "userdata" then
14                 return "<userdata>"
15         elseif type(o) == "nil" then
16                 return "nil"
17         else
18                 error("cannot dump a " .. type(o))
19                 return nil
20         end
21 end
22
23 --------------------------------------------------------------------------------
24 function dump2(o, name, dumped)
25         name = name or "_"
26         dumped = dumped or {}
27         io.write(name, " = ")
28         if type(o) == "number" or type(o) == "string" or type(o) == "boolean"
29                         or type(o) == "function" or type(o) == "nil"
30                         or type(o) == "userdata" then
31                 io.write(basic_dump2(o), "\n")
32         elseif type(o) == "table" then
33                 if dumped[o] then
34                         io.write(dumped[o], "\n")
35                 else
36                         dumped[o] = name
37                         io.write("{}\n") -- new table
38                         for k,v in pairs(o) do
39                                 local fieldname = string.format("%s[%s]", name, basic_dump2(k))
40                                 dump2(v, fieldname, dumped)
41                         end
42                 end
43         else
44                 error("cannot dump a " .. type(o))
45                 return nil
46         end
47 end
48
49 --------------------------------------------------------------------------------
50 function dump(o, dumped)
51         dumped = dumped or {}
52         if type(o) == "number" then
53                 return tostring(o)
54         elseif type(o) == "string" then
55                 return string.format("%q", o)
56         elseif type(o) == "table" then
57                 if dumped[o] then
58                         return "<circular reference>"
59                 end
60                 dumped[o] = true
61                 local t = {}
62                 for k,v in pairs(o) do
63                         t[#t+1] = "[" .. dump(k, dumped) .. "] = " .. dump(v, dumped)
64                 end
65                 return "{" .. table.concat(t, ", ") .. "}"
66         elseif type(o) == "boolean" then
67                 return tostring(o)
68         elseif type(o) == "function" then
69                 return "<function>"
70         elseif type(o) == "userdata" then
71                 return "<userdata>"
72         elseif type(o) == "nil" then
73                 return "nil"
74         else
75                 error("cannot dump a " .. type(o))
76                 return nil
77         end
78 end
79
80 --------------------------------------------------------------------------------
81 function string:split(sep)
82         local sep, fields = sep or ",", {}
83         local pattern = string.format("([^%s]+)", sep)
84         self:gsub(pattern, function(c) fields[#fields+1] = c end)
85         return fields
86 end
87
88 --------------------------------------------------------------------------------
89 function file_exists(filename)
90         local f = io.open(filename, "r")
91         if f==nil then
92                 return false
93         else
94                 f:close()
95                 return true
96         end
97 end
98
99 --------------------------------------------------------------------------------
100 function string:trim()
101         return (self:gsub("^%s*(.-)%s*$", "%1"))
102 end
103
104 assert(string.trim("\n \t\tfoo bar\t ") == "foo bar")
105
106 --------------------------------------------------------------------------------
107 function math.hypot(x, y)
108         local t
109         x = math.abs(x)
110         y = math.abs(y)
111         t = math.min(x, y)
112         x = math.max(x, y)
113         if x == 0 then return 0 end
114         t = t / x
115         return x * math.sqrt(1 + t * t)
116 end
117
118 --------------------------------------------------------------------------------
119 function get_last_folder(text,count)
120         local parts = text:split(DIR_DELIM)
121         
122         if count == nil then
123                 return parts[#parts]
124         end
125         
126         local retval = ""
127         for i=1,count,1 do
128                 retval = retval .. parts[#parts - (count-i)] .. DIR_DELIM
129         end
130         
131         return retval
132 end
133
134 --------------------------------------------------------------------------------
135 function cleanup_path(temppath)
136         
137         local parts = temppath:split("-")
138         temppath = ""   
139         for i=1,#parts,1 do
140                 if temppath ~= "" then
141                         temppath = temppath .. "_"
142                 end
143                 temppath = temppath .. parts[i]
144         end
145         
146         parts = temppath:split(".")
147         temppath = ""   
148         for i=1,#parts,1 do
149                 if temppath ~= "" then
150                         temppath = temppath .. "_"
151                 end
152                 temppath = temppath .. parts[i]
153         end
154         
155         parts = temppath:split("'")
156         temppath = ""   
157         for i=1,#parts,1 do
158                 if temppath ~= "" then
159                         temppath = temppath .. ""
160                 end
161                 temppath = temppath .. parts[i]
162         end
163         
164         parts = temppath:split(" ")
165         temppath = ""   
166         for i=1,#parts,1 do
167                 if temppath ~= "" then
168                         temppath = temppath
169                 end
170                 temppath = temppath .. parts[i]
171         end
172         
173         return temppath
174 end
175
176 local tbl = engine or minetest
177 function tbl.formspec_escape(text)
178         if text ~= nil then
179                 text = string.gsub(text,"\\","\\\\")
180                 text = string.gsub(text,"%]","\\]")
181                 text = string.gsub(text,"%[","\\[")
182                 text = string.gsub(text,";","\\;")
183                 text = string.gsub(text,",","\\,")
184         end
185         return text
186 end
187
188
189 function tbl.splittext(text,charlimit)
190         local retval = {}
191
192         local current_idx = 1
193         
194         local start,stop = string.find(text," ",current_idx)
195         local nl_start,nl_stop = string.find(text,"\n",current_idx)
196         local gotnewline = false
197         if nl_start ~= nil and (start == nil or nl_start < start) then
198                 start = nl_start
199                 stop = nl_stop
200                 gotnewline = true
201         end
202         local last_line = ""
203         while start ~= nil do
204                 if string.len(last_line) + (stop-start) > charlimit then
205                         table.insert(retval,last_line)
206                         last_line = ""
207                 end
208                 
209                 if last_line ~= "" then
210                         last_line = last_line .. " "
211                 end
212                 
213                 last_line = last_line .. string.sub(text,current_idx,stop -1)
214                 
215                 if gotnewline then
216                         table.insert(retval,last_line)
217                         last_line = ""
218                         gotnewline = false
219                 end
220                 current_idx = stop+1
221                 
222                 start,stop = string.find(text," ",current_idx)
223                 nl_start,nl_stop = string.find(text,"\n",current_idx)
224         
225                 if nl_start ~= nil and (start == nil or nl_start < start) then
226                         start = nl_start
227                         stop = nl_stop
228                         gotnewline = true
229                 end
230         end
231         
232         --add last part of text
233         if string.len(last_line) + (string.len(text) - current_idx) > charlimit then
234                         table.insert(retval,last_line)
235                         table.insert(retval,string.sub(text,current_idx))
236         else
237                 last_line = last_line .. " " .. string.sub(text,current_idx)
238                 table.insert(retval,last_line)
239         end
240         
241         return retval
242 end
243
244 --------------------------------------------------------------------------------
245
246 if minetest then
247         local dirs1 = {9, 18, 7, 12}
248         local dirs2 = {20, 23, 22, 21}
249
250         function minetest.rotate_and_place(itemstack, placer, pointed_thing,
251                                 infinitestacks, orient_flags)
252                 orient_flags = orient_flags or {}
253
254                 local unode = minetest.get_node_or_nil(pointed_thing.under)
255                 if not unode then
256                         return
257                 end
258                 local undef = minetest.registered_nodes[unode.name]
259                 if undef and undef.on_rightclick then
260                         undef.on_rightclick(pointed_thing.under, unode, placer,
261                                         itemstack, pointed_thing)
262                         return
263                 end
264                 local pitch = placer:get_look_pitch()
265                 local fdir = minetest.dir_to_facedir(placer:get_look_dir())
266                 local wield_name = itemstack:get_name()
267
268                 local above = pointed_thing.above
269                 local under = pointed_thing.under
270                 local iswall = (above.y == under.y)
271                 local isceiling = not iswall and (above.y < under.y)
272                 local anode = minetest.get_node_or_nil(above)
273                 if not anode then
274                         return
275                 end
276                 local pos = pointed_thing.above
277                 local node = anode
278
279                 if undef and undef.buildable_to then
280                         pos = pointed_thing.under
281                         node = unode
282                         iswall = false
283                 end
284
285                 if minetest.is_protected(pos, placer:get_player_name()) then
286                         minetest.record_protection_violation(pos,
287                                         placer:get_player_name())
288                         return
289                 end
290
291                 local ndef = minetest.registered_nodes[node.name]
292                 if not ndef or not ndef.buildable_to then
293                         return
294                 end
295
296                 if orient_flags.force_floor then
297                         iswall = false
298                         isceiling = false
299                 elseif orient_flags.force_ceiling then
300                         iswall = false
301                         isceiling = true
302                 elseif orient_flags.force_wall then
303                         iswall = true
304                         isceiling = false
305                 elseif orient_flags.invert_wall then
306                         iswall = not iswall
307                 end
308
309                 if iswall then
310                         minetest.set_node(pos, {name = wield_name,
311                                         param2 = dirs1[fdir+1]})
312                 elseif isceiling then
313                         if orient_flags.force_facedir then
314                                 minetest.set_node(pos, {name = wield_name,
315                                                 param2 = 20})
316                         else
317                                 minetest.set_node(pos, {name = wield_name,
318                                                 param2 = dirs2[fdir+1]})
319                         end
320                 else -- place right side up
321                         if orient_flags.force_facedir then
322                                 minetest.set_node(pos, {name = wield_name,
323                                                 param2 = 0})
324                         else
325                                 minetest.set_node(pos, {name = wield_name,
326                                                 param2 = fdir})
327                         end
328                 end
329
330                 if not infinitestacks then
331                         itemstack:take_item()
332                         return itemstack
333                 end
334         end
335
336
337 --------------------------------------------------------------------------------
338 --Wrapper for rotate_and_place() to check for sneak and assume Creative mode
339 --implies infinite stacks when performing a 6d rotation.
340 --------------------------------------------------------------------------------
341
342
343         minetest.rotate_node = function(itemstack, placer, pointed_thing)
344                 minetest.rotate_and_place(itemstack, placer, pointed_thing,
345                                 minetest.setting_getbool("creative_mode"),
346                                 {invert_wall = placer:get_player_control().sneak})
347                 return itemstack
348         end
349 end
350
351 --------------------------------------------------------------------------------
352 function tbl.explode_table_event(evt)
353         if evt ~= nil then
354                 local parts = evt:split(":")
355                 if #parts == 3 then
356                         local t = parts[1]:trim()
357                         local r = tonumber(parts[2]:trim())
358                         local c = tonumber(parts[3]:trim())
359                         if type(r) == "number" and type(c) == "number" and t ~= "INV" then
360                                 return {type=t, row=r, column=c}
361                         end
362                 end
363         end
364         return {type="INV", row=0, column=0}
365 end
366
367 --------------------------------------------------------------------------------
368 function tbl.explode_textlist_event(evt)
369         if evt ~= nil then
370                 local parts = evt:split(":")
371                 if #parts == 2 then
372                         local t = parts[1]:trim()
373                         local r = tonumber(parts[2]:trim())
374                         if type(r) == "number" and t ~= "INV" then
375                                 return {type=t, index=r}
376                         end
377                 end
378         end
379         return {type="INV", index=0}
380 end
381
382 --------------------------------------------------------------------------------
383 -- mainmenu only functions
384 --------------------------------------------------------------------------------
385 if engine ~= nil then
386         engine.get_game = function(index)
387                 local games = game.get_games()
388                 
389                 if index > 0 and index <= #games then
390                         return games[index]
391                 end
392                 
393                 return nil
394         end
395         
396         function fgettext(text, ...)
397                 text = engine.gettext(text)
398                 local arg = {n=select('#', ...), ...}
399                 if arg.n >= 1 then
400                         -- Insert positional parameters ($1, $2, ...)
401                         result = ''
402                         pos = 1
403                         while pos <= text:len() do
404                                 newpos = text:find('[$]', pos)
405                                 if newpos == nil then
406                                         result = result .. text:sub(pos)
407                                         pos = text:len() + 1
408                                 else
409                                         paramindex = tonumber(text:sub(newpos+1, newpos+1))
410                                         result = result .. text:sub(pos, newpos-1) .. tostring(arg[paramindex])
411                                         pos = newpos + 2
412                                 end
413                         end
414                         text = result
415                 end
416                 return engine.formspec_escape(text)
417         end
418 end
419 --------------------------------------------------------------------------------
420 -- core only fct
421 --------------------------------------------------------------------------------
422 if minetest ~= nil then
423         --------------------------------------------------------------------------------
424         function minetest.pos_to_string(pos)
425                 return "(" .. pos.x .. "," .. pos.y .. "," .. pos.z .. ")"
426         end
427 end
428