Minor performance and stability improvements

This commit is contained in:
ElCeejo 2022-06-16 14:57:06 -07:00 committed by GitHub
parent ad475bb790
commit 13ebe1c37d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1196 additions and 1372 deletions

598
api.lua
View file

@ -6,20 +6,9 @@ creatura.api = {}
-- Math -- -- Math --
local pi = math.pi
local pi2 = pi * 2
local abs = math.abs
local floor = math.floor local floor = math.floor
local random = math.random 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) local function clamp(val, min, max)
if val < min then if val < min then
val = min val = min
@ -29,25 +18,18 @@ local function clamp(val, min, max)
return val return val
end end
local vec_dir = vector.direction
local vec_dist = vector.distance local vec_dist = vector.distance
local vec_multi = vector.multiply local vec_multi = vector.multiply
local vec_sub = vector.subtract local vec_equals = vector.equals
local vec_add = vector.add local vec_add = vector.add
local function vec_center(v) 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 end
local function vec_raise(v, n) local function vec_raise(v, n)
if not v then return end if not v then return end
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 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)
end end
--------------- ---------------
@ -55,12 +37,12 @@ end
--------------- ---------------
local function is_value_in_table(tbl, val) local function is_value_in_table(tbl, val)
for _, v in pairs(tbl) do for _, v in pairs(tbl) do
if v == val then if v == val then
return true return true
end end
end end
return false return false
end end
----------------------- -----------------------
@ -72,7 +54,7 @@ end
creatura.registered_movement_methods = {} creatura.registered_movement_methods = {}
function creatura.register_movement_method(name, func) function creatura.register_movement_method(name, func)
creatura.registered_movement_methods[name] = func creatura.registered_movement_methods[name] = func
end end
-- Utility Behaviors -- -- Utility Behaviors --
@ -80,7 +62,7 @@ end
creatura.registered_utilities = {} creatura.registered_utilities = {}
function creatura.register_utility(name, func) function creatura.register_utility(name, func)
creatura.registered_utilities[name] = func creatura.registered_utilities[name] = func
end end
-- Sensors -- -- Sensors --
@ -93,7 +75,7 @@ function creatura.get_node_height_from_def(name)
if def.walkable then if def.walkable then
if def.drawtype == "nodebox" then if def.drawtype == "nodebox" then
if def.node_box 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 if type(def.node_box.fixed[1]) == "number" then
return 0.5 + def.node_box.fixed[5] return 0.5 + def.node_box.fixed[5]
elseif type(def.node_box.fixed[1]) == "table" then elseif type(def.node_box.fixed[1]) == "table" then
@ -113,234 +95,232 @@ function creatura.get_node_height_from_def(name)
end end
function creatura.get_node_def(node) -- Node can be name or pos function creatura.get_node_def(node) -- Node can be name or pos
if type(node) == "table" then if type(node) == "table" then
node = minetest.get_node(node).name node = minetest.get_node(node).name
end end
local def = minetest.registered_nodes[node] or default_node_def local def = minetest.registered_nodes[node] or default_node_def
if def.walkable if def.walkable
and creatura.get_node_height_from_def(node) < 0.26 then and creatura.get_node_height_from_def(node) < 0.26 then
def.walkable = false -- workaround for nodes like snow def.walkable = false -- workaround for nodes like snow
end end
return def return def
end end
function creatura.get_ground_level(pos2, max_diff) function creatura.get_ground_level(pos2, max_diff)
local node = minetest.get_node(pos2) local node = minetest.get_node(pos2)
local node_under = minetest.get_node({ local node_under = minetest.get_node({
x = pos2.x, x = pos2.x,
y = pos2.y - 1, y = pos2.y - 1,
z = pos2.z z = pos2.z
}) })
local walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable local walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable
if walkable then if walkable then
return pos2 return pos2
end end
local diff = 0 if not creatura.get_node_def(node_under.name).walkable then
if not creatura.get_node_def(node_under.name).walkable then for _ = 1, max_diff do
for i = 1, max_diff do pos2.y = pos2.y - 1
pos2.y = pos2.y - 1 node = minetest.get_node(pos2)
node = minetest.get_node(pos2) node_under = minetest.get_node({
node_under = minetest.get_node({ x = pos2.x,
x = pos2.x, y = pos2.y - 1,
y = pos2.y - 1, z = pos2.z
z = pos2.z })
}) walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable
walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable if walkable then break end
if walkable then break end end
end else
else for _ = 1, max_diff do
for i = 1, max_diff do pos2.y = pos2.y + 1
pos2.y = pos2.y + 1 node = minetest.get_node(pos2)
node = minetest.get_node(pos2) node_under = minetest.get_node({
node_under = minetest.get_node({ x = pos2.x,
x = pos2.x, y = pos2.y - 1,
y = pos2.y - 1, z = pos2.z
z = pos2.z })
}) walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable
walkable = creatura.get_node_def(node_under.name).walkable and not creatura.get_node_def(node.name).walkable if walkable then break end
if walkable then break end end
end end
end return pos2
return pos2
end end
function creatura.is_pos_moveable(pos, width, height) function creatura.is_pos_moveable(pos, width, height)
local pos1 = { local pos1 = {
x = pos.x - (width + 0.2), x = pos.x - (width + 0.2),
y = pos.y, y = pos.y,
z = pos.z - (width + 0.2), z = pos.z - (width + 0.2),
} }
local pos2 = { local pos2 = {
x = pos.x + (width + 0.2), x = pos.x + (width + 0.2),
y = pos.y, y = pos.y,
z = pos.z + (width + 0.2), z = pos.z + (width + 0.2),
} }
for z = pos1.z, pos2.z do for z = pos1.z, pos2.z do
for x = pos1.x, pos2.x do for x = pos1.x, pos2.x do
local pos3 = {x = x, y = pos.y + height, z = z} local pos3 = {x = x, y = pos.y + height, z = z}
local pos4 = {x = x, y = pos.y + 0.01, z = z} local pos4 = {x = x, y = pos.y + 0.01, z = z}
local ray = minetest.raycast(pos3, pos4, false, false) local ray = minetest.raycast(pos3, pos4, false, false)
for pointed_thing in ray do for pointed_thing in ray do
if pointed_thing.type == "node" then if pointed_thing.type == "node" then
local name = minetest.get_node(pointed_thing.under).name local name = minetest.get_node(pointed_thing.under).name
if creatura.get_node_def(name).walkable then if creatura.get_node_def(name).walkable then
return false return false
end end
end end
end end
end end
end end
return true return true
end end
local moveable = creatura.is_pos_moveable local moveable = creatura.is_pos_moveable
function creatura.fast_ray_sight(pos1, pos2, water) function creatura.fast_ray_sight(pos1, pos2, water)
local ray = minetest.raycast(pos1, pos2, false, water or false) local ray = minetest.raycast(pos1, pos2, false, water or false)
for pointed_thing in ray do for pointed_thing in ray do
if pointed_thing.type == "node" then if pointed_thing.type == "node" then
return false, vec_dist(pos1, pointed_thing.intersection_point), pointed_thing.ref return false, vec_dist(pos1, pointed_thing.intersection_point), pointed_thing.ref
end end
end end
return true, vec_dist(pos1, pos2) return true, vec_dist(pos1, pos2)
end end
local fast_ray_sight = creatura.fast_ray_sight local fast_ray_sight = creatura.fast_ray_sight
function creatura.get_next_move(self, pos2) function creatura.get_next_move(self, pos2)
local last_move = self._movement_data.last_move local last_move = self._movement_data.last_move
local width = self.width local width = self.width
local height = self.height local height = self.height
local pos = self.object:get_pos() local pos = self.object:get_pos()
pos = { pos = {
x = floor(pos.x), x = floor(pos.x),
y = pos.y + 0.01, y = pos.y + 0.01,
z = floor(pos.z) z = floor(pos.z)
} }
pos.y = pos.y + 0.01 pos.y = pos.y + 0.01
if last_move if last_move
and last_move.pos then and last_move.pos then
local last_call = minetest.get_position_from_hash(last_move.pos) local last_call = minetest.get_position_from_hash(last_move.pos)
local last_move = minetest.get_position_from_hash(last_move.move) last_move = minetest.get_position_from_hash(last_move.move)
if vector.equals(vec_center(last_call), vec_center(pos)) then if vec_equals(vec_center(last_call), vec_center(pos)) then
return last_move return last_move
end end
end end
local neighbors = { local neighbors = {
vec_add(pos, {x = 1, y = 0, z = 0}), vec_add(pos, {x = 1, y = 0, z = 0}),
vec_add(pos, {x = 1, y = 0, z = 1}), vec_add(pos, {x = 1, y = 0, z = 1}),
vec_add(pos, {x = 0, 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 = 1}),
vec_add(pos, {x = -1, y = 0, z = 0}), vec_add(pos, {x = -1, y = 0, z = 0}),
vec_add(pos, {x = -1, y = 0, z = -1}), vec_add(pos, {x = -1, y = 0, z = -1}),
vec_add(pos, {x = 0, 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 = -1})
} }
local _next local _next
table.sort(neighbors, function(a, b) table.sort(neighbors, function(a, b)
return vec_dist(a, pos2) < vec_dist(b, pos2) return vec_dist(a, pos2) < vec_dist(b, pos2)
end) end)
for i = 1, #neighbors do for i = 1, #neighbors do
local neighbor = neighbors[i] local neighbor = neighbors[i]
local can_move = fast_ray_sight(pos, neighbor) local can_move = fast_ray_sight(pos, neighbor)
if vector.equals(neighbor, pos2) then if vec_equals(neighbor, pos2) then
can_move = true can_move = true
end end
if can_move if can_move
and not moveable(neighbor, width, height) then and not moveable(neighbor, width, height) then
can_move = false can_move = false
if moveable(vec_raise(neighbor, 0.5), width, height) then if moveable(vec_raise(neighbor, 0.5), width, height) then
can_move = true can_move = true
end end
end end
if can_move if can_move
and not self:is_pos_safe(neighbor) then and not self:is_pos_safe(neighbor) then
can_move = false can_move = false
end end
if can_move then if can_move then
_next = vec_raise(neighbor, 0.1) _next = vec_raise(neighbor, 0.1)
break break
end end
end end
if _next then if _next then
self._movement_data.last_move = { self._movement_data.last_move = {
pos = minetest.hash_node_position(pos), pos = minetest.hash_node_position(pos),
move = minetest.hash_node_position(_next) move = minetest.hash_node_position(_next)
} }
_next = { _next = {
x = floor(_next.x), x = floor(_next.x),
y = _next.y, y = _next.y,
z = floor(_next.z) z = floor(_next.z)
} }
end end
return _next return _next
end end
function creatura.get_next_move_3d(self, pos2) function creatura.get_next_move_3d(self, pos2)
local last_move = self._movement_data.last_move local last_move = self._movement_data.last_move
local width = self.width local width = self.width
local height = self.height local height = self.height
local scan_width = width * 2 local scan_width = width * 2
local pos = self.object:get_pos() local pos = self.object:get_pos()
pos.y = pos.y + 0.5 pos.y = pos.y + 0.5
if last_move if last_move
and last_move.pos then and last_move.pos then
local last_call = minetest.get_position_from_hash(last_move.pos) local last_call = minetest.get_position_from_hash(last_move.pos)
local last_move = minetest.get_position_from_hash(last_move.move) last_move = minetest.get_position_from_hash(last_move.move)
if vector.equals(vec_center(last_call), vec_center(pos)) then if vec_equals(vec_center(last_call), vec_center(pos)) then
return last_move return last_move
end end
end end
local neighbors = { local neighbors = {
vec_add(pos, {x = scan_width, y = 0, z = 0}), 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 = scan_width, y = 0, z = scan_width}),
vec_add(pos, {x = 0, 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 = scan_width}),
vec_add(pos, {x = -scan_width, y = 0, z = 0}), 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 = -scan_width, y = 0, z = -scan_width}),
vec_add(pos, {x = 0, 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 = -scan_width})
} }
local next local next
table.sort(neighbors, function(a, b) table.sort(neighbors, function(a, b)
return vec_dist(a, pos2) < vec_dist(b, pos2) return vec_dist(a, pos2) < vec_dist(b, pos2)
end) end)
for i = 1, #neighbors do for i = 1, #neighbors do
local neighbor = neighbors[i] 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({x = pos.x, y = neighbor.y, z = pos.z}, neighbor)
if not moveable(vec_raise(neighbor, 0.6), width, height) then if not moveable(vec_raise(neighbor, 0.6), width, height) then
can_move = false can_move = false
end end
if vector.equals(neighbor, pos2) then if vec_equals(neighbor, pos2) then
can_move = true can_move = true
end end
local dist = vec_dist(neighbor, pos2) if can_move then
if can_move then next = neighbor
next = neighbor break
break end
end end
end if next then
if next then self._movement_data.last_move = {
self._movement_data.last_move = { pos = minetest.hash_node_position(pos),
pos = minetest.hash_node_position(pos), move = minetest.hash_node_position(next)
move = minetest.hash_node_position(next) }
} end
end return vec_raise(next, clamp((pos2.y - pos.y) + -0.6, -1, 1))
return vec_raise(next, clamp((pos2.y - pos.y) + -0.6, -1, 1))
end end
function creatura.sensor_floor(self, range, water) function creatura.sensor_floor(self, range, water)
local pos = self.object:get_pos() local pos = self.object:get_pos()
local pos2 = vec_raise(pos, -range) local pos2 = vec_raise(pos, -range)
local _, dist, node = fast_ray_sight(pos, pos2, water or false) local _, dist, node = fast_ray_sight(pos, pos2, water or false)
return dist, node return dist, node
end end
function creatura.sensor_ceil(self, range, water) function creatura.sensor_ceil(self, range, water)
local pos = vec_raise(self.object:get_pos(), self.height) local pos = vec_raise(self.object:get_pos(), self.height)
local pos2 = vec_raise(pos, range) local pos2 = vec_raise(pos, range)
local _, dist, node = fast_ray_sight(pos, pos2, water or false) local _, dist, node = fast_ray_sight(pos, pos2, water or false)
return dist, node return dist, node
end end
-- Misc -- Misc
@ -355,69 +335,69 @@ function creatura.is_valid(mob)
if mob:get_yaw() then return mob end if mob:get_yaw() then return mob end
end end
end end
return false return false
end end
function creatura.is_alive(mob) function creatura.is_alive(mob)
if not creatura.is_valid(mob) then if not creatura.is_valid(mob) then
return false return false
end end
if type(mob) == "table" then if type(mob) == "table" then
return mob.hp > 0 return mob.hp > 0
end end
if mob:is_player() then if mob:is_player() then
return mob:get_hp() > 0 return mob:get_hp() > 0
else else
local ent = mob:get_luaentity() local ent = mob:get_luaentity()
return ent and ent.hp and ent.hp > 0 return ent and ent.hp and ent.hp > 0
end end
end end
function creatura.get_nearby_player(self) 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 for _, object in ipairs(objects) do
if object:is_player() if object:is_player()
and creatura.is_alive(object) then and creatura.is_alive(object) then
return object return object
end end
end end
end end
function creatura.get_nearby_players(self) function creatura.get_nearby_players(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)
local nearby = {} local nearby = {}
for _, object in ipairs(objects) do for _, object in ipairs(objects) do
if object:is_player() if object:is_player()
and creatura.is_alive(object) then and creatura.is_alive(object) then
table.insert(nearby, object) table.insert(nearby, object)
end end
end end
return nearby return nearby
end end
function creatura.get_nearby_entity(self, name) 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 for _, object in ipairs(objects) do
if creatura.is_alive(object) if creatura.is_alive(object)
and not object:is_player() and not object:is_player()
and object ~= self.object and object ~= self.object
and object:get_luaentity().name == name then and object:get_luaentity().name == name then
return object return object
end end
end end
return return
end end
function creatura.get_nearby_entities(self, name) function creatura.get_nearby_entities(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)
local nearby = {} local nearby = {}
for _, object in ipairs(objects) do for _, object in ipairs(objects) do
if creatura.is_alive(object) if creatura.is_alive(object)
and not object:is_player() and not object:is_player()
and object ~= self.object and object ~= self.object
and object:get_luaentity().name == name then and object:get_luaentity().name == name then
table.insert(nearby, object) table.insert(nearby, object)
end end
end end
return nearby return nearby
end end
@ -429,51 +409,51 @@ end
-- Drops -- -- Drops --
function creatura.drop_items(self) function creatura.drop_items(self)
if not self.drops then return end if not self.drops then return end
for i = 1, #self.drops do for i = 1, #self.drops do
local drop_def = self.drops[i] local drop_def = self.drops[i]
local name = drop_def.name local name = drop_def.name
if not name then return end if not name then return end
local min_amount = drop_def.min or 1 local min_amount = drop_def.min or 1
local max_amount = drop_def.max or 2 local max_amount = drop_def.max or 2
local chance = drop_def.chance or 1 local chance = drop_def.chance or 1
local amount = random(min_amount, max_amount) local amount = random(min_amount, max_amount)
if random(chance) < 2 then if random(chance) < 2 then
local pos = self.object:get_pos() local pos = self.object:get_pos()
local item = minetest.add_item(pos, ItemStack(name .. " " .. amount)) local item = minetest.add_item(pos, ItemStack(name .. " " .. amount))
if item then if item then
item:add_velocity({ item:add_velocity({
x = random(-2, 2), x = random(-2, 2),
y = 1.5, y = 1.5,
z = random(-2, 2) z = random(-2, 2)
}) })
end end
end end
end end
end end
-- On Punch -- -- On Punch --
function creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage) function creatura.basic_punch_func(self, puncher, time_from_last_punch, tool_capabilities, direction, damage)
if not puncher then return end if not puncher then return end
local tool = "" local tool = ""
if puncher:is_player() then if puncher:is_player() then
tool = puncher:get_wielded_item():get_name() tool = puncher:get_wielded_item():get_name()
end end
if (self.immune_to if (self.immune_to
and is_value_in_table(self.immune_to, tool)) then and is_value_in_table(self.immune_to, tool)) then
return return
end end
local dir = vec_dir(puncher:get_pos(), self:get_center_pos()) local dir = vec_multi(direction, -1)
self:apply_knockback(dir) self:apply_knockback(dir)
self:hurt(tool_capabilities.damage_groups.fleshy or 2) self:hurt((tool_capabilities.damage_groups.fleshy or damage) or 2)
if random(4) < 2 then if random(4) < 2 then
self:play_sound("hurt") self:play_sound("hurt")
end end
if time_from_last_punch > 0.5 then if time_from_last_punch > 0.5 then
self:play_sound("hit") self:play_sound("hit")
end end
self:indicate_damage() self:indicate_damage()
end end
local path = minetest.get_modpath("creatura") local path = minetest.get_modpath("creatura")

