water_life/behaviors.lua
2022-07-10 12:38:00 +02:00

1275 lines
34 KiB
Lua

local abs = math.abs
local pi = math.pi
local floor = math.floor
local ceil = math.ceil
local sqrt = math.sqrt
local max = math.max
local min = math.min
local pow = math.pow
local sign = math.sign
local time = os.time
local rad = math.rad
local random = water_life.random
local deg=math.deg
local tan = math.tan
local cos = math.cos
local atan=math.atan
-- flying stuff
local function chose_turn(self,a,b)
local remember = mobkit.recall(self,"turn")
if not remember then
if water_life.leftorright() then
remember = "1"
mobkit.remember(self,"time", self.time_total)
mobkit.remember(self,"turn", "1")
else
remember = "0"
mobkit.remember(self,"time", self.time_total)
mobkit.remember(self,"turn", "0")
end
end
if a > b then
mobkit.remember(self,"turn", "1")
mobkit.remember(self,"time", self.time_total)
return false
elseif a < b then
mobkit.remember(self,"turn","0")
mobkit.remember(self,"time", self.time_total)
return true
else
if remember == "0" then
return true
else
return false
end
end
end
local function pitchroll2pitchyaw(aoa,roll)
if roll == 0.0 then
return aoa,0
end
-- assumed vector x=0,y=0,z=1
local p1 = tan(aoa)
local y = cos(roll)*p1
local x = sqrt(p1^2-y^2)
local pitch = atan(y)
local yaw=atan(x)*math.sign(roll)
return pitch,yaw
end
------------------
-- LQ behaviors --
------------------
--[[
params:
lift: [number]
multiplier for lift. faster objects need less, slower need more. typical value: 0.6 for speeds around 4 m/s
pitch: [degrees]
angle between the longitudinal axis and horizontal plane. typical range: <-15.15>
aoa:
[degrees] angle of attack - the angle between the longitudinal axis and velocity vector.
roll: [degrees]
bank angle. positive is right, negative is left, this is how they turn. if set too large they'll loose height rapidly
acc: [number]
propulsion. use with positive pitch to make them fly level or climb, set it to 0 with slight negative pitch to make
them glide. typical value: around 1.0
anim: [string]
animation.
The example uses two simple high level behaviors to keep them between 18 and 24 nodes above ground, seems good already for ambient type flying creatures.
warning: never set_velocity when using these behaviors.
]]
function water_life.lq_fly_aoa(self,lift,aoa,roll,acc,anim)
aoa=rad(aoa)
roll=rad(roll)
local hpitch = 0
local hyaw = 0
local caoa = 0
local laoa = nil
local croll=roll
local lroll = nil
local lastrot = nil
local init = true
local func=function(self)
local rotation=self.object:get_rotation()
local vel = self.object:get_velocity()
local vrot = mobkit.dir_to_rot(vel,lastrot)
lastrot = vrot
if init then
if anim then mobkit.animate(self,anim) end
init = false
end
local accel=self.object:get_acceleration()
-- gradual changes
if abs(roll-rotation.z) > 0.5*self.dtime then
croll = rotation.z+0.5*self.dtime*math.sign(roll-rotation.z)
end
if croll~=lroll then
hpitch,hyaw = pitchroll2pitchyaw(aoa,croll)
lroll = croll
end
local hrot = {x=vrot.x+hpitch,y=vrot.y-hyaw,z=croll}
self.object:set_rotation(hrot)
local hdir = mobkit.rot_to_dir(hrot)
local cross = vector.cross(vel,hdir)
local lift_dir = vector.normalize(vector.cross(cross,hdir))
local daoa = deg(aoa)
-- homegrown formula
local lift_coefficient = 0.24*abs(daoa)*(1/(0.025*daoa+1))^4*math.sign(aoa)
local lift_val = lift*vector.length(vel)^2*lift_coefficient
local lift_acc = vector.multiply(lift_dir,lift_val)
lift_acc=vector.add(vector.multiply(minetest.yaw_to_dir(rotation.y),acc),lift_acc)
self.object:set_acceleration(vector.add(accel,lift_acc))
end
mobkit.queue_low(self,func)
end
function water_life.lq_fly_pitch(self,lift,pitch,roll,acc,anim)
pitch = rad(pitch)
roll=rad(roll)
local cpitch = pitch
local croll = roll
local hpitch = 0
local hyaw = 0
local lpitch = nil
local lroll = nil
local lastrot = nil
local init = true
local func=function(self)
if init then
if anim then mobkit.animate(self,anim) end
init = false
end
local rotation=self.object:get_rotation()
local accel=self.object:get_acceleration()
local vel = self.object:get_velocity()
local speed = vector.length(vel)
local vdir = vector.normalize(vel)
local vrot = mobkit.dir_to_rot(vel,lastrot)
lastrot = vrot
-- gradual changes
if abs(roll-rotation.z) > 0.5*self.dtime then
croll = rotation.z+0.5*self.dtime*math.sign(roll-rotation.z)
end
if abs(pitch-rotation.x) > 0.5*self.dtime then
cpitch = rotation.x+0.5*self.dtime*math.sign(pitch-rotation.x)
end
if cpitch~=lpitch or croll~=lroll then
hpitch,hyaw = pitchroll2pitchyaw(cpitch,croll)
lpitch = cpitch lroll = croll
end
-- angle of attack
local aoa = deg(-vrot.x+cpitch)
-- hull rotation
local hrot = {x=hpitch, y=vrot.y-hyaw, z=croll}
self.object:set_rotation(hrot)
-- hull dir
local hdir = mobkit.rot_to_dir(hrot)
local cross = vector.cross(hdir,vel)
local lift_dir = vector.normalize(vector.cross(hdir,cross))
-- homegrown formula
local lift_coefficient = 0.24*max(aoa,0)*(1/(0.025*max(aoa,0)+1))^4
local lift_val = min(lift*speed^2*lift_coefficient,20)
local lift_acc = vector.multiply(lift_dir,lift_val)
lift_acc=vector.add(vector.multiply(minetest.yaw_to_dir(rotation.y),acc),lift_acc)
accel=vector.add(accel,lift_acc)
-- drag
accel=vector.add(accel,vector.multiply(vdir,-speed*speed*0.02))
-- propeller
accel=vector.add(accel,vector.multiply(hdir,acc))
self.object:set_acceleration(accel)
end
mobkit.queue_low(self,func)
end
function water_life.lq_dumbjump(self,height,anim)
anim = anim or 'stand'
local jump = true
local func=function(self)
local yaw = self.object:get_yaw()
if jump then
mobkit.animate(self,anim)
local dir = minetest.yaw_to_dir(yaw)
dir.y = -mobkit.gravity*sqrt((height+0.35)*2/-mobkit.gravity)
self.object:set_velocity(dir)
jump = false
else -- the eagle has landed
return true
end
end
mobkit.queue_low(self,func)
end
function water_life.lq_dumbwalk(self,dest,speed_factor)
local timer = 3 -- failsafe
speed_factor = speed_factor or 1
local func=function(self)
mobkit.animate(self,'walk')
timer = timer - self.dtime
if timer < 0 then return true end
local pos = mobkit.get_stand_pos(self)
local y = self.object:get_velocity().y
local dir = vector.normalize(vector.direction({x=pos.x,y=0,z=pos.z},
{x=dest.x,y=0,z=dest.z}))
dir = vector.multiply(dir,self.max_speed*speed_factor)
mobkit.turn2yaw(self,minetest.dir_to_yaw(dir))
dir.y = y
self.object:set_velocity(dir)
end
mobkit.queue_low(self,func)
end
function water_life.lq_jumpattack(self,height,target,extra)
local phase=1
local timer=0.5
local tgtbox = target:get_properties().collisionbox
local func=function(self)
local selfname = self.object:get_luaentity().name
if not mobkit.is_alive(target) then return true end
if self.isonground then
if phase==1 then -- collision bug workaround
local vel = self.object:get_velocity()
vel.y = -mobkit.gravity*sqrt(height*2/-mobkit.gravity)
self.object:set_velocity(vel)
mobkit.make_sound(self,'charge')
phase=2
else
mobkit.lq_idle(self,0.3)
return true
end
elseif phase==2 then
local dir = minetest.yaw_to_dir(self.object:get_yaw())
local vy = self.object:get_velocity().y
dir=vector.multiply(dir,6)
dir.y=vy
self.object:set_velocity(dir)
phase=3
elseif phase==3 then -- in air
local tgtpos = target:get_pos()
local pos = self.object:get_pos()
-- calculate attack spot
local yaw = self.object:get_yaw()
local dir = minetest.yaw_to_dir(yaw)
local apos = mobkit.pos_translate2d(pos,yaw,self.attack.range)
if mobkit.is_pos_in_box(apos,tgtpos,tgtbox) then --bite
target:punch(self.object,1,self.attack)
if selfname and target:is_player() then
if selfname == "water_life:snake" then
local meta = target:get_meta()
local name = target:get_player_name()
local join = meta:get_int("jointime")
if not join or
(os.time() - join) >
water_life.newplayerbonus * 86400 then
meta:set_int("snakepoison",1)
water_life.change_hud(target,"poison")
else
local left = water_life.newplayerbonus -
math.floor((os.time() - join) /
86400 * 100) / 100
minetest.chat_send_player(
target:get_player_name(),
minetest.colorize('#fd4000',
">>> A rattlesnake bit you. New player"..
"bonus of "..left.. " days left. Catch "..
"3 snakes to craft antiserum"))
meta:set_int("bitten", 1)
minetest.after(10,function()
meta:set_int("bitten",0)
end,meta)
end
end
end
-- bounce off
local vy = self.object:get_velocity().y
self.object:set_velocity({x=dir.x*-3,y=vy,z=dir.z*-3})
mobkit.make_sound(self,'attack')
phase=4
end
end
end
mobkit.queue_low(self,func)
end
------------------
-- HQ behaviors --
------------------
-- on land only, go to tgt and remove it
function water_life.hq_catch_drop(self,prty,tgt)
local func = function(self)
if self.isinliquid then return true end
if not tgt then return true end
if mobkit.is_queue_empty_low(self) then
local pos = mobkit.get_stand_pos(self)
local tpos = tgt:get_pos()
if pos and tpos then
local dist = vector.distance(pos,tpos)
if dist < 2 then
tgt:remove()
return true
else
if pos.y +0.5 >= tpos.y then
water_life.lq_dumbwalk(self,tpos,0.1)
else
water_life.lq_dumbjump(self,1)
end
end
else
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_aquaidle(self,prty,anim)
local init = true
if not anim then anim = 'def' end
local func = function(self)
if init then
mobkit.animate(self,anim)
self.object:set_velocity({x=0,y=0,z=0})
init = false
end
if self.name == "water_life:alligator" then
if random(100) < 5 then
mobkit.animate(self,'roll')
end
end
end
mobkit.queue_high(self,func,prty)
end
-- same as mobkit.hq_aqua_turn but for large mobs
function water_life.big_hq_aqua_turn(self,prty,tyaw,speed)
local func = function(self)
if not speed then speed = 0.4 end
if speed < 0 then speed = speed * -1 end
local finished=mobkit.turn2yaw(self,tyaw,speed)
if finished then return true end
end
mobkit.queue_high(self,func,prty)
end
-- same as mobkit.hq_aqua_roam but for large mobs
function water_life.big_aqua_roam(self,prty,speed,anim)
local tyaw = 0
local init = true
local prvscanpos = {x=0,y=0,z=0}
local center = self.object:get_pos()
if not anim then anim = 'def' end
local func = function(self)
if init then
mobkit.animate(self,anim)
init = false
end
local pos = mobkit.get_stand_pos(self)
local yaw = self.object:get_yaw()
local scanpos = mobkit.get_node_pos(mobkit.pos_translate2d(pos,yaw,speed))
if not vector.equals(prvscanpos,scanpos) then
prvscanpos=scanpos
local nyaw,height = water_life.aqua_radar_dumb(pos,yaw,speed,true)
if height and height > pos.y then
local vel = self.object:get_velocity()
vel.y = vel.y+0.1
self.object:set_velocity(vel)
end
if yaw ~= nyaw then
tyaw=nyaw
mobkit.hq_aqua_turn(self,prty+1,tyaw,speed)
return
end
end
if mobkit.timer(self,10) then
if vector.distance(pos,center) > water_life.abr*16*0.5 then
tyaw = minetest.dir_to_yaw(vector.direction(pos,
{x=center.x+random()*10-5,y=center.y,
z=center.z+random()*10-5}))
else
if random(10)>=9 then tyaw=tyaw+random()*pi - pi*0.5 end
end
end
if mobkit.timer(self,20) then mobkit.turn2yaw(self,tyaw,-1) end
mobkit.go_forward_horizontal(self,speed)
end
mobkit.queue_high(self,func,prty)
end
-- this is the same as mobkit's, but allows movement in shallow water
function water_life.hq_aqua_roam(self,prty,speed,anim)
if not anim then anim = "def" end
local tyaw = 0
local init = true
local prvscanpos = {x=0,y=0,z=0}
local center = self.object:get_pos()
local func = function(self)
if init then
mobkit.animate(self,anim)
init = false
end
local pos = mobkit.get_stand_pos(self)
local yaw = self.object:get_yaw()
local scanpos = mobkit.get_node_pos(mobkit.pos_translate2d(pos,yaw,speed))
if not vector.equals(prvscanpos,scanpos) then
prvscanpos=scanpos
local nyaw,height = water_life.aqua_radar_dumb(pos,yaw,speed,true,true)
if height and height > pos.y then
local vel = self.object:get_velocity()
vel.y = vel.y+1
self.object:set_velocity(vel)
end
if yaw ~= nyaw then
tyaw=nyaw
mobkit.hq_aqua_turn(self,prty+1,tyaw,speed)
return
end
end
if mobkit.timer(self,1) then
if vector.distance(pos,center) > water_life.abr*16*0.5 then
tyaw = minetest.dir_to_yaw(vector.direction(
pos,{x=center.x+random()*10-5,y=center.y,
z=center.z+random()*10-5}))
else
if random(10)>=9 then tyaw=tyaw+random()*pi - pi*0.5 end
end
end
mobkit.turn2yaw(self,tyaw,3)
mobkit.go_forward_horizontal(self,speed)
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_attack(self,prty,tgtobj)
local func = function(self)
if self.isinliquid then return true end
if not mobkit.is_alive(tgtobj) then return true end
if mobkit.is_queue_empty_low(self) then
local meta = nil
local poison = 0
local noob = 0
local pos = mobkit.get_stand_pos(self)
local tpos = mobkit.get_stand_pos(tgtobj)
local dist = vector.distance(pos,tpos)
if tgtobj:is_player() then
meta = tgtobj:get_meta()
poison = meta:get_int("snakepoison") or 1
noob = meta:get_int("bitten") or 1
else
poison = 1
noob = 1
end
if (dist and dist > 3) or poison > 0 or noob > 0 then
return true
else
mobkit.lq_turn2pos(self,tpos)
local height = tgtobj:is_player() and 0.35 or
tgtobj:get_luaentity().height*0.6
if tpos.y+height>pos.y then
mobkit.make_sound(self,"attack")
water_life.lq_jumpattack(self,tpos.y+height-pos.y,tgtobj)
else
mobkit.lq_dumbwalk(self,mobkit.pos_shift(tpos,
{x=random()-0.5,z=random()-0.5}))
end
end
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_hunt(self, prty, tgtobj, lost, anim)
if not lost then lost = self.view_range end
if random(100) < 20 then mobkit.make_sound(self,"attack") end
local func = function(self)
if not mobkit.is_alive(tgtobj) or not tgtobj then return true end
if self.isinliquid then return true end
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
local dist = vector.distance(pos,opos)
local meta = nil
local poison = 0
local noob = 0
if tgtobj:is_player() then
meta = tgtobj:get_meta()
poison = meta:get_int("snakepoison")
noob = meta:get_int("bitten")
end
if poison > 0 or noob > 0 then return true end
if mobkit.is_in_deep(tgtobj) then
return true
end
if dist > lost or math.abs(pos.y - opos.y) > 5 then
return true
elseif dist > 3 then
water_life.goto_next_waypoint(self,opos)
else
water_life.hq_attack(self,prty+1,tgtobj)
end
end
end
mobkit.queue_high(self,func,prty)
end
-- slowly roam on land, breaks are taken with max of 120 seconds
function water_life.hq_slow_roam(self,prty,idle)
if not idle then idle = random(30,120) end
local func=function(self)
if self.isinliquid then return true end
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
local neighbor = random(8)
local height, tpos, liquidflag = mobkit.is_neighbor_node_reachable(
self,neighbor)
if height and not liquidflag then
mobkit.dumbstep(self,height,tpos,0.1,idle)
end
end
end
mobkit.queue_high(self,func,prty)
end
--find any water nearby and go into it
function water_life.hq_go2water(self,prty,speed)
local pos = mobkit.get_stand_pos(self)
local target = minetest.find_node_near(pos, self.view_range, {"group:water"})
if not speed then speed = 0.1 end
local func=function(self)
if self.isinliquid or not target then return true end
if mobkit.is_queue_empty_low(self) and self.isonground then
pos = mobkit.get_stand_pos(self)
local height = target.y - pos.y
water_life.dumbstep(self,height,target,speed,0)
end
end
mobkit.queue_high(self,func,prty)
end
-- looks for a landing point on shore under air. tgt is optional
-- and must be an object, so it will start searching yaw2tgt - 15 degrees
function water_life.hq_go2land(self,prty,tgt)
local init = false
local offset = 1
local target = nil
local start = 1
if tgt then
local ayaw = water_life.get_yaw_to_object(self,tgt)
if ayaw then start = math.deg(ayaw) -15 end
end
local func = function(self)
local fpos = nil
local pos = mobkit.get_stand_pos(self)
if not init then
for i = start,359,15 do
local yaw = rad(i)
target = mobkit.pos_translate2d(pos,yaw,self.view_range)
fpos = water_life.find_collision(pos,target,false)
if fpos then
target = mobkit.pos_translate2d(pos,yaw,fpos+0.5)
local node=minetest.get_node({x=target.x,y=target.y+1,
z=target.z})
if node.name == "air" then
break
else
target = nil
end
else
target = nil
end
end
init = true
end
if self.isonground then return true end
if target then
local y=self.object:get_velocity().y
local pos2d = {x=pos.x,y=0,z=pos.z}
local dir=vector.normalize(vector.direction(pos2d,target))
local yaw = minetest.dir_to_yaw(dir)
if mobkit.timer(self,1) then
local pos1 = mobkit.pos_shift(mobkit.pos_shift(pos,
{x=-dir.z*offset,z=dir.x*offset}),dir)
local h,l = mobkit.get_terrain_height(pos1)
if h and h>pos.y then
mobkit.lq_freejump(self)
else
local pos2 = mobkit.pos_shift(mobkit.pos_shift(pos,
{x=dir.z*offset,z=-dir.x*offset}),dir)
local h,l = mobkit.get_terrain_height(pos2)
if h and h>pos.y then
mobkit.lq_freejump(self)
end
end
elseif mobkit.turn2yaw(self,yaw) then
dir.y = y
self.object:set_velocity(dir)
end
else
return true
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_snail_move(self,prty)
local ground = mobkit.get_stand_pos(self)
local coraltable = minetest.find_nodes_in_area({x=ground.x-3, y=ground.y-1,
z=ground.z-3}, {x=ground.x+3, y=ground.y, z=ground.z+3}, water_life.urchinspawn)
if not coraltable or #coraltable < 1 then return end
local tgpos = coraltable[random(#coraltable)]
local func = function(self)
if not mobkit.is_alive(self) then return true end
local pos = mobkit.get_stand_pos(self)
local dist = vector.distance(pos,tgpos)
mobkit.drive_to_pos(self,tgpos,0.01,0.1,1.5)
if dist <= 1.8 then return true end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_idle(self,prty,duration,anim)
anim = anim or 'stand'
local init = true
local func=function(self)
if init then
mobkit.animate(self,anim)
init=false
end
duration = duration-self.dtime
if duration <= 0 then return true end
end
mobkit.queue_high(self,func,prty)
end
-- swim to the next "node" which is inside viewrange or quit -- node can be string or table of string
-- if tgtpos is given node will be ignored
function water_life.hq_swimto(self,prty,speed,node,tgtpos)
local endpos = tgtpos
local pos = self.object:get_pos()
local r = self.view_range
if not tgtpos then
endpos = minetest.find_node_near(pos, r, node)
end
if not endpos then return true end
local func = function(self)
local yaw = water_life.get_yaw_to_pos(self,endpos)
if not mobkit.is_alive(self) then return true end
local pos = self.object:get_pos()
if mobkit.timer(self,1) then
if vector.distance(pos,endpos) > 1 then
if endpos.y > pos.y then
local vel = self.object:get_velocity()
vel.y = vel.y+0.4
self.object:set_velocity(vel)
end
if endpos.y < pos.y then
local vel = self.object:get_velocity()
vel.y = vel.y-0.1
self.object:set_velocity(vel)
end
mobkit.hq_aqua_turn(self,prty+5,yaw,speed)
pos = self.object:get_pos()
yaw = water_life.get_yaw_to_pos(self,endpos)
else
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
-- turn around 180degrees from tgtob and swim away until out of sight
function water_life.hq_swimfrom(self,prty,tgtobj,speed,outofsight)
local init = true
local func = function(self)
if not outofsight then outofsight = self.view_range * 1.5 end
if not mobkit.is_alive(tgtobj) then return true end
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
local yaw = water_life.get_yaw_to_object(self,tgtobj) +
math.rad(random(-30,30))+math.rad(180)
local distance = vector.distance(pos,opos)
if self.isonground then return true end
if init then
mobkit.animate(self,"swim")
init=false
end
if distance < outofsight then
local swimto, height = water_life.aqua_radar_dumb(pos,yaw,3)
if height and height > pos.y then
local vel = self.object:get_velocity()
vel.y = vel.y+0.1
self.object:set_velocity(vel)
end
mobkit.hq_aqua_turn(self,51,swimto,speed)
else
return true
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_water_attack(self,tgtobj,prty,speed,shallow)
local pos = self.object:get_pos()
local selfbox = self.object:get_properties().collisionbox
local tgtbox = tgtobj:get_properties().collisionbox
if not speed then speed = 1 end
local func = function(self)
if not mobkit.is_alive(self) or not mobkit.is_alive(tgtobj) or
tgtobj:get_attach() ~= nil then
return true
end
local pos = self.object:get_pos()
local endpos = tgtobj:get_pos()
if not shallow then
if not mobkit.is_in_deep(tgtobj) and vector.distance (pos,endpos) > 2 then
return true
end
else
if not water_life.isinliquid(tgtobj) and vector.distance (pos,endpos) > 2 then
return true
end
end
local yaw = water_life.get_yaw_to_pos(self,endpos)
local entity = nil
if not tgtobj:is_player() then entity = tgtobj:get_luaentity() end
if vector.distance(pos,endpos) > selfbox[5]+tgtbox[5] then
if endpos.y > pos.y+selfbox[5] then
local vel = vector.add(self.object:get_velocity(),{x=0,y=0.5,z=0})
self.object:set_velocity(vel)
end
if endpos.y < pos.y-selfbox[5] then
local vel = vector.add(self.object:get_velocity(),{x=0,y=-0.5,z=0})
self.object:set_velocity(vel)
end
mobkit.hq_aqua_turn(self,prty+5,yaw,speed)
else
if mobkit.is_alive(tgtobj) then
tgtobj:punch(self.object,1,self.attack)
return true
else
return true
end
end
if entity and string.match(entity.name,"petz") and vector.distance(pos,endpos) < 2 then
if mobkit.is_alive(tgtobj) then
mobkit.hurt(entity,self.attack.damage_groups.fleshy or 4)
else
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
-- hq flying behaviors
function water_life.hq_climb(self,prty,fmin,fmax)
if not max then max = 30 end
if not min then min = 20 end
local func=function(self)
if mobkit.timer(self,1) then
local remember = mobkit.recall(self,"time")
if remember then
if self.time_total - remember > 15 then
mobkit.forget(self,"turn")
mobkit.forget(self,"time")
end
end
self.action = "fly"
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local left, right, up, down, under, above = water_life.radar(pos,yaw,32,true)
if (down < 3) and (under >= fmax) then
water_life.hq_glide(self,prty,fmin,fmax)
return true
end
if left > 3 or right > 3 then
local lift = 0.6
local pitch = 8
local roll = 6
local acc = 1.2
roll = (max(left,right)/30 * 7.5)
lift = lift + (down - up) /400
pitch = pitch + (down - up) /30
local turn = chose_turn(self,left,right)
if turn then
mobkit.clear_queue_low(self)
water_life.lq_fly_pitch(self,lift,pitch,roll*-1,acc,'fly')
else
mobkit.clear_queue_low(self)
water_life.lq_fly_pitch(self,lift,pitch,roll,acc,'fly')
end
end
end
if mobkit.timer(self,15)then
mobkit.clear_queue_low(self)
end
if mobkit.is_queue_empty_low(self) then
water_life.lq_fly_pitch(self,0.6,8,(random(2)-1.5)*30,1.2,'fly')
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_glide(self,prty,fmin,fmax)
if not max then fmax = 30 end
if not min then fmin = 20 end
local func = function(self)
if mobkit.timer(self,1) then
self.action = "glide"
local remember = mobkit.recall(self,"time")
if remember then
if self.time_total - remember > 15 then
mobkit.forget(self,"turn")
mobkit.forget(self,"time")
end
end
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local left, right, up, down, under, above = water_life.radar(pos,yaw,32,true)
if (down > 10) or (under < fmin) then
water_life.hq_climb(self,prty,fmin,fmax)
return true
end
if left > 3 or right > 3 then
local lift = 0.6
local pitch = 8
local roll = 0
local acc = 1.2
roll = (max(left,right)/30 *7.5)
local turn = chose_turn(self,left,right)
if turn then
mobkit.clear_queue_low(self)
water_life.lq_fly_pitch(self,lift,pitch,roll*-1,acc,'glide')
else
mobkit.clear_queue_low(self)
water_life.lq_fly_pitch(self,lift,pitch,roll,acc,'glide')
end
end
end
if mobkit.timer(self,20) then
mobkit.clear_queue_low(self)
end
if mobkit.is_queue_empty_low(self) then
water_life.lq_fly_pitch(self,0.6,-4,(random(2)-1.5)*30,0,'glide')
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_water_takeoff(self,prty,anim,tyaw)
local init = true
local startup = true
local turned = false
local timer = 0
if not anim then anim = 'def' end
local pos2 = {}
local func=function(self)
local yaw = self.object:get_yaw()
local pos = self.object:get_pos()
if startup then
if tyaw then yaw = tyaw end
for i = 0,330,30 do
pos2 = mobkit.pos_translate2d(pos,yaw+rad(i),self.view_range*2)
if not water_life.find_collision(pos,pos2,false) then
tyaw = yaw + rad(i)
break
end
end
startup = false
end
if not startup then
if not tyaw then return true end
if mobkit.turn2yaw(self,tyaw,5) then turned = true end
end
if turned then
if init then
self.object:set_velocity({x=0, y=0.5, z=0})
minetest.after(2,function()
mobkit.animate(self,anim)
end)
init = false
end
minetest.after(4,function()
mobkit.animate(self,'fly')
end)
if timer > 3 then
self.object:add_velocity({x=0,y=0.25,z=0})
end
mobkit.go_forward_horizontal(self,3)
timer = timer + self.dtime
if timer > 8 then
local vec = vector.multiply(minetest.yaw_to_dir(tyaw),2)
vec.y = vec.y + 4
self.object:add_velocity(vec)
mobkit.remember(self,"airlife",os.time())
mobkit.forget(self,"landlife")
mobkit.forget(self,"waterlife")
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_fly2obj(self,prty,tgt,break_dist,force)
local func=function(self)
if not break_dist then break_dist = 5 end
if not tgt then
mobkit.clear_queue_high(self)
water_life.hq_climb(self,prty)
return true
end
local wname = ""
local roll = 0
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local tgtpos = tgt:get_pos()
if not tgtpos then return true end
local tgtyaw = tgt:get_yaw()
local tgtspeed = math.floor(vector.length(tgt:get_velocity() or {x=0,y=0,z=0}))
if not tgt:is_player() and tgt:get_luaentity() and
tgt:get_luaentity().name == "water_life:whale" then
tgtyaw = tgtyaw + rad(180) end -- whales moving backwards XD
if tgt:is_player() then
tgtyaw = tgt:get_look_horizontal()
local stack = tgt:get_wielded_item()
wname = stack:get_name()
end
if tgtpos.y < 0 then tgtpos.y = 1 end
if not water_life.gull_bait[wname] and not force then
mobkit.clear_queue_high(self)
mobkit.clear_queue_low(self)
water_life.hq_climb(self,15,4,16)
return true
end
if not tgtyaw or not tgtspeed or not mobkit.is_alive(tgt) or
self.isonground or self.isinliquid then
mobkit.clear_queue_high(self)
water_life.hq_climb(self,prty)
return true
end
local turn = 0
local diff = 0
local lift = 1.2
local pitch = 5
local acc = 0.6
local anim = "fly"
local truetpos=mobkit.pos_translate2d(tgtpos,tgtyaw,tgtspeed*3)
local ddistance = vector.distance(pos,{x=truetpos.x,y= pos.y, z=truetpos.z})
local alpha = atan((pos.y - truetpos.y)/ ddistance)
local truetyaw = water_life.get_yaw_to_pos(self,truetpos)
local realdistance = vector.distance(pos,tgtpos)
local ang2tgt = mobkit.pos_translate2d(pos,truetyaw,15)
if yaw < truetyaw then
turn = -1
elseif yaw > truetyaw then
turn = 1
end
diff = abs(truetyaw - yaw)
if ddistance > 30 and diff <= 0.5 then
turn = 0
end
if ddistance > 20 and diff <= 0.3 then
turn = 0
end
if diff <= 0.1 then
turn = 0
end
if ddistance > 32 then
roll = 15 * turn
elseif ddistance > 22 then
roll = 10 * turn
elseif ddistance > 12 then
roll = 5 * turn
elseif ddistance <= 12 then
roll = 2 * turn
end
if pos.y > truetpos.y + 1 and pos.y > 2 and ddistance < 25 then
anim = "glide"
pitch = -10
elseif pos.y < truetpos.y - 1 then
pitch = 15
else
pitch = 5
end
local left,right,center,up,down = water_life.radar_fast(self,20)
if down and down < 16 then pitch = 15 end
if up and not down and not center then pitch = -10 end
if right and not center then roll = 10 end
if left and not center then roll = -10 end
if left and right and not center then roll = 0 end
if center and down and center < 5 then
mobkit.clear_queue_high(self)
mobkit.clear_queue_low(self)
water_life.hq_climb(self,15,4,16)
return true
end
if water_life.radar_debug then
water_life.temp_show(ang2tgt,1)
for i = 1,10,1 do
water_life.temp_show({x=truetpos.x, y=truetpos.y+i*2,
z=truetpos.z},1)
end
end
mobkit.clear_queue_low(self)
if realdistance < break_dist+0.5 then
if tgt:is_player() then
local bstack = tgt:get_wielded_item()
local bait = bstack:get_name()
if water_life.gull_bait[bait] then
bstack:take_item(1)
tgt:set_wielded_item(bstack)
end
end
mobkit.clear_queue_high(self)
mobkit.clear_queue_low(self)
water_life.hq_climb(self,15,4,16)
return true
else
water_life.lq_fly_pitch(self,lift,pitch,roll,acc,anim)
end
end
mobkit.queue_high(self,func,prty)
end
--snakes
function water_life.hq_snake_warn(self,target,prty,duration,anim)
anim = anim or 'warn'
local init = true
local func=function(self)
local dist = 100
if init then
mobkit.make_sound(self,"warn")
minetest.after(1,function(anim)
mobkit.animate(self,anim)
end,anim)
init=false
end
if not target then
return true
end
local yaw = water_life.get_yaw_to_object(self,target)
if (not target:get_pos()) then
return true
else
dist = water_life.dist2tgt(self,target)
end
self.object:set_yaw(yaw)
duration = duration-self.dtime
if dist > self.view_range then
minetest.after(3,function()
return true
end)
end
if duration <= 0 or dist < 4 then
mobkit.remember(self,"warned",target:get_player_name())
return true
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_snake_move(self,prty,anim)
anim = anim or 'look'
local init = true
local getpos = nil
local func=function(self)
local getpos = nil
local pos = mobkit.get_stand_pos(self)
local yaw = 0
if init then
mobkit.animate(self,anim)
init=false
yaw = rad(random(360))
pos = mobkit.pos_translate2d(pos,yaw,self.view_range+5)
getpos = water_life.find_node_under_air(pos,self.view_range)
end
if getpos then
water_life.hq_idle(self,prty+2,5,anim)
water_life.hq_findpath(self,prty+1,getpos, 1.5,0.1,true)
return true
else
return true
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_snakerun(self,prty,tgtobj)
local init=true
local timer=6
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if self.isinliquid then return true end
if init then
timer = timer-self.dtime
if timer <=0 or vector.distance(self.object:get_pos(),
tgtobj:get_pos()) < 8 then
mobkit.make_sound(self,'scared')
init=false
end
return
end
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
if vector.distance(pos,opos) < (self.view_range*3) then
local tpos = {x=2*pos.x - opos.x,y=opos.y,z=2*pos.z - opos.z}
water_life.goto_next_waypoint(self,tpos)
else
mobkit.clear_queue_low(self)
mobkit.clear_queue_high(self)
water_life.hq_snake_move(self,15)
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function water_life.hq_runfrom(self,prty,tgtobj)
local init=true
local timer=6
local func = function(self)
if self.isinliquid then return true end
if not mobkit.is_alive(tgtobj) then return true end
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
if vector.distance(pos,opos) < self.view_range*1.1 then
local tpos = {x=2*pos.x - opos.x,y=opos.y,z=2*pos.z - opos.z}
mobkit.goto_next_waypoint(self,tpos)
else
self.object:set_velocity({x=0,y=0,z=0})
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
-- dying
function water_life.hq_die(self,anim)
local timer = 5
local start = true
local func = function(self)
if start then
if not anim then
mobkit.lq_fallover(self)
else
mobkit.animate(self,anim)
end
self.logic = function(self) end
start=false
end
timer = timer-self.dtime
if timer < 0 then self.object:remove() end
end
mobkit.queue_high(self,func,100)
end