mirror of
https://github.com/ElCeejo/animalia.git
synced 2025-03-16 04:41:28 +00:00
1663 lines
No EOL
55 KiB
Lua
1663 lines
No EOL
55 KiB
Lua
---------------
|
|
-- Behaviors --
|
|
---------------
|
|
|
|
-- Math --
|
|
|
|
local abs = math.abs
|
|
local random = math.random
|
|
local ceil = math.ceil
|
|
local floor = math.floor
|
|
local rad = math.rad
|
|
|
|
local function average(t)
|
|
local sum = 0
|
|
for _,v in pairs(t) do -- Get the sum of all numbers in t
|
|
sum = sum + v
|
|
end
|
|
return sum / #t
|
|
end
|
|
|
|
local function clamp(val, min, max)
|
|
if val < min then
|
|
val = min
|
|
elseif max < val then
|
|
val = max
|
|
end
|
|
return val
|
|
end
|
|
|
|
-- Vector Math --
|
|
|
|
local vec_dist = vector.distance
|
|
local vec_dir = vector.direction
|
|
local vec_sub = vector.subtract
|
|
local vec_add = vector.add
|
|
local vec_multi = vector.multiply
|
|
local vec_normal = vector.normalize
|
|
|
|
local function vec_raise(v, n)
|
|
return {x = v.x, y = v.y + n, z = v.z}
|
|
end
|
|
|
|
local yaw2dir = minetest.yaw_to_dir
|
|
local dir2yaw = minetest.dir_to_yaw
|
|
|
|
--------------
|
|
-- Settings --
|
|
--------------
|
|
|
|
------------
|
|
-- Tables --
|
|
------------
|
|
|
|
local is_flyable = {}
|
|
local is_liquid = {}
|
|
local is_solid = {}
|
|
|
|
minetest.register_on_mods_loaded(function()
|
|
for name in pairs(minetest.registered_nodes) do
|
|
if name ~= "air" and name ~= "ignore" then
|
|
if minetest.registered_nodes[name].walkable
|
|
or minetest.registered_nodes[name].drawtype == "liquid" then
|
|
is_flyable[name] = true
|
|
if minetest.registered_nodes[name].walkable then
|
|
is_solid[name] = true
|
|
else
|
|
is_liquid[name] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
---------------------
|
|
-- Local Utilities --
|
|
---------------------
|
|
|
|
local moveable = creatura.is_pos_moveable
|
|
local fast_ray_sight = creatura.fast_ray_sight
|
|
|
|
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 = is_solid[node_under.name] and not is_solid[node.name]
|
|
if walkable then
|
|
return pos2
|
|
elseif not walkable then
|
|
if not is_solid[node_under.name] then
|
|
while not is_solid[node_under.name]
|
|
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 is_solid[node.name]
|
|
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_ceiling_positions(pos, range)
|
|
local walkable = minetest.find_nodes_in_area(
|
|
{x = pos.x + range, y = pos.y + range, z = pos.z + range},
|
|
{x = pos.x - range, y = pos.y, z = pos.z - range},
|
|
animalia.walkable_nodes
|
|
)
|
|
if #walkable < 1 then return {} end
|
|
local output = {}
|
|
for i = 1, #walkable do
|
|
local i_pos = walkable[i]
|
|
local under = {
|
|
x = i_pos.x,
|
|
y = i_pos.y - 1,
|
|
z = i_pos.z
|
|
}
|
|
if minetest.get_node(under).name == "air"
|
|
and is_solid[minetest.get_node(i_pos).name] then
|
|
table.insert(output, i_pos)
|
|
end
|
|
end
|
|
return output
|
|
end
|
|
|
|
local function get_obstacle_avoidance(self, lift)
|
|
local pos = self.object:get_pos()
|
|
local yaw = self.object:get_yaw()
|
|
pos.y = pos.y + self.stepheight
|
|
local vel = self.object:get_velocity()
|
|
local vel_len = abs(vector.length(vel))
|
|
if vel_len < 1.5 then
|
|
vel_len = 1.5
|
|
end
|
|
local dir = yaw2dir(yaw)
|
|
dir.y = lift
|
|
local outset = vec_add(pos, vec_multi(dir, vel_len))
|
|
local pos2
|
|
local obstacle = false
|
|
if not fast_ray_sight(pos, outset) then
|
|
pos2 = vec_add(pos, vec_multi(dir, -vel_len))
|
|
obstacle = true
|
|
end
|
|
return pos2, obstacle
|
|
end
|
|
|
|
local function get_wander_pos_3d(self, range)
|
|
local outset = random(range or 4)
|
|
local pos = self.object:get_pos()
|
|
local move_dir = {
|
|
x = random(-10, 10) * 0.1,
|
|
z = random(-10, 10) * 0.1,
|
|
y = random(-10, 10) * 0.1
|
|
}
|
|
local pos2 = vec_add(pos, vec_multi(vec_normal(move_dir), random(1, outset)))
|
|
local sight, dist = fast_ray_sight(pos, pos2, true)
|
|
if not sight then
|
|
pos2 = vec_add(pos, vec_multi(vec_normal(move_dir), dist - 0.5))
|
|
end
|
|
return pos2
|
|
end
|
|
|
|
local function get_boid_members(pos, radius, name, texture_no)
|
|
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
|
|
and object:get_luaentity().texture_no == texture_no then
|
|
object:get_luaentity().boid_heading = rad(random(360))
|
|
table.insert(members, object)
|
|
end
|
|
end
|
|
return members
|
|
end
|
|
|
|
----------------------
|
|
-- Movement Methods --
|
|
----------------------
|
|
|
|
local function movement_fly(self, pos2)
|
|
-- Initial Properties
|
|
local pos = self.object:get_pos()
|
|
local turn_rate = self.turn_rate or 10
|
|
local speed = self.speed or 2
|
|
self:animate("fly")
|
|
self:set_gravity(0)
|
|
-- Collision Avoidance
|
|
local temp_goal = self._movement_data.temp_goal
|
|
local obstacle = self._movement_data.obstacle or false
|
|
if not temp_goal
|
|
or self:pos_in_box(temp_goal, self.width) then
|
|
self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y)
|
|
end
|
|
local neighbor = self._movement_data.temp_goal
|
|
-- Calculate Movement
|
|
local dir = vector.direction(pos, pos2)
|
|
local tyaw = minetest.dir_to_yaw(dir)
|
|
if neighbor then
|
|
local lift = dir.y
|
|
dir = vector.direction(pos, neighbor)
|
|
if not obstacle then
|
|
dir.y = lift
|
|
end
|
|
tyaw = minetest.dir_to_yaw(dir)
|
|
end
|
|
if self._path
|
|
and #self._path > 1 then
|
|
neighbor = self._path[2]
|
|
dir = vector.direction(pos, neighbor)
|
|
tyaw = minetest.dir_to_yaw(dir)
|
|
if self:pos_in_box(neighbor, self.width + 0.2) then
|
|
table.remove(self._path, 1)
|
|
end
|
|
else
|
|
self._path = creatura.find_path(self, pos, pos2, self.width, self.height, 300, false, true)
|
|
end
|
|
-- Apply Movement
|
|
self:turn_to(tyaw, turn_rate)
|
|
self:set_forward_velocity(speed)
|
|
local v_speed = speed * dir.y
|
|
local vel = self.object:get_velocity()
|
|
vel.y = vel.y + (v_speed - vel.y) * 0.2
|
|
self:set_vertical_velocity(vel.y)
|
|
if self:pos_in_box(pos2) then
|
|
self:halt()
|
|
end
|
|
end
|
|
|
|
creatura.register_movement_method("animalia:fly_path", movement_fly)
|
|
|
|
local function movement_fly_waypoints(self, pos2, speed)
|
|
-- Initial Properties
|
|
local pos = self.object:get_pos()
|
|
self:animate("fly")
|
|
self:set_gravity(0)
|
|
-- Collision Avoidance
|
|
local temp_goal = self._movement_data.temp_goal
|
|
local obstacle = self._movement_data.obstacle or false
|
|
if not temp_goal
|
|
or self:pos_in_box(temp_goal, 0.4) then
|
|
self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y)
|
|
end
|
|
local neighbor = self._movement_data.temp_goal
|
|
-- Calculate Movement
|
|
local dir = vector.direction(pos, pos2)
|
|
local tyaw = minetest.dir_to_yaw(dir)
|
|
local turn_rate = self.turn_rate or 10
|
|
local speed = self.speed or 2
|
|
if neighbor then
|
|
local lift = dir.y
|
|
dir = vector.direction(pos, neighbor)
|
|
if not obstacle then
|
|
dir.y = lift
|
|
end
|
|
tyaw = minetest.dir_to_yaw(dir)
|
|
end
|
|
-- Apply Movement
|
|
self:turn_to(boid_angle or tyaw, turn_rate)
|
|
self:set_forward_velocity(speed)
|
|
local v_speed = speed * dir.y
|
|
local vel = self.object:get_velocity()
|
|
vel.y = vel.y + (v_speed - vel.y) * 0.2
|
|
self:set_vertical_velocity(vel.y)
|
|
if self:pos_in_box(pos2) then
|
|
self:halt()
|
|
end
|
|
end
|
|
|
|
creatura.register_movement_method("animalia:fly_waypoints", movement_fly_waypoints)
|
|
|
|
-- Fly Obstacle Avoidance --
|
|
|
|
local function movement_fly_obstacle_avoidance(self, pos2, speed)
|
|
-- Initial Properties
|
|
local pos = self.object:get_pos()
|
|
local turn_rate = self.turn_rate or 10
|
|
local speed = self.speed or 2
|
|
self:animate("fly")
|
|
self:set_gravity(0)
|
|
-- Collision Avoidance
|
|
local temp_goal = self._movement_data.temp_goal
|
|
local obstacle = self._movement_data.obstacle or false
|
|
local timer = self._movement_data.timer
|
|
if not temp_goal
|
|
or self:pos_in_box(temp_goal, 0.4)
|
|
or (timer
|
|
and timer <= 0) then
|
|
self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y)
|
|
temp_goal = self._movement_data.temp_goal
|
|
obstacle = self._movement_data.obstacle or false
|
|
if temp_goal then
|
|
self._movement_data.timer = 3
|
|
end
|
|
end
|
|
if timer then
|
|
self._movement_data.timer = self._movement_data.timer - self.dtime
|
|
end
|
|
-- Calculate Movement
|
|
local dir = vector.direction(pos, pos2)
|
|
local tyaw = minetest.dir_to_yaw(dir)
|
|
if temp_goal
|
|
and obstacle then
|
|
dir = vector.direction(pos, temp_goal)
|
|
dir.y = vec_dir(pos, pos2).y
|
|
tyaw = minetest.dir_to_yaw(dir)
|
|
end
|
|
-- Apply Movement
|
|
self:turn_to(tyaw, turn_rate)
|
|
self:set_forward_velocity(speed)
|
|
local v_speed = (speed) * dir.y
|
|
local vel = self.object:get_velocity()
|
|
vel.y = vel.y + (v_speed - vel.y) * 0.2
|
|
self:set_vertical_velocity(vel.y)
|
|
if self:pos_in_box(pos2, 0.5) then
|
|
self:halt()
|
|
end
|
|
end
|
|
|
|
creatura.register_movement_method("animalia:fly_obstacle_avoidance", movement_fly_obstacle_avoidance)
|
|
|
|
-- Swimming --
|
|
|
|
local function movement_swim_obstacle_avoidance(self, pos2, speed)
|
|
-- Initial Properties
|
|
local pos = self.object:get_pos()
|
|
self:animate("swim")
|
|
self:set_gravity(0)
|
|
-- Collision Avoidance
|
|
local temp_goal = self._movement_data.temp_goal
|
|
local obstacle = self._movement_data.obstacle or false
|
|
local timer = self._movement_data.timer
|
|
if not temp_goal
|
|
or self:pos_in_box(temp_goal, 0.4)
|
|
or (timer
|
|
and timer <= 0) then
|
|
self._movement_data.temp_goal, self._movement_data.obstacle = get_obstacle_avoidance(self, vec_dir(pos, pos2).y)
|
|
temp_goal = self._movement_data.temp_goal
|
|
obstacle = self._movement_data.obstacle or false
|
|
if temp_goal then
|
|
self._movement_data.timer = vec_dist(pos, temp_goal) / self.speed
|
|
end
|
|
end
|
|
if timer then
|
|
self._movement_data.timer = self._movement_data.timer - self.dtime
|
|
end
|
|
-- Calculate Movement
|
|
local dir = vector.direction(pos, pos2)
|
|
local tyaw = minetest.dir_to_yaw(dir)
|
|
if temp_goal
|
|
and obstacle then
|
|
dir = vector.direction(pos, temp_goal)
|
|
tyaw = minetest.dir_to_yaw(dir)
|
|
end
|
|
-- Apply Movement
|
|
self:turn_to(tyaw, turn_rate)
|
|
self:set_forward_velocity(speed)
|
|
local v_speed = speed * dir.y
|
|
local vel = self.object:get_velocity()
|
|
vel.y = vel.y + (v_speed - vel.y) * 0.2
|
|
if not is_liquid[minetest.get_node(vec_raise(pos, 1)).name]
|
|
and vel.y > 0 then
|
|
vel.y = 0
|
|
end
|
|
self:set_vertical_velocity(vel.y)
|
|
if self:pos_in_box(pos2) then
|
|
self:halt()
|
|
end
|
|
end
|
|
|
|
creatura.register_movement_method("animalia:swim_obstacle_avoidance", movement_swim_obstacle_avoidance)
|
|
|
|
-------------
|
|
-- Actions --
|
|
-------------
|
|
|
|
function animalia.action_fall(self)
|
|
local function func(self)
|
|
self:animate("fall")
|
|
self:set_gravity(-1)
|
|
local vel = self.object:get_velocity()
|
|
if vel.y < -3.8 then
|
|
self:set_vertical_velocity(-0.1)
|
|
end
|
|
self._fall_start = nil
|
|
if self.touching_ground then
|
|
return true
|
|
end
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
function animalia.action_punch(self, target)
|
|
local function func(self)
|
|
if not creatura.is_alive(target) then
|
|
return true
|
|
end
|
|
local yaw = self.object:get_yaw()
|
|
local pos = self.object:get_pos()
|
|
local tpos = target:get_pos()
|
|
local dir = vector.direction(pos, tpos)
|
|
local tyaw = minetest.dir_to_yaw(dir)
|
|
self:turn_to(tyaw)
|
|
if self.touching_ground then
|
|
self:animate("leap")
|
|
local jump_vel = vec_multi(dir, self.speed)
|
|
jump_vel.y = 3
|
|
self.object:add_velocity(jump_vel)
|
|
end
|
|
if vec_dist(pos, tpos) < 2 then
|
|
self:punch_target(target)
|
|
return true
|
|
end
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
function animalia.action_latch_to_ceil(self, time, anim)
|
|
local timer = time
|
|
local function func(self)
|
|
self:halt()
|
|
self:set_forward_velocity(0)
|
|
self:set_vertical_velocity(9)
|
|
self:set_gravity(3)
|
|
self:animate(anim or "latch")
|
|
timer = timer - self.dtime
|
|
if timer <= 0 then
|
|
return true
|
|
end
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
function animalia.action_boid_move(self, pos2, timeout, method)
|
|
local boids = get_boid_members(self.object:get_pos(), 6, self.name, self.texture_no)
|
|
local timer = timeout
|
|
local goal = pos2
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
timer = timer - self.dtime
|
|
if #boids > 2 then
|
|
local boid_angle, boid_lift = creatura.get_boid_angle(self, boids, 6)
|
|
if boid_angle then
|
|
local dir2goal = vec_dir(pos, pos2)
|
|
local yaw2goal = minetest.dir_to_yaw(dir2goal)
|
|
boid_angle = boid_angle + (yaw2goal - boid_angle) * 0.15
|
|
local boid_dir = minetest.yaw_to_dir(boid_angle)
|
|
if boid_lift then
|
|
boid_dir.y = boid_lift + (vec_dir(pos, goal).y - boid_lift) * 0.5
|
|
else
|
|
boid_dir.y = vec_dir(pos, goal).y
|
|
end
|
|
pos2 = vec_add(pos, vec_multi(boid_dir, 4))
|
|
end
|
|
end
|
|
if timer <= 0
|
|
or self:pos_in_box(pos2, 0.25) then
|
|
self:halt()
|
|
return true
|
|
end
|
|
self:move(pos2, method or "animalia:fly_obstacle_avoidance", 1)
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
function animalia.action_boid_walk(self, pos2, timeout, method, speed_factor, anim)
|
|
local boids = creatura.get_boid_members(self.object:get_pos(), 12, self.name)
|
|
local timer = timeout
|
|
local move_init = false
|
|
local goal = pos2
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
timer = timer - self.dtime
|
|
if #boids > 2 then
|
|
local boid_angle = creatura.get_boid_angle(self, boids, 12)
|
|
if boid_angle then
|
|
local dir2goal = vec_dir(pos, goal)
|
|
local yaw2goal = minetest.dir_to_yaw(dir2goal)
|
|
boid_angle = boid_angle + (yaw2goal - boid_angle) * 0.15
|
|
local boid_dir = minetest.yaw_to_dir(boid_angle)
|
|
pos2 = get_ground_level(vec_add(pos, vec_multi(boid_dir, 4)), 2)
|
|
end
|
|
end
|
|
if not pos2
|
|
or (move_init
|
|
and not self._movement_data.goal) then
|
|
return true
|
|
end
|
|
if timer <= 0
|
|
or self:pos_in_box({x = goal.x, y = pos.y + 0.1, z = goal.z})
|
|
or vec_dist(pos, goal) < 1 then
|
|
self:halt()
|
|
return true
|
|
end
|
|
self:move(pos2, method or "creatura:obstacle_avoidance", speed_factor or 1, anim or "walk")
|
|
move_init = true
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
function animalia.action_swim(self, pos, timeout, method, speed_factor, anim)
|
|
local timer = timeout or 4
|
|
local function func(self)
|
|
timer = timer - self.dtime
|
|
if timer <= 0
|
|
or self:pos_in_box(pos) then
|
|
self:halt()
|
|
self:set_gravity(0)
|
|
return true
|
|
end
|
|
self:move(pos, method or "animalia:swim_obstacle_avoidance", speed_factor or 0.5, anim)
|
|
self:set_gravity(0)
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
function animalia.action_horse_spin(self, speed, anim)
|
|
local tyaw = random(math.pi * 2)
|
|
local function func(self)
|
|
self:set_gravity(-9.8)
|
|
self:halt()
|
|
self:animate(anim or "stand")
|
|
self:turn_to(tyaw, speed)
|
|
if abs(tyaw - self.object:get_yaw()) < 0.1 then
|
|
return true
|
|
end
|
|
end
|
|
self:set_action(func)
|
|
end
|
|
|
|
---------------
|
|
-- Behaviors --
|
|
---------------
|
|
|
|
------------------------
|
|
-- Register Utilities --
|
|
------------------------
|
|
|
|
-- Wander
|
|
|
|
creatura.register_utility("animalia:wander", function(self, group)
|
|
local idle_time = 3
|
|
local move_probability = 5
|
|
local far_from_group = false
|
|
local group_tick = 1
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
if not self:get_action() then
|
|
local goal
|
|
local move = random(move_probability) < 2
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
goal = self.lasso_pos
|
|
end
|
|
if not goal
|
|
and move then
|
|
goal = self:get_wander_pos(1, 2)
|
|
end
|
|
if group
|
|
and goal
|
|
and group_tick > 3 then
|
|
local range = self.tracking_range * 0.5
|
|
local group_positions = animalia.get_group_positions(self.name, pos, range + 1)
|
|
if #group_positions > 2 then
|
|
local center = animalia.get_average_pos(group_positions)
|
|
if center
|
|
and vec_dist(pos, center) > range * 0.33
|
|
or vec_dist(goal, center) > range * 0.33 then
|
|
goal = center
|
|
far_from_group = true
|
|
else
|
|
far_from_group = false
|
|
end
|
|
end
|
|
group_tick = 0
|
|
end
|
|
if (move
|
|
and goal)
|
|
or far_from_group then
|
|
creatura.action_walk(self, goal, 2, "creatura:neighbors")
|
|
else
|
|
creatura.action_idle(self, idle_time)
|
|
end
|
|
if group then
|
|
group_tick = group_tick + 1
|
|
end
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:skittish_wander", function(self)
|
|
local idle_time = 3
|
|
local move_probability = 3
|
|
local force_move = false
|
|
local avoid_tick = 1
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
if not self:get_action() then
|
|
local goal
|
|
local move = random(move_probability) < 2
|
|
if avoid_tick > 3
|
|
and move then
|
|
local range = self.tracking_range * 0.5
|
|
local player = creatura.get_nearby_player(self)
|
|
if player then
|
|
local target_alive, line_of_sight, player_pos = self:get_target(player)
|
|
if target_alive
|
|
and line_of_sight
|
|
and vec_dist(pos, player_pos) < 8 then
|
|
force_move = true
|
|
local dir = vec_dir(player_pos, pos)
|
|
goal = self:get_wander_pos(2, 3, dir)
|
|
end
|
|
end
|
|
avoid_tick = 0
|
|
end
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
goal = self.lasso_pos
|
|
end
|
|
if not goal
|
|
and move then
|
|
goal = self:get_wander_pos(4, 4)
|
|
end
|
|
if move
|
|
and goal then
|
|
creatura.action_walk(self, goal, 3, "creatura:neighbors", 0.35)
|
|
else
|
|
creatura.action_idle(self, idle_time)
|
|
end
|
|
avoid_tick = avoid_tick + 1
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:skittish_boid_wander", function(self)
|
|
local idle_time = 3
|
|
local move_probability = 3
|
|
local group_tick = 0
|
|
local force_move = false
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
local goal
|
|
if self:timer(3) then
|
|
local range = self.tracking_range * 0.5
|
|
local group_positions = animalia.get_group_positions(self.name, pos, range + 1)
|
|
if #group_positions > 2 then
|
|
local center = animalia.get_average_pos(group_positions)
|
|
if center
|
|
and vec_dist(pos, center) > range then
|
|
goal = center
|
|
force_move = true
|
|
else
|
|
force_move = false
|
|
end
|
|
else
|
|
force_move = false
|
|
end
|
|
group_tick = 2
|
|
local player = creatura.get_nearby_player(self)
|
|
if player then
|
|
local target_alive, line_of_sight, player_pos = self:get_target(player)
|
|
if target_alive
|
|
and line_of_sight
|
|
and vec_dist(pos, player_pos) < 8 then
|
|
force_move = true
|
|
local dir = vec_dir(player_pos, pos)
|
|
goal = self:get_wander_pos(2, 3, dir)
|
|
end
|
|
end
|
|
end
|
|
if not self:get_action() then
|
|
local move = random(move_probability) < 2
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
goal = self.lasso_pos
|
|
end
|
|
if not goal
|
|
and move then
|
|
goal = self:get_wander_pos(4, 4)
|
|
end
|
|
if move
|
|
and goal then
|
|
animalia.action_boid_walk(self, goal, 6, "creatura:neighbors", 0.35)
|
|
else
|
|
creatura.action_idle(self, idle_time)
|
|
end
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:boid_wander", function(self, group)
|
|
local idle_time = 3
|
|
local move_probability = 5
|
|
local group_tick = 1
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
if not self:get_action() then
|
|
local goal
|
|
local move = random(move_probability) < 2
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
goal = self.lasso_pos
|
|
end
|
|
if not goal
|
|
and move then
|
|
goal = self:get_wander_pos(1, 2)
|
|
end
|
|
if group
|
|
and goal
|
|
and group_tick > 3 then
|
|
local range = self.tracking_range * 0.5
|
|
local group_positions = animalia.get_group_positions(self.name, pos, range + 1)
|
|
if #group_positions > 2 then
|
|
local center = animalia.get_average_pos(group_positions)
|
|
if center
|
|
and vec_dist(pos, center) > range * 0.33
|
|
or vec_dist(goal, center) > range * 0.33 then
|
|
goal = center
|
|
far_from_group = true
|
|
else
|
|
far_from_group = false
|
|
end
|
|
end
|
|
group_tick = 0
|
|
end
|
|
if (move
|
|
or far_from_group)
|
|
and goal then
|
|
animalia.action_boid_walk(self, goal, 6, "creatura:neighbors", 0.35)
|
|
else
|
|
creatura.action_idle(self, idle_time)
|
|
end
|
|
group_tick = group_tick + 1
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:wander_water_surface", function(self)
|
|
local idle_time = 3
|
|
local move_probability = 3
|
|
local function func(self)
|
|
if not self.in_liquid then return true end
|
|
local pos = self.object:get_pos()
|
|
local random_goal = get_wander_pos_3d(self, 2)
|
|
if not self:get_action() then
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
random_goal = self.lasso_pos
|
|
end
|
|
if random(move_probability) < 2
|
|
and random_goal then
|
|
animalia.action_swim(self, random_goal)
|
|
else
|
|
creatura.action_idle(self, idle_time, "float")
|
|
end
|
|
end
|
|
self:set_gravity(0)
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- "Eat" nodes
|
|
|
|
creatura.register_utility("animalia:eat_from_turf", function(self)
|
|
local action_init = false
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
local look_dir = yaw2dir(self.object:get_yaw())
|
|
local under = vec_add(pos, vec_multi(look_dir, self.width))
|
|
under.y = pos.y - 0.5
|
|
if not action_init then
|
|
for i, node in ipairs(self.consumable_nodes) do
|
|
if node.name == minetest.get_node(under).name then
|
|
minetest.set_node(under, {name = node.replacement})
|
|
local def = minetest.registered_nodes[node.name]
|
|
local texture = def.tiles[1]
|
|
texture = texture .. "^[resize:8x8"
|
|
minetest.add_particlespawner({
|
|
amount = 6,
|
|
time = 0.1,
|
|
minpos = vector.new(
|
|
pos.x - 0.5,
|
|
pos.y + 0.1,
|
|
pos.z - 0.5
|
|
),
|
|
maxpos = vector.new(
|
|
pos.x + 0.5,
|
|
pos.y + 0.1,
|
|
pos.z + 0.5
|
|
),
|
|
minvel = {x=-1, y=1, z=-1},
|
|
maxvel = {x=1, y=2, z=1},
|
|
minacc = {x=0, y=-5, z=0},
|
|
maxacc = {x=0, y=-9, z=0},
|
|
minexptime = 1,
|
|
maxexptime = 1,
|
|
minsize = 1,
|
|
maxsize = 2,
|
|
collisiondetection = true,
|
|
vertical = false,
|
|
texture = texture,
|
|
})
|
|
self.gotten = false
|
|
self:memorize("gotten", self.gotten)
|
|
if not self:get_action() then
|
|
creatura.action_idle(self, 1, "eat")
|
|
action_init = true
|
|
end
|
|
break
|
|
elseif i == #self.consumable_nodes then
|
|
return true
|
|
end
|
|
end
|
|
elseif not self:get_action() then
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:eat_bug_nodes", function(self)
|
|
local timer = 0.2
|
|
local pos = self.object:get_pos()
|
|
local food = minetest.find_nodes_in_area(vec_sub(pos, 1.5), vec_add(pos, 1.5), self.follow)
|
|
local function func(self)
|
|
pos = self.object:get_pos()
|
|
if food[1] then
|
|
local dist = vector.distance(pos, food[1])
|
|
local dir = vec_dir(pos, food[1])
|
|
local frame = floor(dist * 10)
|
|
self:turn_to(dir2yaw(dir))
|
|
if frame < 15
|
|
and frame > 1 then
|
|
animalia.move_head(self, dir2yaw(dir), dir.y)
|
|
creatura.action_idle(self, 0.1, "tongue_" .. frame)
|
|
timer = timer - self.dtime
|
|
elseif not self:get_action() then
|
|
local pos2 = vec_add(food[1], vec_multi(vec_normal(vec_dir(food[1], pos)), 0.25))
|
|
creatura.action_walk(self, pos2)
|
|
end
|
|
else
|
|
return true
|
|
end
|
|
if timer <= 0
|
|
and food[1] then
|
|
minetest.remove_node(food[1])
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Escape Water
|
|
|
|
creatura.register_utility("animalia:swim_to_land", function(self)
|
|
local init = false
|
|
local tpos = nil
|
|
local function func(self)
|
|
if not init then
|
|
for i = 1, 359, 15 do
|
|
local yaw = rad(i)
|
|
local dir = yaw2dir(yaw)
|
|
tpos = animalia.find_collision(self, dir)
|
|
if tpos then
|
|
local node = minetest.get_node({x = tpos.x, y = tpos.y + 1, z = tpos.z})
|
|
if node.name == "air" then
|
|
break
|
|
else
|
|
tpos = nil
|
|
end
|
|
end
|
|
end
|
|
init = true
|
|
end
|
|
if tpos then
|
|
local pos = self.object:get_pos()
|
|
local yaw = self.object:get_yaw()
|
|
local tyaw = minetest.dir_to_yaw(vec_dir(pos, tpos))
|
|
if abs(tyaw - yaw) > 0.1 then
|
|
self:turn_to(tyaw, 12)
|
|
end
|
|
self:set_gravity(-9.8)
|
|
self:set_forward_velocity(self.speed * 0.66)
|
|
self:animate("walk")
|
|
if vector.distance(pos, tpos) < 1
|
|
or (not self.in_liquid
|
|
and self.touching_ground) then
|
|
return true
|
|
end
|
|
else
|
|
self.liquid_recovery_cooldown = 5
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:flop", function(self)
|
|
local function func(self)
|
|
if self.in_liquid then
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
creatura.action_idle(self, 0.1, "flop")
|
|
end
|
|
self:set_vertical_velocity(0)
|
|
self:set_gravity(-9.8)
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Player Interaction
|
|
|
|
creatura.register_utility("animalia:flee_from_player", function(self, player, range)
|
|
range = range or self.tracking_range
|
|
local function func(self)
|
|
local target_alive, line_of_sight, tpos = self:get_target(player)
|
|
if not target_alive then return true end
|
|
local pos = self.object:get_pos()
|
|
local dir = vec_dir(pos, tpos)
|
|
local escape_pos = vec_add(pos, vec_multi(vec_add(dir, {x = random(-10, 10) * 0.1, y = 0, z = random(-10, 10) * 0.1}), -3))
|
|
if not self:get_action() then
|
|
escape_pos = get_ground_level(escape_pos, 1)
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
escape_pos = self.lasso_pos
|
|
end
|
|
creatura.action_walk(self, escape_pos, 2, "creatura:obstacle_avoidance", 1, "run")
|
|
end
|
|
if vec_dist(pos, tpos) > range then
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:boid_flee_from_player", function(self, player, group)
|
|
local mobs_in_group = animalia.get_group(self)
|
|
if group then
|
|
if #mobs_in_group > 0 then
|
|
for i = 1, #mobs_in_group do
|
|
local mob = mobs_in_group[i]
|
|
mob:get_luaentity():initiate_utility("animalia:boid_flee_from_player", mob:get_luaentity(), player)
|
|
mob:get_luaentity():set_utility_score(1)
|
|
end
|
|
end
|
|
end
|
|
local function func(self)
|
|
local target_alive, line_of_sight, tpos = self:get_target(player)
|
|
if not target_alive then return true end
|
|
local pos = self.object:get_pos()
|
|
local dir = vec_dir(pos, tpos)
|
|
local escape_pos = vec_add(pos, vec_multi(vec_add(dir, {x = random(-10, 10) * 0.1, y = 0, z = random(-10, 10) * 0.1}), -3))
|
|
if not self:get_action() then
|
|
escape_pos = get_ground_level(escape_pos, 1)
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
escape_pos = self.lasso_pos
|
|
end
|
|
animalia.action_boid_walk(self, escape_pos, 6, "creatura:obstacle_avoidance", 1, "run")
|
|
end
|
|
if vec_dist(pos, tpos) > self.tracking_range + (#mobs_in_group or 0) then
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:flee_to_water", function(self)
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
local water = minetest.find_nodes_in_area_under_air(vec_sub(pos, 3), vec_add(pos, 3), {"default:water_source"})
|
|
if water[1]
|
|
and vec_dist(pos, water[1]) < 0.5 then
|
|
return true
|
|
end
|
|
if water[1]
|
|
and not self:get_action() then
|
|
creatura.action_walk(self, water[1])
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:follow_player", function(self, player, force)
|
|
local function func(self)
|
|
local player_alive, line_of_sight, tpos = self:get_target(player)
|
|
-- Return if player is dead, not holding food, or behavior isn't forced
|
|
if not player_alive
|
|
or (not self:follow_wielded_item(player)
|
|
and not force) then
|
|
return true
|
|
end
|
|
local pos = self.object:get_pos()
|
|
local dist = vec_dist(pos, tpos)
|
|
if dist > self.tracking_range then
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
if dist > self:get_hitbox(self)[4] + 1.5 then
|
|
creatura.action_walk(self, tpos, 6, "creatura:pathfind")
|
|
else
|
|
creatura.action_idle(self, 0.1, "stand")
|
|
end
|
|
end
|
|
self.head_tracking = player
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:sporadic_flee", function(self)
|
|
local timer = 18
|
|
self:clear_action()
|
|
if group then
|
|
local mobs_in_group = animalia.get_group(self)
|
|
if #mobs_in_group > 0 then
|
|
for i = 1, #mobs_in_group do
|
|
local mob = mobs_in_group[i]
|
|
animalia.bh_flee(mob:get_luaentity())
|
|
end
|
|
end
|
|
end
|
|
local function func(self)
|
|
local pos = self.object:get_pos()
|
|
local random_goal = {
|
|
x = pos.x + random(-4, 4),
|
|
y = pos.y,
|
|
z = pos.z + random(-4, 4)
|
|
}
|
|
if not self:get_action() then
|
|
random_goal = get_ground_level(random_goal, 1)
|
|
local node = minetest.get_node(random_goal)
|
|
if minetest.registered_nodes[node.name].drawtype == "liquid"
|
|
or minetest.registered_nodes[node.name].walkable then
|
|
return
|
|
end
|
|
if self.lasso_pos
|
|
and vec_dist(pos, self.lasso_pos) > 10 then
|
|
random_goal = self.lasso_pos
|
|
end
|
|
self._movement_data.speed = self.speed * 1.5
|
|
creatura.action_walk(self, random_goal, 4, "creatura:obstacle_avoidance", 1.5)
|
|
end
|
|
timer = timer - self.dtime
|
|
if timer <= 0 then
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Mob Interaction
|
|
|
|
creatura.register_utility("animalia:mammal_breed", function(self)
|
|
local mate = animalia.get_nearby_mate(self, self.name)
|
|
if not mate then self.breeding = false return end
|
|
local breeding_time = 0
|
|
local function func(self)
|
|
if not creatura.is_alive(mate) then
|
|
return true
|
|
end
|
|
local pos = self:get_center_pos()
|
|
local tpos = mate:get_pos()
|
|
local dist = vec_dist(pos, tpos) - abs(self:get_hitbox(self)[4])
|
|
if dist < 1.75 then
|
|
breeding_time = breeding_time + self.dtime
|
|
end
|
|
if breeding_time >= 2 then
|
|
if self.gender == "female" then
|
|
for i = 1, self.birth_count or 1 do
|
|
local object = minetest.add_entity(pos, self.name)
|
|
local ent = object:get_luaentity()
|
|
ent.growth_scale = 0.7
|
|
animalia.initialize_api(ent)
|
|
animalia.protect_from_despawn(ent)
|
|
end
|
|
end
|
|
self.breeding = false
|
|
self.breeding_cooldown = 300
|
|
self:memorize("breeding", self.breeding)
|
|
self:memorize("breeding_time", self.breeding_time)
|
|
self:memorize("breeding_cooldown", self.breeding_cooldown)
|
|
local minp = vector.subtract(pos, 1)
|
|
local maxp = vec_add(pos, 1)
|
|
animalia.particle_spawner(pos, "heart.png", "float", minp, maxp)
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
creatura.action_walk(self, tpos)
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:horse_breed", function(self)
|
|
local mate = animalia.get_nearby_mate(self, self.name)
|
|
if not mate then self.breeding = false return end
|
|
local breeding_time = 0
|
|
local function func(self)
|
|
if not creatura.is_alive(mate) then
|
|
return true
|
|
end
|
|
local pos = self:get_center_pos()
|
|
local tpos = mate:get_pos()
|
|
local dist = vec_dist(pos, tpos) - abs(self:get_hitbox(self)[4])
|
|
if dist < 1.75 then
|
|
breeding_time = breeding_time + self.dtime
|
|
end
|
|
if breeding_time >= 2 then
|
|
if self.gender == "female" then
|
|
local object = minetest.add_entity(pos, self.name)
|
|
object:get_luaentity().growth_scale = 0.7
|
|
local ent = object:get_luaentity()
|
|
local tex_no = self.texture_no
|
|
if random(2) < 2 then
|
|
tex_no = mate:get_luaentity().texture_no
|
|
end
|
|
ent:memorize("texture_no", tex_no)
|
|
ent:memorize("speed", random(mate:get_luaentity().speed, self.speed))
|
|
ent:memorize("jump_power", random(mate:get_luaentity().jump_power, self.jump_power))
|
|
ent:memorize("max_hp", random(mate:get_luaentity().max_hp, self.max_hp))
|
|
ent.speed = ent:recall("speed")
|
|
ent.jump_power = ent:recall("jump_power")
|
|
ent.max_hp = ent:recall("max_hp")
|
|
animalia.initialize_api(ent)
|
|
animalia.protect_from_despawn(ent)
|
|
end
|
|
self.breeding = false
|
|
self.breeding_cooldown = 300
|
|
self:memorize("breeding", self.breeding)
|
|
self:memorize("breeding_time", self.breeding_time)
|
|
self:memorize("breeding_cooldown", self.breeding_cooldown)
|
|
local minp = vector.subtract(pos, 1)
|
|
local maxp = vec_add(pos, 1)
|
|
animalia.particle_spawner(pos, "heart.png", "float", minp, maxp)
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
creatura.action_walk(self, tpos)
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:bird_breed", function(self)
|
|
local mate = animalia.get_nearby_mate(self, self.name)
|
|
if not mate then self.breeding = false return end
|
|
local breeding_time = 0
|
|
local function func(self)
|
|
if not creatura.is_alive(mate) then
|
|
return true
|
|
end
|
|
local pos = self:get_center_pos()
|
|
local tpos = mate:get_pos()
|
|
local dist = vec_dist(pos, tpos) - abs(self:get_hitbox(self)[4])
|
|
if dist < 1.75 then
|
|
breeding_time = breeding_time + self.dtime
|
|
end
|
|
if breeding_time >= 2 then
|
|
if self.gender == "female" then
|
|
minetest.add_particlespawner({
|
|
amount = 6,
|
|
time = 0.25,
|
|
minpos = {x = pos.x - 7/16, y = pos.y - 5/16, z = pos.z - 7/16},
|
|
maxpos = {x = pos.x + 7/16, y = pos.y - 5/16, z = pos.z + 7/16},
|
|
minvel = vector.new(-1, 2, -1),
|
|
maxvel = vector.new(1, 5, 1),
|
|
minacc = vector.new(0, -9.81, 0),
|
|
maxacc = vector.new(0, -9.81, 0),
|
|
collisiondetection = true,
|
|
texture = "animalia_egg_fragment.png",
|
|
})
|
|
for i = 1, self.birth_count or 1 do
|
|
local object = minetest.add_entity(pos, self.name)
|
|
local ent = object:get_luaentity()
|
|
ent.growth_scale = 0.7
|
|
animalia.initialize_api(ent)
|
|
animalia.protect_from_despawn(ent)
|
|
end
|
|
end
|
|
self.breeding = false
|
|
self.breeding_cooldown = 300
|
|
self:memorize("breeding", self.breeding)
|
|
self:memorize("breeding_time", self.breeding_time)
|
|
self:memorize("breeding_cooldown", self.breeding_cooldown)
|
|
local minp = vector.subtract(pos, 1)
|
|
local maxp = vec_add(pos, 1)
|
|
animalia.particle_spawner(pos, "heart.png", "float", minp, maxp)
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
creatura.action_walk(self, tpos)
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:attack", function(self, target, group)
|
|
local punch_init = false
|
|
if group then
|
|
local allies = creatura.get_nearby_entities(self, self.name)
|
|
if #allies > 0 then
|
|
for i = 1, #allies do
|
|
allies[i]:get_luaentity():initiate_utility("animalia:attack", allies[i]:get_luaentity(), target)
|
|
allies[i]:get_luaentity():set_utility_score(1)
|
|
end
|
|
end
|
|
end
|
|
local function func(self)
|
|
local target_alive, line_of_sight, tpos = self:get_target(target)
|
|
if not target_alive then
|
|
return true
|
|
end
|
|
local pos = self.object:get_pos()
|
|
local dist = vec_dist(pos, tpos)
|
|
if not self:get_action() then
|
|
if punch_init then return true end
|
|
--if dist > self:get_hitbox(self)[4] then
|
|
creatura.action_walk(self, tpos, 6, "creatura:theta_pathfind", 1)
|
|
--end
|
|
end
|
|
if dist <= self:get_hitbox(self)[4] + 1
|
|
and not punch_init then
|
|
animalia.action_punch(self, target)
|
|
punch_init = true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Flight
|
|
|
|
creatura.register_utility("animalia:aerial_flock", function(self, scale)
|
|
local range = ceil(8 * scale)
|
|
local function func(self)
|
|
if self:timer(2)
|
|
and self.stamina <= 0 then
|
|
local boids = creatura.get_boid_members(self.object:get_pos(), 6, self.name)
|
|
if #boids > 1 then
|
|
for i = 1, #boids do
|
|
local boid = boids[i]
|
|
local ent = boid:get_luaentity()
|
|
ent.stamina = ent:memorize("stamina", 0)
|
|
ent.is_landed = ent:memorize("is_landed", true)
|
|
end
|
|
end
|
|
end
|
|
local dist2floor = creatura.sensor_floor(self, 2, true)
|
|
local dist2ceil = creatura.sensor_ceil(self, 2, true)
|
|
if self.in_liquid then
|
|
dist2floor = 0
|
|
dist2ceil = 2
|
|
end
|
|
if dist2floor < 2
|
|
and dist2ceil < 2 then
|
|
self.is_landed = true
|
|
return true
|
|
end
|
|
if not self:get_action()
|
|
or (dist2floor < 2
|
|
or dist2ceil < 2) then
|
|
local pos = self.object:get_pos()
|
|
local pos2 = self:get_wander_pos_3d(1, range)
|
|
if dist2ceil < 2 then
|
|
pos2.y = pos.y - 1
|
|
end
|
|
if dist2floor < 2 then
|
|
pos2.y = pos.y + 1
|
|
end
|
|
if self.in_liquid then
|
|
pos2.y = pos.y + 2
|
|
end
|
|
animalia.action_boid_move(self, pos2, 2)
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:aerial_swarm", function(self, scale)
|
|
local function func(self)
|
|
if self:timer(2)
|
|
and self.stamina <= 0 then
|
|
local boids = creatura.get_boid_members(self.object:get_pos(), 6, self.name)
|
|
if #boids > 1 then
|
|
for i = 1, #boids do
|
|
local boid = boids[i]
|
|
local ent = boid:get_luaentity()
|
|
ent.stamina = ent:memorize("stamina", 0)
|
|
ent.is_landed = ent:memorize("is_landed", true)
|
|
end
|
|
end
|
|
end
|
|
local dist2floor = creatura.sensor_floor(self, 2, true)
|
|
local dist2ceil = creatura.sensor_ceil(self, 2, true)
|
|
if self.in_liquid then
|
|
dist2floor = 0
|
|
dist2ceil = 2
|
|
end
|
|
if not self:get_action()
|
|
or (dist2floor < 2
|
|
or dist2ceil < 2) then
|
|
local pos = self.object:get_pos()
|
|
local pos2 = self:get_wander_pos_3d(1, 3)
|
|
if dist2floor < 2 then
|
|
pos2.y = pos.y + 1
|
|
end
|
|
if dist2ceil < 2 then
|
|
pos2.y = pos.y - 1
|
|
end
|
|
animalia.action_boid_move(self, pos2, 2)
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:land", function(self, scale)
|
|
local function func(self)
|
|
if self.touching_ground then return true end
|
|
local _, node = creatura.sensor_floor(self, 3, true)
|
|
if node and is_liquid[node.name] then self.is_landed = false return true end
|
|
scale = scale or 1
|
|
local width = self.width
|
|
local pos = self.object:get_pos()
|
|
local pos2
|
|
if self:timer(1) then
|
|
local offset = random(2 * scale, 3 * scale)
|
|
if random(2) < 2 then
|
|
offset = offset * -1
|
|
end
|
|
pos2 = {
|
|
x = pos.x + offset,
|
|
y = pos.y,
|
|
z = pos.z + offset
|
|
}
|
|
pos2.y = pos2.y - (3 * scale)
|
|
end
|
|
if not self:get_action()
|
|
and pos2 then
|
|
self:animate("fly")
|
|
creatura.action_walk(self, pos2, 2, "animalia:fly_path", 1)
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Swimming
|
|
|
|
creatura.register_utility("animalia:schooling", function(self)
|
|
local pos = self.object:get_pos()
|
|
local water = minetest.find_nodes_in_area(vector.subtract(pos, 5), vector.add(pos, 5), {"group:water"})
|
|
local function func(self)
|
|
if not self:get_action() then
|
|
if #water < 1 then return true end
|
|
local iter = random(#water)
|
|
local pos2 = water[iter]
|
|
table.remove(water, iter)
|
|
animalia.action_boid_move(self, pos2, 2, "animalia:swim_obstacle_avoidance")
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Resist Fall
|
|
|
|
creatura.register_utility("animalia:resist_fall", function(self)
|
|
local function func(self)
|
|
if not self:get_action() then
|
|
animalia.action_fall(self)
|
|
end
|
|
if self.touching_ground
|
|
or self.in_liquid then
|
|
creatura.action_idle(self, "stand")
|
|
self:set_gravity(-9.8)
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Die
|
|
|
|
creatura.register_utility("animalia:die", function(self)
|
|
local timer = 1.5
|
|
local init = false
|
|
local function func(self)
|
|
if not init then
|
|
self:play_sound("death")
|
|
creatura.action_fallover(self)
|
|
init = true
|
|
end
|
|
timer = timer - self.dtime
|
|
if timer <= 0 then
|
|
local pos = self.object:get_pos()
|
|
minetest.add_particlespawner({
|
|
amount = 8,
|
|
time = 0.25,
|
|
minpos = {x = pos.x - 0.1, y = pos.y, z = pos.z - 0.1},
|
|
maxpos = {x = pos.x + 0.1, y = pos.y + 0.1, z = pos.z + 0.1},
|
|
minacc = {x = 0, y = 2, z = 0},
|
|
maxacc = {x = 0, y = 3, z = 0},
|
|
minvel = {x = random(-1, 1), y = -0.25, z = random(-1, 1)},
|
|
maxvel = {x = random(-2, 2), y = -0.25, z = random(-2, 2)},
|
|
minexptime = 0.75,
|
|
maxexptime = 1,
|
|
minsize = 4,
|
|
maxsize = 4,
|
|
texture = "creatura_smoke_particle.png",
|
|
animation = {
|
|
type = 'vertical_frames',
|
|
aspect_w = 4,
|
|
aspect_h = 4,
|
|
length = 1,
|
|
},
|
|
glow = 1
|
|
})
|
|
creatura.drop_items(self)
|
|
self.object:remove()
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Cat Exclusive Behaviors
|
|
|
|
creatura.register_utility("animalia:find_and_break_glass_vessels", function(self)
|
|
local timer = 12
|
|
local pos = self.object:get_pos()
|
|
local pos2 = nil
|
|
local nodes = minetest.find_nodes_in_area(
|
|
vector.subtract(pos, 8),
|
|
vec_add(pos, 8),
|
|
{"vessels:glass_bottle", "vessels:drinking_glass"}
|
|
)
|
|
if #nodes > 0 then
|
|
pos2 = nodes[1]
|
|
end
|
|
local func = function(self)
|
|
if not pos2 then
|
|
return
|
|
end
|
|
pos = self.object:get_pos()
|
|
if not self:get_action() then
|
|
creatura.action_walk(self, pos2, 6, "pathfind")
|
|
end
|
|
if vector.distance(pos, pos2) <= 0.5 then
|
|
creatura.action_idle(self, 0.7, "smack")
|
|
minetest.remove_node(pos2)
|
|
minetest.add_item(pos2, "vessels:glass_fragments")
|
|
if minetest.get_node(pos2).name == "air" then
|
|
return true
|
|
end
|
|
end
|
|
timer = timer - self.dtime
|
|
if timer < 0 then return true end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:walk_ahead_of_player", function(self, player)
|
|
if not player then return end
|
|
local timer = 8
|
|
local func = function(self)
|
|
if not creatura.is_alive(player) then
|
|
return true
|
|
end
|
|
local pos = self.object:get_pos()
|
|
local tpos = player:get_pos()
|
|
local dir = player:get_look_dir()
|
|
tpos.x = tpos.x + dir.x
|
|
tpos.z = tpos.z + dir.z
|
|
self.status = self:memorize("status", "following")
|
|
local dist = vec_dist(pos, tpos)
|
|
if dist > self.view_range then
|
|
self.status = self:memorize("status", "")
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
if vec_dist(pos, tpos) > self.width + 0.5 then
|
|
creatura.action_walk(self, tpos, 6, "pathfind", 0.75)
|
|
else
|
|
creatura.action_idle(self, 0.1, "stand")
|
|
end
|
|
end
|
|
timer = timer - self.dtime
|
|
if timer < 0 then self.status = self:memorize("status", "") return true end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Bat Exclusive Behaviors
|
|
|
|
creatura.register_utility("animalia:return_to_home", function(self)
|
|
local init = false
|
|
local tpos = nil
|
|
local function func(self)
|
|
if not self.home_position then return true end
|
|
local pos = self.object:get_pos()
|
|
local pos2 = self.home_position
|
|
if not self:get_action() then
|
|
creatura.action_walk(self, vec_raise(pos2, -1), 6, "animalia:fly_path", 1)
|
|
end
|
|
local dist = vec_dist(pos, pos2)
|
|
if dist < 2 then
|
|
if is_solid[minetest.get_node(vec_raise(pos, 1)).name] then
|
|
creatura.action_idle(self, 1, "latch")
|
|
self:set_gravity(9.8)
|
|
self.object:set_velocity({x = 0, y = 0, z = 0})
|
|
end
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:find_home", function(self)
|
|
local init = false
|
|
local tpos = nil
|
|
local pos = self.object:get_pos()
|
|
local range = self.tracking_range
|
|
local ceiling = get_ceiling_positions(pos, range / 2)
|
|
local iter = 1
|
|
local function func(self)
|
|
if not ceiling[1] then
|
|
return true
|
|
else
|
|
iter = random(#ceiling)
|
|
end
|
|
pos = self.object:get_pos()
|
|
if not self:get_action() then
|
|
local pos2 = get_wander_pos_3d(self, range)
|
|
local dist2floor = creatura.sensor_floor(self, 5, true)
|
|
local dist2ceil = creatura.sensor_ceil(self, 5, true)
|
|
if dist2floor < 4 then
|
|
pos2.y = pos.y + 2
|
|
elseif dist2ceil < 4 then
|
|
pos2.y = pos.y - 1
|
|
end
|
|
animalia.action_boid_move(self, pos2, 2)
|
|
end
|
|
if ceiling[iter] then
|
|
local pos2 = {
|
|
x = ceiling[iter].x,
|
|
y = ceiling[iter].y - 1,
|
|
z = ceiling[iter].z
|
|
}
|
|
local line_of_sight = fast_ray_sight(pos, pos2)
|
|
if line_of_sight then
|
|
self.home_position = self:memorize("home_position", ceiling[iter])
|
|
return true
|
|
end
|
|
end
|
|
if self:timer(1) then
|
|
iter = iter + 1
|
|
if iter > #ceiling then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Horse Exclusive Behaviors
|
|
|
|
creatura.register_utility("animalia:horse_breaking", function(self)
|
|
local timer = 18
|
|
self:clear_action()
|
|
local function func(self)
|
|
if not self:get_action() then
|
|
animalia.action_horse_spin(self, random(4, 6), "stand")
|
|
end
|
|
timer = timer - self.dtime
|
|
if timer <= 0 then
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
-- Tamed Animal Orders
|
|
|
|
creatura.register_utility("animalia:sit", function(self)
|
|
local function func(self)
|
|
if self.order ~= "sit" then
|
|
return true
|
|
end
|
|
if not self:get_action() then
|
|
creatura.action_idle(self, 0.1, "sit")
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end)
|
|
|
|
creatura.register_utility("animalia:mount", function(self, player)
|
|
local function func(self)
|
|
if not creatura.is_alive(player) then
|
|
return true
|
|
end
|
|
local anim = "stand"
|
|
local control = player:get_player_control()
|
|
local speed_factor = 0
|
|
local vel = self.object:get_velocity()
|
|
if control.up then
|
|
speed_factor = 1
|
|
if control.aux1 then
|
|
speed_factor = 1.5
|
|
end
|
|
end
|
|
if control.jump
|
|
and self.touching_ground then
|
|
self.object:add_velocity({
|
|
x = 0,
|
|
y = self.jump_power + (abs(self._movement_data.gravity) * 0.33),
|
|
z = 0
|
|
})
|
|
elseif not self.touching_ground then
|
|
speed_factor = speed_factor * 0.5
|
|
end
|
|
local total_speed = vector.length(vel)
|
|
if total_speed > 0.2 then
|
|
anim = "walk"
|
|
if control.aux1 then
|
|
anim = "run"
|
|
end
|
|
if not self.touching_ground
|
|
and not self.in_liquid
|
|
and vel.y > 0 then
|
|
anim = "rear_constant"
|
|
end
|
|
end
|
|
self:turn_to(player:get_look_horizontal())
|
|
self:set_forward_velocity(self.speed * speed_factor)
|
|
self:animate(anim)
|
|
if control.sneak
|
|
or not self.rider then
|
|
animalia.mount(self, player)
|
|
return true
|
|
end
|
|
end
|
|
self:set_utility(func)
|
|
end) |