wasplib: add inv management helpers
[waspsaliva.git] / builtin / client / wasplib.lua
blob39f36f3a694315b7983b4a17b85de4045a7f9ae4
2 ws = {}
3 ws.registered_globalhacks = {}
4 ws.displayed_wps={}
6 ws.c = core
8 ws.range=4
9 ws.target=nil
11 local nextact = {}
12 local ghwason={}
14 local nodes_this_tick=0
16 function ws.s(name,value)
17 if value == nil then
18 return ws.c.settings:get(name)
19 else
20 ws.c.settings:set(name,value)
21 return ws.c.settings:get(name)
22 end
23 end
24 function ws.sb(name,value)
25 if value == nil then
26 return ws.c.settings:get_bool(name)
27 else
28 ws.c.settings:set_bool(name,value)
29 return ws.c.settings:get_bool(name)
30 end
31 end
33 function ws.dcm(msg)
34 return minetest.display_chat_message(msg)
35 end
36 function ws.set_bool_bulk(settings,value)
37 if type(settings) ~= 'table' then return false end
38 for k,v in pairs(settings) do
39 minetest.settings:set_bool(v,value)
40 end
41 return true
42 end
44 function ws.shuffle(tbl)
45 for i = #tbl, 2, -1 do
46 local j = math.random(i)
47 tbl[i], tbl[j] = tbl[j], tbl[i]
48 end
49 return tbl
50 end
52 function ws.in_list(val, list)
53 if type(list) ~= "table" then return false end
54 for i, v in pairs(list) do
55 if v == val then
56 return true
57 end
58 end
59 return false
60 end
62 function ws.random_table_element(tbl)
63 local ks = {}
64 for k in pairs(tbl) do
65 table.insert(ks, k)
66 end
67 return tbl[ks[math.random(#ks)]]
68 end
70 function ws.center()
71 --local lp=ws.dircoord(0,0,0)
72 --minetest.localplayer:set_pos(lp)
73 end
75 function ws.globalhacktemplate(setting,func,funcstart,funcstop,daughters,delay)
76 funcstart = funcstart or function() end
77 funcstop = funcstop or function() end
78 delay = delay or 0.5
79 return function()
80 if not minetest.localplayer then return end
81 if minetest.settings:get_bool(setting) then
82 if tps_client and tps_client.ping and tps_client.ping > 1000 then return end
83 nodes_this_tick = 0
84 if nextact[setting] and nextact[setting] > os.clock() then return end
85 nextact[setting] = os.clock() + delay
86 if not ghwason[setting] then
87 if not funcstart() then
88 ws.set_bool_bulk(daughters,true)
89 ghwason[setting] = true
90 --ws.dcm(setting.. " activated")
91 ws.center()
92 minetest.settings:set('last-dir',ws.getdir())
93 minetest.settings:set('last-y',ws.dircoord(0,0,0).y)
94 else minetest.settings:set_bool(setting,false)
95 end
96 else
97 func()
98 end
100 elseif ghwason[setting] then
101 ghwason[setting] = false
102 ws.set_bool_bulk(daughters,false)
103 funcstop()
104 --ws.dcm(setting.. " deactivated")
109 function ws.register_globalhack(func)
110 table.insert(ws.registered_globalhacks,func)
113 function ws.register_globalhacktemplate(name,category,setting,func,funcstart,funcstop,daughters)
114 ws.register_globalhack(ws.globalhacktemplate(setting,func,funcstart,funcstop,daughters))
115 minetest.register_cheat(name,category,setting)
118 ws.rg=ws.register_globalhacktemplate
120 function ws.step_globalhacks(dtime)
121 for i, v in ipairs(ws.registered_globalhacks) do
122 v(dtime)
126 minetest.register_globalstep(function(dtime) ws.step_globalhacks(dtime) end)
127 minetest.settings:set_bool('continuous_forward',false)
128 function ws.on_connect(func)
129 if not minetest.localplayer then minetest.after(0,function() ws.on_connect(func) end) return end
130 if func then func() end
133 ws.on_connect(function()
134 local ldir =minetest.settings:get('last-dir')
135 if ldir then ws.setdir(ldir) end
136 end)
139 -- COORD MAGIC
141 function ws.is_same_pos(pos1,pos2)
142 return vector.distance(vector.round(pos1),vector.round(pos2)) == 0
144 function ws.get_reachable_positions(range,under)
145 under=under or false
146 range=range or 2
147 local rt={}
148 local lp=minetest.localplayer:get_pos()
149 local ylim=range
150 if under then ylim=-1 end
151 for x = -range,range,1 do
152 for y = -range,ylim,1 do
153 for z = -range,range,1 do
154 table.insert(rt,vector.add(lp,vector.new(x,y,z)))
158 return rt
161 function ws.do_area(radius,func,plane)
162 for k,v in pairs(ws.get_reachable_positions(range)) do
163 if not plane or v.y == minetest.localplayer:get_pos().y -1 then
164 func(v)
169 function ws.get_hud_by_texture(texture)
170 local def
171 local i = -1
172 repeat
173 i = i + 1
174 def = minetest.localplayer:hud_get(i)
175 until not def or def.text == texture
176 if def then
177 return def
179 def.number=0
180 return def
184 function ws.display_wp(pos,name)
185 local ix = #ws.displayed_wps + 1
186 ws.displayed_wps[ix] = minetest.localplayer:hud_add({
187 hud_elem_type = 'waypoint',
188 name = name,
189 text = name,
190 number = 0x00ff00,
191 world_pos = pos
193 return ix
196 function ws.clear_wp(ix)
197 table.remove(ws.displayed_wps,ix)
200 function ws.clear_wps()
201 for k,v in ipairs(ws.displayed_wps) do
202 minetest.localplayer:hud_remove(v)
203 table.remove(ws.displayed_wps,k)
207 function ws.register_chatcommand_alias(old, ...)
208 local def = assert(minetest.registered_chatcommands[old])
209 def.name = nil
210 for i = 1, select('#', ...) do
211 minetest.register_chatcommand(select(i, ...), table.copy(def))
215 function ws.round2(num, numDecimalPlaces)
216 return tonumber(string.format("%." .. (numDecimalPlaces or 0) .. "f", num))
219 function ws.pos_to_string(pos)
220 if type(pos) == 'table' then
221 pos = minetest.pos_to_string(vector.round(pos))
223 if type(pos) == 'string' then
224 return pos
226 return pos
229 function ws.string_to_pos(pos)
230 if type(pos) == 'string' then
231 pos = minetest.string_to_pos(pos)
233 if type(pos) == 'table' then
234 return vector.round(pos)
236 return pos
241 --ITEMS
242 function ws.find_item_in_table(items,rnd)
243 if type(items) == 'string' then
244 return minetest.find_item(items)
246 if type(items) ~= 'table' then return end
247 if rnd then items=ws.shuffle(items) end
248 for i, v in pairs(items) do
249 local n = minetest.find_item(v)
250 if n then
251 return n
254 return false
257 function ws.find_empty(inv)
258 for i, v in ipairs(inv) do
259 if v:is_empty() then
260 return i
263 return false
266 function ws.find_named(inv, name)
267 if not inv then return -1 end
268 if not name then return end
269 for i, v in ipairs(inv) do
270 if v:get_name():find(name) then
271 return i
276 function ws.itemnameformat(description)
277 description = description:gsub(string.char(0x1b) .. "%(.@[^)]+%)", "")
278 description = description:match("([^\n]*)")
279 return description
282 function ws.find_nametagged(list, name)
283 for i, v in ipairs(list) do
284 if ws.itemnameformat(v:get_description()) == name then
285 return i
290 local hotbar_slot=8
291 function ws.to_hotbar(it,hslot)
292 local tpos=nil
293 local plinv = minetest.get_inventory("current_player")
294 if hslot and hslot < 10 then
295 tpos=hslot
296 else
297 for i, v in ipairs(plinv.main) do
298 if i<10 and v:is_empty() then
299 tpos = i
300 break
304 if tpos == nil then tpos=hotbar_slot end
305 local mv = InventoryAction("move")
306 mv:from("current_player", "main", it)
307 mv:to("current_player", "main", tpos)
308 mv:apply()
309 return tpos
312 function ws.switch_to_item(itname,hslot)
313 if not minetest.localplayer then return false end
314 local plinv = minetest.get_inventory("current_player")
315 for i, v in ipairs(plinv.main) do
316 if i<10 and v:get_name() == itname then
317 minetest.localplayer:set_wield_index(i)
318 return true
321 local pos = ws.find_named(plinv.main, itname)
322 if pos then
323 minetest.localplayer:set_wield_index(ws.to_hotbar(pos,hslot))
324 return true
326 return false
328 function ws.in_inv(itname)
329 if not minetest.localplayer then return false end
330 local plinv = minetest.get_inventory("current_player")
331 local pos = ws.find_named(plinv.main, itname)
332 if pos then
333 return true
337 function core.switch_to_item(item) return ws.switch_to_item(item) end
339 function ws.switch_inv_or_echest(name,max_count,hslot)
340 if not minetest.localplayer then return false end
341 local plinv = minetest.get_inventory("current_player")
342 if ws.switch_to_item(name) then return true end
344 local epos = ws.find_named(plinv.enderchest, name)
345 if epos then
346 local tpos
347 for i, v in ipairs(plinv.main) do
348 if i < 9 and v:is_empty() then
349 tpos = i
350 break
353 if not tpos then tpos=hotbar_slot end
355 if tpos then
356 local mv = InventoryAction("move")
357 mv:from("current_player", "enderchest", epos)
358 mv:to("current_player", "main", tpos)
359 if max_count then
360 mv:set_count(max_count)
362 mv:apply()
363 minetest.localplayer:set_wield_index(tpos)
364 return true
367 return false
370 local function posround(n)
371 return math.floor(n + 0.5)
374 local function fmt(c)
375 return tostring(posround(c.x))..","..tostring(posround(c.y))..","..tostring(posround(c.z))
378 local function map_pos(value)
379 if value.x then
380 return value
381 else
382 return {x = value[1], y = value[2], z = value[3]}
386 function ws.invparse(location)
387 if type(location) == "string" then
388 if string.match(location, "^[-]?[0-9]+,[-]?[0-9]+,[-]?[0-9]+$") then
389 return "nodemeta:" .. location
390 else
391 return location
393 elseif type(location) == "table" then
394 return "nodemeta:" .. fmt(map_pos(location))
398 function ws.invpos(p)
399 return "nodemeta:"..p.x..","..p.y..","..p.z
403 -- TOOLS
406 local function check_tool(stack, node_groups, old_best_time)
407 local toolcaps = stack:get_tool_capabilities()
408 if not toolcaps then return end
409 local best_time = old_best_time
410 for group, groupdef in pairs(toolcaps.groupcaps) do
411 local level = node_groups[group]
412 if level then
413 local this_time = groupdef.times[level]
414 if this_time and this_time < best_time then
415 best_time = this_time
419 return best_time < old_best_time, best_time
422 local function find_best_tool(nodename, switch)
423 local player = minetest.localplayer
424 local inventory = minetest.get_inventory("current_player")
425 local node_groups = minetest.get_node_def(nodename).groups
426 local new_index = player:get_wield_index()
427 local is_better, best_time = false, math.huge
429 is_better, best_time = check_tool(player:get_wielded_item(), node_groups, best_time)
430 if inventory.hand then
431 is_better, best_time = check_tool(inventory.hand[1], node_groups, best_time)
434 for index, stack in ipairs(inventory.main) do
435 is_better, best_time = check_tool(stack, node_groups, best_time)
436 if is_better then
437 new_index = index
441 return new_index,best_time
444 function ws.get_digtime(nodename)
445 local idx,tm=find_best_tool(nodename)
446 return tm
449 function ws.select_best_tool(pos)
450 local nd=minetest.get_node_or_nil(pos)
451 local nodename='air'
452 if nd then nodename=nd.name end
453 local t=find_best_tool(nodename)
454 minetest.localplayer:set_wield_index(ws.to_hotbar(t,hotbar_slot))
455 --minetest.localplayer:set_wield_index(find_best_tool(nodename))
458 --- COORDS
459 function ws.coord(x, y, z)
460 return vector.new(x,y,z)
462 function ws.ordercoord(c)
463 if c.x == nil then
464 return {x = c[1], y = c[2], z = c[3]}
465 else
466 return c
470 -- x or {x,y,z} or {x=x,y=y,z=z}
471 function ws.optcoord(x, y, z)
472 if y and z then
473 return ws.coord(x, y, z)
474 else
475 return ws.ordercoord(x)
478 function ws.cadd(c1, c2)
479 return vector.add(c1,c2)
480 --return ws.coord(c1.x + c2.x, c1.y + c2.y, c1.z + c2.z)
483 function ws.relcoord(x, y, z, rpos)
484 local pos = rpos or minetest.localplayer:get_pos()
485 pos.y=math.ceil(pos.y)
486 --math.floor(pos.y) + 0.5
487 return ws.cadd(pos, ws.optcoord(x, y, z))
490 local function between(x, y, z) -- x is between y and z (inclusive)
491 return y <= x and x <= z
494 function ws.getdir(yaw) --
495 local rot = yaw or minetest.localplayer:get_yaw() % 360
496 if between(rot, 315, 360) or between(rot, 0, 45) then
497 return "north"
498 elseif between(rot, 135, 225) then
499 return "south"
500 elseif between(rot, 225, 315) then
501 return "east"
502 elseif between(rot, 45, 135) then
503 return "west"
507 function ws.getaxis()
508 local dir=ws.getdir()
509 if dir == "north" or dir == "south" then return "z" end
510 return "x"
512 function ws.setdir(dir) --
513 if dir == "north" then
514 minetest.localplayer:set_yaw(0)
515 elseif dir == "south" then
516 minetest.localplayer:set_yaw(180)
517 elseif dir == "east" then
518 minetest.localplayer:set_yaw(270)
519 elseif dir == "west" then
520 minetest.localplayer:set_yaw(90)
524 function ws.dircoord(f, y, r ,rpos, rdir)
525 local dir= ws.getdir(rdir)
526 local coord = ws.optcoord(f, y, r)
527 local f = coord.x
528 local y = coord.y
529 local r = coord.z
530 local lp= rpos or minetest.localplayer:get_pos()
531 if dir == "north" then
532 return ws.relcoord(r, y, f,rpos)
533 elseif dir == "south" then
534 return ws.relcoord(-r, y, -f,rpos)
535 elseif dir == "east" then
536 return ws.relcoord(f, y, -r,rpos)
537 elseif dir== "west" then
538 return ws.relcoord(-f, y, r,rpos)
540 return ws.relcoord(0, 0, 0,rpos)
543 function ws.get_dimension(pos)
544 if pos.y > -65 then return "overworld"
545 elseif pos.y > -8000 then return "void"
546 elseif pos.y > -27000 then return "end"
547 elseif pos.y > -28930 then return "void"
548 elseif pos.y > -31000 then return "nether"
549 else return "void"
553 function ws.aim(tpos)
554 local ppos=minetest.localplayer:get_pos()
555 local dir=vector.direction(ppos,tpos)
556 local yyaw=0;
557 local pitch=0;
558 if dir.x < 0 then
559 yyaw = math.atan2(-dir.x, dir.z) + (math.pi * 2)
560 else
561 yyaw = math.atan2(-dir.x, dir.z)
563 yyaw = ws.round2(math.deg(yyaw),2)
564 pitch = ws.round2(math.deg(math.asin(-dir.y) * 1),2);
565 minetest.localplayer:set_yaw(yyaw)
566 minetest.localplayer:set_pitch(pitch)
569 function ws.gaim(tpos,v,g)
570 local v = v or 40
571 local g = g or 9.81
572 local ppos=minetest.localplayer:get_pos()
573 local dir=vector.direction(ppos,tpos)
574 local yyaw=0;
575 local pitch=0;
576 if dir.x < 0 then
577 yyaw = math.atan2(-dir.x, dir.z) + (math.pi * 2)
578 else
579 yyaw = math.atan2(-dir.x, dir.z)
581 yyaw = ws.round2(math.deg(yyaw),2)
582 local y = dir.y
583 dir.y = 0
584 local x = vector.length(dir)
585 pitch=math.atan(math.pow(v, 2) / (g * x) + math.sqrt(math.pow(v, 4)/(math.pow(g, 2) * math.pow(x, 2)) - 2 * math.pow(v, 2) * y/(g * math.pow(x, 2)) - 1))
586 --pitch = ws.round2(math.deg(math.asin(-dir.y) * 1),2);
587 minetest.localplayer:set_yaw(yyaw)
588 minetest.localplayer:set_pitch(math.deg(pitch))
591 local function tablearg(arg)
592 local tb={}
593 if type(arg) == 'string' then
594 tb={arg}
595 elseif type(arg) == 'table' then
596 tb=arg
597 elseif type(arg) == 'function' then
598 tb=arg()
600 return tb
603 function ws.isnode(pos,arg)--arg is either an itemstring, a table of itemstrings or a function returning an itemstring
604 local nodename=tablearg(arg)
605 local nd=minetest.get_node_or_nil(pos)
606 if nd and nodename and ws.in_list(nd.name,nodename) then
607 return true
611 function ws.can_place_at(pos)
612 local node = minetest.get_node_or_nil(pos)
613 return (node and (node.name == "air" or node.name=="mcl_core:water_source" or node.name=="mcl_core:water_flowing" or node.name=="mcl_core:lava_source" or node.name=="mcl_core:lava_flowing" or minetest.get_node_def(node.name).buildable_to))
616 -- should check if wield is placeable
617 -- minetest.get_node(wielded:get_name()) ~= nil should probably work
618 -- otherwise it equips armor and eats food
619 function ws.can_place_wielded_at(pos)
620 local wield_empty = minetest.localplayer:get_wielded_item():is_empty()
621 return not wield_empty and ws.can_place_at(pos)
625 function ws.find_any_swap(items,hslot)
626 hslot=hslot or 8
627 for i, v in ipairs(items) do
628 local n = minetest.find_item(v)
629 if n then
630 ws.switch_to_item(v,hslot)
631 return true
634 return false
638 -- swaps to any of the items and places if need be
639 -- returns true if placed and in inventory or already there, false otherwise
641 local lastact=0
642 local lastplc=0
643 local lastdig=0
644 local actint=10
645 function ws.place(pos,items,hslot, place)
646 --if nodes_this_tick > 8 then return end
647 --nodes_this_tick = nodes_this_tick + 1
648 --if not inside_constraints(pos) then return end
649 if not pos then return end
650 items=tablearg(items)
652 place = place or minetest.place_node
654 local node = minetest.get_node_or_nil(pos)
655 if not node then return end
656 -- already there
657 if ws.isnode(pos,items) then
658 return true
659 else
660 local swapped = ws.find_any_swap(items,hslot)
662 -- need to place
663 if swapped and ws.can_place_at(pos) then
664 --minetest.after("0.05",place,pos)
665 place(pos)
666 return true
667 -- can't place
668 else
669 return false
674 function ws.place_if_able(pos)
675 if not pos then return end
676 if not inside_constraints(pos) then return end
677 if ws.can_place_wielded_at(pos) then
678 minetest.place_node(pos)
682 function ws.is_diggable(pos)
683 if not pos then return false end
684 local nd=minetest.get_node_or_nil(pos)
685 if not nd then return false end
686 local n = minetest.get_node_def(nd.name)
687 if n and n.diggable then return true end
688 return false
691 function ws.dig(pos,condition,autotool)
692 --if not inside_constraints(pos) then return end
693 if autotool == nil then autotool = true end
694 if condition and not condition(pos) then return false end
695 if not ws.is_diggable(pos) then return end
696 local nd=minetest.get_node_or_nil(pos)
697 if nd and minetest.get_node_def(nd.name).diggable then
698 if autotool then ws.select_best_tool(pos) end
699 minetest.dig_node(pos)
701 return true
704 function ws.chunk_loaded()
705 local ign=minetest.find_nodes_near(ws.dircoord(0,0,0),10,{'ignore'},true)
706 if #ign == 0 then return true end
707 return false
710 function ws.get_near(nodes,range)
711 range=range or 5
712 local nds=minetest.find_nodes_near(ws.dircoord(0,0,0),rang,nodes,true)
713 if #nds > 0 then return nds end
714 return false
717 function ws.is_laggy()
718 if tps_client and tps_client.ping and tps_client.ping > 1000 then return true end
722 function ws.donodes(poss,func,condition)
723 if ws.is_laggy() then return end
724 local dn_i=0
725 for k,v in pairs(poss) do
726 local nd=minetest.get_node_or_nil(v)
727 if nd and nd.name ~= 'air' then
728 if k > 8 then
729 return
731 if condition == nil or condition(v) then
732 func(v)
733 dn_i = dn_i + 1
739 function ws.dignodes(poss,condition)
740 local func=function(p) ws.dig(p) end
741 ws.donodes(poss,func,condition)
745 function ws.replace(pos,arg)
746 arg=tablearg(arg)
747 local nd=minetest.get_node_or_nil(pos)
748 if nd and not ws.in_list(nd.name,arg) and nd.name ~= 'air' then
749 local tm=ws.get_digtime(nd.name) or 0
750 ws.dig(pos)
751 minetest.after(tm + 0.1,function()
752 ws.place(pos,arg)
753 end)
754 else
755 ws.place(pos,arg)
759 function ws.playeron(p)
760 local pls=minetest.get_player_names()
761 for k,v in pairs(pls) do
762 if v == p then return true end
764 return false
768 function ws.between(x, y, z) -- x is between y and z (inclusive)
769 return y <= x and x <= z
773 local wall_pos1={x=-1255,y=6,z=792}
774 local wall_pos2={x=-1452,y=80,z=981}
775 local iwall_pos1={x=-1266,y=6,z=802}
776 local iwall_pos2={x=-1442,y=80,z=971}
778 function ws.in_cube(tpos,wpos1,wpos2)
779 local xmax=wpos2.x
780 local xmin=wpos1.x
782 local ymax=wpos2.y
783 local ymin=wpos1.y
785 local zmax=wpos2.z
786 local zmin=wpos1.z
787 if wpos1.x > wpos2.x then
788 xmax=wpos1.x
789 xmin=wpos2.x
791 if wpos1.y > wpos2.y then
792 ymax=wpos1.y
793 ymin=wpos2.y
795 if wpos1.z > wpos2.z then
796 zmax=wpos1.z
797 zmin=wpos2.z
799 if ws.between(tpos.x,xmin,xmax) and ws.between(tpos.y,ymin,ymax) and ws.between(tpos.z,zmin,zmax) then
800 return true
802 return false
805 function ws.in_wall(pos)
806 if ws.in_cube(pos,wall_pos1,wall_pos2) and not in_cube(pos,iwall_pos1,iwall_pos2) then
807 return true end
808 return false
811 function ws.inside_wall(pos)
812 local p1=iwall_pos1
813 local p2=iwall_pos2
814 if ws.in_cube(pos,p1,p2) then return true end
815 return false
819 -- DEBUG
820 local function printwieldedmeta()
821 ws.dcm(dump(minetest.localplayer:get_wielded_item():get_meta():to_table()))
823 minetest.register_cheat('ItemMeta','Test',printwieldedmeta)