guards/patrol.lua

297 lines
9.6 KiB
Lua

local abs = math.abs
guards.door_close_delay = 5
guards.get_walkable_pos = function(pos, dist)
local destpos
local rpos = vector.round(pos)
for y = rpos.y+dist-1, rpos.y-dist-1, -1 do
for x = rpos.x-dist, rpos.x+dist do
for z = rpos.z-dist, rpos.z+dist do
local p = {x=x, y=y, z=z}
local node = minetest.get_node(p)
local nodedef = minetest.registered_nodes[node.name]
if not (node.name == "air" or nodedef and (nodedef.walkable == false or nodedef.drawtype == "airlike")) then
p.y = p.y +1
local node = minetest.get_node(p)
local nodedef = minetest.registered_nodes[node.name]
if node.name == "air" or nodedef and (nodedef.walkable == false or nodedef.drawtype == "airlike") then
if destpos == nil or vector.distance(p, pos) < vector.distance(destpos, pos) then
destpos = p
end
end
end
end
end
end
return destpos
end
guards.get_path = function(self, pos)
-- minetest.chat_send_all("GUARDS get_path from "..
-- minetest.serialize(spos).." to "..
-- minetest.serialize(pos))
local spos = self.object:get_pos()
local startpos = vector.round(spos)
startpos.y = startpos.y - 1 -- NPC is to high
local refpos
if vector.distance(spos, pos) > 20 then
refpos = vector.add(spos, vector.multiply(vector.direction(spos, pos), 20))
else
refpos = pos
end
local destpos = guards.get_walkable_pos(refpos, 2)
if not destpos then
destpos = spos
end
startpos.y = startpos.y + 1
local max_drop = (self.fear_height or 6) - 1
local path = minetest.find_path(startpos, destpos, 30, 1, max_drop, "A*_noprefetch")
-- local path = mob_world_interaction.find_path(startpos, destpos, { collisionbox = {1,0,3,4,2}});
-- minetest.chat_send_player("singleplayer", "Path found: "..minetest.serialize(path).." from: "..minetest.pos_to_string(startpos).." to: "..minetest.pos_to_string(destpos))
if not path then
path = { destpos, pos }
--print("fallback path to "..minetest.pos_to_string(pos))
elseif path then
--print("calculated path to "..minetest.pos_to_string(destpos).."for destination"..minetest.pos_to_string(pos))
table.insert(path, pos)
end
--[[
-- from below the NPC to below the target point
destpos.y = destpos.y - 1
-- without objects
local ray = Raycast(startpos, destpos, false, true)
for pointed_thing in ray do
minetest.chat_send_player("singleplayer", "Ray: "..minetest.serialize(pointed_thing))
end
--]]
return path
end
-- the mob first tires to walk in a straight line;
-- if that fails (cliff, collusion, stuck) it will switch to a pahtfinder
guards.switch_to_pathfinding = function(self)
-- the old path no longer works; search a new one
self.follow_path = guards.get_path(self, self.follow_path[#self.follow_path])
self.path_stuck_timer = 0
-- at_cliff isn't updated that frequently; wait a bit
self.wait_a_bit = 2.0
end
-- taken from npcf
guards.get_face_direction = function(self, p1, p2)
if p1 and p2 and p1.x and p2.x and p1.z and p2.z then
local px = p1.x - p2.x
local pz = p2.z - p1.z
return math.atan2(px, pz)
end
end
-- try opening a door, gate or hatch via right-click
guards.try_open_door = function(self, pos, closing_again)
if(not(pos)) then
return false
end
local node = minetest.get_node(pos)
-- no suitable node found
if(not(node) or not(node.name) or not(minetest.registered_nodes[node.name])) then
return false
end
-- something that we (guards) might be able to open by right-clicking?
-- is it a door?
if(doors.registered_doors[node.name]) then
doors.door_toggle(pos, node, nil) --, clicker)
-- is it a normal trapdoor?
elseif(doors.registered_trapdoors[node.name]) then
doors.trapdoor_toggle(pos, node, nil) --, clicker)
-- is it something else that can be right-clicked?
elseif(minetest.registered_nodes[node.name]
and minetest.registered_nodes[node.name].on_rightclick) then
-- and minetest.registered_nodes[node.name].on_rightclick(pos, node, nil)) then
-- guards can't read texts in formspecs
local meta = minetest.get_meta(pos)
if(meta and meta:get_string("formspec") and meta:get_string("formspec") ~= "") then
return false
end
-- do not right-click nodes that have an inventory (they most likely show a
-- formspec - which the NPC can't use anyway)
local inv = meta:get_inventory()
for k, l in pairs(inv:get_lists()) do
return false
end
minetest.registered_nodes[node.name].on_rightclick(pos, node, nil)
else
return false
end
-- close it again after passing through
if(not(closing_again)) then
minetest.after(guards.door_close_delay, guards.try_open_door, self, pos, true)
end
return true
end
guards.on_step = function(self, dtime, moveresult)
if(self.order ~= "patrol" or self.state == "attack") then
return true
end
self.ptimer = (self.ptimer or 0) + dtime
-- if self.ptimer < 0.2 then
-- return true
-- end
local pos = self.object:get_pos()
if(not(self.path_stuck_timer)) then
self.path_stuck_timer = 0
end
-- the NPC has a path which it shall follow
if(self.follow_path and #self.follow_path > 0) then
local p_next = self.follow_path[1]
local dist = abs(pos.x - p_next.x) + abs(pos.z - p_next.z) + abs(pos.y - p_next.y)
local last_pos = self.path_lastpos
if(dist < 0.8) then
-- minetest.set_node({x=p_next.x, y=p_next.y-1, z=p_next.z}, {name="wool:orange"})
table.remove(self.follow_path , 1)
-- not stuck - temporary target reached
self.path_stuck_timer = 0
self.wait_a_bit = 0
elseif(self.wait_a_bit and self.wait_a_bit > 0) then
self.wait_a_bit = self.wait_a_bit - dtime
elseif(self.at_cliff) then
minetest.chat_send_player("singleplayer", "There's a cliff! Searching new path.")
guards.switch_to_pathfinding(self)
elseif(last_pos and (abs(last_pos.x - pos.x) + abs(last_pos.z - pos.z) + abs(last_pos.y - pos.y )< 0.3)) then
self.path_stuck_timer = self.path_stuck_timer + dtime
if self.path_stuck_timer > 5.0 then
local coll_node = 0
local coll_obj = 0
local coll_txt = ""
local did_rightclick = false
if(moveresult and moveresult.collisions and #moveresult.collisions > 1) then
for i, v in ipairs(moveresult.collisions) do
if(v.type and v.type == "object") then
coll_obj = coll_obj + 1
coll_txt = coll_txt.." "..tostring(v.object.name)
elseif(v.type and v.type == "node") then
coll_node = coll_node + 1
coll_txt = coll_txt.." "..minetest.pos_to_string(v.node_pos)
local n = minetest.get_node(v.node_pos)
if(n and n.name) then
coll_txt = coll_txt.."-"..tostring(n.name)
end
-- if it's a door: wait a bit/try again
did_rightclick = guards.try_open_door(self, v.node_pos, false)
end
end
end
minetest.chat_send_player("singleplayer", "I'm stuck! Searching new path."..
" Objects: "..tostring(coll_obj).." Nodes: "..tostring(coll_node)..
" Details: "..tostring(coll_txt))
if(did_rightclick) then
self.path_stuck_timer = 1.0
else
-- if(coll_node > 1) then
guards.switch_to_pathfinding(self)
-- else
---- minetest.chat_send_player("singleplayer", "I'm stuck! There's something in the way.")
-- self.wait_a_bit = 5.0
end
end
else
-- not stuck - just walking to temporary target
self.path_stuck_timer = 0
end
-- store last postion to detect if we get stuck
self.path_lastpos = {x = pos.x, y = pos.y, z = pos.z}
-- either an error occoured..
if(not(self.follow_path)
-- ..or we reached the end of the path
or #self.follow_path < 1) then
if(self.follow_path and #self.follow_path < 1) then
self.state = "position_reached"
elseif(self.at_cliff) then
self.state = "failed_goto_because_cliff"
elseif(self.path_stuck_timer > 0) then
self.state = "failed_goto_because_stuck"
end
-- minetest.chat_send_player("singleplayer","Guard: "..tostring(self.state))
self.path_stuck_timer = 0
self.follow_path = nil
self:set_velocity(0)
self:set_animation("stand")
-- normal walking along the path
-- TODO: do this only when starting to walk?
else
-- go to the next position that belongs to the path
p_next = self.follow_path[1]
local yaw = guards.get_face_direction(self, pos, p_next)
self.object:set_yaw(yaw)
if(self.wait_a_bit and self.wait_a_bit > 0) then
self:set_velocity(0)
self:set_animation("stand")
else
self.wait_a_bit = nil
self:set_velocity(self.walk_velocity)
self:set_animation("walk")
self.state = "follow_path"
end
end
elseif(self.order == "patrol") then
self.var.rest_timer = self.var.rest_timer + self.ptimer
if(self.state == "position_reached") then
local index = self.metadata.patrol_index
local patrol_pos = self.metadata.patrol_points[index]
self.object:set_pos(patrol_pos)
self.path_stuck_timer = 0
self.state = "patrol_waiting"
self:set_velocity(0)
self:set_animation("stand")
elseif self.var.rest_timer > self.metadata.patrol_rest then
local index = self.metadata.patrol_index + 1
if index > #self.metadata.patrol_points then
index = 1
end
local patrol_pos = self.metadata.patrol_points[index]
if patrol_pos then
self.metadata.patrol_index = index
self.follow_path = {patrol_pos}
self.state = "goto_position"
self.var.rest_timer = 0
--[[
local distance = vector.distance(pos, patrol_pos)
minetest.chat_send_player("singleplayer","Guard patrol: "..minetest.pos_to_string(patrol_pos).." d: "..tostring(distance))
if distance > 1.0 then
-- start by trying to walk directly
self.follow_path = {patrol_pos}
else
self.object:set_pos(patrol_pos)
self.metadata.patrol_index = index
self.var.rest_timer = 0
self:set_velocity(0)
self:set_animation("stand")
end
--]]
end
end
end
self.ptimer = 0
-- return false
return true
end