creatura/methods.lua
Jordan Leppert 076e8423ae 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.
2022-02-23 18:00:42 +00:00

369 lines
11 KiB
Lua

-------------
-- Methods --
-------------
local pi = math.pi
local pi2 = pi * 2
local abs = math.abs
local floor = math.floor
local random = math.random
local rad = math.rad
local function diff(a, b) -- Get difference between 2 angles
return math.atan2(math.sin(b - a), math.cos(b - a))
end
local function clamp(val, min, max)
if val < min then
val = min
elseif max < val then
val = max
end
return val
end
local function vec_center(v)
return {x = floor(v.x + 0.5), y = floor(v.y + 0.5), z = floor(v.z + 0.5)}
end
local function vec_raise(v, n)
return {x = v.x, y = v.y + n, z = v.z}
end
local vec_dir = vector.direction
local vec_dist = vector.distance
local vec_multi = vector.multiply
local vec_add = vector.add
local yaw2dir = minetest.yaw_to_dir
-------------
-- Actions --
-------------
-- Actions are more specific behaviors used
-- to compose a Utility.
-- Walk
function creatura.action_walk(self, pos2, timeout, method, speed_factor, anim)
local timer = timeout or 4
local move_init = false
local function func(self)
if not pos2
or (move_init
and not self._movement_data.goal) then return true end
local pos = self.object:get_pos()
timer = timer - self.dtime
if timer <= 0
or self:pos_in_box({x = pos2.x, y = pos.y + 0.1, z = pos2.z}) then
self:halt()
return true
end
self:move(pos2, method or "creatura:neighbors", speed_factor or 0.5, anim)
move_init = true
end
self:set_action(func)
end
function creatura.action_fly(self, pos2, timeout, method, speed_factor, anim)
local timer = timeout or 4
local move_init = false
local function func(self)
if not pos2
or (move_init
and not self._movement_data.goal) then return true end
local pos = self.object:get_pos()
timer = timer - self.dtime
if timer <= 0
or self:pos_in_box(pos2) then
self:halt()
return true
end
self:move(pos2, method, speed_factor or 0.5, anim)
move_init = true
end
self:set_action(func)
end
-- Idle
function creatura.action_idle(self, time, anim)
local timer = time
local function func(self)
self:set_gravity(-9.8)
self:halt()
self:animate(anim or "stand")
timer = timer - self.dtime
if timer <= 0 then
return true
end
end
self:set_action(func)
end
-- Rotate on Z axis in random direction until 90 degree angle is reached
function creatura.action_fallover(self)
local zrot = 0
local init = false
local dir = 1
local function func(self)
if not init then
self:animate("stand")
if random(2) < 2 then
dir = -1
end
init = true
end
local rot = self.object:get_rotation()
local goal = (pi * 0.5) * dir
local dif = abs(rot.z - goal)
zrot = rot.z + (dif * dir) * 0.15
self.object:set_rotation({x = rot.x, y = rot.y, z = zrot})
if (dir > 0 and zrot >= goal)
or (dir < 0 and zrot <= goal) then return true end
end
self:set_action(func)
end
----------------------
-- Movement Methods --
----------------------
-- Pathfinding
function get_line_of_sight(a, b)
local steps = floor(vector.distance(a, b))
local line = {}
for i = 0, steps do
local pos
if steps > 0 then
pos = {
x = a.x + (b.x - a.x) * (i / steps),
y = a.y + (b.y - a.y) * (i / steps),
z = a.z + (b.z - a.z) * (i / steps)
}
else
pos = a
end
table.insert(line, pos)
end
if #line < 1 then
return false
else
for i = 1, #line do
local node = minetest.get_node(line[i])
if minetest.registered_nodes[node.name].walkable then
return false
end
end
end
return true
end
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()
self._path = self._path or {}
local temp_goal
if #self._path == 0 then
self._path = method(self, self.object:get_pos(), pos2, self.width, self.height, 500) or {}
if #self._path > 0 then
temp_goal = self._path[1]
else
temp_goal = creatura.get_next_move(self, pos2)
end
else
temp_goal = self._path[1]
if self:pos_in_box(get_goal_pos(self, temp_goal)) then
table.remove(self._path, 1)
if #self._path > 0 then
temp_goal = self._path[1]
else
temp_goal = nil
end
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
local advance = speed or 2
if temp_goal then
dir = vector.direction(self.object:get_pos(), temp_goal)
tyaw = minetest.dir_to_yaw(dir)
if #self._path < 1
and not self:is_pos_safe(temp_goal) then
self:animate("walk")
self:set_forward_velocity(0)
self:halt()
return
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
self:turn_to(tyaw, turn_rate)
self:animate("walk")
self:set_gravity(-9.8)
-- 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()
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)
local function movement_pathfind(self, pos2, speed)
return movement_generic_pathfind(self, creatura.find_path, pos2, speed)
end
creatura.register_movement_method("creatura:pathfind", movement_pathfind)
-- Obstacle Avoidance
local function moveable(pos, width, height)
local pos1 = {
x = pos.x - (width + 0.2),
y = pos.y,
z = pos.z - (width + 0.2),
}
local pos2 = {
x = pos.x + (width + 0.2),
y = pos.y,
z = pos.z + (width + 0.2),
}
for z = pos1.z, pos2.z do
for x = pos1.x, pos2.x do
local pos3 = {x = x, y = (pos.y + height), z = z}
local pos4 = {x = x, y = pos.y, z = z}
local ray = minetest.raycast(pos3, pos4, false, false)
for pointed_thing in ray do
if pointed_thing.type == "node" then
return false
end
end
end
end
return true
end
local function get_obstacle_avoidance(self, pos2)
local pos = self.object:get_pos()
local yaw = minetest.dir_to_yaw(vec_dir(pos, pos2))
pos.y = pos.y + self.stepheight
local height = self.height
local width = self.width
local outset = vec_center(vec_add(pos, vec_multi(yaw2dir(yaw), width + 0.2)))
local pos2
if not moveable(outset, width, height) then
yaw = self.object:get_yaw()
for i = 1, 89, 45 do
angle = rad(i)
dir = vec_multi(yaw2dir(yaw + angle), width + 0.2)
pos2 = vec_center(vec_add(pos, dir))
if moveable(pos2, width, height) then
break
end
angle = -rad(i)
dir = vec_multi(yaw2dir(yaw + angle), width + 0.2)
pos2 = vec_center(vec_add(pos, dir))
if moveable(pos2, width, height) then
break
end
end
end
return pos2
end
local function movement_obstacle_avoidance(self, pos2, speed)
local pos = self.object:get_pos()
local temp_goal = self._movement_data.temp_goal
if not temp_goal
or self:pos_in_box(temp_goal) then
self._movement_data.temp_goal = get_obstacle_avoidance(self, pos2)
temp_goal = self._movement_data.temp_goal
if temp_goal then
temp_goal.y = floor(pos.y + self.height * 0.5)
end
end
pos2.y = floor(pos2.y + 0.5)
local dir = vector.direction(pos, pos2)
local tyaw = minetest.dir_to_yaw(dir)
local turn_rate = self.turn_rate or 10
if temp_goal then
dir = vector.direction(pos, temp_goal)
tyaw = minetest.dir_to_yaw(dir)
end
local turn_diff = abs(diff(self.object:get_yaw(), tyaw))
self:turn_to(tyaw, turn_rate)
self:animate("walk")
self:set_gravity(-9.8)
self:set_forward_velocity(speed - clamp(turn_diff, 0, speed * 0.66))
if self:pos_in_box({x = pos2.x, y = pos.y + 0.1, z = pos2.z})
or (temp_goal
and not self:is_pos_safe(temp_goal)) then
self:halt()
end
end
creatura.register_movement_method("creatura:obstacle_avoidance", movement_obstacle_avoidance)
-- Neighbors
local function movement_neighbors(self, pos2, speed)
local pos = self.object:get_pos()
local temp_goal = self._movement_data.temp_goal
local width = clamp(self.width, 0.5, 1.5)
if not temp_goal
or self:pos_in_box(temp_goal) then
self._movement_data.temp_goal = creatura.get_next_move(self, pos2)
temp_goal = self._movement_data.temp_goal
end
pos2.y = pos.y + self.height * 0.5
local yaw = self.object:get_yaw()
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
temp_goal.x = math.floor(temp_goal.x + 0.5)
temp_goal.z = math.floor(temp_goal.z + 0.5)
temp_goal.y = pos.y + self.height * 0.5
dir = vector.direction(self.object:get_pos(), temp_goal)
tyaw = minetest.dir_to_yaw(dir)
if not self:is_pos_safe(temp_goal) then
self:set_forward_velocity(0)
self:halt()
return
end
end
local yaw_diff = abs(diff(yaw, tyaw))
self:turn_to(tyaw, turn_rate)
self:set_gravity(-9.8)
if yaw_diff < pi then
self:animate("walk")
self:set_forward_velocity(speed)
end
if self:pos_in_box(pos2) then
self:halt()
end
end
creatura.register_movement_method("creatura:neighbors", movement_neighbors)