nodecore-cd2025/mods/nc_api/item_tool_rakes.lua
Aaron Suen b7e4809e85 Make rakes at least somewhat useful with doors
- Normal non-selective raking mode works when
  pushing a rake with a door.
- The rake does not pick itself up.
- Collected things are dumped at the location of
  the direct raking target.

This doesn't seem super useful right now, but
a rake digging a full stack of some item will cause
all additional stacks of items in its range to settle
near the full stack, which could end up over a chute
allowing the item to fall down, and thus could be
used to collect items that scatter over a wider area.

Before releasing this we might consider making
rake digging selective by default, so these can be
used to sort and filter items.

May also consider making it possible to dig with a
rake in other circumstances, e.g. pushing a rake
against an open side of a storebox that has no room
for the rake will cause the rake to dig that stack and
all nearby matches?

May also consider cleaning up the raking API entirely
to make it easier to directly invoke a rake via
machine digging ... or tidy up the machine digging
API so it's no so hacky.
2021-12-16 18:01:23 -05:00

133 lines
4.1 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, nodecore, pairs, table, vector
= ipairs, math, minetest, nodecore, pairs, table, vector
local math_abs, math_max, table_sort
= math.abs, math.max, table.sort
-- LUALOCALS > ---------------------------------------------------------
-- To register a tool as a rake, provide a callback:
-- on_rake(pos, node, user) returns volume, checkfunc
-- volume: ordered array of relative positions to be dug by rake
-- checkfunc(pos, node, rel): determine if item can be dug
-- rel: relative vector taken from volume array
-- returns true to dig, nil to not dig, false to abort loop
local volcache = {}
function nodecore.rake_volume(dxmax, dymax, dzmax)
dzmax = dzmax or dxmax
local key = minetest.pos_to_string({x = dxmax, y = dymax, z = dzmax})
local rakepos = volcache[key]
if rakepos then return rakepos end
rakepos = {}
for dy = -dymax, dymax do
for dx = -dxmax, dxmax do
for dz = -dzmax, dzmax do
local v = {x = dx, y = dy, z = dz}
v.d = vector.length(v)
v.rxz = math_max(math_abs(dx), math_abs(dz))
v.ry = math_abs(dy)
rakepos[#rakepos + 1] = v
end
end
end
table_sort(rakepos, function(a, b) return a.d < b.d end)
volcache[key] = rakepos
return rakepos
end
function nodecore.rake_index(filterfunc)
local rakable = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_nodes) do
if filterfunc(v, k) then
rakable[k] = true
end
end
end)
return function(_, node) return rakable[node.name] end
end
local function deferfall(func, ...)
local oldfall = minetest.check_for_falling
minetest.check_for_falling = nodecore.fallcheck
local function helper(...)
minetest.check_for_falling = oldfall
return ...
end
return helper(func(...))
end
local laststack
local lastraking
local old_node_dig = minetest.node_dig
minetest.node_dig = function(pos, node, user, ...)
laststack = nodecore.stack_get(pos)
local wield = nodecore.machine_digging and nodecore.machine_digging.tool
or user and user:is_player() and user:get_wielded_item()
print(wield and wield:to_string() or "nada")
lastraking = wield and (wield:get_definition() or {}).on_rake
if lastraking then return deferfall(old_node_dig, pos, node, user, ...) end
return old_node_dig(pos, node, user, ...)
end
local stackonly = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_nodes) do
if v.groups.is_stack_only then stackonly[k] = true end
end
end)
local function matching(_, na, pb, nb)
if stackonly[na.name] then
if not stackonly[nb.name] then return end
return (laststack and nodecore.stack_family(laststack))
== nodecore.stack_family(nodecore.stack_get(pb))
end
return nodecore.stack_family(na.name) == nodecore.stack_family(nb.name)
end
local function dorake(volume, check, pos, node, user, ...)
local sneak = user and user:get_player_control().sneak
local objpos = {}
for _, rel in ipairs(volume) do
local p = vector.add(pos, rel)
if not (nodecore.machine_digging
and vector.equals(nodecore.machine_digging.toolpos, p)) then
local n = minetest.get_node(p)
local allow = (rel.d > 0 or nil) and check(p, n, rel)
if allow == false then break end
if allow and ((not sneak) or matching(pos, node, p, n)) then
minetest.node_dig(p, n, user, ...)
objpos[minetest.hash_node_position(p)] = true
end
end
end
for _, lua in pairs(minetest.luaentities) do
if lua.name == "__builtin:item" then
local p = lua.object and lua.object:get_pos()
if p and objpos[minetest.hash_node_position(
vector.round(p))] then
lua.object:set_pos(pos)
end
end
end
end
local rakelock = {}
nodecore.register_on_dignode("rake handling", function(pos, node, user, ...)
local nowraking = lastraking
if not nowraking then return end
lastraking = nil
if not (pos and node) then return end
local volume, check = nowraking(pos, node, user, ...)
if not (volume and check) then return end
local pname = user and user:is_player()
and user:get_player_name() or nowraking
if rakelock[pname] then return end
rakelock[pname] = true
deferfall(dorake, volume, check, pos, node, user, ...)
rakelock[pname] = nil
end)