210
boids.lua
View file

@ -5,60 +5,51 @@
local random = math.random local random = math.random
local function average(tbl) local function average(tbl)
local sum = 0 local sum = 0
for _,v in pairs(tbl) do -- Get the sum of all numbers in t for _,v in pairs(tbl) do -- Get the sum of all numbers in t
sum = sum + v sum = sum + v
end end
return sum / #tbl return sum / #tbl
end end
local function average_angle(tbl) local function average_angle(tbl)
local sum_sin, sum_cos = 0, 0 local sum_sin, sum_cos = 0, 0
for _, v in pairs(tbl) do for _, v in pairs(tbl) do
sum_sin = sum_sin + math.sin(v) sum_sin = sum_sin + math.sin(v)
sum_cos = sum_cos + math.cos(v) sum_cos = sum_cos + math.cos(v)
end end
return math.atan2(sum_sin, sum_cos) return math.atan2(sum_sin, sum_cos)
end end
local vec_dist = vector.distance local vec_dist = vector.distance
local vec_dir = vector.direction local vec_dir = vector.direction
local vec_len = vector.length
local vec_add = vector.add local vec_add = vector.add
local vec_multi = vector.multiply
local vec_normal = vector.normalize local vec_normal = vector.normalize
local vec_divide = vector.divide 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 function get_average_pos(vectors)
local sum = {x = 0, y = 0, z = 0} local sum = {x = 0, y = 0, z = 0}
for _, vec in pairs(vectors) do sum = vec_add(sum, vec) end for _, vec in pairs(vectors) do sum = vec_add(sum, vec) end
return vec_divide(sum, #vectors) return vec_divide(sum, #vectors)
end end
local function dist_2d(pos1, pos2) local function dist_2d(pos1, pos2)
local a = vector.new( local a = vector.new(
pos1.x, pos1.x,
0, 0,
pos1.z pos1.z
) )
local b = vector.new( local b = vector.new(
pos2.x, pos2.x,
0, 0,
pos2.z pos2.z
) )
return vec_dist(a, b) return vec_dist(a, b)
end end
local yaw2dir = minetest.yaw_to_dir local yaw2dir = minetest.yaw_to_dir
local dir2yaw = minetest.dir_to_yaw local dir2yaw = minetest.dir_to_yaw
-- Refresh Boid Leader --
local last_boid_refresh = minetest.get_us_time()
-- Get Boid Members -- -- Get Boid Members --
-- This function scans within -- This function scans within
@ -70,89 +61,78 @@ local last_boid_refresh = minetest.get_us_time()
-- is in the boid. -- is in the boid.
function creatura.get_boid_members(pos, radius, name) function creatura.get_boid_members(pos, radius, name)
local objects = minetest.get_objects_inside_radius(pos, radius) local objects = minetest.get_objects_inside_radius(pos, radius)
if #objects < 2 then return {} end if #objects < 2 then return {} end
local members = {} local members = {}
local max_boid = minetest.registered_entities[name].max_boids or 7 local max_boid = minetest.registered_entities[name].max_boids or 7
for i = 1, #objects do for i = 1, #objects do
if #members > max_boid then break end if #members > max_boid then break end
local object = objects[i] local object = objects[i]
if object:get_luaentity() if object:get_luaentity()
and object:get_luaentity().name == name then and object:get_luaentity().name == name then
object:get_luaentity().boid_heading = math.rad(random(360)) object:get_luaentity().boid_heading = math.rad(random(360))
table.insert(members, object) table.insert(members, object)
end end
end end
return members return members
end end
-- Calculate Boid angles and offsets. -- Calculate Boid angles and offsets.
local function debugpart(pos, time, part) function creatura.get_boid_angle(self, _boids, range)
minetest.add_particle({ local pos = self.object:get_pos()
pos = pos, local boids = _boids or creatura.get_boid_members(pos, range or 4, self.name)
expirationtime = time or 0.2, if #boids < 3 then return end
size = 8, local yaw = self.object:get_yaw()
glow = 16, local lift = self.object:get_velocity().y
texture = part or "creatura_particle_red.png" -- Add Boid data to tables
}) local closest_pos
end local positions = {}
local angles = {}
function creatura.get_boid_angle(self, boid, range) -- calculates boid angle based on seperation, alignment, and cohesion local lifts = {}
local pos = self.object:get_pos() for i = 1, #boids do
local boids = boid or creatura.get_boid_members(pos, range or 4, self.name) local boid = boids[i]
if #boids < 3 then return end if boid:get_pos() then
local yaw = self.object:get_yaw() local boid_pos = boid:get_pos()
local lift = self.object:get_velocity().y table.insert(positions, boid_pos)
-- Add Boid data to tables if boid ~= self.object then
local closest_pos table.insert(lifts, vec_normal(boid:get_velocity()).y)
local positions = {} table.insert(angles, boid:get_yaw())
local angles = {} if not closest_pos
local lifts = {} or vec_dist(pos, boid_pos) < vec_dist(pos, closest_pos) then
for i = 1, #boids do closest_pos = boid_pos
local boid = boids[i] end
if boid:get_pos() then end
local boid_pos = boid:get_pos() end
local boid_yaw = boid:get_yaw() end
table.insert(positions, boid_pos) if #positions < 3 then return end
if boid ~= self.object then local center = get_average_pos(positions)
table.insert(lifts, vec_normal(boid:get_velocity()).y) local dir2closest = vec_dir(pos, closest_pos)
table.insert(angles, boid:get_yaw()) -- Calculate Parameters
if not closest_pos local alignment = average_angle(angles)
or vec_dist(pos, boid_pos) < vec_dist(pos, closest_pos) then center = vec_add(center, yaw2dir(alignment))
closest_pos = boid_pos local dir2center = vec_dir(pos, center)
end local seperation = yaw + -(dir2yaw(dir2closest) - yaw)
end local cohesion = dir2yaw(dir2center)
end local params = {alignment}
end if self.boid_heading then
if #positions < 3 then return end table.insert(params, yaw + self.boid_heading)
local center = get_average_pos(positions) end
local dir2closest = vec_dir(pos, closest_pos) if dist_2d(pos, closest_pos) < (self.boid_seperation or self.width * 3) then
-- Calculate Parameters table.insert(params, seperation)
local alignment = average_angle(angles) elseif dist_2d(pos, center) > (#boids * 0.33) * (self.boid_seperation or self.width * 3) then
center = vec_add(center, yaw2dir(alignment)) table.insert(params, cohesion)
local dir2center = vec_dir(pos, center) end
local seperation = yaw + -(dir2yaw(dir2closest) - yaw) -- Vertical Params
local cohesion = dir2yaw(dir2center) local vert_alignment = average(lifts)
local params = {alignment} local vert_seperation = (self.speed or 2) * -dir2closest.y
if self.boid_heading then local vert_cohesion = (self.speed or 2) * dir2center.y
table.insert(params, yaw + self.boid_heading) local vert_params = {vert_alignment}
end if math.abs(pos.y - closest_pos.y) < (self.boid_seperation or self.width * 3) then
if dist_2d(pos, closest_pos) < (self.boid_seperation or self.width * 3) then table.insert(vert_params, vert_seperation)
table.insert(params, seperation) elseif math.abs(pos.y - closest_pos.y) > 1.5 * (self.boid_seperation or self.width * 3) then
elseif dist_2d(pos, center) > (#boids * 0.33) * (self.boid_seperation or self.width * 3) then table.insert(vert_params, vert_cohesion + (lift - vert_cohesion) * 0.1)
table.insert(params, cohesion) end
end self.boid_heading = nil
-- Vertical Params return average_angle(params), average_angle(vert_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 end

View file

@ -3,17 +3,17 @@
------------- -------------
local pi = math.pi local pi = math.pi
local pi2 = pi * 2
local abs = math.abs local abs = math.abs
local ceil = math.ceil
local floor = math.floor local floor = math.floor
local random = math.random local random = math.random
local rad = math.rad local rad = math.rad
local atan2 = math.atan2
local sin = math.sin local sin = math.sin
local cos = math.cos local cos = math.cos
local function diff(a, b) -- Get difference between 2 angles 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 end
local function clamp(val, min, max) local function clamp(val, min, max)
@ -26,62 +26,57 @@ local function clamp(val, min, max)
end end
local function vec_center(v) 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)
return {x = v.x, y = v.y + n, z = v.z}
end end
local vec_dir = vector.direction local vec_dir = vector.direction
local vec_dist = vector.distance
local vec_multi = vector.multiply local vec_multi = vector.multiply
local vec_add = vector.add local vec_add = vector.add
local yaw2dir = minetest.yaw_to_dir local yaw2dir = minetest.yaw_to_dir
local dir2yaw = minetest.dir_to_yaw local dir2yaw = minetest.dir_to_yaw
local function debugpart(pos, time, tex) --[[local function debugpart(pos, time, tex)
minetest.add_particle({ minetest.add_particle({
pos = pos, pos = pos,
texture = tex or "creatura_particle_red.png", texture = tex or "creatura_particle_red.png",
expirationtime = time or 3, expirationtime = time or 3,
glow = 6, glow = 6,
size = 12 size = 12
}) })
end end]]
--------------------- ---------------------
-- Local Utilities -- -- Local Utilities --
--------------------- ---------------------
local function get_collision(self, yaw) local function get_collision(self, yaw)
local width = self.width local width = self.width
local height = self.height local height = self.height
local pos = self.object:get_pos() local pos = self.object:get_pos()
pos.y = pos.y + 1 pos.y = pos.y + 1
local pos2 = vec_add(pos, vec_multi(yaw2dir(yaw), width + 5)) local pos2 = vec_add(pos, vec_multi(yaw2dir(yaw), width + 5))
for x = -width, width, width / math.ceil(width) do for x = -width, width, width / ceil(width) do
for y = 0, height, height / math.ceil(height) do for y = 0, height, height / ceil(height) do
local vec1 = { local vec1 = {
x = math.cos(yaw) * ((pos.x + x) - pos.x) + pos.x, x = cos(yaw) * ((pos.x + x) - pos.x) + pos.x,
y = pos.y + y, y = pos.y + y,
z = math.sin(yaw) * ((pos.x + x) - pos.x) + pos.z z = sin(yaw) * ((pos.x + x) - pos.x) + pos.z
} }
local vec2 = { local vec2 = {
x = math.cos(yaw) * ((pos2.x + x) - pos2.x) + pos2.x, x = cos(yaw) * ((pos2.x + x) - pos2.x) + pos2.x,
y = vec1.y, y = vec1.y,
z = math.sin(yaw) * ((pos2.x + x) - pos2.x) + pos2.z z = sin(yaw) * ((pos2.x + x) - pos2.x) + pos2.z
} }
local ray = minetest.raycast(vec1, vec2, false, true) local ray = minetest.raycast(vec1, vec2, false, true)
for pointed_thing in ray do for pointed_thing in ray do
if pointed_thing if pointed_thing
and pointed_thing.type == "node" then and pointed_thing.type == "node" then
return true, pointed_thing.intersection_point return true, pointed_thing.intersection_point
end end
end end
end end
end end
return false return false
end end
------------- -------------
@ -94,59 +89,58 @@ end
-- Walk -- Walk
function creatura.action_walk(self, pos2, timeout, method, speed_factor, anim) function creatura.action_walk(self, pos2, timeout, method, speed_factor, anim)
local timer = timeout or 4 local timer = timeout or 4
local move_init = false local move_init = false
local function func(self) local function func(_self)
if not pos2 if not pos2
or (move_init or (move_init
and not self._movement_data.goal) then return true end and not _self._movement_data.goal) then return true end
local pos = self.object:get_pos() local pos = _self.object:get_pos()
timer = timer - self.dtime timer = timer - _self.dtime
if timer <= 0 if timer <= 0
or self:pos_in_box({x = pos2.x, y = pos.y + 0.1, z = pos2.z}) then or _self:pos_in_box({x = pos2.x, y = pos.y + 0.1, z = pos2.z}) then
self:halt() _self:halt()
return true return true
end end
self:move(pos2, method or "creatura:neighbors", speed_factor or 0.5, anim) _self:move(pos2, method or "creatura:neighbors", speed_factor or 0.5, anim)
move_init = true move_init = true
end end
self:set_action(func) self:set_action(func)
end end
function creatura.action_fly(self, pos2, timeout, method, speed_factor, anim) function creatura.action_fly(self, pos2, timeout, method, speed_factor, anim)
local timer = timeout or 4 local timer = timeout or 4
local move_init = false local move_init = false
local function func(self) local function func(_self)
if not pos2 if not pos2
or (move_init or (move_init
and not self._movement_data.goal) then return true end and not _self._movement_data.goal) then return true end
local pos = self.object:get_pos() timer = timer - _self.dtime
timer = timer - self.dtime if timer <= 0
if timer <= 0 or _self:pos_in_box(pos2) then
or self:pos_in_box(pos2) then _self:halt()
self:halt() return true
return true end
end _self:move(pos2, method, speed_factor or 0.5, anim)
self:move(pos2, method, speed_factor or 0.5, anim) move_init = true
move_init = true end
end self:set_action(func)
self:set_action(func)
end end
-- Idle -- Idle
function creatura.action_idle(self, time, anim) function creatura.action_idle(self, time, anim)
local timer = time local timer = time
local function func(self) local function func(_self)
self:set_gravity(-9.8) _self:set_gravity(-9.8)
self:halt() _self:halt()
self:animate(anim or "stand") _self:animate(anim or "stand")
timer = timer - self.dtime timer = timer - _self.dtime
if timer <= 0 then if timer <= 0 then
return true return true
end end
end end
self:set_action(func) self:set_action(func)
end end
-- Rotate on Z axis in random direction until 90 degree angle is reached -- Rotate on Z axis in random direction until 90 degree angle is reached
@ -154,22 +148,22 @@ end
function creatura.action_fallover(self) function creatura.action_fallover(self)
local zrot = 0 local zrot = 0
local init = false local init = false
local dir = 1 local dir = 1
local function func(self) local function func(_self)
if not init then if not init then
self:animate("stand") _self:animate("stand")
if random(2) < 2 then if random(2) < 2 then
dir = -1 dir = -1
end end
init = true init = true
end end
local rot = self.object:get_rotation() local rot = _self.object:get_rotation()
local goal = (pi * 0.5) * dir local goal = (pi * 0.5) * dir
local dif = abs(rot.z - goal) local dif = abs(rot.z - goal)
zrot = rot.z + (dif * dir) * 0.15 zrot = rot.z + (dif * dir) * 0.15
self.object:set_rotation({x = rot.x, y = rot.y, z = zrot}) _self.object:set_rotation({x = rot.x, y = rot.y, z = zrot})
if (dir > 0 and zrot >= goal) 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 end
self:set_action(func) self:set_action(func)
end end
@ -181,210 +175,208 @@ end
-- Pathfinding -- Pathfinding
creatura.register_movement_method("creatura:pathfind", function(self, pos2) creatura.register_movement_method("creatura:pathfind", function(self, pos2)
-- Movement Data -- Movement Data
local pos = self.object:get_pos() local pos = self.object:get_pos()
local movement_data = self._movement_data local movement_data = self._movement_data
local waypoint = movement_data.waypoint local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5 local speed = movement_data.speed or 5
local path = self._path local path = self._path
if not path or #path < 2 then if not path or #path < 2 then
if get_collision(self, dir2yaw(vec_dir(pos, pos2))) 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 {} self._path = creatura.find_path(self, pos, pos2, self.width, self.height, 200) or {}
end end
else else
waypoint = self._path[2] waypoint = self._path[2]
if self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then 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 -- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
table.remove(self._path, 1) table.remove(self._path, 1)
end end
end end
if not waypoint if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then
waypoint = creatura.get_next_move(self, pos2) waypoint = creatura.get_next_move(self, pos2)
self._movement_data.waypoint = waypoint self._movement_data.waypoint = waypoint
end end
-- Turning -- Turning
local dir2waypoint = vec_dir(pos, pos2) local dir2waypoint = vec_dir(pos, pos2)
if waypoint then if waypoint then
dir2waypoint = vec_dir(pos, waypoint) dir2waypoint = vec_dir(pos, waypoint)
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint) local tgt_yaw = dir2yaw(dir2waypoint)
local turn_rate = abs(self.turn_rate or 5) local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw)) local yaw_diff = abs(diff(yaw, tgt_yaw))
-- Moving -- Moving
self:set_gravity(-9.8) self:set_gravity(-9.8)
if yaw_diff < pi * (turn_rate * 0.1) then if yaw_diff < pi * (turn_rate * 0.1) then
self:set_forward_velocity(speed) self:set_forward_velocity(speed)
else else
self:set_forward_velocity(speed * 0.5) self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5 turn_rate = turn_rate * 1.5
end end
self:animate(movement_data.anim or "walk") self:animate(movement_data.anim or "walk")
self:turn_to(tgt_yaw, turn_rate) self:turn_to(tgt_yaw, turn_rate)
if self:pos_in_box(pos2) if self:pos_in_box(pos2)
or (waypoint or (waypoint
and not self:is_pos_safe(waypoint)) then and not self:is_pos_safe(waypoint)) then
self:halt() self:halt()
end end
end) end)
creatura.register_movement_method("creatura:theta_pathfind", function(self, pos2) creatura.register_movement_method("creatura:theta_pathfind", function(self, pos2)
-- Movement Data -- Movement Data
local pos = self.object:get_pos() local pos = self.object:get_pos()
local movement_data = self._movement_data local movement_data = self._movement_data
local waypoint = movement_data.waypoint local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5 local speed = movement_data.speed or 5
local path = self._path local path = self._path
if not path or #path < 1 then if not path or #path < 1 then
self._path = creatura.find_theta_path(self, pos, pos2, self.width, self.height, 300) or {} self._path = creatura.find_theta_path(self, pos, pos2, self.width, self.height, 300) or {}
else else
waypoint = self._path[2] or self._path[1] 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 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 -- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
table.remove(self._path, 1) table.remove(self._path, 1)
end end
end end
if not waypoint if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}) then
waypoint = creatura.get_next_move(self, pos2) waypoint = creatura.get_next_move(self, pos2)
self._movement_data.waypoint = waypoint self._movement_data.waypoint = waypoint
end end
-- Turning -- Turning
local dir2waypoint = vec_dir(pos, pos2) local dir2waypoint = vec_dir(pos, pos2)
if waypoint then if waypoint then
dir2waypoint = vec_dir(pos, waypoint) dir2waypoint = vec_dir(pos, waypoint)
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint) local tgt_yaw = dir2yaw(dir2waypoint)
local turn_rate = abs(self.turn_rate or 5) local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw)) local yaw_diff = abs(diff(yaw, tgt_yaw))
-- Moving -- Moving
self:set_gravity(-9.8) self:set_gravity(-9.8)
if yaw_diff < pi * (turn_rate * 0.1) then if yaw_diff < pi * (turn_rate * 0.1) then
self:set_forward_velocity(speed) self:set_forward_velocity(speed)
else else
self:set_forward_velocity(speed * 0.5) self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5 turn_rate = turn_rate * 1.5
end end
self:animate(movement_data.anim or "walk") self:animate(movement_data.anim or "walk")
self:turn_to(tgt_yaw, turn_rate) self:turn_to(tgt_yaw, turn_rate)
if self:pos_in_box(pos2) if self:pos_in_box(pos2)
or (waypoint or (waypoint
and not self:is_pos_safe(waypoint)) then and not self:is_pos_safe(waypoint)) then
self:halt() self:halt()
end end
end) end)
-- Neighbors -- Neighbors
creatura.register_movement_method("creatura:neighbors", function(self, pos2) creatura.register_movement_method("creatura:neighbors", function(self, pos2)
-- Movement Data -- Movement Data
local pos = self.object:get_pos() local pos = self.object:get_pos()
local movement_data = self._movement_data local movement_data = self._movement_data
local waypoint = movement_data.waypoint local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5 local speed = movement_data.speed or 5
if not waypoint if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, clamp(self.width, 0.5, 1)) then or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, clamp(self.width, 0.5, 1)) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes -- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
waypoint = creatura.get_next_move(self, pos2) waypoint = creatura.get_next_move(self, pos2)
self._movement_data.waypoint = waypoint self._movement_data.waypoint = waypoint
end end
-- Turning -- Turning
local dir2waypoint = vec_dir(pos, pos2) local dir2waypoint = vec_dir(pos, pos2)
if waypoint then if waypoint then
dir2waypoint = vec_dir(pos, waypoint) dir2waypoint = vec_dir(pos, waypoint)
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint) local tgt_yaw = dir2yaw(dir2waypoint)
local turn_rate = abs(self.turn_rate or 5) local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw)) local yaw_diff = abs(diff(yaw, tgt_yaw))
-- Moving -- Moving
self:set_gravity(-9.8) self:set_gravity(-9.8)
if yaw_diff < pi * 0.25 then if yaw_diff < pi * 0.25 then
self:set_forward_velocity(speed) self:set_forward_velocity(speed)
else else
self:set_forward_velocity(speed * 0.5) self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5 turn_rate = turn_rate * 1.5
end end
self:animate(movement_data.anim or "walk") self:animate(movement_data.anim or "walk")
self:turn_to(tgt_yaw, turn_rate) self:turn_to(tgt_yaw, turn_rate)
if self:pos_in_box(pos2) if self:pos_in_box(pos2)
or (waypoint or (waypoint
and not self:is_pos_safe(waypoint)) then and not self:is_pos_safe(waypoint)) then
self:halt() self:halt()
end end
end) end)
-- Obstacle Avoidance -- Obstacle Avoidance
local function get_obstacle_avoidance(self, goal) local function get_obstacle_avoidance(self, goal)
local width = self.width local width = self.width
local height = self.height local height = self.height
local pos = self.object:get_pos() local pos = self.object:get_pos()
pos.y = pos.y + 1 pos.y = pos.y + 1
local yaw2goal = dir2yaw(vec_dir(pos, goal)) 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 collide, col_pos = get_collision(self, yaw2goal) if not collide then return end
local avd_pos local avd_pos
if collide then for i = 45, 180, 45 do
for i = 45, 180, 45 do local angle = rad(i)
angle = rad(i) local dir = vec_multi(yaw2dir(yaw2goal + angle), width)
dir = vec_multi(yaw2dir(yaw2goal + angle), width) avd_pos = vec_center(vec_add(pos, dir))
avd_pos = vec_center(vec_add(pos, dir)) if not get_collision(self, yaw2goal) then
if not get_collision(self, yaw2goal) then break
break end
end angle = -rad(i)
angle = -rad(i) dir = vec_multi(yaw2dir(yaw2goal + angle), width)
dir = vec_multi(yaw2dir(yaw2goal + angle), width) avd_pos = vec_center(vec_add(pos, dir))
avd_pos = vec_center(vec_add(pos, dir)) if not get_collision(self, yaw2goal) then
if not get_collision(self, yaw2goal) then break
break end
end end
end if col_pos.y - (pos.y + height * 0.5) > 1 then
if col_pos.y - (pos.y + height * 0.5) > 1 then avd_pos.y = avd_pos.y - 3
avd_pos.y = avd_pos.y - 3 elseif (pos.y + height * 0.5) - col_pos.y > 1 then
elseif (pos.y + height * 0.5) - col_pos.y > 1 then avd_pos.y = avd_pos.y + 3
avd_pos.y = avd_pos.y + 3 end
end return avd_pos
end
return avd_pos
end end
creatura.register_movement_method("creatura:obstacle_avoidance", function(self, pos2) creatura.register_movement_method("creatura:obstacle_avoidance", function(self, pos2)
-- Movement Data -- Movement Data
local pos = self.object:get_pos() local pos = self.object:get_pos()
local movement_data = self._movement_data local movement_data = self._movement_data
local waypoint = movement_data.waypoint local waypoint = movement_data.waypoint
local speed = movement_data.speed or 5 local speed = movement_data.speed or 5
if not waypoint if not waypoint
or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, clamp(self.width, 0.5, 1)) then or self:pos_in_box({x = waypoint.x, y = pos.y + self.height * 0.5, z = waypoint.z}, clamp(self.width, 0.5, 1)) then
-- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes -- Waypoint Y axis is raised to avoid large mobs spinning over downward slopes
waypoint = get_obstacle_avoidance(self, pos2) waypoint = get_obstacle_avoidance(self, pos2)
self._movement_data.waypoint = waypoint self._movement_data.waypoint = waypoint
end end
-- Turning -- Turning
local dir2waypoint = vec_dir(pos, pos2) local dir2waypoint = vec_dir(pos, pos2)
if waypoint then if waypoint then
dir2waypoint = vec_dir(pos, waypoint) dir2waypoint = vec_dir(pos, waypoint)
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local tgt_yaw = dir2yaw(dir2waypoint) local tgt_yaw = dir2yaw(dir2waypoint)
local turn_rate = abs(self.turn_rate or 5) local turn_rate = abs(self.turn_rate or 5)
local yaw_diff = abs(diff(yaw, tgt_yaw)) local yaw_diff = abs(diff(yaw, tgt_yaw))
-- Moving -- Moving
self:set_gravity(-9.8) self:set_gravity(-9.8)
if yaw_diff < pi * 0.25 then if yaw_diff < pi * 0.25 then
self:set_forward_velocity(speed) self:set_forward_velocity(speed)
else else
self:set_forward_velocity(speed * 0.5) self:set_forward_velocity(speed * 0.5)
turn_rate = turn_rate * 1.5 turn_rate = turn_rate * 1.5
end end
self:animate(movement_data.anim or "walk") self:animate(movement_data.anim or "walk")
self:turn_to(tgt_yaw, turn_rate) self:turn_to(tgt_yaw, turn_rate)
if self:pos_in_box(pos2) if self:pos_in_box(pos2)
or (waypoint or (waypoint
and not self:is_pos_safe(waypoint)) then and not self:is_pos_safe(waypoint)) then
self:halt() self:halt()
end end
end) end)

