237 lines
7.7 KiB
Lua
237 lines
7.7 KiB
Lua
-- When trying to find a safe spot, the mob makes multiple raycasts
|
|
-- from the mob all around the mob horizontally. This number is
|
|
-- the angle difference in degrees between each ray.
|
|
local FIND_LAND_ANGLE_STEP = 15
|
|
|
|
rp_mobs_mobs.microtasks = {}
|
|
rp_mobs_mobs.tasks = {}
|
|
rp_mobs_mobs.task_queues = {}
|
|
|
|
-- Returns true if the given node (by node name) is a liquid
|
|
rp_mobs_mobs.is_liquid = function(nodename)
|
|
local ndef = minetest.registered_nodes[nodename]
|
|
return ndef and (ndef.liquid_move_physics == true or (ndef.liquid_move_physics == nil and ndef.liquidtype ~= "none"))
|
|
end
|
|
|
|
-- Returns true if node deals damage
|
|
rp_mobs_mobs.is_damaging = function(nodename)
|
|
local ndef = minetest.registered_nodes[nodename]
|
|
return ndef and ndef.damage_per_second > 0
|
|
end
|
|
|
|
-- Returns true if node is walkable
|
|
rp_mobs_mobs.is_walkable = function(nodename)
|
|
local ndef = minetest.registered_nodes[nodename]
|
|
return ndef and ndef.walkable
|
|
end
|
|
|
|
-- Returns true if node is climbable
|
|
rp_mobs_mobs.is_climbable = function(nodename)
|
|
local ndef = minetest.registered_nodes[nodename]
|
|
return ndef and ndef.climbable
|
|
end
|
|
|
|
-- Returns true if the node(s) in front of the mob are safe.
|
|
-- This is considered unsafe:
|
|
-- * damage_per_second > 0
|
|
-- * drowning > 0
|
|
-- * a drop, if greater than cliff_depth
|
|
-- * a drop on a node with high fall_damage_add_percent
|
|
--
|
|
-- Parameters:
|
|
-- * mob: Mob object to check
|
|
-- * cliff_depth: How deep the mob is allowed to fall
|
|
-- * max_fall_damage_add_percent_drop_on: (optional): If set, mob can
|
|
-- not fall on a node with a fall_damage_add_percent group that is higher or equal than this value
|
|
rp_mobs_mobs.is_front_safe = function(mob, cliff_depth, max_fall_damage_add_percent_drop_on)
|
|
local vel = mob.object:get_velocity()
|
|
vel.y = 0
|
|
local yaw = mob.object:get_yaw()
|
|
local dir = vector.normalize(vel)
|
|
if vector.length(dir) > 0.5 then
|
|
yaw = minetest.dir_to_yaw(dir)
|
|
else
|
|
yaw = mob.object:get_yaw()
|
|
dir = minetest.yaw_to_dir(yaw)
|
|
end
|
|
local pos = mob.object:get_pos()
|
|
if mob._front_body_point then
|
|
local fbp = table.copy(mob._front_body_point)
|
|
fbp = vector.rotate_around_axis(fbp, vector.new(0, 1, 0), yaw)
|
|
pos = vector.add(pos, fbp)
|
|
end
|
|
local pos_front = vector.add(pos, dir)
|
|
local node_front = minetest.get_node(pos_front)
|
|
local def_front = minetest.registered_nodes[node_front.name]
|
|
if def_front and (def_front.drowning > 0 or def_front.damage_per_second > 0) then
|
|
return false
|
|
end
|
|
if def_front and not def_front.walkable then
|
|
local safe_drop = false
|
|
for c=1, cliff_depth do
|
|
local cpos = vector.add(pos_front, vector.new(0, -c, 0))
|
|
local cnode = minetest.get_node(cpos)
|
|
local cdef = minetest.registered_nodes[cnode.name]
|
|
if not cdef then
|
|
-- Unknown node
|
|
return false
|
|
elseif cdef.drowning > 0 then
|
|
return false
|
|
elseif cdef.damage_per_second > 0 then
|
|
return false
|
|
elseif cdef.walkable then
|
|
-- Mob doesn't like to land on node with high fall damage addition
|
|
if max_fall_damage_add_percent_drop_on and c > 1 and minetest.get_item_group(cnode.name, "fall_damage_add_percent") >= max_fall_damage_add_percent_drop_on then
|
|
return false
|
|
else
|
|
safe_drop = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not safe_drop then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- This function helps the mob find safe land from a lake or ocean.
|
|
--
|
|
-- Assuming that pos is a position above a large body of
|
|
-- liquid (like a lake or ocean), this function can return
|
|
-- the (approximately) closest position of walkable land
|
|
-- from that position, up to a hardcoded maximum range.
|
|
--
|
|
--
|
|
-- Argument:
|
|
-- * pos: Start position
|
|
-- * find_land_length: How far the mob looks away for safe land (raycast length)
|
|
--
|
|
-- returns: <position>, <angle from position>
|
|
-- or nil, nil if no position found
|
|
rp_mobs_mobs.find_land_from_liquid = function(pos, find_land_length)
|
|
local startpos = table.copy(pos)
|
|
startpos.y = startpos.y - 1
|
|
local startnode = minetest.get_node(startpos)
|
|
if not rp_mobs_mobs.is_liquid(startnode.name) then
|
|
startpos.y = startpos.y - 1
|
|
end
|
|
local vec_y = vector.new(0, 1, 0)
|
|
local best_pos
|
|
local best_dist
|
|
local best_angle
|
|
for angle=0, 359, FIND_LAND_ANGLE_STEP do
|
|
local angle_rad = (angle/360) * (math.pi*2)
|
|
local vec = vector.new(0, 0, 1)
|
|
vec = vector.rotate_around_axis(vec, vec_y, angle_rad)
|
|
vec = vector.multiply(vec, find_land_length)
|
|
local rc = minetest.raycast(startpos, vector.add(startpos, vec), false, false)
|
|
for pt in rc do
|
|
if pt.type == "node" then
|
|
local dist = vector.distance(startpos, pt.under)
|
|
local up = vector.add(pt.under, vector.new(0, 1, 0))
|
|
local upnode = minetest.get_node(up)
|
|
if not best_dist or dist < best_dist then
|
|
-- Ignore if ray collided with overhigh selection boxes (kelp, seagrass, etc.)
|
|
if pt.intersection_point.y - 0.5 < pt.under.y and
|
|
-- Node above must be non-walkable
|
|
not rp_mobs_mobs.is_walkable(upnode.name) then
|
|
best_pos = up
|
|
best_dist = dist
|
|
local pos1 = vector.copy(startpos)
|
|
local pos2 = vector.copy(up)
|
|
pos1.y = 0
|
|
pos2.y = 0
|
|
best_angle = minetest.dir_to_yaw(vector.direction(pos1, pos2))
|
|
break
|
|
end
|
|
end
|
|
if rp_mobs_mobs.is_walkable(upnode.name) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return best_pos, best_angle
|
|
end
|
|
|
|
-- Arguments:
|
|
-- * pos: Start position
|
|
-- * find_land_length: How far the mob looks away for safe land (raycast length)
|
|
--
|
|
-- returns: <position>, <angle from position>
|
|
-- or nil, nil if no position found
|
|
rp_mobs_mobs.find_safe_node_from_pos = function(pos, find_land_length)
|
|
local startpos = table.copy(pos)
|
|
startpos.y = math.floor(startpos.y)
|
|
startpos.y = startpos.y - 1
|
|
local startnode = minetest.get_node(startpos)
|
|
local best_pos
|
|
local best_dist
|
|
local best_angle
|
|
local vec_y = vector.new(0, 1, 0)
|
|
for angle=0, 359, FIND_LAND_ANGLE_STEP do
|
|
local angle_rad = (angle/360) * (math.pi*2)
|
|
local vec = vector.new(0, 0, 1)
|
|
vec = vector.rotate_around_axis(vec, vec_y, angle_rad)
|
|
vec = vector.multiply(vec, find_land_length)
|
|
local rc = minetest.raycast(startpos, vector.add(startpos, vec), false, false)
|
|
for pt in rc do
|
|
if pt.type == "node" then
|
|
local floor = pt.under
|
|
local floornode = minetest.get_node(floor)
|
|
local up = vector.add(floor, vector.new(0, 1, 0))
|
|
local upnode = minetest.get_node(up)
|
|
if rp_mobs_mobs.is_walkable(floornode.name) then
|
|
if rp_mobs_mobs.is_walkable(upnode.name) then
|
|
break
|
|
elseif not rp_mobs_mobs.is_walkable(upnode.name) and not rp_mobs_mobs.is_damaging(upnode.name) then
|
|
local dist = vector.distance(startpos, floor)
|
|
if not best_dist or dist < best_dist then
|
|
best_pos = up
|
|
best_dist = dist
|
|
local pos1 = vector.copy(startpos)
|
|
local pos2 = vector.copy(up)
|
|
pos1.y = 0
|
|
pos2.y = 0
|
|
best_angle = minetest.dir_to_yaw(vector.direction(pos1, pos2))
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return best_pos, best_angle
|
|
end
|
|
|
|
-- Add a "stand still" task to the mob's task queue with
|
|
-- an optional yaw
|
|
rp_mobs_mobs.add_halt_to_task_queue = function(task_queue, mob, set_yaw, idle_min, idle_max)
|
|
local mt_sleep = rp_mobs.microtasks.sleep(math.random(idle_min, idle_max)/1000)
|
|
mt_sleep.start_animation = "idle"
|
|
local task = rp_mobs.create_task({label="stand still"})
|
|
local vel = mob.object:get_velocity()
|
|
vel.x = 0
|
|
vel.z = 0
|
|
local yaw
|
|
if not set_yaw then
|
|
yaw = mob.object:get_yaw()
|
|
else
|
|
yaw = set_yaw
|
|
end
|
|
local mt_yaw = rp_mobs.microtasks.set_yaw(yaw)
|
|
local mt_acceleration = rp_mobs.microtasks.set_acceleration(rp_mobs.GRAVITY_VECTOR)
|
|
|
|
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task)
|
|
if set_yaw then
|
|
rp_mobs.add_microtask_to_task(mob, mt_yaw, task)
|
|
end
|
|
rp_mobs.add_microtask_to_task(mob, rp_mobs.microtasks.move_straight(vel, yaw, vector.new(0.5,0,0.5), 1), task)
|
|
rp_mobs.add_microtask_to_task(mob, mt_sleep, task)
|
|
rp_mobs.add_task_to_task_queue(task_queue, task)
|
|
end
|
|
|
|
|