-- NPC framework navigation control object prototype local mvobj_proto = { is_mining = false, speed = 0, target_pos = nil, _path = nil, _npc = nil, _state = NPCF_ANIM_STAND, _step_timer = 0, walk_param = { find_path = true, find_path_fallback = true, find_path_max_distance = 20, fuzzy_destination = true, fuzzy_destination_distance = 5, teleport_on_stuck = false, } } -- navigation control framework local movement = { mvobj_proto = mvobj_proto, functions = {}, getControl = function(npc) local mvobj if npc._mvobj then mvobj = npc._mvobj else mvobj = npcf.deepcopy(mvobj_proto) mvobj._npc = npc npc._mvobj = mvobj end if npc.object and mvobj._step_init_done ~= true then mvobj.pos = npc.object:getpos() mvobj.yaw = npc.object:getyaw() mvobj.velocity = npc.object:getvelocity() mvobj.acceleration = npc.object:getacceleration() mvobj._step_init_done = true end return mvobj end } local functions = movement.functions -- Stop walking and stand up function mvobj_proto:stay() self.speed = 0 self._state = NPCF_ANIM_STAND end -- Stay and forgot about the way function mvobj_proto:stop() self:stay() self._path = nil self._target_pos_bak = nil self.target_pos = nil self._last_distance = nil end -- look to position function mvobj_proto:look_to(pos) self.yaw = npcf:get_face_direction(self.pos, pos) end -- Stop walking and sitting down function mvobj_proto:sit() self.speed = 0 self.is_mining = false self._state = NPCF_ANIM_SIT end -- Stop walking and lay function mvobj_proto:lay() self.speed = 0 self.is_mining = false self._state = NPCF_ANIM_LAY end -- Start mining function mvobj_proto:mine() self.is_mining = true end -- Stop mining function mvobj_proto:mine_stop() self.is_mining = false end -- teleport to position function mvobj_proto:teleport(pos) self.pos = pos self._npc.object:setpos(pos) self:stay() end -- Change default parameters for walking function mvobj_proto:set_walk_parameter(param) for k,v in pairs(param) do self.walk_param[k] = v end end -- start walking to pos function mvobj_proto:walk(pos, speed, param) if param then self:set_walk_parameter(param) end self._target_pos_bak = self.target_pos self.target_pos = pos self.speed = speed if self.walk_param.find_path == true then self._path = self:get_path(pos) else self._path = { pos } self._path_used = false end if self._path == nil then self:stop() self:look_to(pos) else self._walk_started = true end end -- do a walking step function mvobj_proto:_do_movement_step(dtime) -- step timing / initialization check self._step_timer = self._step_timer + dtime if self._step_timer < 0.1 then return end self._step_timer = 0 movement.getControl(self._npc) self._step_init_done = false self:check_for_stuck() -- check path if self.speed > 0 then if not self._path or not self._path[1] then self:stop() else if( self._door_pos and vector.distance( self.pos, self._door_pos ) > 1.0 ) then mob_world_interaction.close_door( self, self._door_pos ); self._door_pos = nil; end if( self._gate_pos and vector.distance( self.pos, self._gate_pos ) > 1.0 ) then mob_world_interaction.close_door( self, self._gate_pos ); self._gate_pos = nil; end -- open the closed door in front of the npc (the door is the next target on the path) mob_world_interaction.open_door( self, self._path[1], self._path[1] ); local a = table.copy(self.pos) a.y = 0 local b = {x=self._path[1].x, y=0 ,z=self._path[1].z} --print(minetest.pos_to_string(self.pos), minetest.pos_to_string(self._path[1]), vector.distance(a, b),minetest.pos_to_string(self._npc.object:getpos())) --if self._path[2] then print(minetest.pos_to_string(self._path[2])) end if vector.distance(a, b) < 0.4 or (self._path[2] and vector.distance(self.pos, self._path[2]) < vector.distance(self._path[1], self._path[2])) then if self._path[2] then table.remove(self._path, 1) self._walk_started = true else self:stop() end end end end -- check/set yaw if self._path and self._path[1] then self.yaw = npcf:get_face_direction(self.pos, self._path[1]) end self._npc.object:setyaw(self.yaw) -- check/set animation if self.is_mining then if self.speed == 0 then self._state = NPCF_ANIM_MINE else self._state = NPCF_ANIM_WALK_MINE end else if self.speed == 0 then if self._state ~= NPCF_ANIM_SIT and self._state ~= NPCF_ANIM_LAY then self._state = NPCF_ANIM_STAND end else self._state = NPCF_ANIM_WALK end end npcf:set_animation(self._npc, self._state) -- check for current environment local nodepos = table.copy(self.pos) local node = {} nodepos.y = nodepos.y - 0.5 for i = -1, 1 do node[i] = minetest.get_node(nodepos) nodepos.y = nodepos.y + 1 end if string.find(node[-1].name, "^default:water") then self.acceleration = {x=0, y=-4, z=0} self._npc.object:setacceleration(self.acceleration) -- we are walking in water if string.find(node[0].name, "^default:water") or string.find(node[1].name, "^default:water") then -- we are under water. sink if target bellow the current position. otherwise swim up if not self._path or not self._path[1] or self._path[1].y > self.pos.y then self.velocity.y = 3 end end elseif minetest.find_node_near(self.pos, 2, {"group:water"}) then -- Light-footed near water self.acceleration = {x=0, y=-1, z=0} self._npc.object:setacceleration(self.acceleration) elseif minetest.registered_nodes[node[-1].name].walkable ~= false and minetest.registered_nodes[node[0].name].walkable ~= false and -- do not jump when standing inside a door, gate or similar node mob_world_interaction.door_type[node[0].name] == nil then -- jump if in catched in walkable node self.velocity.y = 3 else -- the mob is standing inside a (closed) door; open it if( self._path and self._path[1] ) then mob_world_interaction.open_door( self, {x=self.pos.x,y=self.pos.y-1,z=self.pos.z}, self._path[1] ); end -- walking self.acceleration = {x=0, y=-10, z=0} self._npc.object:setacceleration(self.acceleration) end --check/set velocity self.velocity = npcf:get_walk_velocity(self.speed, self.velocity.y, self.yaw) self._npc.object:setvelocity(self.velocity) end function mvobj_proto:get_path(pos) local startpos = vector.round(self.pos) startpos.y = startpos.y - 1 -- NPC is to high local refpos if vector.distance(self.pos, pos) > self.walk_param.find_path_max_distance then refpos = vector.add(self.pos, vector.multiply(vector.direction(self.pos, pos), self.walk_param.find_path_max_distance)) else refpos = pos end local destpos if self.walk_param.fuzzy_destination == true then destpos = functions.get_walkable_pos(refpos, self.walk_param.fuzzy_destination_distance) end if not destpos then destpos = self.pos end --local path = minetest.find_path(startpos, destpos, 10, 1, 5, "Dijkstra") local path = mob_world_interaction.find_path(startpos, destpos, { collisionbox = {1,0,3,4,2}}); if not path and self.walk_param.find_path_fallback == true then path = { destpos, pos } self._path_used = false --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)) self._path_used = true table.insert(path, pos) end return path end function mvobj_proto:check_for_stuck() -- high difference stuck if self.walk_param.teleport_on_stuck == true and self.target_pos then local teleport_dest -- Big jump / teleport up- or downsite if math.abs(self.pos.x - self.target_pos.x) <= 1 and math.abs(self.pos.z - self.target_pos.z) <= 1 and vector.distance(self.pos, self.target_pos) > 3 then teleport_dest = table.copy(self.target_pos) teleport_dest.y = teleport_dest.y + 1.5 -- teleport over the destination --print("big-jump teleport to "..minetest.pos_to_string(teleport_dest).." for target "..minetest.pos_to_string(self.target_pos)) self:teleport(teleport_dest) end end -- stuck check by distance and speed if (self._target_pos_bak and self.target_pos and self.speed > 0 and self._path_used ~= true and self._last_distance and self._target_pos_bak.x == self.target_pos.x and self._target_pos_bak.y == self.target_pos.y and self._target_pos_bak.z == self.target_pos.z and self._last_distance -0.01 <= vector.distance(self.pos, self.target_pos)) or ( self._walk_started ~= true and self.speed > 0 and math.sqrt( math.pow(self.velocity.x,2) + math.pow(self.velocity.z,2)) < (self.speed/3)) then --print("Stuck") if self.walk_param.teleport_on_stuck == true then local teleport_dest if vector.distance(self.pos, self.target_pos) > 5 then teleport_dest = vector.add(self.pos, vector.multiply(vector.direction(self.pos, self.target_pos), 5)) -- 5 nodes teleport step else teleport_dest = table.copy(self.target_pos) teleport_dest.y = teleport_dest.y + 1.5 -- teleport over the destination end self:teleport(teleport_dest) else self:stay() end elseif self.target_pos then self._last_distance = vector.distance(self.pos, self.target_pos) end self._walk_started = false end --------------------------------------------------------------- -- define framework functions internally used --------------------------------------------------------------- function functions.get_walkable_pos(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 --------------------------------------------------------------- -- Return the framework to calling function --------------------------------------------------------------- return movement