From f4df6599ae0b39f1e6144221fabf01e8fa937fd1 Mon Sep 17 00:00:00 2001 From: ElCeejo <40281901+ElCeejo@users.noreply.github.com> Date: Tue, 21 Jun 2022 09:44:10 -0700 Subject: [PATCH] Movement API re-write --- methods.lua | 383 +++++++++++++++++++++------------------------------ mob_meta.lua | 75 +++++----- 2 files changed, 198 insertions(+), 260 deletions(-) diff --git a/methods.lua b/methods.lua index dc2f1c6..1493859 100644 --- a/methods.lua +++ b/methods.lua @@ -35,7 +35,7 @@ local vec_add = vector.add local yaw2dir = minetest.yaw_to_dir local dir2yaw = minetest.dir_to_yaw ---[[local function debugpart(pos, time, tex) +local function debugpart(pos, time, tex) minetest.add_particle({ pos = pos, texture = tex or "creatura_particle_red.png", @@ -43,7 +43,7 @@ local dir2yaw = minetest.dir_to_yaw glow = 6, size = 12 }) -end]] +end --------------------- -- Local Utilities -- @@ -79,6 +79,32 @@ local function get_collision(self, yaw) return false 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 -- ------------- @@ -88,44 +114,20 @@ end -- 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 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 + self:animate(anim or "walk") if timer <= 0 - or _self:pos_in_box({x = pos2.x, y = pos.y + 0.1, z = pos2.z}) then - _self:halt() + or _self:move_to(pos2, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then 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 - 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 +creatura.action_walk = creatura.action_move -- Idle @@ -174,209 +176,144 @@ end -- Pathfinding -creatura.register_movement_method("creatura: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 < 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 +creatura.register_movement_method("creatura: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) - 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() + local function func(self) + local pos = self.object:get_pos() + if not pos then return end + -- Return true when goal is reached + if self:pos_in_box(goal, box) then + self:halt() + return true + end + tick = tick - self.dtime + if tick <= 0 then + if not waypoint + or self:pos_in_box({x = waypoint.x, y = pos.y + box * 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 < 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 + local yaw = self.object:get_yaw() + local goal_yaw = dir2yaw(goal_dir) + local speed = abs(self.speed or 2) + local turn_rate = abs(self.turn_rate or 5) + -- Movement + local yaw_diff = abs(diff(yaw, goal_yaw)) + 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 + if yaw_diff > 0.1 then + self:turn_to(goal_yaw, turn_rate) + end end + return func end) -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) +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 - 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) - 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 - 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 + return func end) -- Obstacle Avoidance -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 - 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 - 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 = get_obstacle_avoidance(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 +creatura.register_movement_method("creatura:obstacle_avoidance", function(self, goal) + local waypoint + local tick = 0.15 + local box = clamp(self.width, 0.5, 1) 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 - 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() + 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 + 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) \ No newline at end of file diff --git a/mob_meta.lua b/mob_meta.lua index 2636bd7..70aee06 100644 --- a/mob_meta.lua +++ b/mob_meta.lua @@ -149,6 +149,7 @@ function mob:halt() self._movement_data = { goal = nil, method = nil, + func = nil, last_neighbor = nil, gravity = self._movement_data.gravity or -9.8, speed = 0 @@ -564,6 +565,22 @@ function mob:follow_wielded_item(player) 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) local alive = creatura.is_alive(target) if not alive then @@ -651,6 +668,7 @@ function mob:activate(staticdata, dtime) self._movement_data = { goal = nil, method = nil, + func = nil, last_neighbor = nil, gravity = -9.8, speed = 0 @@ -764,16 +782,9 @@ function mob:on_step(dtime, moveresult) self.width = self:get_hitbox()[4] or 0.5 self.height = self:get_height() or 1 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 and self._execute_utilities then self:_execute_utilities() - self:_execute_actions() end -- Die if self.hp <= 0 @@ -975,42 +986,22 @@ end -- Movement Control -function mob:_move() - if not self.object then return end +function mob:move_to(goal, method, speed_factor) + local get_method = creatura.registered_movement_methods[method] local data = self._movement_data - local speed = data.speed - if data.goal then - local pos = data.goal - local method = data.method - local anim = data.anim - if creatura.registered_movement_methods[method] then - local func = creatura.registered_movement_methods[method] - func(self, pos, speed, anim) - end + if get_method + and not data.func then + self._movement_data.func = get_method(self, goal, speed_factor) + return self._movement_data.func(self, goal, speed_factor) + end + if data.func then + local move = data.func + return move(self, goal, speed_factor) end end -- 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 match = true for k, v in pairs(tbl1) do @@ -1094,6 +1085,15 @@ function mob:_execute_utilities() } self:clear_action() 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 @@ -1101,6 +1101,7 @@ end function mob:_vitals() local stand_pos = self.object:get_pos() + if not stand_pos then return end local fall_start = self._fall_start if self.is_falling and not fall_start