From 13ebe1c37dfaebd4c06d0f1a253778ee6917b073 Mon Sep 17 00:00:00 2001 From: ElCeejo <40281901+ElCeejo@users.noreply.github.com> Date: Thu, 16 Jun 2022 14:57:06 -0700 Subject: [PATCH] Minor performance and stability improvements --- api.lua | 598 ++++++++++++++--------------- boids.lua | 210 +++++----- methods.lua | 576 ++++++++++++++-------------- mob_meta.lua | 181 +++------ pathfinder.lua | 1001 +++++++++++++++++++++++------------------------- spawning.lua | 2 +- 6 files changed, 1196 insertions(+), 1372 deletions(-) diff --git a/api.lua b/api.lua index e1dafd6..1ba7871 100644 --- a/api.lua +++ b/api.lua @@ -6,20 +6,9 @@ creatura.api = {} -- Math -- -local pi = math.pi -local pi2 = pi * 2 -local abs = math.abs local floor = math.floor local random = math.random -local sin = math.sin -local cos = math.cos -local atan2 = math.atan2 - -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 @@ -29,25 +18,18 @@ local function clamp(val, min, max) return val end -local vec_dir = vector.direction local vec_dist = vector.distance local vec_multi = vector.multiply -local vec_sub = vector.subtract +local vec_equals = vector.equals local vec_add = vector.add local function vec_center(v) - return {x = floor(v.x + 0.5), y = floor(v.y + 0.5), z = floor(v.z + 0.5)} + 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) - if not v then return end - return {x = v.x, y = v.y + n, z = v.z} -end - -local function dist_2d(pos1, pos2) - local a = {x = pos1.x, y = 0, z = pos1.z} - local b = {x = pos2.x, y = 0, z = pos2.z} - return vec_dist(a, b) + if not v then return end + return {x = v.x, y = v.y + n, z = v.z} end --------------- @@ -55,12 +37,12 @@ end --------------- local function is_value_in_table(tbl, val) - for _, v in pairs(tbl) do - if v == val then - return true - end - end - return false + for _, v in pairs(tbl) do + if v == val then + return true + end + end + return false end ----------------------- @@ -72,7 +54,7 @@ end creatura.registered_movement_methods = {} function creatura.register_movement_method(name, func) - creatura.registered_movement_methods[name] = func + creatura.registered_movement_methods[name] = func end -- Utility Behaviors -- @@ -80,7 +62,7 @@ end creatura.registered_utilities = {} function creatura.register_utility(name, func) - creatura.registered_utilities[name] = func + creatura.registered_utilities[name] = func end -- Sensors -- @@ -93,7 +75,7 @@ function creatura.get_node_height_from_def(name) if def.walkable then if def.drawtype == "nodebox" then if def.node_box - and def.node_box.type == "fixed" then + 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 @@ -113,234 +95,232 @@ function creatura.get_node_height_from_def(name) 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 creatura.get_node_height_from_def(node) < 0.26 then - def.walkable = false -- workaround for nodes like snow - end - return def + 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 creatura.get_node_height_from_def(node) < 0.26 then + def.walkable = false -- workaround for nodes like snow + end + return def end function creatura.get_ground_level(pos2, max_diff) - local node = minetest.get_node(pos2) - local node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - local walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable - if walkable then - return pos2 - end - local diff = 0 - if not creatura.get_node_def(node_under.name).walkable then - for i = 1, max_diff do - pos2.y = pos2.y - 1 - node = minetest.get_node(pos2) - node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable - if walkable then break end - end - else - for i = 1, max_diff do - pos2.y = pos2.y + 1 - node = minetest.get_node(pos2) - node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable - if walkable then break end - end - end - return pos2 + local node = minetest.get_node(pos2) + local node_under = minetest.get_node({ + x = pos2.x, + y = pos2.y - 1, + z = pos2.z + }) + local walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable + if walkable then + return pos2 + end + if not creatura.get_node_def(node_under.name).walkable then + for _ = 1, max_diff do + pos2.y = pos2.y - 1 + node = minetest.get_node(pos2) + node_under = minetest.get_node({ + x = pos2.x, + y = pos2.y - 1, + z = pos2.z + }) + walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable + if walkable then break end + end + else + for _ = 1, max_diff do + pos2.y = pos2.y + 1 + node = minetest.get_node(pos2) + node_under = minetest.get_node({ + x = pos2.x, + y = pos2.y - 1, + z = pos2.z + }) + walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable + if walkable then break end + end + end + return pos2 end function creatura.is_pos_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 + 0.01, z = z} - local ray = minetest.raycast(pos3, pos4, false, false) - for pointed_thing in ray do - if pointed_thing.type == "node" then - local name = minetest.get_node(pointed_thing.under).name - if creatura.get_node_def(name).walkable then - return false - end - end - end - end - end - return true + 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 + 0.01, z = z} + local ray = minetest.raycast(pos3, pos4, false, false) + for pointed_thing in ray do + if pointed_thing.type == "node" then + local name = minetest.get_node(pointed_thing.under).name + if creatura.get_node_def(name).walkable then + return false + end + end + end + end + end + return true end local moveable = creatura.is_pos_moveable function creatura.fast_ray_sight(pos1, pos2, water) - local ray = minetest.raycast(pos1, pos2, false, water or false) - for pointed_thing in ray do - if pointed_thing.type == "node" then - return false, vec_dist(pos1, pointed_thing.intersection_point), pointed_thing.ref - end - end - return true, vec_dist(pos1, pos2) + local ray = minetest.raycast(pos1, pos2, false, water or false) + for pointed_thing in ray do + if pointed_thing.type == "node" then + return false, vec_dist(pos1, pointed_thing.intersection_point), pointed_thing.ref + end + end + return true, vec_dist(pos1, pos2) end local fast_ray_sight = creatura.fast_ray_sight function creatura.get_next_move(self, pos2) - local last_move = self._movement_data.last_move - local width = self.width - local height = self.height - local pos = self.object:get_pos() - 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) - local last_move = minetest.get_position_from_hash(last_move.move) - if vector.equals(vec_center(last_call), vec_center(pos)) then - return last_move - end - end - local neighbors = { - vec_add(pos, {x = 1, y = 0, z = 0}), - vec_add(pos, {x = 1, y = 0, z = 1}), - vec_add(pos, {x = 0, y = 0, z = 1}), - vec_add(pos, {x = -1, y = 0, z = 1}), - vec_add(pos, {x = -1, y = 0, z = 0}), - vec_add(pos, {x = -1, y = 0, z = -1}), - vec_add(pos, {x = 0, y = 0, z = -1}), - vec_add(pos, {x = 1, y = 0, z = -1}) - } - 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(pos, neighbor) - if vector.equals(neighbor, pos2) then - can_move = true - end - 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 self:is_pos_safe(neighbor) then - can_move = false - end - if can_move then - _next = vec_raise(neighbor, 0.1) - break - end - end - if _next then - self._movement_data.last_move = { - pos = minetest.hash_node_position(pos), - move = minetest.hash_node_position(_next) - } - _next = { - x = floor(_next.x), - y = _next.y, - z = floor(_next.z) - } - end - return _next + local last_move = self._movement_data.last_move + local width = self.width + local height = self.height + local pos = self.object:get_pos() + 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) + last_move = minetest.get_position_from_hash(last_move.move) + if vec_equals(vec_center(last_call), vec_center(pos)) then + return last_move + end + end + local neighbors = { + vec_add(pos, {x = 1, y = 0, z = 0}), + vec_add(pos, {x = 1, y = 0, z = 1}), + vec_add(pos, {x = 0, y = 0, z = 1}), + vec_add(pos, {x = -1, y = 0, z = 1}), + vec_add(pos, {x = -1, y = 0, z = 0}), + vec_add(pos, {x = -1, y = 0, z = -1}), + vec_add(pos, {x = 0, y = 0, z = -1}), + vec_add(pos, {x = 1, y = 0, z = -1}) + } + 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(pos, neighbor) + if vec_equals(neighbor, pos2) then + can_move = true + end + 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 self:is_pos_safe(neighbor) then + can_move = false + end + if can_move then + _next = vec_raise(neighbor, 0.1) + break + end + end + if _next then + self._movement_data.last_move = { + pos = minetest.hash_node_position(pos), + move = minetest.hash_node_position(_next) + } + _next = { + x = floor(_next.x), + y = _next.y, + z = floor(_next.z) + } + end + return _next end function creatura.get_next_move_3d(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 = pos.y + 0.5 - if last_move - and last_move.pos then - local last_call = minetest.get_position_from_hash(last_move.pos) - local last_move = minetest.get_position_from_hash(last_move.move) - if vector.equals(vec_center(last_call), vec_center(pos)) then - return last_move - end - end - local neighbors = { - vec_add(pos, {x = scan_width, y = 0, z = 0}), - vec_add(pos, {x = scan_width, y = 0, z = scan_width}), - vec_add(pos, {x = 0, y = 0, z = scan_width}), - vec_add(pos, {x = -scan_width, y = 0, z = scan_width}), - vec_add(pos, {x = -scan_width, y = 0, z = 0}), - vec_add(pos, {x = -scan_width, y = 0, z = -scan_width}), - vec_add(pos, {x = 0, y = 0, z = -scan_width}), - vec_add(pos, {x = scan_width, y = 0, z = -scan_width}) - } - 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) - if not moveable(vec_raise(neighbor, 0.6), width, height) then - can_move = false - end - if vector.equals(neighbor, pos2) then - can_move = true - end - local dist = vec_dist(neighbor, pos2) - if can_move then - next = neighbor - break - end - end - if next then - self._movement_data.last_move = { - pos = minetest.hash_node_position(pos), - move = minetest.hash_node_position(next) - } - end - return vec_raise(next, clamp((pos2.y - pos.y) + -0.6, -1, 1)) + 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 = pos.y + 0.5 + if last_move + and last_move.pos then + local last_call = minetest.get_position_from_hash(last_move.pos) + last_move = minetest.get_position_from_hash(last_move.move) + if vec_equals(vec_center(last_call), vec_center(pos)) then + return last_move + end + end + local neighbors = { + vec_add(pos, {x = scan_width, y = 0, z = 0}), + vec_add(pos, {x = scan_width, y = 0, z = scan_width}), + vec_add(pos, {x = 0, y = 0, z = scan_width}), + vec_add(pos, {x = -scan_width, y = 0, z = scan_width}), + vec_add(pos, {x = -scan_width, y = 0, z = 0}), + vec_add(pos, {x = -scan_width, y = 0, z = -scan_width}), + vec_add(pos, {x = 0, y = 0, z = -scan_width}), + vec_add(pos, {x = scan_width, y = 0, z = -scan_width}) + } + 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) + if not moveable(vec_raise(neighbor, 0.6), width, height) then + can_move = false + end + if vec_equals(neighbor, pos2) then + can_move = true + end + if can_move then + next = neighbor + break + end + end + if next then + self._movement_data.last_move = { + pos = minetest.hash_node_position(pos), + move = minetest.hash_node_position(next) + } + end + return vec_raise(next, clamp((pos2.y - pos.y) + -0.6, -1, 1)) end function creatura.sensor_floor(self, range, water) - local pos = self.object:get_pos() - local pos2 = vec_raise(pos, -range) - local _, dist, node = fast_ray_sight(pos, pos2, water or false) - return dist, node + local pos = self.object:get_pos() + local pos2 = vec_raise(pos, -range) + local _, dist, node = fast_ray_sight(pos, pos2, water or false) + return dist, node end function creatura.sensor_ceil(self, range, water) - local pos = vec_raise(self.object:get_pos(), self.height) - local pos2 = vec_raise(pos, range) - local _, dist, node = fast_ray_sight(pos, pos2, water or false) - return dist, node + local pos = vec_raise(self.object:get_pos(), self.height) + local pos2 = vec_raise(pos, range) + local _, dist, node = fast_ray_sight(pos, pos2, water or false) + return dist, node end -- Misc @@ -355,69 +335,69 @@ function creatura.is_valid(mob) if mob:get_yaw() then return mob end end end - return false + return false end function creatura.is_alive(mob) - if not creatura.is_valid(mob) then - return false - end - if type(mob) == "table" then - return mob.hp > 0 - end - if mob:is_player() then - return mob:get_hp() > 0 - else - local ent = mob:get_luaentity() - return ent and ent.hp and ent.hp > 0 - end + if not creatura.is_valid(mob) then + return false + end + if type(mob) == "table" then + return mob.hp > 0 + end + if mob:is_player() then + return mob:get_hp() > 0 + else + local ent = mob:get_luaentity() + return ent and ent.hp and ent.hp > 0 + end end function creatura.get_nearby_player(self) - local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) + local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) for _, object in ipairs(objects) do if object:is_player() - and creatura.is_alive(object) then - return object - end + and creatura.is_alive(object) then + return object + end end end function creatura.get_nearby_players(self) - local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) - local nearby = {} + local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) + local nearby = {} for _, object in ipairs(objects) do if object:is_player() - and creatura.is_alive(object) then - table.insert(nearby, object) - end + and creatura.is_alive(object) then + table.insert(nearby, object) + end end return nearby end function creatura.get_nearby_entity(self, name) - local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) + local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) for _, object in ipairs(objects) do if creatura.is_alive(object) - and not object:is_player() - and object ~= self.object - and object:get_luaentity().name == name then - return object - end + and not object:is_player() + and object ~= self.object + and object:get_luaentity().name == name then + return object + end end return end function creatura.get_nearby_entities(self, name) - local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) - local nearby = {} + local objects = minetest.get_objects_inside_radius(self:get_center_pos(), self.tracking_range) + local nearby = {} for _, object in ipairs(objects) do if creatura.is_alive(object) - and not object:is_player() - and object ~= self.object - and object:get_luaentity().name == name then - table.insert(nearby, object) - end + and not object:is_player() + and object ~= self.object + and object:get_luaentity().name == name then + table.insert(nearby, object) + end end return nearby end @@ -429,51 +409,51 @@ end -- Drops -- function creatura.drop_items(self) - if not self.drops then return end - for i = 1, #self.drops do - local drop_def = self.drops[i] - local name = drop_def.name - if not name then return end - local min_amount = drop_def.min or 1 - local max_amount = drop_def.max or 2 - local chance = drop_def.chance or 1 - local amount = random(min_amount, max_amount) - if random(chance) < 2 then - local pos = self.object:get_pos() - local item = minetest.add_item(pos, ItemStack(name .. " " .. amount)) - if item then - item:add_velocity({ - x = random(-2, 2), - y = 1.5, - z = random(-2, 2) - }) - end - end - end + if not self.drops then return end + for i = 1, #self.drops do + local drop_def = self.drops[i] + local name = drop_def.name + if not name then return end + local min_amount = drop_def.min or 1 + local max_amount = drop_def.max or 2 + local chance = drop_def.chance or 1 + local amount = random(min_amount, max_amount) + if random(chance) < 2 then + local pos = self.object:get_pos() + local item = minetest.add_item(pos, ItemStack(name .. " " .. amount)) + if item then + item:add_velocity({ + x = random(-2, 2), + y = 1.5, + z = random(-2, 2) + }) + end + end + end end -- On Punch -- function creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) - if not puncher then return end - local tool = "" - if puncher:is_player() then - tool = puncher:get_wielded_item():get_name() - end - if (self.immune_to - and is_value_in_table(self.immune_to, tool)) then - return - end - local dir = vec_dir(puncher:get_pos(), self:get_center_pos()) - self:apply_knockback(dir) - self:hurt(tool_capabilities.damage_groups.fleshy or 2) - if random(4) < 2 then - self:play_sound("hurt") - end - if time_from_last_punch > 0.5 then - self:play_sound("hit") - end - self:indicate_damage() + if not puncher then return end + local tool = "" + if puncher:is_player() then + tool = puncher:get_wielded_item():get_name() + end + if (self.immune_to + and is_value_in_table(self.immune_to, tool)) then + return + end + local dir = vec_multi(direction, -1) + self:apply_knockback(dir) + self:hurt((tool_capabilities.damage_groups.fleshy or damage) or 2) + if random(4) < 2 then + self:play_sound("hurt") + end + if time_from_last_punch > 0.5 then + self:play_sound("hit") + end + self:indicate_damage() end local path = minetest.get_modpath("creatura") diff --git a/boids.lua b/boids.lua index 1c6be5a..049ac63 100644 --- a/boids.lua +++ b/boids.lua @@ -5,60 +5,51 @@ local random = math.random local function average(tbl) - local sum = 0 - for _,v in pairs(tbl) do -- Get the sum of all numbers in t - sum = sum + v - end - return sum / #tbl + local sum = 0 + for _,v in pairs(tbl) do -- Get the sum of all numbers in t + sum = sum + v + end + return sum / #tbl end local function average_angle(tbl) - local sum_sin, sum_cos = 0, 0 - for _, v in pairs(tbl) do - sum_sin = sum_sin + math.sin(v) - sum_cos = sum_cos + math.cos(v) - end - return math.atan2(sum_sin, sum_cos) + local sum_sin, sum_cos = 0, 0 + for _, v in pairs(tbl) do + sum_sin = sum_sin + math.sin(v) + sum_cos = sum_cos + math.cos(v) + end + return math.atan2(sum_sin, sum_cos) end local vec_dist = vector.distance local vec_dir = vector.direction -local vec_len = vector.length local vec_add = vector.add -local vec_multi = vector.multiply local vec_normal = vector.normalize local vec_divide = vector.divide -local function vec_raise(v, n) - return {x = v.x, y = v.y + n, z = v.z} -end local function get_average_pos(vectors) - local sum = {x = 0, y = 0, z = 0} - for _, vec in pairs(vectors) do sum = vec_add(sum, vec) end - return vec_divide(sum, #vectors) + local sum = {x = 0, y = 0, z = 0} + for _, vec in pairs(vectors) do sum = vec_add(sum, vec) end + return vec_divide(sum, #vectors) 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) + 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 yaw2dir = minetest.yaw_to_dir local dir2yaw = minetest.dir_to_yaw --- Refresh Boid Leader -- - -local last_boid_refresh = minetest.get_us_time() - -- Get Boid Members -- -- This function scans within @@ -70,89 +61,78 @@ local last_boid_refresh = minetest.get_us_time() -- is in the boid. function creatura.get_boid_members(pos, radius, name) - local objects = minetest.get_objects_inside_radius(pos, radius) - if #objects < 2 then return {} end - local members = {} - local max_boid = minetest.registered_entities[name].max_boids or 7 - for i = 1, #objects do - if #members > max_boid then break end - local object = objects[i] - if object:get_luaentity() - and object:get_luaentity().name == name then - object:get_luaentity().boid_heading = math.rad(random(360)) - table.insert(members, object) - end - end - return members + local objects = minetest.get_objects_inside_radius(pos, radius) + if #objects < 2 then return {} end + local members = {} + local max_boid = minetest.registered_entities[name].max_boids or 7 + for i = 1, #objects do + if #members > max_boid then break end + local object = objects[i] + if object:get_luaentity() + and object:get_luaentity().name == name then + object:get_luaentity().boid_heading = math.rad(random(360)) + table.insert(members, object) + end + end + return members end -- Calculate Boid angles and offsets. -local function debugpart(pos, time, part) - minetest.add_particle({ - pos = pos, - expirationtime = time or 0.2, - size = 8, - glow = 16, - texture = part or "creatura_particle_red.png" - }) -end - -function creatura.get_boid_angle(self, boid, range) -- calculates boid angle based on seperation, alignment, and cohesion - local pos = self.object:get_pos() - local boids = boid or creatura.get_boid_members(pos, range or 4, self.name) - if #boids < 3 then return end - local yaw = self.object:get_yaw() - local lift = self.object:get_velocity().y - -- Add Boid data to tables - local closest_pos - local positions = {} - local angles = {} - local lifts = {} - for i = 1, #boids do - local boid = boids[i] - if boid:get_pos() then - local boid_pos = boid:get_pos() - local boid_yaw = boid:get_yaw() - table.insert(positions, boid_pos) - if boid ~= self.object then - table.insert(lifts, vec_normal(boid:get_velocity()).y) - table.insert(angles, boid:get_yaw()) - if not closest_pos - or vec_dist(pos, boid_pos) < vec_dist(pos, closest_pos) then - closest_pos = boid_pos - end - end - end - end - if #positions < 3 then return end - local center = get_average_pos(positions) - local dir2closest = vec_dir(pos, closest_pos) - -- Calculate Parameters - local alignment = average_angle(angles) - center = vec_add(center, yaw2dir(alignment)) - local dir2center = vec_dir(pos, center) - local seperation = yaw + -(dir2yaw(dir2closest) - yaw) - local cohesion = dir2yaw(dir2center) - local params = {alignment} - if self.boid_heading then - table.insert(params, yaw + self.boid_heading) - end - if dist_2d(pos, closest_pos) < (self.boid_seperation or self.width * 3) then - table.insert(params, seperation) - elseif dist_2d(pos, center) > (#boids * 0.33) * (self.boid_seperation or self.width * 3) then - table.insert(params, cohesion) - end - -- Vertical Params - local vert_alignment = average(lifts) - local vert_seperation = (self.speed or 2) * -dir2closest.y - local vert_cohesion = (self.speed or 2) * dir2center.y - local vert_params = {vert_alignment} - if math.abs(pos.y - closest_pos.y) < (self.boid_seperation or self.width * 3) then - table.insert(vert_params, vert_seperation) - elseif math.abs(pos.y - closest_pos.y) > 1.5 * (self.boid_seperation or self.width * 3) then - table.insert(vert_params, vert_cohesion + (lift - vert_cohesion) * 0.1) - end - self.boid_heading = nil - return average_angle(params), average_angle(vert_params) +function creatura.get_boid_angle(self, _boids, range) + local pos = self.object:get_pos() + local boids = _boids or creatura.get_boid_members(pos, range or 4, self.name) + if #boids < 3 then return end + local yaw = self.object:get_yaw() + local lift = self.object:get_velocity().y + -- Add Boid data to tables + local closest_pos + local positions = {} + local angles = {} + local lifts = {} + for i = 1, #boids do + local boid = boids[i] + if boid:get_pos() then + local boid_pos = boid:get_pos() + table.insert(positions, boid_pos) + if boid ~= self.object then + table.insert(lifts, vec_normal(boid:get_velocity()).y) + table.insert(angles, boid:get_yaw()) + if not closest_pos + or vec_dist(pos, boid_pos) < vec_dist(pos, closest_pos) then + closest_pos = boid_pos + end + end + end + end + if #positions < 3 then return end + local center = get_average_pos(positions) + local dir2closest = vec_dir(pos, closest_pos) + -- Calculate Parameters + local alignment = average_angle(angles) + center = vec_add(center, yaw2dir(alignment)) + local dir2center = vec_dir(pos, center) + local seperation = yaw + -(dir2yaw(dir2closest) - yaw) + local cohesion = dir2yaw(dir2center) + local params = {alignment} + if self.boid_heading then + table.insert(params, yaw + self.boid_heading) + end + if dist_2d(pos, closest_pos) < (self.boid_seperation or self.width * 3) then + table.insert(params, seperation) + elseif dist_2d(pos, center) > (#boids * 0.33) * (self.boid_seperation or self.width * 3) then + table.insert(params, cohesion) + end + -- Vertical Params + local vert_alignment = average(lifts) + local vert_seperation = (self.speed or 2) * -dir2closest.y + local vert_cohesion = (self.speed or 2) * dir2center.y + local vert_params = {vert_alignment} + if math.abs(pos.y - closest_pos.y) < (self.boid_seperation or self.width * 3) then + table.insert(vert_params, vert_seperation) + elseif math.abs(pos.y - closest_pos.y) > 1.5 * (self.boid_seperation or self.width * 3) then + table.insert(vert_params, vert_cohesion + (lift - vert_cohesion) * 0.1) + end + self.boid_heading = nil + return average_angle(params), average_angle(vert_params) end \ No newline at end of file diff --git a/methods.lua b/methods.lua index bddc604..dc2f1c6 100644 --- a/methods.lua +++ b/methods.lua @@ -3,17 +3,17 @@ ------------- local pi = math.pi -local pi2 = pi * 2 local abs = math.abs +local ceil = math.ceil local floor = math.floor local random = math.random local rad = math.rad - +local atan2 = math.atan2 local sin = math.sin local cos = math.cos local function diff(a, b) -- Get difference between 2 angles - return math.atan2(math.sin(b - a), math.cos(b - a)) + return atan2(sin(b - a), cos(b - a)) end local function clamp(val, min, max) @@ -26,62 +26,57 @@ local function clamp(val, min, max) 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} + return {x = floor(v.x + 0.5), y = floor(v.y + 0.5), z = floor(v.z + 0.5)} 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 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 +--[[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]] --------------------- -- Local Utilities -- --------------------- local function get_collision(self, yaw) - local width = self.width - local height = self.height - local pos = self.object:get_pos() - pos.y = pos.y + 1 - local pos2 = vec_add(pos, vec_multi(yaw2dir(yaw), width + 5)) - for x = -width, width, width / math.ceil(width) do - for y = 0, height, height / math.ceil(height) do - local vec1 = { - x = math.cos(yaw) * ((pos.x + x) - pos.x) + pos.x, - y = pos.y + y, - z = math.sin(yaw) * ((pos.x + x) - pos.x) + pos.z - } - local vec2 = { - x = math.cos(yaw) * ((pos2.x + x) - pos2.x) + pos2.x, - y = vec1.y, - z = math.sin(yaw) * ((pos2.x + x) - pos2.x) + pos2.z - } - local ray = minetest.raycast(vec1, vec2, false, true) - for pointed_thing in ray do - if pointed_thing - and pointed_thing.type == "node" then - return true, pointed_thing.intersection_point - end - end - end - end - return false + local width = self.width + local height = self.height + local pos = self.object:get_pos() + pos.y = pos.y + 1 + local pos2 = vec_add(pos, vec_multi(yaw2dir(yaw), width + 5)) + for x = -width, width, width / ceil(width) do + for y = 0, height, height / ceil(height) do + local vec1 = { + x = cos(yaw) * ((pos.x + x) - pos.x) + pos.x, + y = pos.y + y, + z = sin(yaw) * ((pos.x + x) - pos.x) + pos.z + } + local vec2 = { + x = cos(yaw) * ((pos2.x + x) - pos2.x) + pos2.x, + y = vec1.y, + z = sin(yaw) * ((pos2.x + x) - pos2.x) + pos2.z + } + local ray = minetest.raycast(vec1, vec2, false, true) + for pointed_thing in ray do + if pointed_thing + and pointed_thing.type == "node" then + return true, pointed_thing.intersection_point + end + end + end + end + return false end ------------- @@ -94,59 +89,58 @@ end -- 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) + 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) + 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 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) + 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 @@ -154,22 +148,22 @@ end function creatura.action_fallover(self) local zrot = 0 local init = false - local dir = 1 - local function func(self) + local dir = 1 + local function func(_self) if not init then - self:animate("stand") - if random(2) < 2 then - dir = -1 - end + _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}) + 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 + or (dir < 0 and zrot <= goal) then return true end end self:set_action(func) end @@ -181,210 +175,208 @@ 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 - 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 + -- 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 + 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) 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: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 + -- 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: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 + -- 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 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 outset = vec_add(pos, vec_multi(yaw2dir(yaw2goal), width)) - local collide, col_pos = get_collision(self, yaw2goal) - local avd_pos - if collide then - for i = 45, 180, 45 do - 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 - 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 - end - return avd_pos + 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 - 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 + -- 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 + 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 end) \ No newline at end of file diff --git a/mob_meta.lua b/mob_meta.lua index 76da0eb..fbcdb0e 100644 --- a/mob_meta.lua +++ b/mob_meta.lua @@ -15,12 +15,7 @@ local cos = math.cos local atan2 = math.atan2 local function diff(a, b) -- Get difference between 2 angles - return math.atan2(math.sin(b - a), math.cos(b - a)) -end - -local function round(n, dec) - local x = 10^(dec or 0) - return math.floor(n * x + 0.5) / x + return atan2(sin(b - a), cos(b - a)) end local vec_dir = vector.direction @@ -50,89 +45,13 @@ end -- Local Utilities -- -local default_node_def = {walkable = true} -- both ignore and unknown nodes are walkable - -local function get_node_height(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 - -local function get_node_def(name) - local def = minetest.registered_nodes[name] or default_node_def - if def.walkable - and get_node_height(name) < 0.26 then - def.walkable = false -- workaround for nodes like snow - end - return def -end - -local function get_ground_level(pos2, max_diff) - local node = minetest.get_node(pos2) - local node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - local walkable = get_node_def(node_under.name) and not get_node_def(node.name) - if walkable then - return pos2 - end - local diff = 0 - if not get_node_def(node_under.name) then - for i = 1, max_diff do - pos2.y = pos2.y - 1 - node = minetest.get_node(pos2) - node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - walkable = get_node_def(node_under.name) and not get_node_def(node.name) - if walkable then break end - end - else - for i = 1, max_diff do - pos2.y = pos2.y + 1 - node = minetest.get_node(pos2) - node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - walkable = get_node_def(node_under.name) and not get_node_def(node.name) - if walkable then break end - end - end - return pos2 -end - local function is_value_in_table(tbl, val) - for _, v in pairs(tbl) do - if v == val then - return true - end - end - return false + for _, v in pairs(tbl) do + if v == val then + return true + end + end + return false end ------------------------- @@ -204,7 +123,7 @@ end function mob:indicate_damage() self._original_texture_mod = self._original_texture_mod or self.object:get_texture_mod() self.object:set_texture_mod(self._original_texture_mod .. "^[colorize:#FF000040") - core.after(0.2, function() + minetest.after(0.2, function() if creatura.is_alive(self) then self.object:set_texture_mod(self._original_texture_mod) end @@ -265,8 +184,8 @@ end -- Sets Velocity to desired speed in mobs current look direction -function mob:set_forward_velocity(speed) - local speed = speed or self._movement_data.speed +function mob:set_forward_velocity(_speed) + local speed = _speed or self._movement_data.speed local dir = minetest.yaw_to_dir(self.object:get_yaw()) local vel = vec_multi(dir, speed) vel.y = self.object:get_velocity().y @@ -367,12 +286,11 @@ end function mob:get_wander_pos(min_range, max_range, dir) local pos = vec_center(self.object:get_pos()) pos.y = floor(pos.y + 0.5) - local node = minetest.get_node(pos) - if get_node_def(node.name).walkable then -- Occurs if small mob is touching a fence + if creatura.get_node_def(pos).walkable then -- Occurs if small mob is touching a fence local offset = vector.add(pos, vec_multi(vec_dir(pos, self.object:get_pos()), 1.5)) pos.x = floor(offset.x + 0.5) pos.z = floor(offset.z + 0.5) - pos = get_ground_level(pos, 1) + pos = creatura.get_ground_level(pos, 1) end local width = self.width local outset = random(min_range, max_range) @@ -383,16 +301,16 @@ function mob:get_wander_pos(min_range, max_range, dir) z = random(-10, 10) * 0.1 }) local pos2 = vec_add(pos, vec_multi(move_dir, width)) - if get_node_def(minetest.get_node(pos2).name).walkable + if creatura.get_node_def(pos2).walkable and not dir then - for i = 1, 3 do + for _ = 1, 3 do move_dir = { x = move_dir.z, y = 0, z = move_dir.x * -1 } pos2 = vec_add(pos, vec_multi(move_dir, width)) - if not get_node_def(minetest.get_node(pos2).name).walkable then + if not creatura.get_node_def(pos2).walkable then break end end @@ -401,14 +319,12 @@ function mob:get_wander_pos(min_range, max_range, dir) end for i = 1, outset do local a_pos = vec_add(pos2, vec_multi(move_dir, i)) - local a_node = minetest.get_node(a_pos) local b_pos = {x = a_pos.x, y = a_pos.y - 1, z = a_pos.z} - local b_node = minetest.get_node(b_pos) - if get_node_def(a_node.name).walkable - or not get_node_def(b_node.name).walkable then - a_pos = get_ground_level(a_pos, floor(self.stepheight or 1)) + if creatura.get_node_def(a_pos).walkable + or not creatura.get_node_def(b_pos).walkable then + a_pos = creatura.get_ground_level(a_pos, floor(self.stepheight or 1)) end - if not get_node_def(a_node.name).walkable then + if not creatura.get_node_def(a_pos).walkable then pos2 = a_pos else break @@ -419,12 +335,11 @@ end function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias) local pos = vec_center(self.object:get_pos()) - local node = minetest.get_node(pos) - if get_node_def(node.name).walkable then -- Occurs if small mob is touching a fence + if creatura.get_node_def(pos).walkable then -- Occurs if small mob is touching a fence local offset = vector.add(pos, vec_multi(vec_dir(pos, self.object:get_pos()), 1.5)) pos.x = floor(offset.x + 0.5) pos.z = floor(offset.z + 0.5) - pos = get_ground_level(pos, 1) + pos = creatura.get_ground_level(pos, 1) end local width = self.width local outset = random(min_range, max_range) @@ -435,16 +350,16 @@ function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias) z = random(-10, 10) * 0.1 }) local pos2 = vec_add(pos, vec_multi(move_dir, width)) - if get_node_def(minetest.get_node(pos2).name).walkable + if creatura.get_node_def(pos2).walkable and not dir then - for i = 1, 3 do + for _ = 1, 3 do move_dir = { x = move_dir.z, y = move_dir.y, z = move_dir.x * -1 } pos2 = vec_add(pos, vec_multi(move_dir, width)) - if not get_node_def(minetest.get_node(pos2).name).walkable then + if not creatura.get_node_def(pos2).walkable then break end end @@ -453,11 +368,10 @@ function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias) end for i = 1, outset do local a_pos = vec_add(pos2, vec_multi(move_dir, i)) - local a_node = minetest.get_node(a_pos) - if get_node_def(a_node.name).walkable then - a_pos = get_ground_level(a_pos, floor(self.stepheight or 1)) + if creatura.get_node_def(a_pos).walkable then + a_pos = creatura.get_ground_level(a_pos, floor(self.stepheight or 1)) end - if not get_node_def(a_node.name).walkable then + if not creatura.get_node_def(a_pos).walkable then pos2 = a_pos else break @@ -471,8 +385,8 @@ function mob:is_pos_safe(pos) local node = minetest.get_node(pos) if not node then return false end if minetest.get_item_group(node.name, "igniter") > 0 - or get_node_def(node.name).drawtype == "liquid" - or get_node_def(minetest.get_node(vec_raise(pos, -1)).name).drawtype == "liquid" then return false end + or creatura.get_node_def(node.name).drawtype == "liquid" + or creatura.get_node_def(vec_raise(pos, -1)).drawtype == "liquid" then return false end local fall_safe = false if self.max_fall ~= 0 then for i = 1, self.max_fall or 3 do @@ -481,7 +395,7 @@ function mob:is_pos_safe(pos) y = floor(mob_pos.y + 0.5) - i, z = pos.z } - if get_node_def(minetest.get_node(fall_pos).name).walkable then + if creatura.get_node_def(fall_pos).walkable then fall_safe = true break end @@ -809,9 +723,9 @@ function mob:activate(staticdata, dtime) if self.timer and type(self.timer) == "number" then -- fix crash for converted mobs_redo mobs - self.timer = function(self, n) - local t1 = floor(self.active_time) - local t2 = floor(self.active_time + self.dtime) + self.timer = function(_self, n) + local t1 = floor(_self.active_time) + local t2 = floor(_self.active_time + _self.dtime) if t2 > t1 and t2%n == 0 then return true end end end @@ -830,7 +744,6 @@ function mob:staticdata() end function mob:on_step(dtime, moveresult) - --local us_time = minetest.get_us_time() if not self.hp then return end self.dtime = dtime or 0.09 self.moveresult = moveresult or {} @@ -851,11 +764,12 @@ function mob:on_step(dtime, moveresult) self.width = self:get_hitbox()[4] or 0.5 self.height = self:get_height() or 1 end - self:_light_physics() + --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() @@ -912,7 +826,9 @@ local function do_step(self) and abs(vel.x + vel.z) > 0 then local border = self._border local yaw_offset = vec_add(pos, vec_multi(minetest.yaw_to_dir(self.object:get_yaw()), self.width + 0.7)) - table.sort(border, function(a, b) return vec_dist(vec_add(pos, a), yaw_offset) < vec_dist(vec_add(pos, b), yaw_offset) end) + table.sort(border, function(a, b) + return vec_dist(vec_add(pos, a), yaw_offset) < vec_dist(vec_add(pos, b), yaw_offset) + end) local step_pos = vec_center(vec_add(pos, border[1])) local halfway = vec_add(pos, vec_multi(vec_dir(pos, step_pos), 0.5)) halfway.y = step_pos.y @@ -923,7 +839,6 @@ local function do_step(self) end end else - local vel = self.object:get_velocity() self.object:set_velocity(vector.new(vel.x, 7, vel.z)) if self._step.y < pos.y - 0.5 then self.object:set_velocity(vector.new(vel.x, 0.5, vel.z)) @@ -943,7 +858,6 @@ local function collision_detection(self) local width = self.width + 0.25 local objects = minetest.get_objects_in_area(vec_sub(pos, width), vec_add(pos, width)) if #objects < 2 then return end - local col_no = 0 for i = 2, #objects do local object = objects[i] if creatura.is_alive(object) @@ -975,7 +889,6 @@ local function water_physics(self) floor_pos.y = floor_pos.y + 0.01 local surface_pos = floor_pos local floor_node = minetest.get_node(floor_pos) - local surface_node = minetest.get_node(surface_pos) if minetest.get_item_group(floor_node.name, "liquid") < 1 then self.object:set_acceleration({ x = 0, @@ -1012,7 +925,7 @@ local function water_physics(self) }) local hydrodynamics = self.hydrodynamics_multiplier or 0.7 local vel_y = vel.y - if self.bouyancy_multiplier == 0 then -- if bouyancy is disabled drag will be applied to keep awuatic mobs from drifting + if self.bouyancy_multiplier == 0 then vel_y = vel.y * hydrodynamics end self.object:set_velocity({ @@ -1084,7 +997,6 @@ end function mob:_execute_actions() if not self.object then return end - local task = self._task if #self._task > 0 then local func = self._task[#self._task].func if func(self) then @@ -1128,7 +1040,7 @@ function mob:_execute_utilities() func = nil, score = 0 } - if (self:timer(self.task_timer or 1) + if (self:timer(self.util_timer or 1) or not self._utility_data.func) and is_alive then for i = 1, #self.utility_stack do @@ -1163,7 +1075,8 @@ function mob:_execute_utilities() self._utility_data = loop_data else local no_data = not self._utility_data.utility and not self._utility_data.args - local new_util = self._utility_data.utility ~= loop_data.utility or not tbl_equals(self._utility_data.args, loop_data.args) + local same_args = tbl_equals(self._utility_data.args, loop_data.args) + local new_util = self._utility_data.utility ~= loop_data.utility or not same_args if no_data or new_util then -- if utilities are different or utilities are the same and args are different set new data self._utility_data = loop_data @@ -1218,9 +1131,10 @@ function mob:_vitals() end if self:timer(1) then local head_pos = vec_raise(stand_pos, self.height) - local head_def = get_node_def(minetest.get_node(head_pos).name) + local head_node = minetest.get_node(head_pos) + local head_def = creatura.get_node_def(head_node.name) if head_def.drawtype == "liquid" - and minetest.get_item_group(minetest.get_node(head_pos).name, "water") > 0 then + and minetest.get_item_group(head_node.name, "water") > 0 then if self._breath <= 0 then self:hurt(1) self:indicate_damage() @@ -1232,8 +1146,9 @@ function mob:_vitals() self:memorize("_breath", self._breath) end end - local stand_def = get_node_def(minetest.get_node(stand_pos).name) - if minetest.get_item_group(minetest.get_node(stand_pos).name, "fire") > 0 + local stand_node = minetest.get_node(stand_pos) + local stand_def = creatura.get_node_def(stand_node.name) + if minetest.get_item_group(stand_node.name, "fire") > 0 and stand_def.damage_per_second then local damage = stand_def.damage_per_second local resist = self.fire_resistance or 0.5 diff --git a/pathfinder.lua b/pathfinder.lua index 2f0daff..53dd1f3 100644 --- a/pathfinder.lua +++ b/pathfinder.lua @@ -8,309 +8,298 @@ local theta_star_alloted_time = tonumber(minetest.settings:get("creatura_theta_s local floor = math.floor local abs = math.abs -local function is_node_walkable(name) - local def = minetest.registered_nodes[name] - return def and def.walkable -end - -local function is_node_liquid(name) - local def = minetest.registered_nodes[name] - return def and def.drawtype == "liquid" -end +local vec_dist = vector.distance local moveable = creatura.is_pos_moveable -local function get_ground_level(pos2, max_height) - local node = minetest.get_node(pos2) - local node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - local height = 0 - local walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable - if walkable then - return pos2 - elseif not walkable then - if not creatura.get_node_def(node_under.name).walkable then - while not creatura.get_node_def(node_under.name).walkable - and height < max_height do - pos2.y = pos2.y - 1 - node_under = minetest.get_node({ - x = pos2.x, - y = pos2.y - 1, - z = pos2.z - }) - height = height + 1 - end - else - while creatura.get_node_def(node.name).walkable - and height < max_height do - pos2.y = pos2.y + 1 - node = minetest.get_node(pos2) - height = height + 1 - end - end - return pos2 - end -end - local function get_distance(start_pos, end_pos) - local distX = abs(start_pos.x - end_pos.x) - local distZ = abs(start_pos.z - end_pos.z) + local distX = abs(start_pos.x - end_pos.x) + local distZ = abs(start_pos.z - end_pos.z) - if distX > distZ then - return 14 * distZ + 10 * (distX - distZ) - else - return 14 * distX + 10 * (distZ - distX) - end + if distX > distZ then + return 14 * distZ + 10 * (distX - distZ) + else + return 14 * distX + 10 * (distZ - distX) + end end local function get_distance_to_neighbor(start_pos, end_pos) - local distX = abs(start_pos.x - end_pos.x) - local distY = abs(start_pos.y - end_pos.y) - local distZ = abs(start_pos.z - end_pos.z) + local distX = abs(start_pos.x - end_pos.x) + local distY = abs(start_pos.y - end_pos.y) + local distZ = abs(start_pos.z - end_pos.z) - if distX > distZ then - return (14 * distZ + 10 * (distX - distZ)) * (distY + 1) - else - return (14 * distX + 10 * (distZ - distX)) * (distY + 1) - end + if distX > distZ then + return (14 * distZ + 10 * (distX - distZ)) * (distY + 1) + else + return (14 * distX + 10 * (distZ - distX)) * (distY + 1) + end end local function is_on_ground(pos) - local ground = { - x = pos.x, - y = pos.y - 1, - z = pos.z - } - if is_node_walkable(minetest.get_node(ground).name) then - return true - end - return false + local ground = { + x = pos.x, + y = pos.y - 1, + z = pos.z + } + if creatura.get_node_def(ground).walkable then + return true + end + return false end local function vec_raise(v, n) - return {x = v.x, y = v.y + n, z = v.z} + return {x = v.x, y = v.y + n, z = v.z} +end + +local function get_line_of_sight(a, b) + local steps = floor(vec_dist(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 creatura.get_node_def(node.name).walkable then + return false + end + end + end + return true end -- Find a path from start to goal function creatura.find_path(self, start, goal, obj_width, obj_height, max_open, climb, fly, swim) - climb = climb or false - fly = fly or false - swim = swim or false + climb = climb or false + fly = fly or false + swim = swim or false - start = self._path_data.start or start + start = self._path_data.start or start - self._path_data.start = start + self._path_data.start = start - local path_neighbors = { - {x = 1, y = 0, z = 0}, - {x = 1, y = 0, z = 1}, - {x = 0, y = 0, z = 1}, - {x = -1, y = 0, z = 1}, - {x = -1, y = 0, z = 0}, - {x = -1, y = 0, z = -1}, - {x = 0, y = 0, z = -1}, - {x = 1, y = 0, z = -1} - } + local path_neighbors = { + {x = 1, y = 0, z = 0}, + {x = 1, y = 0, z = 1}, + {x = 0, y = 0, z = 1}, + {x = -1, y = 0, z = 1}, + {x = -1, y = 0, z = 0}, + {x = -1, y = 0, z = -1}, + {x = 0, y = 0, z = -1}, + {x = 1, y = 0, z = -1} + } - if climb then - table.insert(path_neighbors, {x = 0, y = 1, z = 0}) - end + if climb then + table.insert(path_neighbors, {x = 0, y = 1, z = 0}) + end - if fly - or swim then - path_neighbors = { - -- Central - {x = 1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = -1, y = 0, z = 0}, - {x = 0, y = 0, z = -1}, - -- Directly Up or Down - {x = 0, y = 1, z = 0}, - {x = 0, y = -1, z = 0} - } - end + if fly + or swim then + path_neighbors = { + -- Central + {x = 1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = -1}, + -- Directly Up or Down + {x = 0, y = 1, z = 0}, + {x = 0, y = -1, z = 0} + } + end - local function get_neighbors(pos, width, height, tbl, open, closed) - local result = {} - for i = 1, #tbl do - local neighbor = vector.add(pos, tbl[i]) - if neighbor.y == pos.y - and not fly - and not swim then - neighbor = get_ground_level(neighbor, 1) - end - local can_move = get_line_of_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor) - if swim then - can_move = true - end - if not moveable(vec_raise(neighbor, -0.49), width, height) then - can_move = false - if neighbor.y == pos.y - and moveable(vec_raise(neighbor, 0.51), width, height) then - neighbor = vec_raise(neighbor, 1) - can_move = true - end - end - if vector.equals(neighbor, goal) then - can_move = true - end - if open[minetest.hash_node_position(neighbor)] - or closed[minetest.hash_node_position(neighbor)] then - can_move = false - end - if can_move - and ((is_on_ground(neighbor) - or (fly or swim)) - or (neighbor.x == pos.x - and neighbor.z == pos.z - and climb)) - and (not swim - or is_node_liquid(minetest.get_node(neighbor).name)) then - table.insert(result, neighbor) - end - end - return result - end + local function get_neighbors(pos, width, height, tbl, open, closed) + local result = {} + for i = 1, #tbl do + local neighbor = vector.add(pos, tbl[i]) + if neighbor.y == pos.y + and not fly + and not swim then + neighbor = creatura.get_ground_level(neighbor, 1) + end + local can_move = get_line_of_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor) + if swim then + can_move = true + end + if not moveable(vec_raise(neighbor, -0.49), width, height) then + can_move = false + if neighbor.y == pos.y + and moveable(vec_raise(neighbor, 0.51), width, height) then + neighbor = vec_raise(neighbor, 1) + can_move = true + end + end + if vector.equals(neighbor, goal) then + can_move = true + end + if open[minetest.hash_node_position(neighbor)] + or closed[minetest.hash_node_position(neighbor)] then + can_move = false + end + if can_move + and ((is_on_ground(neighbor) + or (fly or swim)) + or (neighbor.x == pos.x + and neighbor.z == pos.z + and climb)) + and (not swim + or creatura.get_node_def(neighbor).drawtype == "liquid") then + table.insert(result, neighbor) + end + end + return result + end - local function find_path(start, goal) - local us_time = minetest.get_us_time() + local function find_path(_start, _goal) + local us_time = minetest.get_us_time() - start = { - x = floor(start.x + 0.5), - y = floor(start.y + 0.5), - z = floor(start.z + 0.5) - } - - goal = { - x = floor(goal.x + 0.5), - y = floor(goal.y + 0.5), - z = floor(goal.z + 0.5) - } + _start = { + x = floor(_start.x + 0.5), + y = floor(_start.y + 0.5), + z = floor(_start.z + 0.5) + } - if goal.x == start.x - and goal.z == start.z then -- No path can be found - return nil - end - - local openSet = self._path_data.open or {} - - local closedSet = self._path_data.closed or {} - - local start_index = minetest.hash_node_position(start) - - openSet[start_index] = { - pos = start, - parent = nil, - gScore = 0, - fScore = get_distance(start, goal) - } - - local count = self._path_data.count or 1 - - while count > 0 do - if minetest.get_us_time() - us_time > a_star_alloted_time then - self._path_data = { - start = start, - open = openSet, - closed = closedSet, - count = count - } - return - end - -- Initialize ID and data - local current_id - local current - - -- Get an initial id in open set - for i, v in pairs(openSet) do - current_id = i - current = v - break - end - - -- Find lowest f cost - for i, v in pairs(openSet) do - if v.fScore < current.fScore then - current_id = i - current = v - end - end - - -- Add lowest fScore to closedSet and remove from openSet - openSet[current_id] = nil - closedSet[current_id] = current + _goal = { + x = floor(_goal.x + 0.5), + y = floor(_goal.y + 0.5), + z = floor(_goal.z + 0.5) + } - self._path_data.open = openSet - self._path_data.closedSet = closedSet + if _goal.x == _start.x + and _goal.z == _start.z then -- No path can be found + return nil + end - -- Reconstruct path if end is reached - if ((is_on_ground(goal) - or fly) - and current_id == minetest.hash_node_position(goal)) - or (not fly - and not is_on_ground(goal) - and goal.x == current.pos.x - and goal.z == current.pos.z) then - local path = {} - local fail_safe = 0 - for k, v in pairs(closedSet) do - fail_safe = fail_safe + 1 - end - repeat - if not closedSet[current_id] then return end - table.insert(path, closedSet[current_id].pos) - current_id = closedSet[current_id].parent - until current_id == start_index or #path >= fail_safe - if not closedSet[current_id] then self._path_data = {} return nil end - table.insert(path, closedSet[current_id].pos) - local reverse_path = {} - repeat table.insert(reverse_path, table.remove(path)) until #path == 0 - self._path_data = {} - return reverse_path - end - - count = count - 1 - - local adjacent = get_neighbors(current.pos, obj_width, obj_height, path_neighbors, openSet, closedSet) - - -- Go through neighboring nodes - for i = 1, #adjacent do - local neighbor = { - pos = adjacent[i], - parent = current_id, - gScore = 0, - fScore = 0 - } - local temp_gScore = current.gScore + get_distance_to_neighbor(current.pos, neighbor.pos) - local new_gScore = 0 - if openSet[minetest.hash_node_position(neighbor.pos)] then - new_gScore = openSet[minetest.hash_node_position(neighbor.pos)].gScore - end - if (temp_gScore < new_gScore - or not openSet[minetest.hash_node_position(neighbor.pos)]) - and not closedSet[minetest.hash_node_position(neighbor.pos)] then - if not openSet[minetest.hash_node_position(neighbor.pos)] then - count = count + 1 - end - local hCost = get_distance_to_neighbor(neighbor.pos, goal) - neighbor.gScore = temp_gScore - neighbor.fScore = temp_gScore + hCost - openSet[minetest.hash_node_position(neighbor.pos)] = neighbor - end - end - if count > (max_open or 100) then - self._path_data = {} - return - end - end - self._path_data = {} - return nil - end - return find_path(start, goal) + local openSet = self._path_data.open or {} + + local closedSet = self._path_data.closed or {} + + local start_index = minetest.hash_node_position(_start) + + openSet[start_index] = { + pos = _start, + parent = nil, + gScore = 0, + fScore = get_distance(_start, _goal) + } + + local count = self._path_data.count or 1 + + while count > 0 do + if minetest.get_us_time() - us_time > a_star_alloted_time then + self._path_data = { + start = _start, + open = openSet, + closed = closedSet, + count = count + } + return + end + -- Initialize ID and data + local current_id + local current + + -- Get an initial id in open set + for i, v in pairs(openSet) do + current_id = i + current = v + break + end + + -- Find lowest f cost + for i, v in pairs(openSet) do + if v.fScore < current.fScore then + current_id = i + current = v + end + end + + -- Add lowest fScore to closedSet and remove from openSet + openSet[current_id] = nil + closedSet[current_id] = current + + self._path_data.open = openSet + self._path_data.closedSet = closedSet + + -- Reconstruct path if end is reached + if ((is_on_ground(_goal) + or fly) + and current_id == minetest.hash_node_position(_goal)) + or (not fly + and not is_on_ground(_goal) + and _goal.x == current.pos.x + and _goal.z == current.pos.z) then + local path = {} + local fail_safe = 0 + for _ in pairs(closedSet) do + fail_safe = fail_safe + 1 + end + repeat + if not closedSet[current_id] then return end + table.insert(path, closedSet[current_id].pos) + current_id = closedSet[current_id].parent + until current_id == start_index or #path >= fail_safe + if not closedSet[current_id] then self._path_data = {} return nil end + table.insert(path, closedSet[current_id].pos) + local reverse_path = {} + repeat table.insert(reverse_path, table.remove(path)) until #path == 0 + self._path_data = {} + return reverse_path + end + + count = count - 1 + + local adjacent = get_neighbors(current.pos, obj_width, obj_height, path_neighbors, openSet, closedSet) + + -- Go through neighboring nodes + for i = 1, #adjacent do + local neighbor = { + pos = adjacent[i], + parent = current_id, + gScore = 0, + fScore = 0 + } + local temp_gScore = current.gScore + get_distance_to_neighbor(current.pos, neighbor.pos) + local new_gScore = 0 + if openSet[minetest.hash_node_position(neighbor.pos)] then + new_gScore = openSet[minetest.hash_node_position(neighbor.pos)].gScore + end + if (temp_gScore < new_gScore + or not openSet[minetest.hash_node_position(neighbor.pos)]) + and not closedSet[minetest.hash_node_position(neighbor.pos)] then + if not openSet[minetest.hash_node_position(neighbor.pos)] then + count = count + 1 + end + local hCost = get_distance_to_neighbor(neighbor.pos, _goal) + neighbor.gScore = temp_gScore + neighbor.fScore = temp_gScore + hCost + openSet[minetest.hash_node_position(neighbor.pos)] = neighbor + end + end + if count > (max_open or 100) then + self._path_data = {} + return + end + end + self._path_data = {} + return nil + end + return find_path(start, goal) end @@ -318,269 +307,237 @@ end -- Theta* -- ------------ -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 creatura.get_node_def(node.name).walkable then - return false - end - end - end - return true -end - function creatura.find_theta_path(self, start, goal, obj_width, obj_height, max_open, climb, fly, swim) - climb = climb or false - fly = fly or false - swim = swim or false + climb = climb or false + fly = fly or false + swim = swim or false - start = self._path_data.start or start + start = self._path_data.start or start - self._path_data.start = start + self._path_data.start = start - local path_neighbors = { - {x = 1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = -1, y = 0, z = 0}, - {x = 0, y = 0, z = -1}, - } + local path_neighbors = { + {x = 1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = -1}, + } - if climb then - table.insert(path_neighbors, {x = 0, y = 1, z = 0}) - end + if climb then + table.insert(path_neighbors, {x = 0, y = 1, z = 0}) + end - if fly - or swim then - path_neighbors = { - -- Central - {x = 1, y = 0, z = 0}, - {x = 0, y = 0, z = 1}, - {x = -1, y = 0, z = 0}, - {x = 0, y = 0, z = -1}, - -- Directly Up or Down - {x = 0, y = 1, z = 0}, - {x = 0, y = -1, z = 0} - } - end + if fly + or swim then + path_neighbors = { + -- Central + {x = 1, y = 0, z = 0}, + {x = 0, y = 0, z = 1}, + {x = -1, y = 0, z = 0}, + {x = 0, y = 0, z = -1}, + -- Directly Up or Down + {x = 0, y = 1, z = 0}, + {x = 0, y = -1, z = 0} + } + end - local function get_neighbors(pos, width, height, tbl, open, closed) - local result = {} - for i = 1, #tbl do - local neighbor = vector.add(pos, tbl[i]) - if neighbor.y == pos.y - and not fly - and not swim then - neighbor = get_ground_level(neighbor, 1) - end - local can_move = get_line_of_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor) - if swim then - can_move = true - end - if not moveable(vec_raise(neighbor, -0.49), width, height) then - can_move = false - if neighbor.y == pos.y - and moveable(vec_raise(neighbor, 0.51), width, height) then - neighbor = vec_raise(neighbor, 1) - can_move = true - end - end - if vector.equals(neighbor, goal) then - can_move = true - end - if open[minetest.hash_node_position(neighbor)] - or closed[minetest.hash_node_position(neighbor)] then - can_move = false - end - if can_move - and ((is_on_ground(neighbor) - or (fly or swim)) - or (neighbor.x == pos.x - and neighbor.z == pos.z - and climb)) - and (not swim - or is_node_liquid(minetest.get_node(neighbor).name)) then - table.insert(result, neighbor) - end - end - return result - end + local function get_neighbors(pos, width, height, tbl, open, closed) + local result = {} + for i = 1, #tbl do + local neighbor = vector.add(pos, tbl[i]) + if neighbor.y == pos.y + and not fly + and not swim then + neighbor = creatura.get_ground_level(neighbor, 1) + end + local can_move = get_line_of_sight({x = pos.x, y = neighbor.y, z = pos.z}, neighbor) + if swim then + can_move = true + end + if not moveable(vec_raise(neighbor, -0.49), width, height) then + can_move = false + if neighbor.y == pos.y + and moveable(vec_raise(neighbor, 0.51), width, height) then + neighbor = vec_raise(neighbor, 1) + can_move = true + end + end + if vector.equals(neighbor, goal) then + can_move = true + end + if open[minetest.hash_node_position(neighbor)] + or closed[minetest.hash_node_position(neighbor)] then + can_move = false + end + if can_move + and ((is_on_ground(neighbor) + or (fly or swim)) + or (neighbor.x == pos.x + and neighbor.z == pos.z + and climb)) + and (not swim + or creatura.get_node_def(neighbor).drawtype == "liquid") then + table.insert(result, neighbor) + end + end + return result + end - local function find_path(start, goal) - local us_time = minetest.get_us_time() + local function find_path(_start, _goal) + local us_time = minetest.get_us_time() - start = { - x = floor(start.x + 0.5), - y = floor(start.y + 0.5), - z = floor(start.z + 0.5) - } - - goal = { - x = floor(goal.x + 0.5), - y = floor(goal.y + 0.5), - z = floor(goal.z + 0.5) - } + _start = { + x = floor(_start.x + 0.5), + y = floor(_start.y + 0.5), + z = floor(_start.z + 0.5) + } - if goal.x == start.x - and goal.z == start.z then -- No path can be found - return nil - end - - local openSet = self._path_data.open or {} - - local closedSet = self._path_data.closed or {} - - local start_index = minetest.hash_node_position(start) - - openSet[start_index] = { - pos = start, - parent = nil, - gScore = 0, - fScore = get_distance(start, goal) - } - - local count = self._path_data.count or 1 - - while count > 0 do - if minetest.get_us_time() - us_time > theta_star_alloted_time then - self._path_data = { - start = start, - open = openSet, - closed = closedSet, - count = count - } - return - end + _goal = { + x = floor(_goal.x + 0.5), + y = floor(_goal.y + 0.5), + z = floor(_goal.z + 0.5) + } - -- Initialize ID and data - local current_id - local current + if _goal.x == _start.x + and _goal.z == _start.z then -- No path can be found + return nil + end - -- Get an initial id in open set - for i, v in pairs(openSet) do - current_id = i - current = v - break - end + local openSet = self._path_data.open or {} - -- Find lowest f cost - for i, v in pairs(openSet) do - if v.fScore < current.fScore then - current_id = i - current = v - end - end + local closedSet = self._path_data.closed or {} - -- Add lowest fScore to closedSet and remove from openSet - openSet[current_id] = nil - closedSet[current_id] = current + local start_index = minetest.hash_node_position(_start) - -- Reconstruct path if end is reached - if (is_on_ground(goal) - and current_id == minetest.hash_node_position(goal)) - or (not is_on_ground(goal) - and goal.x == current.pos.x - and goal.z == current.pos.z) then - local path = {} - local fail_safe = 0 - for k, v in pairs(closedSet) do - fail_safe = fail_safe + 1 - end - repeat - if not closedSet[current_id] then return end - table.insert(path, closedSet[current_id].pos) - current_id = closedSet[current_id].parent - until current_id == start_index or #path >= fail_safe - if not closedSet[current_id] then self._path_data = {} return nil end - table.insert(path, closedSet[current_id].pos) - local reverse_path = {} - repeat table.insert(reverse_path, table.remove(path)) until #path == 0 - self._path_data = {} - return reverse_path - end - - count = count - 1 - - local adjacent = get_neighbors(current.pos, obj_width, obj_height, path_neighbors, openSet, closedSet) - - -- Go through neighboring nodes - for i = 1, #adjacent do - local neighbor = { - pos = adjacent[i], - parent = current_id, - gScore = 0, - fScore = 0 - } - if not openSet[minetest.hash_node_position(neighbor.pos)] - and not closedSet[minetest.hash_node_position(neighbor.pos)] then - local current_parent = closedSet[current.parent] or closedSet[start_index] - if not current_parent then - current_parent = openSet[current.parent] or openSet[start_index] - end - if current_parent - and get_line_of_sight(current_parent.pos, neighbor.pos) then - local temp_gScore = current_parent.gScore + get_distance_to_neighbor(current_parent.pos, neighbor.pos) - local new_gScore = 999 - if openSet[minetest.hash_node_position(neighbor.pos)] then - new_gScore = openSet[minetest.hash_node_position(neighbor.pos)].gScore - end - if temp_gScore < new_gScore then - local hCost = get_distance_to_neighbor(neighbor.pos, goal) - neighbor.gScore = temp_gScore - neighbor.fScore = temp_gScore + hCost - neighbor.parent = minetest.hash_node_position(current_parent.pos) - if openSet[minetest.hash_node_position(neighbor.pos)] then - openSet[minetest.hash_node_position(neighbor.pos)] = nil - end - openSet[minetest.hash_node_position(neighbor.pos)] = neighbor - count = count + 1 - end - else - local temp_gScore = current.gScore + get_distance_to_neighbor(current_parent.pos, neighbor.pos) - local new_gScore = 999 - if openSet[minetest.hash_node_position(neighbor.pos)] then - new_gScore = openSet[minetest.hash_node_position(neighbor.pos)].gScore - end - if temp_gScore < new_gScore then - local hCost = get_distance_to_neighbor(neighbor.pos, goal) - neighbor.gScore = temp_gScore - neighbor.fScore = temp_gScore + hCost - if openSet[minetest.hash_node_position(neighbor.pos)] then - openSet[minetest.hash_node_position(neighbor.pos)] = nil - end - openSet[minetest.hash_node_position(neighbor.pos)] = neighbor - count = count + 1 - end - end - end - end - if count > (max_open or 100) then - self._path_data = {} - return - end - end - self._path_data = {} - return nil - end - return find_path(start, goal) + openSet[start_index] = { + pos = _start, + parent = nil, + gScore = 0, + fScore = get_distance(_start, _goal) + } + + local count = self._path_data.count or 1 + + while count > 0 do + if minetest.get_us_time() - us_time > theta_star_alloted_time then + self._path_data = { + start = _start, + open = openSet, + closed = closedSet, + count = count + } + return + end + + -- Initialize ID and data + local current_id + local current + + -- Get an initial id in open set + for i, v in pairs(openSet) do + current_id = i + current = v + break + end + + -- Find lowest f cost + for i, v in pairs(openSet) do + if v.fScore < current.fScore then + current_id = i + current = v + end + end + + -- Add lowest fScore to closedSet and remove from openSet + openSet[current_id] = nil + closedSet[current_id] = current + + -- Reconstruct path if end is reached + if (is_on_ground(_goal) + and current_id == minetest.hash_node_position(_goal)) + or (not is_on_ground(_goal) + and _goal.x == current.pos.x + and _goal.z == current.pos.z) then + local path = {} + local fail_safe = 0 + for _ in pairs(closedSet) do + fail_safe = fail_safe + 1 + end + repeat + if not closedSet[current_id] then return end + table.insert(path, closedSet[current_id].pos) + current_id = closedSet[current_id].parent + until current_id == start_index or #path >= fail_safe + if not closedSet[current_id] then self._path_data = {} return nil end + table.insert(path, closedSet[current_id].pos) + local reverse_path = {} + repeat table.insert(reverse_path, table.remove(path)) until #path == 0 + self._path_data = {} + return reverse_path + end + + count = count - 1 + + local adjacent = get_neighbors(current.pos, obj_width, obj_height, path_neighbors, openSet, closedSet) + + -- Go through neighboring nodes + for i = 1, #adjacent do + local neighbor = { + pos = adjacent[i], + parent = current_id, + gScore = 0, + fScore = 0 + } + if not openSet[minetest.hash_node_position(neighbor.pos)] + and not closedSet[minetest.hash_node_position(neighbor.pos)] then + local current_parent = closedSet[current.parent] or closedSet[start_index] + if not current_parent then + current_parent = openSet[current.parent] or openSet[start_index] + end + if current_parent + and get_line_of_sight(current_parent.pos, neighbor.pos) then + local temp_gScore = current_parent.gScore + get_distance_to_neighbor(current_parent.pos, neighbor.pos) + local new_gScore = 999 + if openSet[minetest.hash_node_position(neighbor.pos)] then + new_gScore = openSet[minetest.hash_node_position(neighbor.pos)].gScore + end + if temp_gScore < new_gScore then + local hCost = get_distance_to_neighbor(neighbor.pos, _goal) + neighbor.gScore = temp_gScore + neighbor.fScore = temp_gScore + hCost + neighbor.parent = minetest.hash_node_position(current_parent.pos) + if openSet[minetest.hash_node_position(neighbor.pos)] then + openSet[minetest.hash_node_position(neighbor.pos)] = nil + end + openSet[minetest.hash_node_position(neighbor.pos)] = neighbor + count = count + 1 + end + else + local temp_gScore = current.gScore + get_distance_to_neighbor(current_parent.pos, neighbor.pos) + local new_gScore = 999 + if openSet[minetest.hash_node_position(neighbor.pos)] then + new_gScore = openSet[minetest.hash_node_position(neighbor.pos)].gScore + end + if temp_gScore < new_gScore then + local hCost = get_distance_to_neighbor(neighbor.pos, _goal) + neighbor.gScore = temp_gScore + neighbor.fScore = temp_gScore + hCost + if openSet[minetest.hash_node_position(neighbor.pos)] then + openSet[minetest.hash_node_position(neighbor.pos)] = nil + end + openSet[minetest.hash_node_position(neighbor.pos)] = neighbor + count = count + 1 + end + end + end + end + if count > (max_open or 100) then + self._path_data = {} + return + end + end + self._path_data = {} + return nil + end + return find_path(start, goal) end diff --git a/spawning.lua b/spawning.lua index e7148f3..6dbbded 100644 --- a/spawning.lua +++ b/spawning.lua @@ -350,4 +350,4 @@ minetest.register_abm({ end end end, -})]] +})]] \ No newline at end of file