diff --git a/api.lua b/api.lua index 001ec92..50cfacc 100644 --- a/api.lua +++ b/api.lua @@ -139,14 +139,17 @@ function creatura.is_pos_moveable(pos, width, height) y = pos.y, z = pos.z + (width + 0.2), } - for x = pos1.x, pos2.x do - for z = pos1.z, pos2.z do - local pos3 = {x = x, y = (pos.y + height), z = z} - local pos4 = {x = pos3.x, y = pos.y, z = pos3.z} + 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 + 0.01, z = z} local ray = minetest.raycast(pos3, pos4, false, false) for pointed_thing in ray do if pointed_thing.type == "node" then - return false + local name = minetest.get_node(pointed_thing.under).name + if minetest.registered_nodes[name].walkable then + return false + end end end end @@ -172,9 +175,13 @@ function creatura.get_next_move(self, pos2) local last_move = self._movement_data.last_move local width = self.width local height = self.height - local scan_width = width * 2 local pos = self.object:get_pos() - pos.y = floor(pos.y + 0.5) + pos = { + x = floor(pos.x), + y = pos.y + 0.01, + z = floor(pos.z) + } + pos.y = pos.y + 0.01 if last_move and last_move.pos then local last_call = minetest.get_position_from_hash(last_move.pos) @@ -193,36 +200,44 @@ function creatura.get_next_move(self, pos2) vec_add(pos, {x = 0, y = 0, z = -1}), vec_add(pos, {x = 1, y = 0, z = -1}) } - local next + local _next table.sort(neighbors, function(a, b) return vec_dist(a, pos2) < vec_dist(b, pos2) end) for i = 1, #neighbors do local neighbor = neighbors[i] - local can_move = fast_ray_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor) + local can_move = fast_ray_sight(pos, neighbor) if vector.equals(neighbor, pos2) then can_move = true end - if not self:is_pos_safe(vec_raise(neighbor, 0.6)) then + if can_move + and not moveable(neighbor, width, height) then can_move = false + if moveable(vec_raise(neighbor, 0.5), width, height) then + can_move = true + end end if can_move - and not moveable(vec_raise(neighbor, 0.6), width, height) then + and not self:is_pos_safe(neighbor) then can_move = false end - local dist = vec_dist(neighbor, pos2) if can_move then - next = neighbor + _next = vec_raise(neighbor, 0.1) break end end - if next then + if _next then self._movement_data.last_move = { pos = minetest.hash_node_position(pos), - move = minetest.hash_node_position(next) + move = minetest.hash_node_position(_next) + } + _next = { + x = floor(_next.x), + y = _next.y, + z = floor(_next.z) } end - return next + return _next end function creatura.get_next_move_3d(self, pos2) @@ -371,8 +386,42 @@ function creatura.get_nearby_entities(self, name) return nearby end -function creatura.get_node_def(pos) - local def = minetest.registered_nodes[minetest.get_node(pos).name] +local default_node_def = {walkable = true} -- both ignore and unknown nodes are walkable + +function minetest.get_node_height_from_def(name) + local def = minetest.registered_nodes[name] + if not def then return 0.5 end + if def.walkable then + if def.drawtype == "nodebox" then + if def.node_box + and def.node_box.type == "fixed" then + if type(def.node_box.fixed[1]) == "number" then + return 0.5 + def.node_box.fixed[5] + elseif type(def.node_box.fixed[1]) == "table" then + return 0.5 + def.node_box.fixed[1][5] + else + return 1 + end + else + return 1 + end + else + return 1 + end + else + return 1 + end +end + +function creatura.get_node_def(node) -- Node can be name or pos + if type(node) == "table" then + node = minetest.get_node(node).name + end + local def = minetest.registered_nodes[node] or default_node_def + if def.walkable + and minetest.get_node_height_from_def(node) < 0.26 then + def.walkable = false -- workaround for nodes like snow + end return def end diff --git a/init.lua b/init.lua index bbced09..a5b920d 100644 --- a/init.lua +++ b/init.lua @@ -2,8 +2,8 @@ creatura = {} local path = minetest.get_modpath("creatura") -dofile(path.."/pathfinder.lua") dofile(path.."/api.lua") +dofile(path.."/pathfinder.lua") dofile(path.."/methods.lua") -- Optional Files -- diff --git a/methods.lua b/methods.lua index c86f04c..77bd578 100644 --- a/methods.lua +++ b/methods.lua @@ -35,6 +35,17 @@ local vec_dist = vector.distance local vec_multi = vector.multiply local vec_add = vector.add local yaw2dir = minetest.yaw_to_dir +local dir2yaw = minetest.dir_to_yaw + +local function debugpart(pos, time, tex) + minetest.add_particle({ + pos = pos, + texture = tex or "creatura_particle_red.png", + expirationtime = time or 3, + glow = 6, + size = 12 + }) +end ------------- -- Actions -- @@ -132,154 +143,137 @@ end -- 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 movement_theta_pathfind(self, pos2, speed) +creatura.register_movement_method("creatura:pathfind", function(self, pos2) + -- Movement Data local pos = self.object:get_pos() - local goal = pos2 - self._path = self._path or {} - local temp_goal = self._movement_data.temp_goal - if not temp_goal - or self:pos_in_box({x = temp_goal.x, y = pos.y + self.height * 0.5, z = temp_goal.z}) then - self._movement_data.temp_goal = creatura.get_next_move(self, pos2) - temp_goal = self._movement_data.temp_goal - end - if #self._path < 1 then - self._path = creatura.find_theta_path(self, self.object:get_pos(), pos2, self.width, self.height, 500) or {} + 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 + self._path = creatura.find_path(self, pos, pos2, self.width, self.height, 200) or {} else - temp_goal = self._path[2] or self._path[1] - if self:pos_in_box({x = temp_goal.x, y = pos.y + self.height * 0.5, z = temp_goal.z}) then + 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 - goal.y = pos.y + 0.5 - 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 < 1 - and not self:is_pos_safe(temp_goal) then - self:animate("walk") - self:set_forward_velocity(0) - self:halt() - return - 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 - self:turn_to(tyaw, turn_rate) - self:animate("walk") + -- 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_forward_velocity(speed or 2) - if self:pos_in_box(goal) then - self:halt() - end -end - -creatura.register_movement_method("creatura:theta_pathfind", movement_theta_pathfind) - -local function movement_pathfind(self, pos2, speed) - local pos = self.object:get_pos() - local goal = pos2 - 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 {} + if yaw_diff < pi * (turn_rate * 0.1) then + self:animate(movement_data.anim or "walk") + self:set_forward_velocity(speed) 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 + self:set_forward_velocity(speed * 0.5) + turn_rate = turn_rate * 1.5 end - goal.y = pos.y + 0.5 - 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) + self:turn_to(tgt_yaw, turn_rate) 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: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 + 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:animate(movement_data.anim or "walk") + self:set_forward_velocity(speed) + else + self:set_forward_velocity(speed * 0.5) + turn_rate = turn_rate * 1.5 + end + self:turn_to(tgt_yaw, turn_rate) + if self:pos_in_box(pos2) 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 = 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:animate(movement_data.anim or "walk") + self:set_forward_velocity(speed) + else + self:set_forward_velocity(speed * 0.5) + turn_rate = turn_rate * 1.5 + end + self:turn_to(tgt_yaw, turn_rate) + if self:pos_in_box(pos2) then + self:halt() + end +end) -- 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 moveable = creatura.is_pos_moveable local function get_obstacle_avoidance(self, pos2) local pos = self.object:get_pos() @@ -309,78 +303,38 @@ local function get_obstacle_avoidance(self, pos2) return pos2 end -local function movement_obstacle_avoidance(self, pos2, speed) +creatura.register_movement_method("creatura:obstacle_avoidance", function(self, pos2) + -- Movement Data 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 + 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 - 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) + -- Turning + local dir2waypoint = vec_dir(pos, pos2) + if waypoint then + dir2waypoint = vec_dir(pos, waypoint) 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) + local tgt_yaw = dir2yaw(dir2waypoint) + local turn_rate = self.turn_rate or 5 + local yaw_diff = abs(diff(yaw, tgt_yaw)) + -- Moving self:set_gravity(-9.8) - if yaw_diff < pi then - self:animate("walk") + if yaw_diff < pi * 0.25 then + self:animate(movement_data.anim or "walk") self:set_forward_velocity(speed) + else + self:set_forward_velocity(speed * 0.5) + turn_rate = turn_rate * 1.5 end + self:turn_to(tgt_yaw, turn_rate) if self:pos_in_box(pos2) then self:halt() end -end - -creatura.register_movement_method("creatura:neighbors", movement_neighbors) - +end) \ No newline at end of file diff --git a/mob_meta.lua b/mob_meta.lua index 21dfe6a..b7c86d8 100644 --- a/mob_meta.lua +++ b/mob_meta.lua @@ -38,16 +38,6 @@ local function vec_raise(v, n) return {x = v.x, y = v.y + n, z = v.z} end -local function vec_compress(v) - return {x = round(v.x, 2), y = round(v.y, 2), z = round(v.z, 2)} -end - -local function dist_2d(pos1, pos2) - local a = vector.new(pos1.x, 0, pos1.z) - local b = vector.new(pos2.x, 0, pos2.z) - return vec_dist(a, b) -end - local function fast_ray_sight(pos1, pos2) local ray = minetest.raycast(pos1, pos2, false, false) for pointed_thing in ray do @@ -1243,6 +1233,17 @@ function creatura.register_mob(name, def) def.collisionbox = hitbox def._creatura_mob = true + def.sounds = def.sounds or {} + + if not def.sounds.hit then + def.sounds.hit = { + name = "creatura_hit", + gain = 0.5, + distance = 16, + variations = 3 + } + end + def.on_activate = function(self, staticdata, dtime) return self:activate(staticdata, dtime) end diff --git a/pathfinder.lua b/pathfinder.lua index d08707b..d9c0efd 100644 --- a/pathfinder.lua +++ b/pathfinder.lua @@ -18,31 +18,7 @@ local function is_node_liquid(name) return def and def.drawtype == "liquid" end -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 moveable = creatura.is_pos_moveable local function get_ground_level(pos2, max_height) local node = minetest.get_node(pos2)