2024-01-04 11:15:02 -08:00
|
|
|
------------
|
|
|
|
-- Mob AI --
|
|
|
|
------------
|
|
|
|
|
|
|
|
-- Math --
|
|
|
|
|
|
|
|
local abs = math.abs
|
|
|
|
local atan2 = math.atan2
|
|
|
|
local cos = math.cos
|
|
|
|
local min = math.min
|
|
|
|
local max = math.max
|
|
|
|
local floor = math.floor
|
|
|
|
local pi = math.pi
|
|
|
|
local pi2 = pi * 2
|
|
|
|
local sin = math.sin
|
|
|
|
local rad = math.rad
|
|
|
|
local random = math.random
|
|
|
|
|
|
|
|
local function diff(a, b) -- Get difference between 2 angles
|
|
|
|
return atan2(sin(b - a), cos(b - a))
|
|
|
|
end
|
|
|
|
|
|
|
|
local function clamp(val, minn, maxn)
|
|
|
|
if val < minn then
|
|
|
|
val = minn
|
|
|
|
elseif maxn < val then
|
|
|
|
val = maxn
|
|
|
|
end
|
|
|
|
return val
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Vector Math --
|
|
|
|
|
|
|
|
local vec_add, vec_dot, vec_dir, vec_dist, vec_multi, vec_normal,
|
|
|
|
vec_round, vec_sub = vector.add, vector.dot, vector.direction, vector.distance,
|
|
|
|
vector.multiply, vector.normalize, vector.round, vector.subtract
|
|
|
|
|
|
|
|
local dir2yaw = minetest.dir_to_yaw
|
|
|
|
local yaw2dir = minetest.yaw_to_dir
|
|
|
|
|
|
|
|
-----------------
|
|
|
|
-- Local Tools --
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
local farming_enabled = minetest.get_modpath("farming") and farming.registered_plants
|
|
|
|
|
|
|
|
if farming_enabled then
|
|
|
|
minetest.register_on_mods_loaded(function()
|
|
|
|
for name, def in pairs(minetest.registered_nodes) do
|
|
|
|
local item_string = name:sub(1, #name - 2)
|
|
|
|
local item_name = item_string:split(":")[2]
|
|
|
|
local growth_stage = tonumber(name:sub(-1)) or 1
|
|
|
|
if farming.registered_plants[item_string]
|
|
|
|
or farming.registered_plants[item_name] then
|
|
|
|
def.groups.crop = growth_stage
|
|
|
|
end
|
|
|
|
minetest.register_node(":" .. name, def)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
local animate_player = {}
|
|
|
|
|
|
|
|
if minetest.get_modpath("default")
|
|
|
|
and minetest.get_modpath("player_api") then
|
|
|
|
animate_player = player_api.set_animation
|
|
|
|
elseif minetest.get_modpath("mcl_player") then
|
|
|
|
animate_player = mcl_player.player_set_animation
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_group_positions(self)
|
|
|
|
local objects = creatura.get_nearby_objects(self, self.name)
|
|
|
|
local group = {}
|
|
|
|
for _, object in ipairs(objects) do
|
|
|
|
local obj_pos = object and object:get_pos()
|
|
|
|
if obj_pos then table.insert(group, obj_pos) end
|
|
|
|
end
|
|
|
|
return group
|
|
|
|
end
|
|
|
|
|
|
|
|
local function calc_altitude(self, pos2)
|
|
|
|
local height_half = self.height * 0.5
|
|
|
|
local center_y = pos2.y + height_half
|
|
|
|
local calc_pos = {x = pos2.x, y = center_y, z = pos2.z}
|
|
|
|
local range = (height_half + 2)
|
|
|
|
local offset = {x = 0, y = range, z = 0}
|
|
|
|
local ceil_pos, floor_pos = vec_add(calc_pos, offset), vec_sub(calc_pos, offset)
|
|
|
|
local ray_up = minetest.raycast(calc_pos, ceil_pos, false, true):next()
|
|
|
|
local ray_down = minetest.raycast(calc_pos, floor_pos, false, true):next()
|
|
|
|
ceil_pos = (ray_up and ray_up.above) or ceil_pos
|
|
|
|
floor_pos = (ray_down and ray_down.above) or floor_pos
|
|
|
|
|
|
|
|
local dist_up = ceil_pos.y - center_y
|
|
|
|
local dist_down = floor_pos.y - center_y
|
|
|
|
|
|
|
|
local altitude = (dist_up + dist_down) / 2
|
|
|
|
|
|
|
|
return ((calc_pos.y + altitude) - center_y) / range * 2
|
|
|
|
end
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
--[[local function calc_steering_and_lift(self, pos, pos2, dir, steer_method)
|
2024-01-04 11:15:02 -08:00
|
|
|
local steer_to = creatura.calc_steering(self, pos2, steer_method or creatura.get_context_small)
|
|
|
|
pos2 = vec_add(pos, steer_to)
|
|
|
|
local lift = creatura.get_avoidance_lift(self, pos2, 2)
|
|
|
|
steer_to.y = (lift ~= 0 and lift) or dir.y
|
|
|
|
return steer_to
|
|
|
|
end
|
|
|
|
|
|
|
|
local function calc_steering_and_lift_aquatic(self, pos, pos2, dir, steer_method)
|
|
|
|
local steer_to = creatura.calc_steering(self, pos2, steer_method or creatura.get_context_small_aquatic)
|
|
|
|
local lift = creatura.get_avoidance_lift_aquatic(self, vec_add(pos, steer_to), 2)
|
|
|
|
steer_to.y = (lift ~= 0 and lift) or dir.y
|
|
|
|
return steer_to
|
2024-01-10 17:30:54 -08:00
|
|
|
end]]
|
2024-01-04 11:15:02 -08:00
|
|
|
|
|
|
|
local function get_obstacle(pos, water)
|
|
|
|
local pos2 = {x = pos.x, y = pos.y, z = pos.z}
|
|
|
|
local n_def = creatura.get_node_def(pos2)
|
|
|
|
if n_def.walkable
|
|
|
|
or (water and (n_def.groups.liquid or 0) > 0) then
|
|
|
|
pos2.y = pos.y + 1
|
|
|
|
n_def = creatura.get_node_def(pos2)
|
|
|
|
local col_max = n_def.walkable or (water and (n_def.groups.liquid or 0) > 0)
|
|
|
|
pos2.y = pos.y - 1
|
|
|
|
local col_min = col_max and (n_def.walkable or (water and (n_def.groups.liquid or 0) > 0))
|
|
|
|
if col_min then
|
|
|
|
return pos
|
|
|
|
else
|
|
|
|
pos2.y = pos.y + 1
|
|
|
|
return pos2
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function animalia.get_steering_context(self, goal, steer_dir, interest, danger, range)
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
pos = vec_round(pos)
|
|
|
|
local width = self.width or 0.5
|
|
|
|
|
|
|
|
local check_pos = vec_add(pos, steer_dir)
|
|
|
|
local collision = get_obstacle(check_pos)
|
|
|
|
local unsafe_pos = not collision and not self:is_pos_safe(check_pos) and check_pos
|
|
|
|
|
|
|
|
if collision
|
|
|
|
or unsafe_pos then
|
|
|
|
local dir2goal = vec_normal(vec_dir(pos, goal))
|
|
|
|
local dir2col = vec_normal(vec_dir(pos, collision or unsafe_pos))
|
|
|
|
local dist2col = vec_dist(pos, collision or unsafe_pos) - width
|
|
|
|
local dot_score = vec_dot(dir2col, dir2goal)
|
|
|
|
local dist_score = (range - dist2col) / range
|
|
|
|
interest = interest - dot_score
|
|
|
|
danger = dist_score
|
|
|
|
end
|
|
|
|
return interest, danger
|
|
|
|
end
|
|
|
|
|
|
|
|
--------------
|
|
|
|
-- Movement --
|
|
|
|
--------------
|
|
|
|
|
|
|
|
-- Obstacle Avoidance
|
|
|
|
|
|
|
|
function animalia.obstacle_avoidance(self, goal, water)
|
|
|
|
local steer_method = water and creatura.get_context_small_aquatic or animalia.get_steering_context
|
|
|
|
local dir = creatura.calc_steering(self, goal, steer_method)
|
|
|
|
|
|
|
|
local lift_method = water and creatura.get_avoidance_lift_aquatic or creatura.get_avoidance_lift
|
|
|
|
local lift = lift_method(self, vec_add(self.stand_pos, dir), 2)
|
|
|
|
dir.y = (lift ~= 0 and lift) or dir.y
|
|
|
|
|
|
|
|
return dir
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Methods
|
|
|
|
|
|
|
|
creatura.register_movement_method("animalia:fly_wide", function(self)
|
|
|
|
local steer_to
|
|
|
|
local steer_int = 0
|
|
|
|
self:set_gravity(0)
|
|
|
|
local function func(_self, goal, speed_factor)
|
|
|
|
local pos = _self.object:get_pos()
|
|
|
|
if not pos or not goal then return end
|
|
|
|
if vec_dist(pos, goal) < clamp(self.width, 0.5, 1) then
|
|
|
|
_self:halt()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
-- Calculate Movement
|
|
|
|
local turn_rate = 2.5
|
|
|
|
local speed = abs(_self.speed or 2) * speed_factor or 0.5
|
|
|
|
steer_int = (steer_int > 0 and steer_int - _self.dtime) or 1 / max(speed, 1)
|
|
|
|
steer_to = (steer_int <= 0 and creatura.calc_steering(_self, goal)) or steer_to
|
|
|
|
local dir = steer_to or vec_dir(pos, goal)
|
|
|
|
local altitude = calc_altitude(self, vec_add(pos, dir))
|
|
|
|
dir.y = (altitude ~= 0 and altitude) or dir.y
|
|
|
|
|
|
|
|
if vec_dot(dir, yaw2dir(_self.object:get_yaw())) > 0.2 then -- Steer faster for major obstacles
|
|
|
|
turn_rate = 5
|
|
|
|
end
|
|
|
|
-- Apply Movement
|
|
|
|
_self:turn_to(dir2yaw(dir), turn_rate)
|
|
|
|
_self:set_forward_velocity(speed)
|
|
|
|
_self:set_vertical_velocity(speed * dir.y)
|
|
|
|
end
|
|
|
|
return func
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Steering Methods
|
|
|
|
|
|
|
|
creatura.register_movement_method("animalia:steer", function(self)
|
|
|
|
local steer_to
|
|
|
|
local steer_int = 0
|
|
|
|
|
|
|
|
local radius = 2 -- Arrival Radius
|
|
|
|
|
|
|
|
self:set_gravity(-9.8)
|
|
|
|
local function func(_self, goal, speed_factor)
|
|
|
|
-- Vectors
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos or not goal then return end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, goal)
|
|
|
|
local dir = vec_dir(pos, goal)
|
|
|
|
|
|
|
|
-- Movement Params
|
|
|
|
local vel = self.speed * speed_factor
|
|
|
|
local turn_rate = self.turn_rate
|
|
|
|
local mag = min(radius - ((radius - dist) / 1), 1)
|
|
|
|
vel = vel * mag
|
|
|
|
|
|
|
|
-- Steering
|
|
|
|
steer_int = (steer_int > 0 and steer_int - _self.dtime) or 1 / max(vel, 1)
|
|
|
|
steer_to = steer_int <= 0 and animalia.obstacle_avoidance(_self, goal) or steer_to
|
|
|
|
|
|
|
|
-- Apply Movement
|
|
|
|
_self:turn_to(minetest.dir_to_yaw(steer_to or dir), turn_rate)
|
|
|
|
_self:set_forward_velocity(vel)
|
|
|
|
end
|
|
|
|
return func
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_movement_method("animalia:steer_no_gravity", function(self)
|
|
|
|
local steer_to
|
|
|
|
local steer_int = 0
|
|
|
|
|
|
|
|
local radius = 2 -- Arrival Radius
|
|
|
|
|
|
|
|
self:set_gravity(0)
|
|
|
|
local function func(_self, goal, speed_factor)
|
|
|
|
-- Vectors
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos or not goal then return end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, goal)
|
|
|
|
local dir = vec_dir(pos, goal)
|
|
|
|
|
|
|
|
-- Movement Params
|
|
|
|
local vel = self.speed * speed_factor
|
|
|
|
local turn_rate = self.turn_rate
|
|
|
|
local mag = min(radius - ((radius - dist) / 1), 1)
|
|
|
|
vel = vel * mag
|
|
|
|
|
|
|
|
-- Steering
|
|
|
|
steer_int = (steer_int > 0 and steer_int - _self.dtime) or 1 / max(vel, 1)
|
|
|
|
steer_to = steer_int <= 0 and animalia.obstacle_avoidance(_self, goal, _self.max_breath == 0) or steer_to
|
|
|
|
|
|
|
|
-- Apply Movement
|
|
|
|
_self:turn_to(minetest.dir_to_yaw(steer_to or dir), turn_rate)
|
|
|
|
_self:set_forward_velocity(vel)
|
|
|
|
_self:set_vertical_velocity(dir.y * vel)
|
|
|
|
end
|
|
|
|
return func
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Simple Methods
|
|
|
|
|
|
|
|
creatura.register_movement_method("animalia:move", function(self)
|
|
|
|
local radius = 2 -- Arrival Radius
|
|
|
|
|
|
|
|
self:set_gravity(-9.8)
|
|
|
|
local function func(_self, goal, speed_factor)
|
|
|
|
-- Vectors
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos or not goal then return end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, goal)
|
|
|
|
local dir = vec_dir(pos, goal)
|
|
|
|
|
|
|
|
-- Movement Params
|
|
|
|
local vel = self.speed * speed_factor
|
|
|
|
local turn_rate = self.turn_rate
|
|
|
|
local mag = min(radius - ((radius - dist) / 1), 1)
|
|
|
|
vel = vel * mag
|
|
|
|
|
|
|
|
-- Apply Movement
|
|
|
|
_self:turn_to(minetest.dir_to_yaw(dir), turn_rate)
|
|
|
|
_self:set_forward_velocity(vel)
|
|
|
|
end
|
|
|
|
return func
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_movement_method("animalia:move_no_gravity", function(self)
|
|
|
|
local radius = 2 -- Arrival Radius
|
|
|
|
|
|
|
|
self:set_gravity(0)
|
|
|
|
local function func(_self, goal, speed_factor)
|
|
|
|
-- Vectors
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos or not goal then return end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, goal)
|
|
|
|
local dir = vec_dir(pos, goal)
|
|
|
|
|
|
|
|
-- Movement Params
|
|
|
|
local vel = self.speed * speed_factor
|
|
|
|
local turn_rate = self.turn_rate
|
|
|
|
local mag = min(radius - ((radius - dist) / 1), 1)
|
|
|
|
vel = vel * mag
|
|
|
|
|
|
|
|
-- Apply Movement
|
|
|
|
_self:turn_to(minetest.dir_to_yaw(dir), turn_rate)
|
|
|
|
_self:set_forward_velocity(vel)
|
|
|
|
_self:set_vertical_velocity(vel * dir.y)
|
|
|
|
end
|
|
|
|
return func
|
|
|
|
end)
|
|
|
|
|
|
|
|
-------------
|
|
|
|
-- Actions --
|
|
|
|
-------------
|
|
|
|
|
|
|
|
function animalia.action_walk(self, time, speed, animation, pos2)
|
|
|
|
local timeout = time or 3
|
|
|
|
local speed_factor = speed or 0.5
|
|
|
|
local anim = animation or "walk"
|
|
|
|
|
|
|
|
local wander_radius = 2
|
|
|
|
|
|
|
|
local dir = pos2 and vec_dir(self.stand_pos, pos2)
|
|
|
|
local function func(mob)
|
|
|
|
local pos, yaw = mob.object:get_pos(), mob.object:get_yaw()
|
|
|
|
if not pos or not yaw then return true end
|
|
|
|
|
|
|
|
dir = pos2 and vec_dir(pos, pos2) or minetest.yaw_to_dir(yaw)
|
|
|
|
|
|
|
|
local wander_point = vec_add(pos, vec_multi(dir, wander_radius + 0.5))
|
|
|
|
local goal = vec_add(wander_point, vec_multi(minetest.yaw_to_dir(random(pi2)), wander_radius))
|
|
|
|
|
|
|
|
local safe = true
|
|
|
|
|
|
|
|
if mob.max_fall then
|
|
|
|
safe = mob:is_pos_safe(goal)
|
|
|
|
end
|
|
|
|
|
|
|
|
if timeout <= 0
|
|
|
|
or not safe
|
|
|
|
or mob:move_to(goal, "animalia:steer", speed_factor) then
|
|
|
|
mob:halt()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then return true end
|
|
|
|
|
|
|
|
mob:animate(anim)
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
|
|
|
function animalia.action_swim(self, time, speed, animation, pos2)
|
|
|
|
local timeout = time or 3
|
|
|
|
local speed_factor = speed or 0.5
|
|
|
|
local anim = animation or "swim"
|
|
|
|
|
|
|
|
local wander_radius = 2
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
local pos, yaw = mob.object:get_pos(), mob.object:get_yaw()
|
|
|
|
if not pos or not yaw then return true end
|
|
|
|
|
|
|
|
if not mob.in_liquid then return true end
|
|
|
|
|
|
|
|
local steer_direction = pos2 and vec_dir(pos, pos2)
|
|
|
|
|
|
|
|
if not steer_direction then
|
|
|
|
local wander_point = {
|
|
|
|
x = pos.x + -sin(yaw) * (wander_radius + 0.5),
|
|
|
|
y = pos.y,
|
|
|
|
z = pos.z + cos(yaw) * (wander_radius + 0.5)
|
|
|
|
}
|
|
|
|
local wander_angle = random(pi2)
|
|
|
|
|
|
|
|
steer_direction = vec_dir(pos, {
|
|
|
|
x = wander_point.x + -sin(wander_angle) * wander_radius,
|
|
|
|
y = wander_point.y + (random(-10, 10) / 10),
|
|
|
|
z = wander_point.z + cos(wander_angle) * wander_radius
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Boids
|
|
|
|
local boid_dir = mob.uses_boids and creatura.get_boid_dir(mob)
|
|
|
|
if boid_dir then
|
|
|
|
steer_direction = {
|
|
|
|
x = (steer_direction.x + boid_dir.x) / 2,
|
|
|
|
y = (steer_direction.y + boid_dir.y) / 2,
|
|
|
|
z = (steer_direction.z + boid_dir.z) / 2
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local goal = vec_add(pos, vec_multi(steer_direction, mob.width + 2))
|
|
|
|
|
|
|
|
if timeout <= 0
|
|
|
|
or mob:move_to(goal, "animalia:steer_no_gravity", speed_factor) then
|
|
|
|
mob:halt()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then return true end
|
|
|
|
|
|
|
|
mob:animate(anim)
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
|
|
|
function animalia.action_fly(self, time, speed, animation, pos2, turn)
|
|
|
|
local timeout = time or 3
|
|
|
|
local speed_factor = speed or 0.5
|
|
|
|
local anim = animation or "fly"
|
|
|
|
local turn_rate = turn or 1.5
|
|
|
|
|
|
|
|
local wander_radius = 2
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
local pos, yaw = mob.object:get_pos(), mob.object:get_yaw()
|
|
|
|
if not pos or not yaw then return true end
|
|
|
|
|
|
|
|
local steer_direction = pos2 and vec_dir(pos, pos2)
|
|
|
|
|
|
|
|
if not steer_direction then
|
|
|
|
local wander_point = {
|
|
|
|
x = pos.x + -sin(yaw) * (wander_radius + turn_rate),
|
|
|
|
y = pos.y,
|
|
|
|
z = pos.z + cos(yaw) * (wander_radius + turn_rate)
|
|
|
|
}
|
|
|
|
local wander_angle = random(pi2)
|
|
|
|
|
|
|
|
steer_direction = vec_dir(pos, {
|
|
|
|
x = wander_point.x + -sin(wander_angle) * wander_radius,
|
|
|
|
y = wander_point.y + (random(-10, 10) / 10) * turn_rate,
|
|
|
|
z = wander_point.z + cos(wander_angle) * wander_radius
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Boids
|
|
|
|
local boid_dir = mob.uses_boids and creatura.get_boid_dir(mob)
|
|
|
|
if boid_dir then
|
|
|
|
steer_direction = {
|
|
|
|
x = (steer_direction.x + boid_dir.x) / 2,
|
|
|
|
y = (steer_direction.y + boid_dir.y) / 2,
|
|
|
|
z = (steer_direction.z + boid_dir.z) / 2
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
local goal = vec_add(pos, vec_multi(steer_direction, mob.width + 2))
|
|
|
|
|
|
|
|
if timeout <= 0
|
|
|
|
or mob:move_to(goal, "animalia:steer_no_gravity", speed_factor) then
|
|
|
|
mob:halt()
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then return true end
|
|
|
|
|
|
|
|
mob:animate(anim)
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Latch to pos
|
|
|
|
-- if self.animations["latch_ceiling"] then latch to ceiling end
|
|
|
|
-- if self.animations["latch_wall"] then latch to wall end
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
local latch_ceil_offset = {x = 0, y = 1, z = 0}
|
|
|
|
local latch_wall_offset = {
|
|
|
|
{x = 1, y = 0, z = 0},
|
|
|
|
{x = 0, y = 0, z = 1},
|
|
|
|
{x = -1, y = 0, z = 0},
|
|
|
|
{x = 0, y = 0, z = -1}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function animalia.action_latch(self)
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
|
|
|
|
local ceiling
|
|
|
|
if self.animations["latch_ceiling"] then
|
|
|
|
ceiling = vec_add(pos, latch_ceil_offset)
|
|
|
|
|
|
|
|
if not creatura.get_node_def(ceiling).walkable then
|
|
|
|
ceiling = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local wall
|
|
|
|
if self.animations["latch_wall"] then
|
|
|
|
for n = 1, 4 do
|
|
|
|
wall = vec_add(self.stand_pos, latch_wall_offset[n])
|
|
|
|
|
|
|
|
if creatura.get_node_def(wall).walkable then
|
|
|
|
break
|
|
|
|
else
|
|
|
|
wall = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local function func(mob)
|
|
|
|
mob:set_gravity(0)
|
|
|
|
|
|
|
|
if ceiling then
|
|
|
|
mob:animate("latch_ceiling")
|
|
|
|
mob:set_vertical_velocity(1)
|
|
|
|
mob:set_forward_velocity(0)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if wall then
|
|
|
|
mob:animate("latch_wall")
|
|
|
|
mob.object:set_yaw(minetest.dir_to_yaw(vec_dir(pos, wall)))
|
|
|
|
mob:set_vertical_velocity(0)
|
|
|
|
mob:set_forward_velocity(1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
function animalia.action_pursue(self, target, timeout, method, speed_factor, anim)
|
|
|
|
local timer = timeout or 4
|
|
|
|
local goal
|
|
|
|
local function func(_self)
|
|
|
|
local target_alive, line_of_sight, tgt_pos = _self:get_target(target)
|
|
|
|
if not target_alive then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
goal = goal or tgt_pos
|
|
|
|
timer = timer - _self.dtime
|
|
|
|
self:animate(anim or "walk")
|
|
|
|
local safe = true
|
|
|
|
if _self.max_fall
|
|
|
|
and _self.max_fall > 0 then
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
safe = _self:is_pos_safe(goal)
|
|
|
|
end
|
|
|
|
if line_of_sight
|
|
|
|
and vec_dist(goal, tgt_pos) > 3 then
|
|
|
|
goal = tgt_pos
|
|
|
|
end
|
|
|
|
if timer <= 0
|
|
|
|
or not safe
|
|
|
|
or _self:move_to(goal, method or "creatura:obstacle_avoidance", speed_factor or 0.5) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
|
|
|
function animalia.action_melee(self, target)
|
|
|
|
local stage = 1
|
|
|
|
local is_animated = self.animations["melee"] ~= nil
|
|
|
|
local timeout = 1
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
local target_pos = target and target:get_pos()
|
|
|
|
if not target_pos then return true end
|
|
|
|
|
|
|
|
local pos = mob.stand_pos
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
local dir = vec_dir(pos, target_pos)
|
|
|
|
|
|
|
|
local anim = is_animated and mob:animate("melee", "stand")
|
|
|
|
|
|
|
|
if stage == 1 then
|
|
|
|
mob.object:add_velocity({x = dir.x * 3, y = 2, z = dir.z * 3})
|
|
|
|
|
|
|
|
stage = 2
|
|
|
|
end
|
|
|
|
|
|
|
|
if stage == 2
|
|
|
|
and dist < mob.width + 1 then
|
|
|
|
mob:punch_target(target)
|
|
|
|
local knockback = minetest.calculate_knockback(
|
|
|
|
target, mob.object, 1.0,
|
|
|
|
{damage_groups = {fleshy = mob.damage}},
|
|
|
|
dir, 2.0, mob.damage
|
|
|
|
)
|
|
|
|
target:add_velocity({x = dir.x * knockback, y = dir.y * knockback, z = dir.z * knockback})
|
|
|
|
|
|
|
|
stage = 3
|
|
|
|
end
|
|
|
|
|
|
|
|
if stage == 3
|
|
|
|
and (not is_animated
|
|
|
|
or anim == "stand") then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then return true end
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
function animalia.action_play(self, target)
|
|
|
|
local stage = 1
|
|
|
|
local is_animated = self.animations["play"] ~= nil
|
|
|
|
local timeout = 1
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
local target_pos = target and target:get_pos()
|
|
|
|
if not target_pos then return true end
|
|
|
|
|
|
|
|
local pos = mob.stand_pos
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
local dir = vec_dir(pos, target_pos)
|
|
|
|
|
|
|
|
local anim = is_animated and mob:animate("play", "stand")
|
|
|
|
|
|
|
|
if stage == 1 then
|
|
|
|
mob.object:add_velocity({x = dir.x * 3, y = 2, z = dir.z * 3})
|
|
|
|
|
|
|
|
stage = 2
|
|
|
|
end
|
|
|
|
|
|
|
|
if stage == 2
|
|
|
|
and dist < mob.width + 1 then
|
|
|
|
animalia.add_trust(mob, target, 1)
|
|
|
|
|
|
|
|
stage = 3
|
|
|
|
end
|
|
|
|
|
|
|
|
if stage == 3
|
|
|
|
and (not is_animated
|
|
|
|
or anim == "stand") then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then return true end
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
function animalia.action_float(self, time, anim)
|
|
|
|
local timer = time
|
|
|
|
local function func(_self)
|
|
|
|
_self:set_gravity(-0.14)
|
|
|
|
_self:halt()
|
|
|
|
_self:animate(anim or "foat")
|
|
|
|
timer = timer - _self.dtime
|
|
|
|
if timer <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
|
|
|
function animalia.action_dive_attack(self, target, timeout)
|
|
|
|
timeout = timeout or 12
|
|
|
|
local timer = timeout
|
|
|
|
local width = self.width or 0.5
|
|
|
|
local punch_init = false
|
|
|
|
local anim
|
|
|
|
local function func(_self)
|
|
|
|
-- Tick down timers
|
|
|
|
timer = timer - _self.dtime
|
|
|
|
if timer <= 0 then return true end
|
|
|
|
|
|
|
|
-- Get positions
|
|
|
|
local pos = _self.stand_pos
|
|
|
|
local tgt_pos = target and target:get_pos()
|
|
|
|
if not tgt_pos then return true end
|
|
|
|
local dist = vec_dist(pos, tgt_pos)
|
|
|
|
|
|
|
|
if punch_init then
|
|
|
|
anim = _self:animate("fly_punch", "fly")
|
|
|
|
if anim == "fly" then return true end
|
|
|
|
else
|
|
|
|
anim = _self:animate("fly")
|
|
|
|
end
|
|
|
|
|
|
|
|
if dist > width + 1 then
|
|
|
|
local method = "animalia:move_no_gravity"
|
|
|
|
if dist > 4 then
|
|
|
|
method = "animalia:steer_no_gravity"
|
|
|
|
end
|
|
|
|
_self:move_to(tgt_pos, method, 1)
|
|
|
|
elseif not punch_init then
|
|
|
|
_self:punch_target(target)
|
|
|
|
punch_init = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_action(func)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Behaviors
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
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()
|
|
|
|
if not pos then return end
|
|
|
|
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)
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
-- Basic --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_idle", function(self, timeout, anim)
|
|
|
|
local timer = timeout or 1
|
|
|
|
local init = false
|
|
|
|
local function func(mob)
|
|
|
|
if not init then
|
|
|
|
creatura.action_idle(mob, timeout, anim)
|
|
|
|
end
|
|
|
|
timer = timer - mob.dtime
|
|
|
|
if timer <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_wander", function(self)
|
|
|
|
local idle_max = 4
|
|
|
|
local move_chance = 3
|
|
|
|
local graze_chance = 16
|
|
|
|
|
|
|
|
--local iter = 1
|
|
|
|
local range = self.tracking_range
|
|
|
|
|
|
|
|
local center
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.stand_pos
|
|
|
|
|
|
|
|
if mob:timer(2) then
|
|
|
|
--iter = iter < 3 and iter + 1 or 1 -- Iterate to 3, then reset to 1
|
|
|
|
|
|
|
|
-- Grazing Behavior
|
|
|
|
if mob.is_grazing_mob
|
|
|
|
and random(graze_chance) < 2 then
|
|
|
|
local yaw = mob.object:get_yaw()
|
|
|
|
if not yaw then return true end
|
|
|
|
|
|
|
|
local turf_pos = {
|
|
|
|
x = pos.x + -sin(yaw) * mob.width,
|
|
|
|
y = pos.y - 0.5,
|
|
|
|
z = pos.z + cos(yaw) * mob.width
|
|
|
|
}
|
|
|
|
|
|
|
|
if animalia.eat_turf(mob, turf_pos) then
|
|
|
|
animalia.add_break_particle(turf_pos)
|
|
|
|
creatura.action_idle(mob, 1, "eat")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Herding Behavior
|
|
|
|
if mob.is_herding_mob then
|
|
|
|
center = animalia.get_average_pos(get_group_positions(mob)) or pos
|
|
|
|
|
|
|
|
if vec_dist(pos, center) < range / 4 then
|
|
|
|
center = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Skittish Behavior
|
|
|
|
if mob.is_skittish_mob then
|
|
|
|
local plyr = creatura.get_nearby_player(mob)
|
|
|
|
local plyr_alive, los, plyr_pos = mob:get_target(plyr)
|
|
|
|
if plyr_alive
|
|
|
|
and los then
|
|
|
|
center = vec_add(pos, vec_dir(plyr_pos, pos))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if random(move_chance) < 2 then
|
|
|
|
animalia.action_walk(mob, 3, 0.2, "walk", center)
|
|
|
|
center = false
|
|
|
|
else
|
|
|
|
creatura.action_idle(mob, random(idle_max), "stand")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_seek_pos", function(self, pos2, timeout)
|
|
|
|
timeout = timeout or 3
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos or not pos2 then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
local anim = (mob.animations["run"] and "run") or "walk"
|
|
|
|
animalia.action_walk(mob, 1, 1, anim, pos2)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_seek_food", function(self)
|
|
|
|
local timeout = 3
|
|
|
|
|
|
|
|
local food = animalia.get_dropped_food(self)
|
|
|
|
local food_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
local food_pos = food and food:get_pos()
|
|
|
|
if not pos or not food_pos then return true, 10 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, food_pos)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not food_reached then
|
|
|
|
food_reached = true
|
|
|
|
|
|
|
|
local anim = (mob.animations["eat"] and "eat") or "stand"
|
|
|
|
creatura.action_idle(mob, 1, anim)
|
|
|
|
animalia.eat_dropped_item(mob, food)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if food_reached then return true, 10 end
|
|
|
|
local anim = (mob.animations["run"] and "run") or "walk"
|
|
|
|
animalia.action_walk(mob, 1, 1, anim, food_pos)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_seek_crop", function(self)
|
|
|
|
local timeout = 12
|
|
|
|
|
|
|
|
local crop = animalia.find_crop(self)
|
|
|
|
local crop_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos or not crop then return true, 30 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, crop)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not crop_reached then
|
|
|
|
crop_reached = true
|
|
|
|
|
|
|
|
local anim = (mob.animations["eat"] and "eat") or "stand"
|
|
|
|
creatura.action_idle(mob, 1, anim)
|
|
|
|
animalia.eat_crop(mob, crop)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if crop_reached then return true, 10 end
|
|
|
|
animalia.action_walk(mob, 2, 0.5, "walk", crop)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_flee", function(self, target)
|
|
|
|
local function func(mob)
|
|
|
|
local pos, target_pos = mob.object:get_pos(), target:get_pos()
|
|
|
|
if not pos or not target_pos then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
animalia.action_walk(mob, 0.5, 1, "run", vec_add(pos, vec_dir(target_pos, pos)))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_attack", function(self, target)
|
|
|
|
local has_attacked = false
|
|
|
|
local has_warned = not self.warn_before_attack
|
|
|
|
local function func(mob)
|
|
|
|
local target_alive, _, target_pos = mob:get_target(target)
|
|
|
|
if not target_alive then return true end
|
|
|
|
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if has_attacked then return true, 2 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
|
|
|
|
if dist > mob.width + 1 then
|
|
|
|
if not has_warned
|
|
|
|
and dist > mob.width + 2 then
|
|
|
|
local yaw = mob.object:get_yaw()
|
|
|
|
local yaw_to_target = minetest.dir_to_yaw(vec_dir(pos, target_pos))
|
|
|
|
|
|
|
|
if abs(diff(yaw, yaw_to_target)) > pi / 2 then
|
|
|
|
animalia.action_pursue(mob, target)
|
|
|
|
else
|
|
|
|
creatura.action_idle(mob, 0.5, "warn")
|
|
|
|
end
|
|
|
|
return
|
|
|
|
else
|
|
|
|
animalia.action_pursue(mob, target, 0.5)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
animalia.action_melee(mob, target)
|
|
|
|
has_attacked = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:basic_breed", function(self)
|
2024-01-10 17:30:54 -08:00
|
|
|
local mate = animalia.get_nearby_mate(self, self.name)
|
2024-01-04 11:15:02 -08:00
|
|
|
|
|
|
|
local timer = 0
|
|
|
|
local function func(mob)
|
2024-01-10 17:30:54 -08:00
|
|
|
if not mob.breeding then return true end
|
2024-01-04 11:15:02 -08:00
|
|
|
|
|
|
|
local pos, target_pos = mob.object:get_pos(), mate and mate:get_pos()
|
|
|
|
if not pos or not target_pos then return true end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
timer = dist < mob.width + 0.5 and timer + mob.dtime or timer
|
|
|
|
|
|
|
|
if timer > 2 then
|
|
|
|
local mate_entity = mate:get_luaentity()
|
|
|
|
|
|
|
|
mob.breeding = mob:memorize("breeding", false)
|
|
|
|
mob.breeding_cooldown = mob:memorize("breeding_cooldown", 300)
|
2024-01-10 17:30:54 -08:00
|
|
|
mate_entity.breeding = mate_entity:memorize("breeding", false)
|
|
|
|
mate_entity.breeding_cooldown = mate_entity:memorize("breeding_cooldown", 300)
|
2024-01-04 11:15:02 -08:00
|
|
|
|
|
|
|
animalia.particle_spawner(pos, "heart.png", "float")
|
|
|
|
|
|
|
|
for _ = 1, mob.birth_count or 1 do
|
|
|
|
if mob.add_child then
|
|
|
|
mob:add_child(mate_entity)
|
|
|
|
else
|
|
|
|
local object = minetest.add_entity(pos, mob.name)
|
|
|
|
local ent = object:get_luaentity()
|
|
|
|
ent.growth_scale = 0.7
|
|
|
|
animalia.initialize_api(ent)
|
|
|
|
animalia.protect_from_despawn(ent)
|
|
|
|
end
|
|
|
|
end
|
2024-01-10 17:30:54 -08:00
|
|
|
return true, 60
|
2024-01-04 11:15:02 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
animalia.action_pursue(mob, mate)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Swim --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:swim_wander", function(self)
|
|
|
|
local move_chance = 2
|
|
|
|
local idle_max = 4
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
if not mob:get_action() then
|
|
|
|
if not mob.in_liquid then
|
|
|
|
creatura.action_idle(mob, 1, "flop")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob.idle_in_water
|
|
|
|
or random(move_chance) < 2 then
|
|
|
|
animalia.action_swim(mob, 0.5)
|
|
|
|
else
|
|
|
|
animalia.action_float(mob, random(idle_max), "float")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:swim_seek_land", function(self)
|
|
|
|
local land_pos
|
|
|
|
|
|
|
|
self:set_gravity(-9.8)
|
|
|
|
local function func(mob)
|
|
|
|
if not land_pos then
|
|
|
|
for i = 0, 330, 30 do
|
|
|
|
land_pos = animalia.find_collision(mob, yaw2dir(rad(i)))
|
|
|
|
|
|
|
|
if land_pos then
|
|
|
|
land_pos.y = land_pos.y + 1
|
|
|
|
if minetest.get_node(land_pos).name == "air" then
|
|
|
|
break
|
|
|
|
else
|
|
|
|
land_pos = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not land_pos then return true end
|
|
|
|
end
|
|
|
|
|
|
|
|
local pos, yaw = mob.object:get_pos(), mob.object:get_yaw()
|
|
|
|
if not yaw then return end
|
|
|
|
|
|
|
|
local tyaw = dir2yaw(vec_dir(pos, land_pos))
|
|
|
|
if abs(tyaw - yaw) > 0.1 then
|
|
|
|
mob:turn_to(tyaw, 12)
|
|
|
|
end
|
|
|
|
|
|
|
|
mob:set_forward_velocity(mob.speed * 0.5)
|
|
|
|
mob:animate("walk")
|
|
|
|
if vec_dist(pos, land_pos) < 1
|
|
|
|
or (not mob.in_liquid
|
|
|
|
and mob.touching_ground) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Fly --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:fly_wander", function(self, turn_rate)
|
|
|
|
local move_chance = 2
|
|
|
|
local idle_max = 4
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
if not mob:get_action() then
|
|
|
|
if not mob.idle_while_flying
|
|
|
|
or random(move_chance) < 2 then
|
|
|
|
animalia.action_fly(mob, 1, 0.5, "fly", nil, turn_rate)
|
|
|
|
else
|
|
|
|
animalia.action_hover(mob, random(idle_max), "hover")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:fly_seek_home", function(self)
|
|
|
|
local home = self.home_position
|
|
|
|
local roost = self.roost_action or creatura.action_idle
|
|
|
|
local is_home = self.is_roost or function(pos, home_pos)
|
|
|
|
if abs(pos.x - home_pos.x) < 0.5
|
|
|
|
and abs(pos.z - home_pos.z) < 0.5
|
|
|
|
and abs(pos.y - home_pos.y) < 0.75 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos or not home then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if is_home(pos, home) then
|
|
|
|
roost(mob, 1)
|
|
|
|
return
|
|
|
|
end
|
|
|
|
creatura.action_move(mob, home, 3, "animalia:steer_no_gravity", 1, "fly")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:fly_seek_land", function(self)
|
|
|
|
local landed = false
|
|
|
|
local function func(_self)
|
|
|
|
if not _self:get_action() then
|
|
|
|
if landed then return true end
|
|
|
|
if _self.touching_ground then
|
|
|
|
creatura.action_idle(_self, 0.5, "stand")
|
|
|
|
landed = true
|
|
|
|
else
|
|
|
|
local pos2 = _self:get_wander_pos_3d(3, 6)
|
|
|
|
if pos2 then
|
|
|
|
local dist2floor = creatura.sensor_floor(_self, 10, true)
|
|
|
|
pos2.y = pos2.y - dist2floor
|
|
|
|
creatura.action_move(_self, pos2, 3, "animalia:move_no_gravity", 0.6, "fly")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:fly_seek_food", function(self)
|
|
|
|
local timeout = 3
|
|
|
|
|
|
|
|
local food = animalia.get_dropped_food(self)
|
|
|
|
local food_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
local food_pos = food and food:get_pos()
|
|
|
|
if not pos or not food_pos then return true, 5 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, food_pos)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not food_reached then
|
|
|
|
food_reached = true
|
|
|
|
|
|
|
|
local anim = (mob.animations["eat"] and "eat") or "stand"
|
|
|
|
creatura.action_idle(mob, 1, anim)
|
|
|
|
animalia.eat_dropped_item(mob, food)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if food_reached then return true, 10 end
|
|
|
|
animalia.action_fly(mob, 1, 1, "fly", food_pos, 3)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Horse --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:horse_tame", function(self)
|
|
|
|
local trust = 5
|
|
|
|
local player = self.rider
|
|
|
|
local player_props = player and player:get_properties()
|
|
|
|
if not player_props then return end
|
|
|
|
local player_size = player_props.visual_size
|
|
|
|
local mob_size = self.visual_size
|
|
|
|
local adj_size = {
|
|
|
|
x = player_size.x / mob_size.x,
|
|
|
|
y = player_size.y / mob_size.y
|
|
|
|
}
|
|
|
|
if player_size.x ~= adj_size.x then
|
|
|
|
player:set_properties({
|
|
|
|
visual_size = adj_size
|
|
|
|
})
|
|
|
|
end
|
|
|
|
local function func(_self)
|
|
|
|
local pos = _self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
if not player or not creatura.is_alive(player) then return true end
|
|
|
|
|
|
|
|
-- Increase Taming progress while Players view is aligned with the Horses
|
|
|
|
local yaw, plyr_yaw = _self.object:get_yaw(), player:get_look_horizontal()
|
|
|
|
local yaw_diff = abs(diff(yaw, plyr_yaw))
|
|
|
|
|
|
|
|
trust = yaw_diff < pi / 3 and trust + _self.dtime or trust - _self.dtime * 0.5
|
|
|
|
|
|
|
|
if trust >= 10 then -- Tame
|
|
|
|
_self.owner = _self:memorize("owner", player:get_player_name())
|
|
|
|
animalia.protect_from_despawn(_self)
|
|
|
|
animalia.mount(_self, player)
|
|
|
|
animalia.particle_spawner(pos, "creatura_particle_green.png", "float")
|
|
|
|
elseif trust <= 0 then -- Fail
|
|
|
|
animalia.mount(_self, player)
|
|
|
|
animalia.particle_spawner(pos, "creatura_particle_red.png", "float")
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Actions
|
|
|
|
if not _self:get_action() then
|
|
|
|
if random(3) < 2 then
|
|
|
|
creatura.action_idle(_self, 0.5, "punch_aoe")
|
|
|
|
else
|
|
|
|
animalia.action_walk(_self, 2, 0.75, "run")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Dismount
|
|
|
|
if not player
|
|
|
|
or player:get_player_control().sneak then
|
|
|
|
animalia.mount(_self, player)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:horse_ride", function(self, player)
|
|
|
|
local player_props = player and player:get_properties()
|
|
|
|
if not player_props then return end
|
|
|
|
local player_size = player_props.visual_size
|
|
|
|
local mob_size = self.visual_size
|
|
|
|
local adj_size = {
|
|
|
|
x = player_size.x / mob_size.x,
|
|
|
|
y = player_size.y / mob_size.y
|
|
|
|
}
|
|
|
|
if player_size.x ~= adj_size.x then
|
|
|
|
player:set_properties({
|
|
|
|
visual_size = adj_size
|
|
|
|
})
|
|
|
|
end
|
|
|
|
|
|
|
|
local function func(_self)
|
|
|
|
if not creatura.is_alive(player) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
local anim = "stand"
|
|
|
|
local speed_x = 0
|
|
|
|
local tyaw = player:get_look_horizontal()
|
|
|
|
local control = player:get_player_control()
|
|
|
|
local vel = _self.object:get_velocity()
|
2024-01-10 17:30:54 -08:00
|
|
|
if not tyaw then return true end
|
|
|
|
|
|
|
|
if control.sneak
|
|
|
|
or not _self.rider then
|
|
|
|
animalia.mount(_self, player)
|
|
|
|
return true
|
|
|
|
end
|
2024-01-04 11:15:02 -08:00
|
|
|
|
|
|
|
animate_player(player, "sit", 30)
|
|
|
|
|
|
|
|
if _self:timer(1) then
|
|
|
|
player_props = player and player:get_properties()
|
|
|
|
if player_props.visual_size.x ~= adj_size.x then
|
|
|
|
player:set_properties({
|
|
|
|
visual_size = adj_size
|
|
|
|
})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if control.up then
|
|
|
|
speed_x = 1
|
|
|
|
anim = "walk"
|
|
|
|
if control.aux1 then
|
|
|
|
speed_x = 1.5
|
|
|
|
anim = "run"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Jump Control
|
|
|
|
if control.jump
|
|
|
|
and _self.touching_ground
|
|
|
|
and vel.y < 1 then
|
|
|
|
_self.object:add_velocity({
|
|
|
|
x = 0,
|
|
|
|
y = _self.jump_power * 2,
|
|
|
|
z = 0
|
|
|
|
})
|
|
|
|
elseif not _self.touching_ground then
|
|
|
|
speed_x = speed_x * 0.75
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Rear Animation when jumping
|
|
|
|
if not _self.touching_ground
|
|
|
|
and not _self.in_liquid
|
|
|
|
and vel.y > 0 then
|
|
|
|
anim = "rear"
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Steering
|
|
|
|
local yaw = _self.object:get_yaw()
|
|
|
|
|
|
|
|
_self.head_tracking = nil
|
|
|
|
animalia.move_head(_self, tyaw, 0)
|
|
|
|
|
|
|
|
if speed_x > 0 and control.left then tyaw = tyaw + pi * 0.25 end
|
|
|
|
if speed_x > 0 and control.right then tyaw = tyaw - pi * 0.25 end
|
|
|
|
if abs(yaw - tyaw) > 0.1 then
|
|
|
|
_self:turn_to(tyaw, _self.turn_rate)
|
|
|
|
end
|
|
|
|
|
|
|
|
_self:set_forward_velocity(_self.speed * speed_x)
|
|
|
|
_self:animate(anim)
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Eagle --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:eagle_attack", function(self, target)
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
local _, is_visible, target_pos = mob:get_target(target)
|
|
|
|
|
|
|
|
if not pos or not target_pos then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
local vantage_pos = {
|
|
|
|
x = target_pos.x,
|
|
|
|
y = target_pos.y + 6,
|
|
|
|
z = target_pos.z
|
|
|
|
}
|
|
|
|
local dist = vec_dist(pos, vantage_pos)
|
|
|
|
|
|
|
|
if dist > 8 then
|
|
|
|
animalia.action_fly(mob, 1, 1, "fly", vantage_pos, 2)
|
|
|
|
elseif not is_visible then
|
|
|
|
animalia.action_fly(mob, 1, 0.5, "glide", vantage_pos, 4)
|
|
|
|
else
|
|
|
|
animalia.action_dive_attack(mob, target, 6)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Cat --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:cat_seek_vessel", function(self)
|
|
|
|
local timeout = 12
|
|
|
|
|
|
|
|
local vessel
|
|
|
|
local vessel_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos then return true end
|
|
|
|
|
|
|
|
if not vessel then
|
|
|
|
local nodes = minetest.find_nodes_in_area(vec_sub(pos, 6), vec_add(pos, 6),
|
|
|
|
{"vessels:glass_bottle", "vessels:drinking_glass"}) or {}
|
|
|
|
|
|
|
|
if #nodes < 1 then return true, 10 end
|
|
|
|
|
|
|
|
vessel = nodes[random(#nodes)]
|
|
|
|
end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, vessel)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not vessel_reached then
|
|
|
|
vessel_reached = true
|
|
|
|
|
|
|
|
creatura.action_idle(mob, 1)
|
|
|
|
|
|
|
|
if not minetest.is_protected(vessel, "") then
|
|
|
|
minetest.remove_node(vessel)
|
|
|
|
minetest.add_item(vessel, "vessels:glass_fragments")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if vessel_reached then return true end
|
|
|
|
|
|
|
|
animalia.action_walk(mob, 1, 1, "run", vessel)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:cat_follow_owner", function(self, player)
|
|
|
|
local timeout = 6
|
|
|
|
local attack_chance = 6
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
local owner = player or (mob.owner and minetest.get_player_by_name(mob.owner))
|
|
|
|
if not owner then return true end
|
|
|
|
|
|
|
|
local pos, target_pos = mob.object:get_pos(), owner:get_pos()
|
|
|
|
if not pos or not target_pos then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
|
|
|
|
if dist > mob.width + 0.5 then
|
|
|
|
animalia.action_pursue(mob, owner)
|
|
|
|
else
|
|
|
|
if random(attack_chance) < 2 then
|
|
|
|
animalia.action_melee(mob, owner)
|
|
|
|
else
|
2024-01-04 21:25:51 -08:00
|
|
|
creatura.action_idle(mob, 1)
|
2024-01-04 11:15:02 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
creatura.register_utility("animalia:cat_play_with_owner", function(self)
|
|
|
|
local timeout = 6
|
|
|
|
--local attack_chance = 6
|
|
|
|
|
|
|
|
local has_played = false
|
|
|
|
|
|
|
|
local function func(mob)
|
|
|
|
local owner = mob.owner and minetest.get_player_by_name(mob.owner)
|
|
|
|
if not owner then return true end
|
|
|
|
|
|
|
|
local item = owner:get_wielded_item()
|
|
|
|
local item_name = item and item:get_name()
|
|
|
|
|
|
|
|
if item_name ~= "animalia:cat_toy" then return true, 5 end
|
|
|
|
|
|
|
|
local pos, target_pos = mob.object:get_pos(), owner:get_pos()
|
|
|
|
if not pos or not target_pos then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if has_played then return true, 20 end
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
|
|
|
|
if dist > mob.width + 0.5 then
|
|
|
|
animalia.action_pursue(mob, owner)
|
|
|
|
else
|
|
|
|
animalia.action_play(mob, owner)
|
|
|
|
has_played = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
-- Frog --
|
|
|
|
|
|
|
|
local function get_bug_pos(self)
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
|
|
|
|
local food = minetest.find_nodes_in_area(
|
|
|
|
vec_sub(pos, 3),
|
|
|
|
vec_add(pos, 3),
|
|
|
|
self.follow
|
|
|
|
) or {}
|
|
|
|
|
|
|
|
return #food > 0 and food[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:frog_seek_bug", function(self)
|
|
|
|
local timeout = 12
|
|
|
|
|
|
|
|
local bug = get_bug_pos(self)
|
|
|
|
local bug_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos or not bug then return true, 30 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, bug)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not bug_reached then
|
|
|
|
bug_reached = true
|
|
|
|
|
|
|
|
local dir = vec_dir(pos, bug)
|
|
|
|
local frame = floor(dist * 10)
|
|
|
|
|
|
|
|
self.object:set_yaw(dir2yaw(dir))
|
|
|
|
animalia.move_head(self, dir2yaw(dir), dir.y)
|
|
|
|
creatura.action_idle(self, 0.4, "tongue_" .. frame)
|
|
|
|
|
|
|
|
minetest.remove_node(bug)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if bug_reached then return true, 10 end
|
|
|
|
animalia.action_walk(mob, 2, 0.5, "walk", bug)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
-- Opossum
|
|
|
|
|
|
|
|
local function grow_crop(crop)
|
|
|
|
local crop_name = minetest.get_node(crop).name
|
|
|
|
local growth_stage = tonumber(crop_name:sub(-1)) or 1
|
|
|
|
local new_name = crop_name:sub(1, #crop_name - 1) .. (growth_stage + 1)
|
|
|
|
local new_def = minetest.registered_nodes[new_name]
|
|
|
|
|
|
|
|
if new_def then
|
|
|
|
local p2 = new_def.place_param2 or 1
|
|
|
|
minetest.set_node(crop, {name = new_name, param2 = p2})
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:opossum_seek_crop", function(self)
|
|
|
|
local timeout = 12
|
|
|
|
|
|
|
|
local crop = animalia.find_crop(self)
|
|
|
|
local crop_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos or not crop then return true, 30 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, crop)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not crop_reached then
|
|
|
|
crop_reached = true
|
|
|
|
|
|
|
|
creatura.action_idle(mob, 1, "clean_crop")
|
|
|
|
grow_crop(crop)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if crop_reached then return true, 10 end
|
|
|
|
animalia.action_walk(mob, 2, 0.5, "walk", crop)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
-- Rat --
|
|
|
|
|
|
|
|
local function find_chest(self)
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
|
|
|
|
local nodes = minetest.find_nodes_with_meta(vec_sub(pos, 6), vec_add(pos, 6)) or {}
|
|
|
|
local pos2
|
|
|
|
for _, node_pos in ipairs(nodes) do
|
|
|
|
local meta = minetest.get_meta(node_pos)
|
|
|
|
if meta:get_string("owner") == "" then
|
|
|
|
local inv = minetest.get_inventory({type = "node", pos = node_pos})
|
|
|
|
if inv
|
|
|
|
and inv:get_list("main") then
|
|
|
|
pos2 = node_pos
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return pos2
|
|
|
|
end
|
|
|
|
|
|
|
|
local function take_food_from_chest(self, pos)
|
|
|
|
local inv = minetest.get_inventory({type = "node", pos = pos})
|
|
|
|
if inv
|
|
|
|
and inv:get_list("main") then
|
|
|
|
for i, stack in ipairs(inv:get_list("main")) do
|
|
|
|
local item_name = stack:get_name()
|
|
|
|
local def = minetest.registered_items[item_name]
|
|
|
|
for group in pairs(def.groups) do
|
|
|
|
if group:match("food_") then
|
|
|
|
stack:take_item()
|
|
|
|
inv:set_stack("main", i, stack)
|
|
|
|
animalia.add_food_particle(self, item_name)
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:rat_seek_chest", function(self)
|
|
|
|
local timeout = 12
|
|
|
|
|
|
|
|
local chest = find_chest(self)
|
|
|
|
local chest_reached = false
|
|
|
|
local function func(mob)
|
|
|
|
local pos = mob.object:get_pos()
|
|
|
|
if not pos or not chest then return true, 30 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, chest)
|
|
|
|
if dist < mob.width + 0.5
|
|
|
|
and not chest_reached then
|
|
|
|
chest_reached = true
|
|
|
|
|
|
|
|
creatura.action_idle(mob, 1, "eat")
|
|
|
|
take_food_from_chest(mob, chest)
|
|
|
|
end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
if chest_reached then return true, 10 end
|
|
|
|
animalia.action_walk(mob, 2, 0.5, "walk", chest)
|
|
|
|
end
|
|
|
|
|
|
|
|
timeout = timeout - mob.dtime
|
|
|
|
if timeout <= 0 then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
-- Tamed --
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:tamed_idle", function(self)
|
|
|
|
local function func(mob)
|
|
|
|
if not mob.owner or mob.order ~= "stay" then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
2024-01-04 21:25:51 -08:00
|
|
|
creatura.action_idle(mob, 1)
|
2024-01-04 11:15:02 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
creatura.register_utility("animalia:tamed_follow_owner", function(self, player)
|
|
|
|
local function func(mob)
|
|
|
|
local owner = player or (mob.owner and minetest.get_player_by_name(mob.owner))
|
|
|
|
if not owner then return true end
|
|
|
|
|
|
|
|
local pos, target_pos = mob.object:get_pos(), owner:get_pos()
|
|
|
|
if not pos or not target_pos then return true end
|
|
|
|
|
|
|
|
if not mob:get_action() then
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
|
|
|
|
if dist > mob.width + 1 then
|
|
|
|
animalia.action_pursue(mob, owner)
|
|
|
|
else
|
2024-01-04 21:25:51 -08:00
|
|
|
creatura.action_idle(mob, 1)
|
2024-01-04 11:15:02 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self:set_utility(func)
|
|
|
|
end)
|
|
|
|
|
|
|
|
------------
|
|
|
|
-- Mob AI --
|
|
|
|
-------------
|
|
|
|
|
|
|
|
animalia.mob_ai = {}
|
|
|
|
|
|
|
|
animalia.mob_ai.basic_wander = {
|
|
|
|
utility = "animalia:basic_wander",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
return 0.1, {self}
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.basic_flee = {
|
|
|
|
utility = "animalia:basic_flee",
|
|
|
|
get_score = function(self)
|
|
|
|
local puncher = self._puncher
|
|
|
|
if puncher
|
|
|
|
and puncher:get_pos() then
|
|
|
|
return 0.6, {self, puncher}
|
|
|
|
end
|
|
|
|
self._puncher = nil
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.basic_breed = {
|
|
|
|
utility = "animalia:basic_breed",
|
|
|
|
get_score = function(self)
|
|
|
|
if self.breeding
|
|
|
|
and animalia.get_nearby_mate(self, self.name) then
|
|
|
|
return 0.6, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.basic_attack = {
|
|
|
|
utility = "animalia:basic_attack",
|
|
|
|
get_score = function(self)
|
|
|
|
return animalia.get_attack_score(self, self.attack_list)
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.basic_seek_crop = {
|
|
|
|
utility = "animalia:basic_seek_crop",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if random(8) < 2 then
|
|
|
|
return 0.2, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.basic_seek_food = {
|
|
|
|
utility = "animalia:basic_seek_food",
|
|
|
|
get_score = function(self)
|
|
|
|
if random(1) < 8 then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Fly
|
|
|
|
|
|
|
|
animalia.mob_ai.fly_wander = {
|
|
|
|
utility = "animalia:fly_wander",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
return 0.1, {self}
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.fly_landing_wander = {
|
|
|
|
utility = "animalia:fly_wander",
|
|
|
|
get_score = function(self)
|
|
|
|
if self.is_landed then
|
|
|
|
local player = creatura.get_nearby_player(self)
|
|
|
|
if player then
|
|
|
|
self.is_landed = self:memorize("is_landed", false)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not self.is_landed
|
|
|
|
or self.in_liquid then
|
|
|
|
return 0.2, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.fly_seek_food = {
|
|
|
|
utility = "animalia:fly_seek_food",
|
|
|
|
get_score = function(self)
|
|
|
|
if random(8) < 2 then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.fly_seek_land = {
|
|
|
|
utility = "animalia:fly_seek_land",
|
|
|
|
get_score = function(self)
|
|
|
|
if self.is_landed
|
|
|
|
and not self.touching_ground
|
|
|
|
and not self.in_liquid
|
|
|
|
and creatura.sensor_floor(self, 3, true) > 2 then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Swim
|
|
|
|
|
|
|
|
animalia.mob_ai.swim_seek_land = {
|
|
|
|
utility = "animalia:swim_seek_land",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if self.in_liquid then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.swim_wander = {
|
|
|
|
utility = "animalia:swim_wander",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
return 0.1, {self}
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Tamed
|
|
|
|
|
|
|
|
animalia.mob_ai.tamed_follow_owner = {
|
|
|
|
utility = "animalia:tamed_follow_owner",
|
|
|
|
get_score = function(self)
|
|
|
|
if self.owner
|
|
|
|
and self.order == "follow" then
|
|
|
|
return 0.4, {self}
|
|
|
|
end
|
|
|
|
|
|
|
|
local lasso_holder = type(self._lassod_to) == "string" and minetest.get_player_by_name(self._lassod_to)
|
|
|
|
local player = lasso_holder or creatura.get_nearby_player(self)
|
|
|
|
|
|
|
|
if lasso_holder
|
|
|
|
or self:follow_wielded_item(player) then
|
|
|
|
return 0.4, {self, player}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.tamed_stay = {
|
|
|
|
utility = "animalia:basic_idle",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
local order = self.order or "wander"
|
|
|
|
if order == "sit" then
|
|
|
|
return 0.5, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Bat
|
|
|
|
|
|
|
|
animalia.mob_ai.bat_seek_home = {
|
|
|
|
utility = "animalia:fly_seek_home",
|
|
|
|
get_score = function(self)
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
local home = animalia.is_day and self.home_position
|
|
|
|
if (home
|
|
|
|
and home.x
|
|
|
|
and vec_dist(pos, home) < 8)
|
|
|
|
or self.is_landed then
|
|
|
|
return 0.4, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Cat
|
|
|
|
|
|
|
|
animalia.mob_ai.cat_seek_vessel = {
|
|
|
|
utility = "animalia:cat_seek_vessel",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if random(8) < 2 then
|
|
|
|
return 0.2, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.cat_follow_owner = {
|
|
|
|
utility = "animalia:cat_follow_owner",
|
|
|
|
get_score = function(self)
|
|
|
|
local trust = (self.owner and self.trust[self.owner]) or 0
|
|
|
|
|
|
|
|
if trust
|
|
|
|
and trust > 4
|
|
|
|
and self.order == "follow" then
|
|
|
|
return 0.4, {self}
|
|
|
|
end
|
|
|
|
|
|
|
|
local lasso_holder = type(self._lassod_to) == "string" and minetest.get_player_by_name(self._lassod_to)
|
|
|
|
|
|
|
|
if lasso_holder then
|
|
|
|
return 0.6, {self, lasso_holder}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.cat_stay = {
|
|
|
|
utility = "animalia:basic_idle",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
local trust = (self.owner and self.trust[self.owner]) or 0
|
|
|
|
if trust < 5 then return 0 end
|
|
|
|
|
|
|
|
local order = self.order or "wander"
|
|
|
|
if order == "sit" then
|
|
|
|
return 0.5, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
animalia.mob_ai.cat_play_with_owner = {
|
|
|
|
utility = "animalia:cat_play_with_owner",
|
|
|
|
get_score = function(self)
|
|
|
|
local trust = (self.owner and self.trust[self.owner]) or 0
|
|
|
|
|
|
|
|
if trust
|
|
|
|
and trust > 1
|
|
|
|
and random(4) < 2 then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
-- Eagle
|
|
|
|
|
|
|
|
animalia.mob_ai.eagle_attack = {
|
|
|
|
utility = "animalia:eagle_attack",
|
|
|
|
get_score = function(self)
|
|
|
|
if random(12) > 1
|
|
|
|
and (self:get_utility() or "") ~= "animalia:eagle_attack" then
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
|
|
|
|
local target = self._target or creatura.get_nearby_object(self, {"animalia:rat", "animalia:song_bird"})
|
|
|
|
local tgt_pos = target and target:get_pos()
|
|
|
|
if tgt_pos then
|
|
|
|
return 0.4, {self, target}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Fox
|
|
|
|
|
|
|
|
animalia.mob_ai.fox_flee = {
|
|
|
|
utility = "animalia:basic_flee",
|
|
|
|
get_score = function(self)
|
|
|
|
local target = self._puncher or creatura.get_nearby_player(self)
|
|
|
|
local pos, target_pos = self.object:get_pos(), target and target:get_pos()
|
|
|
|
if not pos or not target_pos then self._puncher = nil return 0 end
|
|
|
|
|
|
|
|
local dist = vec_dist(pos, target_pos)
|
|
|
|
local score = ((self.tracking_range - dist) / self.tracking_range) * 0.5
|
|
|
|
|
|
|
|
if target:get_player_control().sneak then score = score * 0.5 end
|
|
|
|
|
|
|
|
self._puncher = target
|
|
|
|
return score, {self, target}
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Frog
|
|
|
|
|
|
|
|
animalia.mob_ai.frog_breed = {
|
|
|
|
utility = "animalia:basic_breed",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if self.breeding
|
|
|
|
and animalia.get_nearby_mate(self, self.name)
|
|
|
|
and self.in_liquid then
|
|
|
|
return 1, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.frog_flop = {
|
|
|
|
utility = "animalia:basic_idle",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if not self.in_liquid
|
|
|
|
and self.growth_scale < 0.8 then
|
|
|
|
return 1, {self, 1, "flop"}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.frog_seek_water = {
|
|
|
|
utility = "animalia:basic_seek_pos",
|
|
|
|
get_score = function(self)
|
|
|
|
if self.in_liquid then return 0 end
|
|
|
|
|
|
|
|
local pos = self.object:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
|
|
|
|
local water = minetest.find_nodes_in_area(vec_sub(pos, 3), vec_add(pos, 3), {"group:water"})
|
|
|
|
if not water[1] then return 0 end
|
|
|
|
|
|
|
|
local player = self._target
|
|
|
|
local plyr_name = player and player:is_player() and player:get_player_name()
|
|
|
|
|
|
|
|
if plyr_name then
|
|
|
|
local plyr_pos = player and player:get_pos()
|
|
|
|
local trust = self.trust[plyr_name] or 0
|
|
|
|
return (10 - (vec_dist(pos, plyr_pos) + trust)) * 0.1, {self, water[1]}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
animalia.mob_ai.frog_seek_bug = {
|
|
|
|
utility = "animalia:frog_seek_bug",
|
|
|
|
get_score = function(self)
|
|
|
|
if random(8) < 2 then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
-- Opossum
|
|
|
|
|
|
|
|
animalia.mob_ai.opossum_feint = {
|
|
|
|
utility = "animalia:basic_idle",
|
|
|
|
get_score = function(self)
|
|
|
|
local target = self._puncher or creatura.get_nearby_player(self)
|
|
|
|
local pos, tgt_pos = self.object:get_pos(), target and target:get_pos()
|
|
|
|
if not pos then return end
|
|
|
|
if not tgt_pos then self._puncher = nil return 0 end
|
|
|
|
local sneaking = target:get_player_control().sneak
|
|
|
|
if not sneaking then
|
|
|
|
local dist = vec_dist(pos, tgt_pos)
|
|
|
|
local score = (self.tracking_range - dist) / self.tracking_range
|
|
|
|
self._puncher = target
|
|
|
|
return score / 3, {self, 5, "feint"}
|
|
|
|
end
|
|
|
|
self._puncher = nil
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-01-10 17:30:54 -08:00
|
|
|
animalia.mob_ai.opossum_seek_crop = {
|
|
|
|
utility = "animalia:opossum_seek_crop",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if random(8) < 2 then
|
|
|
|
return 0.4, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2024-01-04 11:15:02 -08:00
|
|
|
-- Rat
|
|
|
|
|
|
|
|
animalia.mob_ai.rat_seek_chest = {
|
|
|
|
utility = "animalia:rat_seek_chest",
|
|
|
|
step_delay = 0.25,
|
|
|
|
get_score = function(self)
|
|
|
|
if random(8) < 2 then
|
|
|
|
return 0.3, {self}
|
|
|
|
end
|
|
|
|
return 0
|
|
|
|
end
|
|
|
|
}
|