Movement API re-write

This commit is contained in:
ElCeejo 2022-06-21 09:44:10 -07:00 committed by GitHub
parent ce14c85924
commit f4df6599ae
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 198 additions and 260 deletions

View file

@ -35,7 +35,7 @@ local vec_add = vector.add
local yaw2dir = minetest.yaw_to_dir local yaw2dir = minetest.yaw_to_dir
local dir2yaw = minetest.dir_to_yaw local dir2yaw = minetest.dir_to_yaw
--[[local function debugpart(pos, time, tex) local function debugpart(pos, time, tex)
minetest.add_particle({ minetest.add_particle({
pos = pos, pos = pos,
texture = tex or "creatura_particle_red.png", texture = tex or "creatura_particle_red.png",
@ -43,7 +43,7 @@ local dir2yaw = minetest.dir_to_yaw
glow = 6, glow = 6,
size = 12 size = 12
}) })
end]] end
--------------------- ---------------------
-- Local Utilities -- -- Local Utilities --
@ -79,6 +79,32 @@ local function get_collision(self, yaw)
return false return false
end end
local function get_obstacle_avoidance(self, goal)
local width = self.width
local height = self.height
local pos = self.object:get_pos()
pos.y = pos.y + 1
local yaw2goal = dir2yaw(vec_dir(pos, goal))
local collide, col_pos = get_collision(self, yaw2goal)
if not collide then return end
local avd_pos
for i = 45, 180, 45 do
local angle = rad(i)
local dir = vec_multi(yaw2dir(yaw2goal + angle), width)
avd_pos = vec_center(vec_add(pos, dir))
if not get_collision(self, yaw2goal) then
break
end
angle = -rad(i)
dir = vec_multi(yaw2dir(yaw2goal + angle), width)
avd_pos = vec_center(vec_add(pos, dir))
if not get_collision(self, yaw2goal) then
break
end
end
return avd_pos
end
------------- -------------
-- Actions -- -- Actions --
------------- -------------
@ -88,44 +114,20 @@ end
-- Walk -- Walk
function creatura.action_walk(self, pos2, timeout, method, speed_factor, anim) function creatura.action_move(self, pos2, timeout, method, speed_factor, anim)
local timer = timeout or 4 local timer = timeout or 4
local move_init = false
local function func(_self) 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 timer = timer - _self.dtime
self:animate(anim or "walk")
if timer <= 0 if timer <= 0
or _self:pos_in_box({x = pos2.x, y = pos.y + 0.1, z = pos2.z}) then or _self:move_to(pos2, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then
_self:halt()
return true return true
end end
_self:move(pos2, method or "creatura:neighbors", speed_factor or 0.5, anim)
move_init = true
end end
self:set_action(func) self:set_action(func)
end end
function creatura.action_fly(self, pos2, timeout, method, speed_factor, anim) creatura.action_walk = creatura.action_move
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
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 -- Idle
@ -174,209 +176,144 @@ end
-- Pathfinding -- Pathfinding
creatura.register_movement_method("creatura:pathfind", function(self, pos2) creatura.register_movement_method("creatura:pathfind", function(self, goal)
-- Movement Data local path = {}
local pos = self.object:get_pos() local waypoint
local movement_data = self._movement_data local tick = 0.15
local waypoint = movement_data.waypoint local box = clamp(self.width, 0.5, 1)
local speed = movement_data.speed or 5
local path = self._path
if not path or #path < 2 then
if get_collision(self, dir2yaw(vec_dir(pos, pos2))) then
self._path = creatura.find_path(self, pos, pos2, self.width, self.height, 200) or {}
end
else
waypoint = self._path[2]
if self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
table.remove(self._path, 1)
end
end
if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then
waypoint = creatura.get_next_move(self, pos2)
self._movement_data.waypoint = waypoint
end
-- Turning
local dir2waypoint = vec_dir(pos, pos2)
if waypoint then
dir2waypoint = vec_dir(pos, waypoint)
end
local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint)
local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw))
-- Moving
self:set_gravity(-9.8) self:set_gravity(-9.8)
if yaw_diff < pi * (turn_rate * 0.1) then local function func(self)
self:set_forward_velocity(speed) local pos = self.object:get_pos()
else if not pos then return end
self:set_forward_velocity(speed * 0.5) -- Return true when goal is reached
turn_rate = turn_rate * 1.5 if self:pos_in_box(goal, box) then
end
self:animate(movement_data.anim or "walk")
self:turn_to(tgt_yaw, turn_rate)
if self:pos_in_box(pos2)
or (waypoint
and not self:is_pos_safe(waypoint)) then
self:halt() self:halt()
return true
end end
end) tick = tick - self.dtime
if tick <= 0 then
creatura.register_movement_method("creatura:theta_pathfind", function(self, pos2)
-- Movement Data
local pos = self.object:get_pos()
local movement_data = self._movement_data
local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5
local path = self._path
if not path or #path < 1 then
self._path = creatura.find_theta_path(self, pos, pos2, self.width, self.height, 300) or {}
else
waypoint = self._path[2] or self._path[1]
if self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
table.remove(self._path, 1)
end
end
if not waypoint if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then or self:pos_in_box({x = waypoint.x, y = pos.y + box * 0.5, z = waypoint.z}, box) then
waypoint = creatura.get_next_move(self, pos2) -- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
self._movement_data.waypoint = waypoint waypoint = get_obstacle_avoidance(self, goal)
end end
-- Turning tick = 0.15
local dir2waypoint = vec_dir(pos, pos2) end
-- Get movement direction
local goal_dir = vec_dir(pos, goal)
if waypoint then if waypoint then
dir2waypoint = vec_dir(pos, waypoint) -- There's an obstruction, time to find a path
if #path < 2 then
path = creatura.find_path(self, pos, goal, self.width, self.height, 200) or {}
else
waypoint = path[2]
if self:pos_in_box(path[1], box) then
table.remove(path, 1)
end
end
goal_dir = vec_dir(pos, waypoint)
debugpart(waypoint)
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint) local goal_yaw = dir2yaw(goal_dir)
local speed = abs(self.speed or 2)
local turn_rate = abs(self.turn_rate or 5) local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw)) -- Movement
-- Moving local yaw_diff = abs(diff(yaw, goal_yaw))
self:set_gravity(-9.8)
if yaw_diff < pi * (turn_rate * 0.1) then
self:set_forward_velocity(speed)
else
self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5
end
self:animate(movement_data.anim or "walk")
self:turn_to(tgt_yaw, turn_rate)
if self:pos_in_box(pos2)
or (waypoint
and not self:is_pos_safe(waypoint)) then
self:halt()
end
end)
-- Neighbors
creatura.register_movement_method("creatura:neighbors", function(self, pos2)
-- Movement Data
local pos = self.object:get_pos()
local movement_data = self._movement_data
local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5
if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, clamp(self.width, 0.5, 1)) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
waypoint = creatura.get_next_move(self, pos2)
self._movement_data.waypoint = waypoint
end
-- Turning
local dir2waypoint = vec_dir(pos, pos2)
if waypoint then
dir2waypoint = vec_dir(pos, waypoint)
end
local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint)
local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw))
-- Moving
self:set_gravity(-9.8)
if yaw_diff < pi * 0.25 then if yaw_diff < pi * 0.25 then
self:set_forward_velocity(speed) self:set_forward_velocity(speed)
else else
self:set_forward_velocity(speed * 0.5) self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5 turn_rate = turn_rate * 1.5
end end
self:animate(movement_data.anim or "walk") if yaw_diff > 0.1 then
self:turn_to(tgt_yaw, turn_rate) self:turn_to(goal_yaw, turn_rate)
if self:pos_in_box(pos2)
or (waypoint
and not self:is_pos_safe(waypoint)) then
self:halt()
end end
end
return func
end)
creatura.register_movement_method("creatura:theta_pathfind", function(self, goal)
local path = {}
local waypoint
local tick = 0.15
local box = clamp(self.width, 0.5, 1)
self:set_gravity(-9.8)
local function func(self)
local pos = self.object:get_pos()
if not pos then return end
tick = tick - self.dtime
if tick <= 0 then
if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, box) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
waypoint = get_obstacle_avoidance(self, goal)
end
tick = 0.15
end
-- Get movement direction
local goal_dir = vec_dir(pos, goal)
if waypoint then
-- There's an obstruction, time to find a path
if #path < 1 then
path = creatura.find_path(self, pos, goal, self.width, self.height, 300) or {}
else
waypoint = path[2] or path[1]
end
goal_dir = vec_dir(pos, waypoint)
end
local yaw = self.object:get_yaw()
local goal_yaw = dir2yaw(goal_dir)
if abs(yaw - goal_yaw) > 0.1 then
self:turn_to(goal_yaw, self.turn_rate or 6)
end
-- Set Velocity
self:set_forward_velocity(self.speed or 2)
-- Return true when goal is reached
if self:pos_in_box(goal, box) then
self:halt()
return true
end
end
return func
end) end)
-- Obstacle Avoidance -- Obstacle Avoidance
local function get_obstacle_avoidance(self, goal) creatura.register_movement_method("creatura:obstacle_avoidance", function(self, goal)
local width = self.width local waypoint
local height = self.height local tick = 0.15
local box = clamp(self.width, 0.5, 1)
self:set_gravity(-9.8)
local function func(self)
local pos = self.object:get_pos() local pos = self.object:get_pos()
pos.y = pos.y + 1 if not pos then return end
local yaw2goal = dir2yaw(vec_dir(pos, goal)) tick = tick - self.dtime
local collide, col_pos = get_collision(self, yaw2goal) if tick <= 0 then
if not collide then return end
local avd_pos
for i = 45, 180, 45 do
local angle = rad(i)
local dir = vec_multi(yaw2dir(yaw2goal + angle), width)
avd_pos = vec_center(vec_add(pos, dir))
if not get_collision(self, yaw2goal) then
break
end
angle = -rad(i)
dir = vec_multi(yaw2dir(yaw2goal + angle), width)
avd_pos = vec_center(vec_add(pos, dir))
if not get_collision(self, yaw2goal) then
break
end
end
if col_pos.y - (pos.y + height * 0.5) > 1 then
avd_pos.y = avd_pos.y - 3
elseif (pos.y + height * 0.5) - col_pos.y > 1 then
avd_pos.y = avd_pos.y + 3
end
return avd_pos
end
creatura.register_movement_method("creatura:obstacle_avoidance", function(self, pos2)
-- Movement Data
local pos = self.object:get_pos()
local movement_data = self._movement_data
local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5
if not waypoint if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, clamp(self.width, 0.5, 1)) then or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, box) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes -- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
waypoint = get_obstacle_avoidance(self, pos2) waypoint = get_obstacle_avoidance(self, goal)
self._movement_data.waypoint = waypoint
end end
-- Turning tick = 0.15
local dir2waypoint = vec_dir(pos, pos2) end
-- Get movement direction
local goal_dir = vec_dir(pos, goal)
if waypoint then if waypoint then
dir2waypoint = vec_dir(pos, waypoint) goal_dir = vec_dir(pos, waypoint)
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint) local goal_yaw = dir2yaw(goal_dir)
local turn_rate = abs(self.turn_rate or 5) if abs(yaw - goal_yaw) > 0.1 then
local yaw_diff = abs(diff(yaw, tgt_yaw)) self:turn_to(goal_yaw, self.turn_rate or 6)
-- Moving
self:set_gravity(-9.8)
if yaw_diff < pi * 0.25 then
self:set_forward_velocity(speed)
else
self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5
end end
self:animate(movement_data.anim or "walk") -- Set Velocity
self:turn_to(tgt_yaw, turn_rate) self:set_forward_velocity(self.speed or 2)
if self:pos_in_box(pos2) -- Return true when goal is reached
or (waypoint if self:pos_in_box(goal, box) then
and not self:is_pos_safe(waypoint)) then
self:halt() self:halt()
return true
end end
end
return func
end) end)

