344 lines
16 KiB
Lua

-- Behavior functions for land animals
-- Velocity reduction when dead and not in a liquid
local DEATH_DRAG = 0.05
-- Velocity reduction when dead and in a liquid
local DEATH_DRAG_LIQUID = 0.2
-- Time range (ms) to continue walking after escaping damaging nodes
local FINALIZE_ESCAPE_MIN = 500
local FINALIZE_ESCAPE_MAX = 1000
local random_yaw = function()
return math.random(0, 360) / 360 * (math.pi*2)
end
-- Decider functions for random roaming behavior (and following)
local create_roam_decider_start = function(settings)
return function(task_queue, mob)
-- Initial task to stand still
local yaw = random_yaw()
rp_mobs_mobs.add_halt_to_task_queue(task_queue, mob, yaw, settings.idle_duration_min, settings.idle_duration_max)
end
end
local create_roam_decider_empty = function(settings)
return function(task_queue, mob)
local task_roam
local mt_sleep = rp_mobs.microtasks.sleep(math.random(settings.idle_duration_min, settings.idle_duration_max)/1000)
mt_sleep.start_animation = "idle"
if mob._env_node.name == "ignore" then
task_roam = rp_mobs.create_task({label="stand still"})
rp_mobs.add_microtask_to_task(mob, mt_sleep, task_roam)
elseif rp_mobs_mobs.is_damaging(mob._env_node.name) then
task_roam = rp_mobs.create_task({label="escape from damaging node"})
local yaw
-- Find direction to walk to
local safepos, safeangle = rp_mobs_mobs.find_safe_node_from_pos(mob.object:get_pos(), settings.find_land_length)
local walk_duration
-- Prefer walking towards safe place
if safepos and safeangle then
yaw = safeangle
walk_duration = math.random(settings.find_land_duration_min, settings.find_land_duration_max)/1000
else
-- If no safe place found, walk randomly (panic!)
yaw = random_yaw()
walk_duration = math.random(settings.walk_duration_min, settings.walk_duration_max)/1000
end
local mt_walk = rp_mobs.microtasks.walk_straight(settings.walk_speed, yaw, nil, nil, false, walk_duration)
local mt_acceleration = rp_mobs.microtasks.set_acceleration(rp_mobs.GRAVITY_VECTOR)
local mt_yaw = rp_mobs.microtasks.set_yaw(yaw)
mt_walk.start_animation = "walk"
rp_mobs.add_microtask_to_task(mob, mt_yaw, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_walk, task_roam)
elseif rp_mobs_mobs.is_liquid(mob._env_node.name) then
task_roam = rp_mobs.create_task({label="swim upwards"})
local yaw = random_yaw()
local mt_yaw = rp_mobs.microtasks.set_yaw(yaw)
local move_vector = vector.new(0, settings.liquid_rise_speed, 0)
local mt_swim_up = rp_mobs.microtasks.move_straight(move_vector, yaw, vector.new(2, 0.3, 2))
local mt_acceleration = rp_mobs.microtasks.set_acceleration(vector.zero())
rp_mobs.add_microtask_to_task(mob, mt_yaw, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_swim_up, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_sleep, task_roam)
elseif rp_mobs_mobs.is_liquid(mob._env_node_floor.name) then
task_roam = rp_mobs.create_task({label="swim on liquid surface"})
local yaw
-- Find direction to walk to
local landpos, landangle = rp_mobs_mobs.find_land_from_liquid(mob.object:get_pos(), settings.find_land_length)
local walk_duration
-- Prefer walking towards land. Mob wants to stay dry. ;-)
if landpos and landangle then
-- towards land
yaw = landangle
walk_duration = math.random(settings.find_safe_land_duration_min, settings.find_safe_land_duration_max)/1000
else
-- If no land found, go randomly on water
yaw = random_yaw()
walk_duration = math.random(settings.walk_duration_min, settings.walk_duration_max)/1000
end
local mt_walk = rp_mobs.microtasks.walk_straight(settings.walk_speed, yaw, settings.jump_strength, settings.jump_clear_height, true, walk_duration)
local mt_yaw = rp_mobs.microtasks.set_yaw(yaw)
local mt_acceleration = rp_mobs.microtasks.set_acceleration(vector.zero())
mt_walk.start_animation = "walk"
rp_mobs.add_microtask_to_task(mob, mt_yaw, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_walk, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_sleep, task_roam)
else
task_roam = rp_mobs.create_task({label="roam land"})
local yaw = random_yaw()
local walk_duration = math.random(settings.walk_duration_min, settings.walk_duration_max)/1000
local mt_walk = rp_mobs.microtasks.walk_straight(settings.walk_speed, yaw, settings.jump_strength, settings.jump_clear_height, true, walk_duration)
local mt_yaw = rp_mobs.microtasks.set_yaw(yaw)
local mt_acceleration = rp_mobs.microtasks.set_acceleration(rp_mobs.GRAVITY_VECTOR)
mt_walk.start_animation = "walk"
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_yaw, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_walk, task_roam)
rp_mobs.add_microtask_to_task(mob, mt_sleep, task_roam)
end
rp_mobs.add_task_to_task_queue(task_queue, task_roam)
end
end
local create_roam_decider_step = function(settings)
return function(task_queue, mob, dtime)
-- Re-enable following after a few seconds
if mob._temp_custom_state.no_follow then
mob._temp_custom_state.no_follow_timer = mob._temp_custom_state.no_follow_timer + dtime
if mob._temp_custom_state.no_follow_timer > settings.no_follow_time then
mob._temp_custom_state.no_follow = false
mob._temp_custom_state.no_follow_timer = 0
end
end
if mob._env_node then
local current = task_queue.tasks:getFirst()
if current and current.data then
-- Escape from damaging node has reached safety
if current.data.label == "escape from damaging node" then
local damaging = rp_mobs_mobs.is_damaging(mob._env_node.name)
local liquid = rp_mobs_mobs.is_liquid(mob._env_node.name)
if not damaging or liquid then
-- End escape task
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
if not liquid then
-- Continue walking for a few steps so the mob is in a safe distance
-- from the danger
local task = rp_mobs.create_task({label="roam land"})
local yaw = mob.object:get_yaw()
local walk_duration = math.random(FINALIZE_ESCAPE_MIN, FINALIZE_ESCAPE_MAX)/1000
local mt_walk = rp_mobs.microtasks.walk_straight(settings.walk_speed, yaw, settings.jump_strength, settings.jump_clear_height, true, walk_duration)
mt_walk.start_animation = "walk"
local mt_acceleration = rp_mobs.microtasks.set_acceleration(rp_mobs.GRAVITY_VECTOR)
local mt_sleep = rp_mobs.microtasks.sleep(math.random(settings.idle_duration_min, settings.idle_duration_max)/1000)
mt_sleep.start_animation = "idle"
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task)
rp_mobs.add_microtask_to_task(mob, mt_walk, task)
rp_mobs.add_microtask_to_task(mob, mt_sleep, task)
rp_mobs.add_task_to_task_queue(task_queue, task)
end
end
-- Stop following player or partner if gone
elseif (current.data.label == "follow player holding food" and not mob._temp_custom_state.closest_food_player) or
(current.data.label == "hunt player" and not mob._temp_custom_state.angry_at) or
(current.data.label == "follow mating partner" and not mob._temp_custom_state.closest_mating_partner) then
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
rp_mobs_mobs.add_halt_to_task_queue(task_queue, mob, nil, settings.idle_duration_min, settings.idle_duration_max)
-- Update land movement (roam, standing, following)
-- Note: The follow tasks are all considered to be land movement.
-- There is no following while swimming!
elseif current.data.label == "roam land" or current.data.label == "stand still" or
current.data.label == "follow player holding food" or current.data.label == "follow mating partner" or
current.data.label == "hunt player" then
-- Abort when in damaging or liquid node
if rp_mobs_mobs.is_damaging(mob._env_node.name) or rp_mobs_mobs.is_liquid(mob._env_node.name) then
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
-- Abort and stop movement when walking towards of a cliff or other dangerous node
elseif not rp_mobs_mobs.is_front_safe(mob, settings.fall_height, settings.max_fall_damage_add_percent_drop_on) and current.data.label ~= "stand still" then
local vel = mob.object:get_velocity()
vel.y = 0
if vector.length(vel) > 0.01 then
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
rp_mobs_mobs.add_halt_to_task_queue(task_queue, mob, nil, settings.idle_duration_min, settings.idle_duration_max)
-- Disable following for a few seconds if mob just avoided a danger
if current.data.label == "follow player holding food" or current.data.label == "follow mating partner" or current.data.label == "hunt player" then
mob._temp_custom_state.no_follow = true
mob._temp_custom_state.no_follow_timer = 0
end
end
-- Follow player or mating partner
elseif (mob._temp_custom_state.closest_mating_partner or mob._temp_custom_state.closest_food_player or mob._temp_custom_state.angry_at) and not mob._temp_custom_state.no_follow then
local target, task_label
-- If horny, following mating partner
if mob._horny and mob._temp_custom_state.closest_mating_partner then
if mob._temp_custom_state.closest_mating_partner:get_luaentity() then
target = mob._temp_custom_state.closest_mating_partner
task_label = "follow mating partner"
end
end
if mob._temp_custom_state.closest_player then
-- Hunt player
if mob._temp_custom_state.angry_at then
local player = mob._temp_custom_state.angry_at
if player and player:is_player() then
target = player
task_label = "hunt player"
end
-- Follow player holding food only if not horny
elseif not mob._horny then
local player = mob._temp_custom_state.closest_food_player
if player then
target = player
task_label = "follow player holding food"
end
end
end
if target then
if task_label == current.data.label then
-- We're already doing this task - no change
return
end
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
local task = rp_mobs.create_task({label=task_label})
local mt_acceleration = rp_mobs.microtasks.set_acceleration(rp_mobs.GRAVITY_VECTOR)
rp_mobs.add_microtask_to_task(mob, mt_acceleration, task)
local speed, stop_at_reached, stop_at_object_collision
if task_label == "hunt player" then
speed = settings.hunt_speed or settings.walk_speed
stop_at_reached = settings.dogfight == true
stop_at_object_collision = false
else
speed = settings.walk_speed
stop_at_reached = true
stop_at_object_collision = true
end
local mt_follow = rp_mobs.microtasks.walk_straight_towards(speed, "object", target, true, settings.follow_reach_distance, settings.follow_max_distance, settings.jump_strength, settings.jump_clear_height, stop_at_reached, stop_at_object_collision, settings.follow_give_up_time)
if task_label == "hunt player" then
mt_follow.start_animation = "run"
else
mt_follow.start_animation = "walk"
end
rp_mobs.add_microtask_to_task(mob, mt_follow, task)
-- Dogfight, if enabled in settings
local dogfight = false
if settings.dogfight and task_label == "hunt player" then
dogfight = true
local mt_attack = rp_mobs_mobs.microtasks.dogfight(settings.dogfight_range, settings.dogfight_toolcaps, settings.dogfight_interval)
rp_mobs.add_microtask_to_task(mob, mt_attack, task)
end
if not dogfight then
local mt_sleep = rp_mobs.microtasks.sleep(math.random(settings.idle_duration_min, settings.idle_duration_max)/1000)
mt_sleep.start_animation = "idle"
rp_mobs.add_microtask_to_task(mob, mt_sleep, task)
end
rp_mobs.add_task_to_task_queue(task_queue, task)
end
end
-- Surface from liquid
elseif current.data.label == "swim upwards" then
if not rp_mobs_mobs.is_liquid(mob._env_node.name) then
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
local vel = vector.zero()
-- Transitionary task to reset velocity.
local task = rp_mobs.create_task({label="surface"})
rp_mobs.add_microtask_to_task(mob, rp_mobs.microtasks.move_straight(vel, mob.object:get_yaw()), task)
rp_mobs.add_task_to_task_queue(task_queue, task)
end
-- Reaching land or air when swimming on liquid
elseif current.data.label == "swim on liquid surface" then
if not rp_mobs_mobs.is_liquid(mob._env_node.name) and not rp_mobs_mobs.is_liquid(mob._env_node_floor.name) then
rp_mobs.end_current_task_in_task_queue(mob, task_queue)
rp_mobs_mobs.add_halt_to_task_queue(task_queue, mob, nil, settings.idle_duration_min, settings.idle_duration_max)
end
end
end
end
end
end
--[[ settings = {
idle_duration_min: Random idle duration in ms (minimum)
idle_duration_max: Random idle duration in ms (maximum)
find_land_duration_min: Random duration of finding land when on water, in ms (minimum)
find_land_duration_max: Random duration of finding land when on water, in ms (maximum)
find_safe_land_duration_min: Duration in ms to find safe land (non-damaging nodes) (minimum)
find_safe_land_duration_max: Duration in ms to find safe land (non-damaging nodes) (maximum)
walk_speed: How fast it walks
walk_duration_min: Random walk duration in ms (minimum)
walk_duration_max: Random walk duration in ms (maximum)
liquid_rise_speed: How fast it rises in a lquid
jump_strength: How strong it jumps
jump_clear_height: Up to how many nodes high it will try to jump to higher land
fall_height: Maximum fall height
max_fall_damage_add_percent_drop_on: When mob is about to decide to fall
on a block, this is the maximum fall_damage_add_percent group value
this node is allowed to have. Otherwise, the mob will avoid this node.
follow_reach_distance: When the mob is this far away from a follow target,
or closer, the target is supposed to be "reached" and the mob stops walking.
follow_max_distance: Maximum distance at which mob is willing to follow
follow_give_up_time: Stop following after this many seconds (if not reached first)
no_follow_time: If mob stops following due to danger, this is is the time (in ms)
that the mob will not follow anything after that happened.
]]
rp_mobs_mobs.task_queues.land_roam = function(settings)
local roam_decider_empty = create_roam_decider_empty(settings)
local roam_decider_step = create_roam_decider_step(settings)
local roam_decider_start = create_roam_decider_start(settings)
local tq = rp_mobs.create_task_queue(roam_decider_empty, roam_decider_step, roam_decider_start)
return tq
end
-- Returns a default dying_step functionn for `rp_mobs.handle_dying`.
-- If enabled, this can toggle gravity in liquids climbable nodes,
-- and put the mob smoothly to a halt using drag.
-- This assumes that `rp_mobs.scan_environment` is used.
-- Parameters:
-- * `can_swim`: turn off gravity in swimmable nodes
-- * `can_climb`: turn off gravity in climbable nodes
rp_mobs_mobs.get_dying_step = function(can_swim, can_climb)
return function(mob, dtime)
if not mob._env_node then
return
end
local drag, drag_axes
local grav = mob._temp_custom_state.dying_gravity == true
if (can_swim and (rp_mobs_mobs.is_liquid(mob._env_node.name) or rp_mobs_mobs.is_liquid(mob._env_node_floor.name)))
or (can_climb and (rp_mobs_mobs.is_climbable(mob._env_node.name) or rp_mobs_mobs.is_climbable(mob._env_node_floor.name))) then
-- Make mob come to a halt horizontally and vertically
drag = vector.new(DEATH_DRAG, DEATH_DRAG_LIQUID, DEATH_DRAG)
drag_axes = { "x", "y", "z" }
if grav then
mob.object:set_acceleration(vector.zero())
mob._temp_custom_state.dying_gravity = false
end
else
-- Make mob come to a halt horizontally
drag = vector.new(DEATH_DRAG, 0, DEATH_DRAG)
drag_axes = { "x", "z" }
if not grav then
mob.object:set_acceleration(rp_mobs.GRAVITY_VECTOR)
mob._temp_custom_state.dying_gravity = true
end
end
rp_mobs.drag(mob, dtime, drag, drag_axes)
end
end