diff --git a/README.md b/README.md index b7a82c9..c01bc70 100644 --- a/README.md +++ b/README.md @@ -373,40 +373,52 @@ Submitted data can then be captured in the NPC's own 'on_receive_fields' callbac Note that form text fields, dropdown, list and checkbox selections are automatically stored in the NPC's metadata table. Image/Button clicks, however, are not. -Control Framework +Movement Framework ---- ## Methods -### npcf.control_framework.getControl(npc_ref) -Constructor for the control object. Returns the reference. +### mvobj = npcf.movement.getControl(npc_ref) + +Constructor for the movement control object. Returns the reference. Note, the framework will be activated for NPC on first usage. -### control:stay() +### mvobj:stay() Stop walking, stand up -### control:look_to(pos) +### mvobj:look_to(pos) Look (set yaw) to direction of position pos -### control:sit() +### mvobj:sit() Stop walking and sit down -### control:lay() +### mvobj:lay() Stop walking and lay down -### control:mine() +### mvobj:mine() Begin the mining / digging / attacking animation -### control:mine_stop() +### mvobj:mine_stop() Stop the mining / digging / attacking animation -### control:walk(pos, speed, parameter) +### mvobj:teleport(pos) +Teleport the NPC to given position + +### mvobj:walk(pos, speed, parameter) Find the way and walk to position pos with given speed. For parameter check the set_walk_parameter documentation -###control_proto:stop() +###mvobj:stop() Stay and forgot about the destination -### control:set_walk_parameter(parameter) + +###mvobj:get_path(pos) +Calculate the path. Is used internally in mvobj:walk + +###mvobj:check_for_stuck() +Check if the NPC is stuck. Teleport or stay in this case. +This method is called in framework each step so there is no need to call it by self + +### mvobj:set_walk_parameter(parameter) key-value table to change the walking path determination parameter - find_path @@ -431,7 +443,7 @@ key-value table to change the walking path determination parameter false: forgot about the destination in case of stuck ## Attributes (should be used read-only) -control.is_mining - mining animation is active -control.speed - walking speed -control.real_speed - The "real" speed calculated on velocity -target_pos - Position vector that the NPC try to reach +mvobj.is_mining - mining animation is active +mvobj.speed - walking speed +mvobj.real_speed - The "real" speed calculated on velocity +mvobj.target_pos - Position vector that the NPC try to reach diff --git a/npcf/control.lua b/npcf/movement.lua similarity index 63% rename from npcf/control.lua rename to npcf/movement.lua index 4900972..645da8c 100644 --- a/npcf/control.lua +++ b/npcf/movement.lua @@ -1,5 +1,5 @@ -- NPC framework navigation control object prototype -local control_proto = { +local mvobj_proto = { is_mining = false, speed = 0, target_pos = nil, @@ -18,39 +18,39 @@ local control_proto = { } -- navigation control framework -local control_framework = { - control_proto = control_proto, +local movement = { + mvobj_proto = mvobj_proto, functions = {}, getControl = function(npc) - local control - if npc._control then - control = npc._control + local mvobj + if npc._mvobj then + mvobj = npc._mvobj else - control = npcf.deepcopy(control_proto) - control._npc = npc - npc._control = control + mvobj = npcf.deepcopy(mvobj_proto) + mvobj._npc = npc + npc._mvobj = mvobj end - if npc.object and control._step_init_done ~= true then - control.pos = npc.object:getpos() - control.yaw = npc.object:getyaw() - control.velocity = npc.object:getvelocity() - control.acceleration = npc.object:getacceleration() - control._step_init_done = true + 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 control + return mvobj end } -local functions = control_framework.functions +local functions = movement.functions -- Stop walking and stand up -function control_proto:stay() +function mvobj_proto:stay() self.speed = 0 self._state = NPCF_ANIM_STAND end -- Stay and forgot about the way -function control_proto:stop() +function mvobj_proto:stop() self:stay() self._path = nil self._target_pos_bak = nil @@ -59,43 +59,50 @@ function control_proto:stop() end -- look to position -function control_proto:look_to(pos) +function mvobj_proto:look_to(pos) self.yaw = npcf:get_face_direction(self.pos, pos) end -- Stop walking and sitting down -function control_proto:sit() +function mvobj_proto:sit() self.speed = 0 self.is_mining = false self._state = NPCF_ANIM_SIT end -- Stop walking and lay -function control_proto:lay() +function mvobj_proto:lay() self.speed = 0 self.is_mining = false self._state = NPCF_ANIM_LAY end -- Start mining -function control_proto:mine() +function mvobj_proto:mine() self.is_mining = true end -- Stop mining -function control_proto:mine_stop() +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 control_proto:set_walk_parameter(param) +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 control_proto:walk(pos, speed, param) +function mvobj_proto:walk(pos, speed, param) if param then self:set_walk_parameter(param) end @@ -103,7 +110,7 @@ function control_proto:walk(pos, speed, param) self.target_pos = pos self.speed = speed if self.walk_param.find_path == true then - self._path = functions.get_path(self, pos) + self._path = self:get_path(pos) else self._path = { pos } self._path_used = false @@ -118,17 +125,17 @@ function control_proto:walk(pos, speed, param) end -- do a walking step -function control_proto:_do_control_step(dtime) +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 - control_framework.getControl(self._npc) + movement.getControl(self._npc) self._step_init_done = false - functions.check_for_stuck(self) + self:check_for_stuck() -- check path if self.speed > 0 then @@ -202,7 +209,6 @@ function control_proto:_do_control_step(dtime) self._npc.object:setacceleration(self.acceleration) elseif minetest.registered_nodes[node[-1].name].walkable ~= false and minetest.registered_nodes[node[0].name].walkable ~= false then - print("up!") -- jump if in catched in walkable node self.velocity.y = 3 else @@ -216,6 +222,82 @@ function control_proto:_do_control_step(dtime) 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") + + 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 --------------------------------------------------------------- @@ -244,85 +326,7 @@ function functions.get_walkable_pos(pos, dist) return destpos end -function functions.get_path(control, pos) - local startpos = vector.round(control.pos) - startpos.y = startpos.y - 1 -- NPC is to high - local refpos - if vector.distance(control.pos, pos) > control.walk_param.find_path_max_distance then - refpos = vector.add(control.pos, vector.multiply(vector.direction(control.pos, pos), control.walk_param.find_path_max_distance)) - else - refpos = pos - end - - local destpos - if control.walk_param.fuzzy_destination == true then - destpos = functions.get_walkable_pos(refpos, control.walk_param.fuzzy_destination_distance) - end - if not destpos then - destpos = control.pos - end - local path = minetest.find_path(startpos, destpos, 10, 1, 5, "Dijkstra") - - if not path and control.walk_param.find_path_fallback == true then - path = { destpos, pos } - control._path_used = false - print("fallback path to", minetest.pos_to_string(pos)) - elseif path then - print("calculated path to", minetest.pos_to_string(destpos), minetest.pos_to_string(pos)) - control._path_used = true - table.insert(path, pos) - end - return path -end - -function functions.check_for_stuck(control) - --- high difference stuck - if control.walk_param.teleport_on_stuck == true and control.target_pos then - local teleport_dest - -- Big jump / teleport up- or downsite - if math.abs(control.pos.x - control.target_pos.x) <= 1 and - math.abs(control.pos.z - control.target_pos.z) <= 1 and - vector.distance(control.pos, control.target_pos) > 3 then - teleport_dest = table.copy(control.target_pos) - teleport_dest.y = teleport_dest.y + 1.5 -- teleport over the destination - control.pos = teleport_dest - control._npc.object:setpos(control.pos) - control:stay() - print("big-jump teleport to", minetest.pos_to_string(teleport_dest), "for", minetest.pos_to_string(control.target_pos)) - end - end - - -- stuck check by distance and speed - if (control._target_pos_bak and control.target_pos and control.speed > 0 and - control._path_used ~= true and control._last_distance and - control._target_pos_bak.x == control.target_pos.x and - control._target_pos_bak.y == control.target_pos.y and - control._target_pos_bak.z == control.target_pos.z and - control._last_distance -0.01 <= vector.distance(control.pos, control.target_pos)) or - ( control._walk_started ~= true and control.speed > 0 and - math.sqrt( math.pow(control.velocity.x,2) + math.pow(control.velocity.z,2)) < (control.speed/3)) then - print("Stuck") - if control.walk_param.teleport_on_stuck == true then - local teleport_dest - if vector.distance(control.pos, control.target_pos) > 5 then - teleport_dest = vector.add(control.pos, vector.multiply(vector.direction(control.pos, control.target_pos), 5)) -- 5 nodes teleport step - else - teleport_dest = table.copy(control.target_pos) - teleport_dest.y = teleport_dest.y + 1.5 -- teleport over the destination - end - control.pos = teleport_dest - control._npc.object:setpos(control.pos) - control:stay() - else - control:stay() - end - elseif control.target_pos then - control._last_distance = vector.distance(control.pos, control.target_pos) - end - control._walk_started = false -end --------------------------------------------------------------- -- Return the framework to calling function --------------------------------------------------------------- -return control_framework +return movement diff --git a/npcf/npcf.lua b/npcf/npcf.lua index 4c8829c..f80bc1c 100644 --- a/npcf/npcf.lua +++ b/npcf/npcf.lua @@ -73,8 +73,8 @@ npcf = { animation_state = 0, animation_speed = 30, }, - -- control functions - control_framework = dofile(NPCF_MODPATH.."/control.lua"), + -- movement control functions + movement = dofile(NPCF_MODPATH.."/movement.lua"), deepcopy = deepcopy } @@ -242,8 +242,8 @@ function npcf:register_npc(name, def) if type(def.on_step) == "function" then self.timer = self.timer + dtime def.on_step(self, dtime) - if self._control then - self._control:_do_control_step(dtime) + if self._mvobj then + self._mvobj:_do_movement_step(dtime) end end else diff --git a/npcf_builder/init.lua b/npcf_builder/init.lua index 1a30fbd..27a404d 100644 --- a/npcf_builder/init.lua +++ b/npcf_builder/init.lua @@ -225,42 +225,44 @@ npcf:register_npc("npcf_builder:npc" ,{ end end, on_step = function(self, dtime) - local control = npcf.control_framework.getControl(self) + local mv_obj = npcf.movement.getControl(self) + local pos = mv_obj.pos if self.timer > 1 then self.timer = 0 if not self.owner then return end - control:mine_stop() + mv_obj:mine_stop() if self.metadata.building == true then local nodedata local schemlib_node local distance if not SCHEMLIB_PATH then nodedata = self.var.nodedata[self.metadata.index] - distance = vector.distance(control.pos, nodedata.pos) - control:walk(nodedata.pos, get_speed(distance), {teleport_on_stuck = true}) + distance = vector.distance(pos, nodedata.pos) + mv_obj:walk(nodedata.pos, get_speed(distance), {teleport_on_stuck = true}) else if not self.my_ai_data then self.my_ai_data = {} end schemlib_node = schemlib.npc_ai.plan_target_get({ plan = self.schemlib_plan, - npcpos = control.pos, + npcpos = pos, savedata = self.my_ai_data}) if not schemlib_node then --stuck in plan - control:stop() + mv_obj:stop() if self.schemlib_plan.data.nodecount == 0 then reset_build(self) end return end - distance = vector.distance(control.pos, schemlib_node.world_pos) - control:walk(schemlib_node.world_pos, get_speed(distance), {teleport_on_stuck = true}) + distance = vector.distance(pos, schemlib_node.world_pos) + mv_obj:walk(schemlib_node.world_pos, get_speed(distance), {teleport_on_stuck = true}) end if distance < 4 then - control:mine() - control.speed = 1 + mv_obj:mine() + mv_obj.speed = 1 + mv_obj:set_walk_parameter({teleport_on_stuck = false}) if SCHEMLIB_PATH then schemlib.npc_ai.place_node(schemlib_node, self.schemlib_plan) self.schemlib_plan:del_node(schemlib_node.plan_pos) @@ -270,8 +272,8 @@ npcf:register_npc("npcf_builder:npc" ,{ self.var.selected = "" else self.metadata.building = false - control:mine_stop() - control:stop() + mv_obj:mine_stop() + mv_obj:stop() end end if self.schemlib_plan.data.nodecount == 0 then @@ -281,7 +283,7 @@ npcf:register_npc("npcf_builder:npc" ,{ if minetest.registered_nodes[nodedata.node.name].sounds then local soundspec = minetest.registered_nodes[nodedata.node.name].sounds.place if soundspec then - soundspec.pos = control.pos + soundspec.pos = pos minetest.sound_play(soundspec.name, soundspec) end end @@ -292,8 +294,8 @@ npcf:register_npc("npcf_builder:npc" ,{ self.var.selected = "" else self.metadata.building = false - control:stop() - control:mine_stop() + mv_obj:stop() + mv_obj:mine_stop() local i = 0 for k,v in pairs(self.var.nodelist) do i = i + 1 @@ -310,12 +312,12 @@ npcf:register_npc("npcf_builder:npc" ,{ end end end - elseif vector.equals(control.pos, self.origin.pos) == false then - local distance = vector.distance(control.pos, self.origin.pos) + elseif vector.equals(pos, self.origin.pos) == false then + local distance = vector.distance(pos, self.origin.pos) if distance > 1 then - control:walk(self.origin.pos, get_speed(distance), {teleport_on_stuck = true}) + mv_obj:walk(self.origin.pos, get_speed(distance), {teleport_on_stuck = true}) else - control.yaw = self.origin.yaw + mv_obj.yaw = self.origin.yaw end end end diff --git a/npcf_guard/init.lua b/npcf_guard/init.lua index 319f989..46852d1 100644 --- a/npcf_guard/init.lua +++ b/npcf_guard/init.lua @@ -101,12 +101,13 @@ npcf:register_npc("npcf_guard:npc", { end end, on_step = function(self, dtime) - local control = npcf.control_framework.getControl(self) if self.timer > 1 then + local move_obj = npcf.movement.getControl(self) + local pos = move_obj.pos local target = {object=nil, distance=0} local min_dist = 1000 - control:mine_stop() - for _,object in ipairs(minetest.get_objects_inside_radius(control.pos, TARGET_RADIUS)) do + move_obj:mine_stop() + for _,object in ipairs(minetest.get_objects_inside_radius(pos, TARGET_RADIUS)) do local to_target = false if object:is_player() then if GUARD_ATTACK_PLAYERS == true and self.metadata.attack_players == "true" then @@ -129,10 +130,10 @@ npcf:register_npc("npcf_guard:npc", { end if to_target == true then local op = object:getpos() - local dv = vector.subtract(control.pos, op) + local dv = vector.subtract(pos, op) local dy = math.abs(dv.y - 1) if dy < math.abs(dv.x) or dy < math.abs(dv.z) then - local dist = math.floor(vector.distance(control.pos, op)) + local dist = math.floor(vector.distance(pos, op)) if dist < min_dist then target.object = object target.distance = dist @@ -143,9 +144,9 @@ npcf:register_npc("npcf_guard:npc", { end if target.object then if target.distance < 3 then - control:mine() - control:stay() - control:look_to(target.object:getpos()) + move_obj:mine() + move_obj:stay() + move_obj:look_to(target.object:getpos()) local tool_caps = {full_punch_interval=1.0, damage_groups={fleshy=1}} local item = self.metadata.wielditem if item ~= "" and minetest.registered_items[item] then @@ -157,19 +158,19 @@ npcf:register_npc("npcf_guard:npc", { end if target.distance > 2 then local speed = get_speed(target.distance) * 1.1 - control:walk(target.object:getpos(), speed) + move_obj:walk(target.object:getpos(), speed) end elseif self.metadata.follow_owner == "true" then local player = minetest.get_player_by_name(self.owner) if player then local p = player:getpos() - local distance = vector.distance(control.pos, {x=p.x, y=control.pos.y, z=p.z}) + local distance = vector.distance(pos, {x=p.x, y=pos.y, z=p.z}) if distance > 3 then - control:walk(p, get_speed(distance)) + move_obj:walk(p, get_speed(distance)) else - control:stay() + move_obj:stay() end - control:mine_stop() + move_obj:mine_stop() end elseif self.metadata.patrol == "true" then self.var.rest_timer = self.var.rest_timer + self.timer @@ -180,25 +181,24 @@ npcf:register_npc("npcf_guard:npc", { end local patrol_pos = self.metadata.patrol_points[index] if patrol_pos then - local distance = vector.distance(control.pos, patrol_pos) + local distance = vector.distance(pos, patrol_pos) if distance > 1 then - control:walk(patrol_pos, PATROL_SPEED) + move_obj:walk(patrol_pos, PATROL_SPEED) else - self.object:setpos(patrol_pos) - control:stay() + move_obj:teleport(patrol_pos) self.metadata.patrol_index = index self.var.rest_timer = 0 end end end - elseif vector.equals(control.pos, self.origin.pos) == false then - local distance = vector.distance(control.pos, self.origin.pos) + elseif vector.equals(pos, self.origin.pos) == false then + local distance = vector.distance(pos, self.origin.pos) if distance > 1 then - control:walk(self.origin.pos, get_speed(distance)) + move_obj:walk(self.origin.pos, get_speed(distance)) else - self.object:setpos(self.origin.pos) - control.look_to(self.origin.pos) - control:stay() + move_obj:teleport(self.origin.pos) + move_obj:stay() + move_obj.yaw = self.origin.yaw end end self.timer = 0