View file

@ -149,6 +149,7 @@ function mob:halt()
self._movement_data = { self._movement_data = {
goal = nil, goal = nil,
method = nil, method = nil,
func = nil,
last_neighbor = nil, last_neighbor = nil,
gravity = self._movement_data.gravity or -9.8, gravity = self._movement_data.gravity or -9.8,
speed = 0 speed = 0
@ -564,6 +565,22 @@ function mob:follow_wielded_item(player)
end end
end end
function mob:follow_item(stack)
if not stack
or not self.follow then return end
local name = stack:get_name()
if type(self.follow) == "string"
and (name == self.follow
or minetest.get_item_group(name, self.follow:split(":")[2]) > 0) then
return stack, name
end
if type(self.follow) == "table"
and (is_value_in_table(self.follow, name)
or is_group_in_table(self.follow, name)) then
return stack, name
end
end
function mob:get_target(target) function mob:get_target(target)
local alive = creatura.is_alive(target) local alive = creatura.is_alive(target)
if not alive then if not alive then
@ -651,6 +668,7 @@ function mob:activate(staticdata, dtime)
self._movement_data = { self._movement_data = {
goal = nil, goal = nil,
method = nil, method = nil,
func = nil,
last_neighbor = nil, last_neighbor = nil,
gravity = -9.8, gravity = -9.8,
speed = 0 speed = 0
@ -764,16 +782,9 @@ function mob:on_step(dtime, moveresult)
self.width = self:get_hitbox()[4] or 0.5 self.width = self:get_hitbox()[4] or 0.5
self.height = self:get_height() or 1 self.height = self:get_height() or 1
end end
--local us_time = minetest.get_us_time()
-- Movement Control
if self._move then
self:_move()
end
--minetest.chat_send_all(minetest.get_us_time() - us_time)
if self.utility_stack if self.utility_stack
and self._execute_utilities then and self._execute_utilities then
self:_execute_utilities() self:_execute_utilities()
self:_execute_actions()
end end
-- Die -- Die
if self.hp <= 0 if self.hp <= 0
@ -975,42 +986,22 @@ end
-- Movement Control -- Movement Control
function mob:_move() function mob:move_to(goal, method, speed_factor)
if not self.object then return end local get_method = creatura.registered_movement_methods[method]
local data = self._movement_data local data = self._movement_data
local speed = data.speed if get_method
if data.goal then and not data.func then
local pos = data.goal self._movement_data.func = get_method(self, goal, speed_factor)
local method = data.method return self._movement_data.func(self, goal, speed_factor)
local anim = data.anim
if creatura.registered_movement_methods[method] then
local func = creatura.registered_movement_methods[method]
func(self, pos, speed, anim)
end end
if data.func then
local move = data.func
return move(self, goal, speed_factor)
end end
end end
-- Execute Actions -- Execute Actions
function mob:_execute_actions()
if not self.object then return end
if #self._task > 0 then
local func = self._task[#self._task].func
if func(self) then
self._task[#self._task] = nil
self:clear_action()
return
end
end
local action = self._action
if type(action) ~= "table" then
local func = action
if func(self) then
self:clear_action()
end
end
end
local function tbl_equals(tbl1, tbl2) local function tbl_equals(tbl1, tbl2)
local match = true local match = true
for k, v in pairs(tbl1) do for k, v in pairs(tbl1) do
@ -1094,6 +1085,15 @@ function mob:_execute_utilities()
} }
self:clear_action() self:clear_action()
end end
--local us_time = minetest.get_us_time()
local action = self._action
if action
and type(action) ~= "table" then
if action(self) then
self:clear_action()
end
end
--minetest.chat_send_all(minetest.get_us_time() - us_time)
end end
end end
@ -1101,6 +1101,7 @@ end
function mob:_vitals() function mob:_vitals()
local stand_pos = self.object:get_pos() local stand_pos = self.object:get_pos()
if not stand_pos then return end
local fall_start = self._fall_start local fall_start = self._fall_start
if self.is_falling if self.is_falling
and not fall_start and not fall_start