people/actions/tunnel.lua

346 lines
12 KiB
Lua

local dbg
if moddebug then dbg=moddebug.dbg("people") else dbg={v1=function() end,v2=function() end,v3=function() end} end
local function rotate(rel, dir)
if dir.z < 0 then
return {x=-rel.x, y=rel.y, z=-rel.z}
elseif dir.z > 0 then
return {x=rel.x, y=rel.y, z=rel.z}
elseif dir.x > 0 then
return {x=rel.z, y=rel.y, z=-rel.x}
else
return {x=-rel.z, y=rel.y, z=rel.x}
end
end
people.actions.tunnel = function(state)
if type(state.action.distance) ~= "number" then
dbg.v1(state.ent.name.." has invalid distance for tunnel action")
return true, false
end
if type(state.action.dir) ~= "table" then
dbg.v1(state.ent.name.." has invalid dir for tunnel action")
return true, false
end
if math.abs(state.action.dir.y) > 1 then
dbg.v1(state.ent.name.." has invalid y component of dir for tunnel action")
return true, false
end
if math.abs(state.action.dir.x) + math.abs(state.action.dir.z) ~= 1 then
dbg.v1(state.ent.name.." has invalid x/z components of dir for tunnel action")
return true, false
end
-- On starting, keep the original position and distance. This will allow
-- us to break off and resume if, for example, we run out of materials or
-- room to carry the spoils.
if not state.action.startpos then
state.action.startpos = vector.round(state.pos)
state.action.startdist = state.action.distance
end
if state.action.distance <= 0 then
if state.action.endface then
local endoff = {y=0, z=-1}
local endface = {y=0, z=-1}
if state.action.endface.left and state.action.endface.right then
if math.random() < 0.5 then
endoff.x = -1
else
endoff.x = 1
end
elseif state.action.endface.left then
endoff.x = -1
elseif state.action.endface.right then
endoff.x = 1
end
endface.x = 2 * endoff.x
endoff = rotate(endoff, state.action.dir)
endface = rotate(endface, state.action.dir)
state.action.endface = nil
state.action = {"go", rel=endoff, successaction={"face",
pos=vector.add(state.pos, endface),
successaction=state.action}}
return false
end
dbg.v1(state.ent.name.." completed tunnelling")
return true, true
end
-- digpos will be the base centre of the new cross-sectional area
-- we're going to dig.
local digpos = vector.add(vector.round(state.pos), state.action.dir)
-- Tweak (hack) because if we're laying steps, we're standing on them as
-- we go down. This sucks, and even more so because we might want to
-- skip the steps until another time if we don't have the materials
if state.action.dir.y == -1 and state.action.steps and state.action.distance ~= state.action.startdist then
digpos.y = digpos.y - 1
end
local height = 2
if state.action.dir.y ~= 0 then
if state.action.steps then
height = 4
else
height = 3
end
end
-- Work out if either or both sides of the tunnel should be open at
-- this point...
local leftopen = false
local rightopen = false
if state.action.distance < 4 and state.action.openside then
if state.action.openside.left then leftopen = true end
if state.action.openside.right then rightopen = true end
end
-- Make it so we dig from top to bottom when tunnelling downwards, and
-- bottom to top otherwise.
local yfrom, yto, ydir
if state.action.dir.y < 0 then
yfrom = height
yto = 0
ydir = -1
else
yfrom = 0
yto = height
ydir = 1
end
-- "Special" positions are those where supports and lighting are placed
local sdist = 0
if state.action.sdist then
sdist = state.action.sdist
end
local special_sup = (state.action.startdist - state.action.distance - sdist + 2) % 8 == 7
local special_light = (state.action.startdist - state.action.distance - sdist) % 4 == 3
-- TODO - the following is all a bit ineffecient, because we look at the
-- whole cross-section every time through. We could keep track of
-- where we've got to during a run and skip some.
-- On the other hand, there are things like falling gravel and
-- sand to take into account.
for y = yfrom, yto, ydir do
for x = -1, 1 do
local thisoff = rotate({x=x, y=y, z=0}, state.action.dir)
local thispos = vector.add(digpos, thisoff)
local dignode = minetest.get_node(thispos)
local openside = false
if (x == -1 and leftopen) or
(x == 1 and rightopen) then
openside = true
end
-- Steps at the bottom row if needed.
if y == 0 and state.action.steps and state.action.dir.y ~= 0 then
if dignode.name == "air" then
state.action = {"place", pos=thispos, item=state.action.steps, successaction=state.action,
param2=minetest.dir_to_facedir({x=-state.action.dir.x,y=0,z=-state.action.dir.z}, false)}
return false
elseif dignode.name ~= state.action.steps then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
elseif y == 2 and x ~= 0 and not openside and state.action.lighting
and special_light then
-- We don't actually place lighting (e.g. torches) here,
-- because we do walls later and that would disturb them.
-- But we need to make sure we don't remove existing ones.
if dignode.name ~= "air" and
dignode.name ~= state.action.lighting then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
elseif y == height and not openside and state.action.support_top
and special_sup then
if dignode.name == "air" then
state.action = {"place", pos=thispos, item=state.action.support_top, successaction=state.action}
return false
elseif dignode.name ~= state.action.support_top then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
elseif x ~= 0 and not openside and state.action.support_side
and special_sup then
-- As with lighting, we don't actually put in the side supports
-- now, because they're placed against the wall which we might
-- not have done yet.
if dignode.name ~= "air" and dignode.name ~= state.action.support_side then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
else
if dignode.name ~= "air" then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
end
end
end
if state.action.floor and state.action.dir.y == 0 then
for x = -1, 1 do
local thisoff = rotate({x=x, y=-1, z=0}, state.action.dir)
local thispos = vector.add(digpos, thisoff)
local dignode = minetest.get_node(thispos)
if dignode.name == "air" then
state.action = {"place", pos=thispos, item=state.action.floor, successaction=state.action}
return false
elseif dignode.name ~= state.action.floor then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
end
end
local wall = state.action.wall
if special_sup and state.action.support_wall then
wall = state.action.support_wall
end
if wall and not (leftopen and rightopen) then
local sx = -2
local fx = 2
if leftopen then
sx = fx
elseif rightopen then
fx = sx
end
for y = 0, height + 1 do
for x = sx, fx, 4 do
thisoff = rotate({x=x, y=y, z=0}, state.action.dir)
local thispos = vector.add(digpos, thisoff)
local dignode = minetest.get_node(thispos)
if dignode.name == "air" then
state.action = {"place", pos=thispos, item=wall, successaction=state.action}
return false
elseif dignode.name ~= wall then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
end
end
end
if state.action.lighting and special_light and
not (leftopen and rightopen) and
people.is_carrying(state.ent, state.action.lighting) > 0 then
local sx = -1
local fx = 1
if leftopen then
sx = fx
elseif rightopen then
fx = sx
end
for x = sx, fx, 2 do
local thisoff = rotate({x=x, y=2, z=0}, state.action.dir)
local thispos = vector.add(digpos, thisoff)
local dignode = minetest.get_node(thispos)
if dignode.name ~= state.action.lighting then
local againstdir = rotate({x=x, y=0, z=0}, state.action.dir)
state.action = {"place", pos=thispos, item=state.action.lighting, successaction=state.action,
againstdir=againstdir}
return false
end
end
end
if state.action.support_side and special_sup and
not (leftopen and rightopen) and
people.is_carrying(state.ent, state.action.support_side) > 0 then
local sx = -1
local fx = 1
if leftopen then
sx = fx
elseif rightopen then
fx = sx
end
local sy = 0
if state.action.steps then
sy = 1
end
for y = sy, height do
for x = sx, fx, 2 do
thisoff = rotate({x=x, y=y, z=0}, state.action.dir)
local thispos = vector.add(digpos, thisoff)
local dignode = minetest.get_node(thispos)
if dignode.name ~= state.action.support_side then
local param2
if state.action.support_side_facein then
local facevec = rotate({x=-x, y=0, z=0},
state.action.dir)
if facevec.x > 0 then
param2 = 4 * 3
elseif facevec.x < 0 then
param2 = 4 * 4
elseif facevec.z > 0 then
param2 = 4 * 1
else
param2 = 4 * 2
end
end
state.action = {"place", pos=thispos,
item=state.action.support_side,
successaction=state.action,
param2=param2}
return false
end
end
end
end
local ceiling = state.action.ceiling
if special_sup and state.action.support_ceiling then
ceiling = state.action.support_ceiling
end
if ceiling then
for x = -1, 1 do
local thisoff = rotate({x=x, y=height+1, z=0}, state.action.dir)
local thispos = vector.add(digpos, thisoff)
local dignode = minetest.get_node(thispos)
if dignode.name == "air" then
state.action = {"place", pos=thispos, item=ceiling,
successaction=state.action}
return false
elseif dignode.name ~= ceiling then
state.action = {"dig", pos=thispos, successaction=state.action}
return false
end
end
end
-- We've cleared the cross-section at the next position, so we can move
-- forward a step...
state.action.distance = state.action.distance - 1
state.action = {"go", pos=digpos, successaction=state.action}
return false
end