297 lines
9.6 KiB
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
|