Movement Improvements

Pathfinding movement functions for A* and theta have been combined into one generic function.
Ensure next goal is always set if there is a valid path.
Checking if mob has reached the next goal improved - now works no matter the mob height, and the height of the node below the goal.
mob:pos_in_box now can take a size param that is a table. Using this to add stepheight to the box height, to fix issues with mobs stepping up onto next node before hitting the goal.
Mobs now won't overshoot their goal, if they're very close to it (less than mob width) their speed reduces.
The more a mob is turning, the less forward velocity it has. This should fix spinning a lot.

mob:pos_in_box now can take a size param that is a table, containing the separate width and height.

pathfinding.moveable no longer pads an extra 0.2 around the width of mob when checking if there is sufficient space for the mob.

pathfinding.get_neighbors now uses max_fall for the maximum distance we can go down, instead of stepheight.
This commit is contained in:
Jordan Leppert 2022-02-21 16:50:08 +00:00 committed by Jordan Leppert
parent 35069011d6
commit 076e8423ae
3 changed files with 75 additions and 81 deletions

View file

@ -14,12 +14,12 @@ local function diff(a, b) -- Get difference between 2 angles
end end
local function clamp(val, min, max) local function clamp(val, min, max)
if val < min then if val < min then
val = min val = min
elseif max < val then elseif max < val then
val = max val = max
end end
return val return val
end end
local function vec_center(v) local function vec_center(v)
@ -104,26 +104,26 @@ end
-- Rotate on Z axis in random direction until 90 degree angle is reached -- Rotate on Z axis in random direction until 90 degree angle is reached
function creatura.action_fallover(self) function creatura.action_fallover(self)
local zrot = 0 local zrot = 0
local init = false local init = false
local dir = 1 local dir = 1
local function func(self) local function func(self)
if not init then if not init then
self:animate("stand") self:animate("stand")
if random(2) < 2 then if random(2) < 2 then
dir = -1 dir = -1
end end
init = true init = true
end end
local rot = self.object:get_rotation() local rot = self.object:get_rotation()
local goal = (pi * 0.5) * dir local goal = (pi * 0.5) * dir
local dif = abs(rot.z - goal) local dif = abs(rot.z - goal)
zrot = rot.z + (dif * dir) * 0.15 zrot = rot.z + (dif * dir) * 0.15
self.object:set_rotation({x = rot.x, y = rot.y, z = zrot}) self.object:set_rotation({x = rot.x, y = rot.y, z = zrot})
if (dir > 0 and zrot >= goal) if (dir > 0 and zrot >= goal)
or (dir < 0 and zrot <= goal) then return true end or (dir < 0 and zrot <= goal) then return true end
end end
self:set_action(func) self:set_action(func)
end end
---------------------- ----------------------
@ -164,26 +164,40 @@ function get_line_of_sight(a, b)
return true return true
end end
local function movement_theta_pathfind(self, pos2, speed) local function get_goal_pos(self, goal)
local node_name = minetest.get_node(vec_raise(goal, -1)).name
local node_height = creatura.get_node_height(node_name) - 1
return vector.new(goal.x, goal.y + node_height, goal.z)
end
local function movement_generic_pathfind(self, method, pos2, speed)
local pos = self.object:get_pos() local pos = self.object:get_pos()
self._path = self._path or {} self._path = self._path or {}
local temp_goal = self._movement_data.temp_goal local temp_goal
if not temp_goal if #self._path == 0 then
or self:pos_in_box({x = temp_goal.x, y = pos.y + self.height * 0.5, z = temp_goal.z}) then self._path = method(self, self.object:get_pos(), pos2, self.width, self.height, 500) or {}
self._movement_data.temp_goal = creatura.get_next_move(self, pos2) if #self._path > 0 then
temp_goal = self._movement_data.temp_goal temp_goal = self._path[1]
end else
if #self._path < 1 then temp_goal = creatura.get_next_move(self, pos2)
self._path = creatura.find_theta_path(self, self.object:get_pos(), pos2, self.width, self.height, 500) or {} end
else else
temp_goal = self._path[2] or self._path[1] temp_goal = self._path[1]
if self:pos_in_box({x = temp_goal.x, y = pos.y + self.height * 0.5, z = temp_goal.z}) then if self:pos_in_box(get_goal_pos(self, temp_goal)) then
table.remove(self._path, 1) table.remove(self._path, 1)
if #self._path > 0 then
temp_goal = self._path[1]
else
temp_goal = nil
end
end end
end end
local dir = vector.direction(self.object:get_pos(), pos2) local dir = vector.direction(self.object:get_pos(), pos2)
local tyaw = minetest.dir_to_yaw(dir) local tyaw = minetest.dir_to_yaw(dir)
local turn_rate = self.turn_rate or 10 local turn_rate = self.turn_rate or 10
local advance = speed or 2
if temp_goal then if temp_goal then
dir = vector.direction(self.object:get_pos(), temp_goal) dir = vector.direction(self.object:get_pos(), temp_goal)
tyaw = minetest.dir_to_yaw(dir) tyaw = minetest.dir_to_yaw(dir)
@ -194,59 +208,33 @@ local function movement_theta_pathfind(self, pos2, speed)
self:halt() self:halt()
return return
end end
local goal_dist = vector.distance(vector.new(pos.x, 0, pos.z), vector.new(temp_goal.x, 0, temp_goal.z))
if goal_dist < advance / 10 then
advance = math.min(advance, goal_dist * 10)
end
end end
self:turn_to(tyaw, turn_rate) self:turn_to(tyaw, turn_rate)
self:animate("walk") self:animate("walk")
self:set_gravity(-9.8) self:set_gravity(-9.8)
self:set_forward_velocity(speed or 2)
if self:pos_in_box(pos2) then -- The more we're turning, the less we should be moving forward
local turn_amount = math.min(self.dtime * turn_rate, abs(tyaw - self.object:get_yaw()) % pi2) / (self.dtime * turn_rate)
advance = advance * (1 - turn_amount)
self:set_forward_velocity(advance)
if self:pos_in_box(get_goal_pos(self, pos2)) then
self:halt() self:halt()
end end
end end
local function movement_theta_pathfind(self, pos2, speed)
return movement_generic_pathfind(self, creatura.find_theta_path, pos2, speed)
end
creatura.register_movement_method("creatura:theta_pathfind", movement_theta_pathfind) creatura.register_movement_method("creatura:theta_pathfind", movement_theta_pathfind)
local function movement_pathfind(self, pos2, speed) local function movement_pathfind(self, pos2, speed)
local pos = self.object:get_pos() return movement_generic_pathfind(self, creatura.find_path, pos2, speed)
local temp_goal = self._movement_data.temp_goal
self._path = self._path or {}
if (not temp_goal
or self:pos_in_box({x = temp_goal.x, y = pos.y + self.height * 0.5, z = temp_goal.z}))
and #self._path < 1 then
self._movement_data.temp_goal = creatura.get_next_move(self, pos2)
temp_goal = self._movement_data.temp_goal
end
if #self._path < 2 then
self._path = creatura.find_path(self, self.object:get_pos(), pos2, self.width, self.height, 100) or {}
else
temp_goal = self._path[2]
if self:pos_in_box({x = temp_goal.x, y = pos.y + self.height * 0.5, z = temp_goal.z}) then
table.remove(self._path, 1)
end
end
local dir = vector.direction(self.object:get_pos(), pos2)
local tyaw = minetest.dir_to_yaw(dir)
local turn_rate = self.turn_rate or 10
if temp_goal then
dir = vector.direction(self.object:get_pos(), temp_goal)
tyaw = minetest.dir_to_yaw(dir)
if #self._path < 2
and not self:is_pos_safe(temp_goal) then
self:animate("walk")
self:set_forward_velocity(0)
self:halt()
return
end
end
self:turn_to(tyaw, turn_rate)
self:animate("walk")
self:set_gravity(-9.8)
self:set_forward_velocity(speed or 2)
if self:pos_in_box(pos2) then
self:halt()
end
end end
creatura.register_movement_method("creatura:pathfind", movement_pathfind) creatura.register_movement_method("creatura:pathfind", movement_pathfind)
-- Obstacle Avoidance -- Obstacle Avoidance
@ -379,4 +367,3 @@ local function movement_neighbors(self, pos2, speed)
end end
creatura.register_movement_method("creatura:neighbors", movement_neighbors) creatura.register_movement_method("creatura:neighbors", movement_neighbors)