View file

@ -15,12 +15,7 @@ local cos = math.cos
local atan2 = math.atan2 local atan2 = math.atan2
local function diff(a, b) -- Get difference between 2 angles 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 round(n, dec)
local x = 10^(dec or 0)
return math.floor(n * x + 0.5) / x
end end
local vec_dir = vector.direction local vec_dir = vector.direction
@ -50,89 +45,13 @@ end
-- Local Utilities -- -- 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) local function is_value_in_table(tbl, val)
for _, v in pairs(tbl) do for _, v in pairs(tbl) do
if v == val then if v == val then
return true return true
end end
end end
return false return false
end end
------------------------- -------------------------
@ -204,7 +123,7 @@ end
function mob:indicate_damage() function mob:indicate_damage()
self._original_texture_mod = self._original_texture_mod or self.object:get_texture_mod() 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") 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 if creatura.is_alive(self) then
self.object:set_texture_mod(self._original_texture_mod) self.object:set_texture_mod(self._original_texture_mod)
end end
@ -265,8 +184,8 @@ end
-- Sets Velocity to desired speed in mobs current look direction -- Sets Velocity to desired speed in mobs current look direction
function mob:set_forward_velocity(speed) function mob:set_forward_velocity(_speed)
local speed = speed or self._movement_data.speed local speed = _speed or self._movement_data.speed
local dir = minetest.yaw_to_dir(self.object:get_yaw()) local dir = minetest.yaw_to_dir(self.object:get_yaw())
local vel = vec_multi(dir, speed) local vel = vec_multi(dir, speed)
vel.y = self.object:get_velocity().y vel.y = self.object:get_velocity().y
@ -367,12 +286,11 @@ end
function mob:get_wander_pos(min_range, max_range, dir) function mob:get_wander_pos(min_range, max_range, dir)
local pos = vec_center(self.object:get_pos()) local pos = vec_center(self.object:get_pos())
pos.y = floor(pos.y + 0.5) pos.y = floor(pos.y + 0.5)
local node = minetest.get_node(pos) if creatura.get_node_def(pos).walkable then -- Occurs if small mob is touching a fence
if get_node_def(node.name).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)) local offset = vector.add(pos, vec_multi(vec_dir(pos, self.object:get_pos()), 1.5))
pos.x = floor(offset.x + 0.5) pos.x = floor(offset.x + 0.5)
pos.z = floor(offset.z + 0.5) pos.z = floor(offset.z + 0.5)
pos = get_ground_level(pos, 1) pos = creatura.get_ground_level(pos, 1)
end end
local width = self.width local width = self.width
local outset = random(min_range, max_range) 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 z = random(-10, 10) * 0.1
}) })
local pos2 = vec_add(pos, vec_multi(move_dir, width)) 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 and not dir then
for i = 1, 3 do for _ = 1, 3 do
move_dir = { move_dir = {
x = move_dir.z, x = move_dir.z,
y = 0, y = 0,
z = move_dir.x * -1 z = move_dir.x * -1
} }
pos2 = vec_add(pos, vec_multi(move_dir, width)) 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 break
end end
end end
@ -401,14 +319,12 @@ function mob:get_wander_pos(min_range, max_range, dir)
end end
for i = 1, outset do for i = 1, outset do
local a_pos = vec_add(pos2, vec_multi(move_dir, i)) 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_pos = {x = a_pos.x, y = a_pos.y - 1, z = a_pos.z}
local b_node = minetest.get_node(b_pos) if creatura.get_node_def(a_pos).walkable
if get_node_def(a_node.name).walkable or not creatura.get_node_def(b_pos).walkable then
or not get_node_def(b_node.name).walkable then a_pos = creatura.get_ground_level(a_pos, floor(self.stepheight or 1))
a_pos = get_ground_level(a_pos, floor(self.stepheight or 1))
end end
if not get_node_def(a_node.name).walkable then if not creatura.get_node_def(a_pos).walkable then
pos2 = a_pos pos2 = a_pos
else else
break break
@ -419,12 +335,11 @@ end
function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias) function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias)
local pos = vec_center(self.object:get_pos()) local pos = vec_center(self.object:get_pos())
local node = minetest.get_node(pos) if creatura.get_node_def(pos).walkable then -- Occurs if small mob is touching a fence
if get_node_def(node.name).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)) local offset = vector.add(pos, vec_multi(vec_dir(pos, self.object:get_pos()), 1.5))
pos.x = floor(offset.x + 0.5) pos.x = floor(offset.x + 0.5)
pos.z = floor(offset.z + 0.5) pos.z = floor(offset.z + 0.5)
pos = get_ground_level(pos, 1) pos = creatura.get_ground_level(pos, 1)
end end
local width = self.width local width = self.width
local outset = random(min_range, max_range) 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 z = random(-10, 10) * 0.1
}) })
local pos2 = vec_add(pos, vec_multi(move_dir, width)) 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 and not dir then
for i = 1, 3 do for _ = 1, 3 do
move_dir = { move_dir = {
x = move_dir.z, x = move_dir.z,
y = move_dir.y, y = move_dir.y,
z = move_dir.x * -1 z = move_dir.x * -1
} }
pos2 = vec_add(pos, vec_multi(move_dir, width)) 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 break
end end
end end
@ -453,11 +368,10 @@ function mob:get_wander_pos_3d(min_range, max_range, dir, vert_bias)
end end
for i = 1, outset do for i = 1, outset do
local a_pos = vec_add(pos2, vec_multi(move_dir, i)) local a_pos = vec_add(pos2, vec_multi(move_dir, i))
local a_node = minetest.get_node(a_pos) if creatura.get_node_def(a_pos).walkable then
if get_node_def(a_node.name).walkable then a_pos = creatura.get_ground_level(a_pos, floor(self.stepheight or 1))
a_pos = get_ground_level(a_pos, floor(self.stepheight or 1))
end end
if not get_node_def(a_node.name).walkable then if not creatura.get_node_def(a_pos).walkable then
pos2 = a_pos pos2 = a_pos
else else
break break
@ -471,8 +385,8 @@ function mob:is_pos_safe(pos)
local node = minetest.get_node(pos) local node = minetest.get_node(pos)
if not node then return false end if not node then return false end
if minetest.get_item_group(node.name, "igniter") > 0 if minetest.get_item_group(node.name, "igniter") > 0
or get_node_def(node.name).drawtype == "liquid" or creatura.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(vec_raise(pos, -1)).drawtype == "liquid" then return false end
local fall_safe = false local fall_safe = false
if self.max_fall ~= 0 then if self.max_fall ~= 0 then
for i = 1, self.max_fall or 3 do 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, y = floor(mob_pos.y + 0.5) - i,
z = pos.z 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 fall_safe = true
break break
end end
@ -809,9 +723,9 @@ function mob:activate(staticdata, dtime)
if self.timer if self.timer
and type(self.timer) == "number" then -- fix crash for converted mobs_redo mobs and type(self.timer) == "number" then -- fix crash for converted mobs_redo mobs
self.timer = function(self, n) self.timer = function(_self, n)
local t1 = floor(self.active_time) local t1 = floor(_self.active_time)
local t2 = floor(self.active_time + self.dtime) local t2 = floor(_self.active_time + _self.dtime)
if t2 > t1 and t2%n == 0 then return true end if t2 > t1 and t2%n == 0 then return true end
end end
end end
@ -830,7 +744,6 @@ function mob:staticdata()
end end
function mob:on_step(dtime, moveresult) function mob:on_step(dtime, moveresult)
--local us_time = minetest.get_us_time()
if not self.hp then return end if not self.hp then return end
self.dtime = dtime or 0.09 self.dtime = dtime or 0.09
self.moveresult = moveresult or {} self.moveresult = moveresult or {}
@ -851,11 +764,12 @@ function mob:on_step(dtime, moveresult)
self.width = self:get_hitbox()[4] or 0.5 self.width = self:get_hitbox()[4] or 0.5
self.height = self:get_height() or 1 self.height = self:get_height() or 1
end end
self:_light_physics() --local us_time = minetest.get_us_time()
-- Movement Control -- Movement Control
if self._move then if self._move then
self:_move() self:_move()
end end
--minetest.chat_send_all(minetest.get_us_time() - us_time)
if self.utility_stack if self.utility_stack
and self._execute_utilities then and self._execute_utilities then
self:_execute_utilities() self:_execute_utilities()
@ -912,7 +826,9 @@ local function do_step(self)
and abs(vel.x + vel.z) > 0 then and abs(vel.x + vel.z) > 0 then
local border = self._border local border = self._border
local yaw_offset = vec_add(pos, vec_multi(minetest.yaw_to_dir(self.object:get_yaw()), self.width + 0.7)) 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 step_pos = vec_center(vec_add(pos, border[1]))
local halfway = vec_add(pos, vec_multi(vec_dir(pos, step_pos), 0.5)) local halfway = vec_add(pos, vec_multi(vec_dir(pos, step_pos), 0.5))
halfway.y = step_pos.y halfway.y = step_pos.y
@ -923,7 +839,6 @@ local function do_step(self)
end end
end end
else else
local vel = self.object:get_velocity()
self.object:set_velocity(vector.new(vel.x, 7, vel.z)) self.object:set_velocity(vector.new(vel.x, 7, vel.z))
if self._step.y < pos.y - 0.5 then if self._step.y < pos.y - 0.5 then
self.object:set_velocity(vector.new(vel.x, 0.5, vel.z)) 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 width = self.width + 0.25
local objects = minetest.get_objects_in_area(vec_sub(pos, width), vec_add(pos, width)) local objects = minetest.get_objects_in_area(vec_sub(pos, width), vec_add(pos, width))
if #objects < 2 then return end if #objects < 2 then return end
local col_no = 0
for i = 2, #objects do for i = 2, #objects do
local object = objects[i] local object = objects[i]
if creatura.is_alive(object) if creatura.is_alive(object)
@ -975,7 +889,6 @@ local function water_physics(self)
floor_pos.y = floor_pos.y + 0.01 floor_pos.y = floor_pos.y + 0.01
local surface_pos = floor_pos local surface_pos = floor_pos
local floor_node = minetest.get_node(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 if minetest.get_item_group(floor_node.name, "liquid") < 1 then
self.object:set_acceleration({ self.object:set_acceleration({
x = 0, x = 0,
@ -1012,7 +925,7 @@ local function water_physics(self)
}) })
local hydrodynamics = self.hydrodynamics_multiplier or 0.7 local hydrodynamics = self.hydrodynamics_multiplier or 0.7
local vel_y = vel.y 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 vel_y = vel.y * hydrodynamics
end end
self.object:set_velocity({ self.object:set_velocity({
@ -1084,7 +997,6 @@ end
function mob:_execute_actions() function mob:_execute_actions()
if not self.object then return end if not self.object then return end
local task = self._task
if #self._task > 0 then if #self._task > 0 then
local func = self._task[#self._task].func local func = self._task[#self._task].func
if func(self) then if func(self) then
@ -1128,7 +1040,7 @@ function mob:_execute_utilities()
func = nil, func = nil,
score = 0 score = 0
} }
if (self:timer(self.task_timer or 1) if (self:timer(self.util_timer or 1)
or not self._utility_data.func) or not self._utility_data.func)
and is_alive then and is_alive then
for i = 1, #self.utility_stack do for i = 1, #self.utility_stack do
@ -1163,7 +1075,8 @@ function mob:_execute_utilities()
self._utility_data = loop_data self._utility_data = loop_data
else else
local no_data = not self._utility_data.utility and not self._utility_data.args 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 if no_data
or new_util then -- if utilities are different or utilities are the same and args are different set new 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 self._utility_data = loop_data
@ -1218,9 +1131,10 @@ function mob:_vitals()
end end
if self:timer(1) then if self:timer(1) then
local head_pos = vec_raise(stand_pos, self.height) 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" 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 if self._breath <= 0 then
self:hurt(1) self:hurt(1)
self:indicate_damage() self:indicate_damage()
@ -1232,8 +1146,9 @@ function mob:_vitals()
self:memorize("_breath", self._breath) self:memorize("_breath", self._breath)
end end
end end
local stand_def = get_node_def(minetest.get_node(stand_pos).name) local stand_node = minetest.get_node(stand_pos)
if minetest.get_item_group(minetest.get_node(stand_pos).name, "fire") > 0 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 and stand_def.damage_per_second then
local damage = stand_def.damage_per_second local damage = stand_def.damage_per_second
local resist = self.fire_resistance or 0.5 local resist = self.fire_resistance or 0.5

File diff suppressed because it is too large Load diff

View file

@ -350,4 +350,4 @@ minetest.register_abm({
end end
end end
end, end,
})]] })]]