View file

@ -268,12 +268,19 @@ end
function mob:pos_in_box(pos, size) function mob:pos_in_box(pos, size)
if not pos then return false end if not pos then return false end
local center = self:get_center_pos() local center = self:get_center_pos()
local width = size or self.width
local height = size or (self.height * 0.5) local width = math.max(self.width, 0.5)
if not size local height = (self.height * 0.5)
and self.width < 0.5 then if size then
width = 0.5 if type(size) == "table" then
width = size[1]
height = size[2]
else
width = size
height = size
end
end end
local edge_a = { local edge_a = {
x = center.x - width, x = center.x - width,
y = center.y - height, y = center.y - height,

View file

@ -20,14 +20,14 @@ end
local function moveable(pos, width, height) local function moveable(pos, width, height)
local pos1 = { local pos1 = {
x = pos.x - (width + 0.2), x = pos.x - width,
y = pos.y, y = pos.y,
z = pos.z - (width + 0.2), z = pos.z - width,
} }
local pos2 = { local pos2 = {
x = pos.x + (width + 0.2), x = pos.x + width,
y = pos.y, y = pos.y,
z = pos.z + (width + 0.2), z = pos.z + width,
} }
for z = pos1.z, pos2.z do for z = pos1.z, pos2.z do
for x = pos1.x, pos2.x do for x = pos1.x, pos2.x do
@ -141,7 +141,7 @@ local function get_neighbors(self, pos, goal, swim, fly, climb, tbl, open, close
local height = self.height local height = self.height
local result = {} local result = {}
local max_up = self.stepheight or 1 local max_up = self.stepheight or 1
local max_down = self.stepheight or 1 local max_down = self.max_fall or 1
local node_name = minetest.get_node(pos).name local node_name = minetest.get_node(pos).name
-- Get the height of the node collision box (and of its node box, if different) -- Get the height of the node collision box (and of its node box, if different)