Compare commits

...

2 Commits

Author SHA1 Message Date
cora bdad4362b0 add mobkit techdemo (cows+wolves) 2022-02-13 15:16:49 +01:00
cora ff5fc89885 hard-disable natural spawning of old mobs 2022-02-13 15:06:39 +01:00
29 changed files with 3464 additions and 1 deletions

View File

@ -222,7 +222,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
--print(dump(biomes))
-- Do mobs spawn at all?
if not mobs_spawn then
if true or not mobs_spawn then --MOBSTUDY disable natural spawning of old mobs(redo)
return
end

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 TheTermos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,6 @@
# mobkit
Entity API for Minetest
This library is meant to be shared between mods</br>
Please do not write to the mobkit namespace ('mobkit' global table),</br>
nor include own copies of mobkit in your mods and modpacks.

View File

@ -0,0 +1,830 @@
local abs = math.abs
local pi = math.pi
local floor = math.floor
local ceil = math.ceil
local random = math.random
local sqrt = math.sqrt
local max = math.max
local min = math.min
local tan = math.tan
local pow = math.pow
local dbg = minetest.chat_send_all
local abr = tonumber(minetest.get_mapgen_setting('active_block_range')) or 3
local neighbors ={
{x=1,z=0},
{x=1,z=1},
{x=0,z=1},
{x=-1,z=1},
{x=-1,z=0},
{x=-1,z=-1},
{x=0,z=-1},
{x=1,z=-1}
}
function [yournamespace].dir2neighbor(dir)
dir.y=0
dir=vector.round(vector.normalize(dir))
for k,v in ipairs(neighbors) do
if v.x == dir.x and v.z == dir.z then return k end
end
return 1
end
function [yournamespace].neighbor_shift(neighbor,shift) -- int shift: minus is left, plus is right
return (8+neighbor+shift-1)%8+1
end
function [yournamespace].is_neighbor_node_reachable(self,neighbor) -- todo: take either number or pos
local offset = neighbors[neighbor]
local pos=mobkit.get_stand_pos(self)
local tpos = mobkit.get_node_pos(mobkit.pos_shift(pos,offset))
local recursteps = ceil(self.jump_height)+1
local height, liquidflag = mobkit.get_terrain_height(tpos,recursteps)
if height and abs(height-pos.y) <= self.jump_height then
tpos.y = height
height = height - pos.y
-- don't cut corners
if neighbor % 2 == 0 then -- diagonal neighbors are even
local n2 = neighbor-1 -- left neighbor never < 0
offset = neighbors[n2]
local t2 = mobkit.get_node_pos(mobkit.pos_shift(pos,offset))
local h2 = mobkit.get_terrain_height(t2,recursteps)
if h2 and h2 - pos.y > 0.02 then return end
n2 = (neighbor+1)%8 -- right neighbor
offset = neighbors[n2]
t2 = mobkit.get_node_pos(mobkit.pos_shift(pos,offset))
h2 = mobkit.get_terrain_height(t2,recursteps)
if h2 and h2 - pos.y > 0.02 then return end
end
-- check headroom
if tpos.y+self.height-pos.y > 1 then -- if head in next node above, else no point checking headroom
local snpos = mobkit.get_node_pos(pos)
local pos1 = {x=pos.x,y=snpos.y+1,z=pos.z} -- current pos plus node up
local pos2 = {x=tpos.x,y=tpos.y+self.height,z=tpos.z} -- target head pos
local nodes = mobkit.get_nodes_in_area(pos1,pos2,true)
for p,node in pairs(nodes) do
if snpos.x==p.x and snpos.z==p.z then
if node.name=='ignore' or node.walkable then return end
else
if node.name=='ignore' or
(node.walkable and mobkit.get_node_height(p)>tpos.y+0.001) then return end
end
end
end
return height, tpos, liquidflag
else
return
end
end
function [yournamespace].get_next_waypoint(self,tpos)
local pos = mobkit.get_stand_pos(self)
local dir=vector.direction(pos,tpos)
local neighbor = [yournamespace].dir2neighbor(dir)
local function update_pos_history(self,pos)
table.insert(self.pos_history,1,pos)
if #self.pos_history > 2 then table.remove(self.pos_history,#self.pos_history) end
end
local nogopos = self.pos_history[2]
local height, pos2, liquidflag = [yournamespace].is_neighbor_node_reachable(self,neighbor)
if height and not liquidflag
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
local heightl = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,-1))
if heightl and abs(heightl-height)<0.001 then
local heightr = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,1))
if heightr and abs(heightr-height)<0.001 then
dir.y = 0
local dirn = vector.normalize(dir)
local npos = mobkit.get_node_pos(mobkit.pos_shift(pos,neighbors[neighbor]))
local factor = abs(dirn.x) > abs(dirn.z) and abs(npos.x-pos.x) or abs(npos.z-pos.z)
pos2=mobkit.pos_shift(pos,{x=dirn.x*factor,z=dirn.z*factor})
end
end
update_pos_history(self,pos2)
return height, pos2
else
for i=1,3 do
-- scan left
local height, pos2, liq = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,-i*self.path_dir))
if height and not liq
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
update_pos_history(self,pos2)
return height,pos2
end
-- scan right
height, pos2, liq = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,i*self.path_dir))
if height and not liq
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
update_pos_history(self,pos2)
return height,pos2
end
end
--scan rear
height, pos2, liquidflag = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,4))
if height and not liquidflag
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
update_pos_history(self,pos2)
return height,pos2
end
end
-- stuck condition here
table.remove(self.pos_history,2)
self.path_dir = self.path_dir*-1 -- subtle change in pathfinding
end
function [yournamespace].get_next_waypoint_fast(self,tpos,nogopos)
local pos = mobkit.get_stand_pos(self)
local dir=vector.direction(pos,tpos)
local neighbor = [yournamespace].dir2neighbor(dir)
local height, pos2, liquidflag = [yournamespace].is_neighbor_node_reachable(self,neighbor)
if height and not liquidflag then
local fast = false
heightl = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,-1))
if heightl and abs(heightl-height)<0.001 then
heightr = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,1))
if heightr and abs(heightr-height)<0.001 then
fast = true
dir.y = 0
local dirn = vector.normalize(dir)
local npos = mobkit.get_node_pos(mobkit.pos_shift(pos,neighbors[neighbor]))
local factor = abs(dirn.x) > abs(dirn.z) and abs(npos.x-pos.x) or abs(npos.z-pos.z)
pos2=mobkit.pos_shift(pos,{x=dirn.x*factor,z=dirn.z*factor})
end
end
return height, pos2, fast
else
for i=1,4 do
-- scan left
height, pos2, liq = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,-i))
if height and not liq then return height,pos2 end
-- scan right
height, pos2, liq = [yournamespace].is_neighbor_node_reachable(self,[yournamespace].neighbor_shift(neighbor,i))
if height and not liq then return height,pos2 end
end
end
end
function [yournamespace].goto_next_waypoint(self,tpos)
local height, pos2 = [yournamespace].get_next_waypoint(self,tpos)
if not height then return false end
if height <= 0.01 then
local yaw = self.object:get_yaw()
local tyaw = minetest.dir_to_yaw(vector.direction(self.object:get_pos(),pos2))
if abs(tyaw-yaw) > 1 then
[yournamespace].lq_turn2pos(self,pos2)
end
[yournamespace].lq_dumbwalk(self,pos2)
else
[yournamespace].lq_turn2pos(self,pos2)
[yournamespace].lq_dumbjump(self,height)
end
return true
end
----------------------------
-- BEHAVIORS
----------------------------
-- LOW LEVEL QUEUE FUNCTIONS
----------------------------
function [yournamespace].lq_turn2pos(self,tpos)
local func=function(self)
local pos = self.object:get_pos()
return mobkit.turn2yaw(self,
minetest.dir_to_yaw(vector.direction(pos,tpos)))
end
mobkit.queue_low(self,func)
end
function [yournamespace].lq_idle(self,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_low(self,func)
end
function [yournamespace].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
if mobkit.is_there_yet2d(pos,minetest.yaw_to_dir(self.object:get_yaw()),dest) then
-- if mobkit.isnear2d(pos,dest,0.25) then
if not self.isonground or abs(dest.y-pos.y) > 0.1 then -- prevent uncontrolled fall when velocity too high
-- if abs(dest.y-pos.y) > 0.1 then -- isonground too slow for speeds > 4
self.object:set_velocity({x=0,y=y,z=0})
end
return true
end
if self.isonground then
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)
-- self.object:set_yaw(minetest.dir_to_yaw(dir))
mobkit.turn2yaw(self,minetest.dir_to_yaw(dir))
dir.y = y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
-- initial velocity for jump height h, v= a*sqrt(h*2/a) ,add 20%
function [yournamespace].lq_dumbjump(self,height,anim)
anim = anim or 'stand'
local jump = true
local func=function(self)
local yaw = self.object:get_yaw()
if self.isonground then
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
else
local dir = minetest.yaw_to_dir(yaw)
local vel = self.object:get_velocity()
if self.lastvelocity.y < 0.9 then
dir = vector.multiply(dir,3)
end
dir.y = vel.y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
function [yournamespace].lq_jumpout(self)
local phase = 1
local func=function(self)
local vel=self.object:get_velocity()
if phase == 1 then
vel.y=vel.y+5
self.object:set_velocity(vel)
phase = 2
else
if vel.y < 0 then return true end
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir.y=vel.y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
function [yournamespace].lq_freejump(self)
local phase = 1
local func=function(self)
local vel=self.object:get_velocity()
if phase == 1 then
vel.y=vel.y+6
self.object:set_velocity(vel)
phase = 2
else
if vel.y <= 0.01 then return true end
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir.y=vel.y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
function [yournamespace].lq_jumpattack(self,height,target)
local init=true
local timer=0.5
local tgtbox = target:get_properties().collisionbox
local func=function(self)
if not mobkit.is_alive(target) then return true end
if self.isonground then
if init then -- collision bug workaround
local vel = self.object:get_velocity()
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir=vector.multiply(dir,6)
dir.y = -mobkit.gravity*sqrt(height*2/-mobkit.gravity)
self.object:set_velocity(dir)
mobkit.make_sound(self,'charge')
init=false
else
[yournamespace].lq_idle(self,0.3)
return true
end
else
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)
-- bounce off
local vy = self.object:get_velocity().y
self.object:set_velocity({x=dir.x*-3,y=vy,z=dir.z*-3})
-- play attack sound if defined
mobkit.make_sound(self,'attack')
return true
end
end
end
mobkit.queue_low(self,func)
end
function [yournamespace].lq_fallover(self)
local zrot = 0
local init = true
local func=function(self)
if init then
local vel = self.object:get_velocity()
self.object:set_velocity(mobkit.pos_shift(vel,{y=1}))
mobkit.animate(self,'stand')
init = false
end
zrot=zrot+pi*0.05
local rot = self.object:get_rotation()
self.object:set_rotation({x=rot.x,y=rot.y,z=zrot})
if zrot >= pi*0.5 then return true end
end
mobkit.queue_low(self,func)
end
-----------------------------
-- HIGH LEVEL QUEUE FUNCTIONS
-----------------------------
function [yournamespace].dumbstep(self,height,tpos,speed_factor,idle_duration)
if height <= 0.001 then
[yournamespace].lq_turn2pos(self,tpos)
[yournamespace].lq_dumbwalk(self,tpos,speed_factor)
else
[yournamespace].lq_turn2pos(self,tpos)
[yournamespace].lq_dumbjump(self,height)
end
idle_duration = idle_duration or 6
[yournamespace].lq_idle(self,random(ceil(idle_duration*0.5),idle_duration))
end
function [yournamespace].hq_roam(self,prty)
local func=function(self)
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 = [yournamespace].is_neighbor_node_reachable(self,neighbor)
if height and not liquidflag then [yournamespace].dumbstep(self,height,tpos,0.3) end
end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_follow0(self,tgtobj) -- probably delete this one
local func = function(self)
if not 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) > 3 then
local neighbor = [yournamespace].dir2neighbor(vector.direction(pos,opos))
if not neighbor then return true end --temp debug
local height, tpos = [yournamespace].is_neighbor_node_reachable(self,neighbor)
if height then [yournamespace].dumbstep(self,height,tpos)
else
for i=1,4 do --scan left
height, tpos = [yournamespace].is_neighbor_node_reachable(self,(8+neighbor-i-1)%8+1)
if height then [yournamespace].dumbstep(self,height,tpos)
break
end --scan right
height, tpos = [yournamespace].is_neighbor_node_reachable(self,(neighbor+i-1)%8+1)
if height then [yournamespace].dumbstep(self,height,tpos)
break
end
end
end
else
[yournamespace].lq_idle(self,1)
end
end
end
mobkit.queue_high(self,func,0)
end
function [yournamespace].hq_follow(self,prty,tgtobj)
local func = function(self)
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) > 3 then
[yournamespace].goto_next_waypoint(self,opos)
else
[yournamespace].lq_idle(self,1)
end
end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_goto(self,prty,tpos)
local func = function(self)
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
if vector.distance(pos,tpos) > 3 then
[yournamespace].goto_next_waypoint(self,tpos)
else
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_runfrom(self,prty,tgtobj)
local init=true
local timer=6
local func = function(self)
if not mobkit.is_alive(tgtobj) 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*1.1 then
local tpos = {x=2*pos.x - opos.x,
y=opos.y,
z=2*pos.z - opos.z}
[yournamespace].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
function [yournamespace].hq_hunt(self,prty,tgtobj)
local func = function(self)
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()
local dist = vector.distance(pos,opos)
if dist > self.view_range then
return true
elseif dist > 3 then
[yournamespace].goto_next_waypoint(self,opos)
else
[yournamespace].hq_attack(self,prty+1,tgtobj)
end
end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_warn(self,prty,tgtobj)
local timer=0
local tgttime = 0
local init = true
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if init then
mobkit.animate(self,'stand')
init = false
end
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
local dist = vector.distance(pos,opos)
if dist > 11 then
return true
elseif dist < 4 or timer > 12 then -- too close man
-- mobkit.clear_queue_high(self)
mobkit.remember(self,'hate',tgtobj:get_player_name())
[yournamespace].hq_hunt(self,prty+1,tgtobj) -- priority
else
timer = timer+self.dtime
if mobkit.is_queue_empty_low(self) then
[yournamespace].lq_turn2pos(self,opos)
end
-- make noise in random intervals
if timer > tgttime then
mobkit.make_sound(self,'warn')
-- if self.sounds and self.sounds.warn then
-- minetest.sound_play(self.sounds.warn, {object=self.object})
-- end
tgttime = timer + 1.1 + random()*1.5
end
end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_die(self)
local timer = 5
local start = true
local func = function(self)
if start then
[yournamespace].lq_fallover(self)
self.logic = function(self) end -- brain dead as well
start=false
end
timer = timer-self.dtime
if timer < 0 then self.object:remove() end
end
mobkit.queue_high(self,func,100)
end
function [yournamespace].hq_attack(self,prty,tgtobj)
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if mobkit.is_queue_empty_low(self) then
local pos = mobkit.get_stand_pos(self)
-- local tpos = tgtobj:get_pos()
local tpos = mobkit.get_stand_pos(tgtobj)
local dist = vector.distance(pos,tpos)
if dist > 3 then
return true
else
[yournamespace].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
[yournamespace].lq_jumpattack(self,tpos.y+height-pos.y,tgtobj)
else
[yournamespace].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 [yournamespace].hq_liquid_recovery(self,prty) -- scan for nearest land
local radius = 1
local yaw = 0
local func = function(self)
if not self.isinliquid then return true end
local pos=self.object:get_pos()
local vec = minetest.yaw_to_dir(yaw)
local pos2 = mobkit.pos_shift(pos,vector.multiply(vec,radius))
local height, liquidflag = mobkit.get_terrain_height(pos2)
if height and not liquidflag then
[yournamespace].hq_swimto(self,prty,pos2)
return true
end
yaw=yaw+pi*0.25
if yaw>2*pi then
yaw = 0
radius=radius+1
if radius > self.view_range then
self.hp = 0
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_swimto(self,prty,tpos)
local box = self.object:get_properties().collisionbox
local cols = {}
local func = function(self)
if not self.isinliquid then
if self.isonground then return true end
return false
end
local pos = mobkit.get_stand_pos(self)
local y=self.object:get_velocity().y
local pos2d = {x=pos.x,y=tpos.y,z=pos.z}
local dir=vector.normalize(vector.direction(pos2d,tpos))
local yaw = minetest.dir_to_yaw(dir)
if mobkit.timer(self,1) then
cols = mobkit.get_box_displace_cols(pos,box,dir,1)
for _,p in ipairs(cols[1]) do
p.y=pos.y
local h,l = mobkit.get_terrain_height(p)
if h and h>pos.y and self.isinliquid then
[yournamespace].lq_freejump(self)
break
end
end
elseif mobkit.turn2yaw(self,yaw) then
dir.y = y
self.object:set_velocity(dir)
end
end
mobkit.queue_high(self,func,prty)
end
---------------------
-- AQUATIC
---------------------
-- MACROS
local function aqua_radar_dumb(pos,yaw,range,reverse)
range = range or 4
local function okpos(p)
local node = mobkit.nodeatpos(p)
if node then
if node.drawtype == 'liquid' then
local nodeu = mobkit.nodeatpos(mobkit.pos_shift(p,{y=1}))
local noded = mobkit.nodeatpos(mobkit.pos_shift(p,{y=-1}))
if (nodeu and nodeu.drawtype == 'liquid') or (noded and noded.drawtype == 'liquid') then
return true
else
return false
end
else
local h,l = mobkit.get_terrain_height(p)
if h then
local node2 = mobkit.nodeatpos({x=p.x,y=h+1.99,z=p.z})
if node2 and node2.drawtype == 'liquid' then return true, h end
else
return false
end
end
else
return false
end
end
local fpos = mobkit.pos_translate2d(pos,yaw,range)
local ok,h = okpos(fpos)
if not ok then
local ffrom, fto, fstep
if reverse then
ffrom, fto, fstep = 3,1,-1
else
ffrom, fto, fstep = 1,3,1
end
for i=ffrom, fto, fstep do
local ok,h = okpos(mobkit.pos_translate2d(pos,yaw+i,range))
if ok then return yaw+i,h end
ok,h = okpos(mobkit.pos_translate2d(pos,yaw-i,range))
if ok then return yaw-i,h end
end
return yaw+pi,h
else
return yaw, h
end
end
function [yournamespace].is_in_deep(target)
if not target then return false end
local nodepos = mobkit.get_stand_pos(target)
local node1 = mobkit.nodeatpos(nodepos)
nodepos.y=nodepos.y+1
local node2 = mobkit.nodeatpos(nodepos)
nodepos.y=nodepos.y-2
local node3 = mobkit.nodeatpos(nodepos)
if node1 and node2 and node3 and node1.drawtype=='liquid' and (node2.drawtype=='liquid' or node3.drawtype=='liquid') then
return true
end
end
-- HQ behaviors
function [yournamespace].hq_aqua_roam(self,prty,speed)
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,'def')
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 = aqua_radar_dumb(pos,yaw,speed,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
[yournamespace].hq_aqua_turn(self,prty+1,tyaw,speed)
return
end
end
if mobkit.timer(self,1) then
if vector.distance(pos,center) > 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)
-- local yaw = self.object:get_yaw()
mobkit.go_forward_horizontal(self,speed)
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_aqua_turn(self,prty,tyaw,speed)
local func = function(self)
local finished=mobkit.turn2yaw(self,tyaw)
-- local yaw = self.object:get_yaw()
mobkit.go_forward_horizontal(self,speed)
if finished then return true end
end
mobkit.queue_high(self,func,prty)
end
function [yournamespace].hq_aqua_attack(self,prty,tgtobj,speed)
local tyaw = 0
local prvscanpos = {x=0,y=0,z=0}
local init = true
local tgtbox = tgtobj:get_properties().collisionbox
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if init then
mobkit.animate(self,'fast')
mobkit.make_sound(self,'attack')
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 = aqua_radar_dumb(pos,yaw,speed*0.5)
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
[yournamespace].hq_aqua_turn(self,prty+1,tyaw,speed)
return
end
end
local tpos = tgtobj:get_pos()
local tyaw=minetest.dir_to_yaw(vector.direction(pos,tpos))
mobkit.turn2yaw(self,tyaw,3)
local yaw = self.object:get_yaw()
if mobkit.timer(self,1) then
if not [yournamespace].is_in_deep(tgtobj) then return true end
local vel = self.object:get_velocity()
if tpos.y>pos.y+0.5 then self.object:set_velocity({x=vel.x,y=vel.y+0.5,z=vel.z})
elseif tpos.y<pos.y-0.5 then self.object:set_velocity({x=vel.x,y=vel.y-0.5,z=vel.z}) end
end
if mobkit.is_pos_in_box(mobkit.pos_translate2d(pos,yaw,self.attack.range),tpos,tgtbox) then --bite
tgtobj:punch(self.object,1,self.attack)
[yournamespace].hq_aqua_turn(self,prty,yaw-pi,speed)
return true
end
mobkit.go_forward_horizontal(self,speed)
end
mobkit.queue_high(self,func,prty)
end

View File

@ -0,0 +1,830 @@
local abs = math.abs
local pi = math.pi
local floor = math.floor
local ceil = math.ceil
local random = math.random
local sqrt = math.sqrt
local max = math.max
local min = math.min
local tan = math.tan
local pow = math.pow
local dbg = minetest.chat_send_all
local abr = tonumber(minetest.get_mapgen_setting('active_block_range')) or 3
local neighbors ={
{x=1,z=0},
{x=1,z=1},
{x=0,z=1},
{x=-1,z=1},
{x=-1,z=0},
{x=-1,z=-1},
{x=0,z=-1},
{x=1,z=-1}
}
function mobkit.dir2neighbor(dir)
dir.y=0
dir=vector.round(vector.normalize(dir))
for k,v in ipairs(neighbors) do
if v.x == dir.x and v.z == dir.z then return k end
end
return 1
end
function mobkit.neighbor_shift(neighbor,shift) -- int shift: minus is left, plus is right
return (8+neighbor+shift-1)%8+1
end
function mobkit.is_neighbor_node_reachable(self,neighbor) -- todo: take either number or pos
local offset = neighbors[neighbor]
local pos=mobkit.get_stand_pos(self)
local tpos = mobkit.get_node_pos(mobkit.pos_shift(pos,offset))
local recursteps = ceil(self.jump_height)+1
local height, liquidflag = mobkit.get_terrain_height(tpos,recursteps)
if height and abs(height-pos.y) <= self.jump_height then
tpos.y = height
height = height - pos.y
-- don't cut corners
if neighbor % 2 == 0 then -- diagonal neighbors are even
local n2 = neighbor-1 -- left neighbor never < 0
offset = neighbors[n2]
local t2 = mobkit.get_node_pos(mobkit.pos_shift(pos,offset))
local h2 = mobkit.get_terrain_height(t2,recursteps)
if h2 and h2 - pos.y > 0.02 then return end
n2 = (neighbor+1)%8 -- right neighbor
offset = neighbors[n2]
t2 = mobkit.get_node_pos(mobkit.pos_shift(pos,offset))
h2 = mobkit.get_terrain_height(t2,recursteps)
if h2 and h2 - pos.y > 0.02 then return end
end
-- check headroom
if tpos.y+self.height-pos.y > 1 then -- if head in next node above, else no point checking headroom
local snpos = mobkit.get_node_pos(pos)
local pos1 = {x=pos.x,y=snpos.y+1,z=pos.z} -- current pos plus node up
local pos2 = {x=tpos.x,y=tpos.y+self.height,z=tpos.z} -- target head pos
local nodes = mobkit.get_nodes_in_area(pos1,pos2,true)
for p,node in pairs(nodes) do
if snpos.x==p.x and snpos.z==p.z then
if node.name=='ignore' or node.walkable then return end
else
if node.name=='ignore' or
(node.walkable and mobkit.get_node_height(p)>tpos.y+0.001) then return end
end
end
end
return height, tpos, liquidflag
else
return
end
end
function mobkit.get_next_waypoint(self,tpos)
local pos = mobkit.get_stand_pos(self)
local dir=vector.direction(pos,tpos)
local neighbor = mobkit.dir2neighbor(dir)
local function update_pos_history(self,pos)
table.insert(self.pos_history,1,pos)
if #self.pos_history > 2 then table.remove(self.pos_history,#self.pos_history) end
end
local nogopos = self.pos_history[2]
local height, pos2, liquidflag = mobkit.is_neighbor_node_reachable(self,neighbor)
if height and not liquidflag
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
local heightl = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,-1))
if heightl and abs(heightl-height)<0.001 then
local heightr = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,1))
if heightr and abs(heightr-height)<0.001 then
dir.y = 0
local dirn = vector.normalize(dir)
local npos = mobkit.get_node_pos(mobkit.pos_shift(pos,neighbors[neighbor]))
local factor = abs(dirn.x) > abs(dirn.z) and abs(npos.x-pos.x) or abs(npos.z-pos.z)
pos2=mobkit.pos_shift(pos,{x=dirn.x*factor,z=dirn.z*factor})
end
end
update_pos_history(self,pos2)
return height, pos2
else
for i=1,3 do
-- scan left
local height, pos2, liq = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,-i*self.path_dir))
if height and not liq
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
update_pos_history(self,pos2)
return height,pos2
end
-- scan right
height, pos2, liq = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,i*self.path_dir))
if height and not liq
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
update_pos_history(self,pos2)
return height,pos2
end
end
--scan rear
height, pos2, liquidflag = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,4))
if height and not liquidflag
and not (nogopos and mobkit.isnear2d(pos2,nogopos,0.1)) then
update_pos_history(self,pos2)
return height,pos2
end
end
-- stuck condition here
table.remove(self.pos_history,2)
self.path_dir = self.path_dir*-1 -- subtle change in pathfinding
end
function mobkit.get_next_waypoint_fast(self,tpos,nogopos)
local pos = mobkit.get_stand_pos(self)
local dir=vector.direction(pos,tpos)
local neighbor = mobkit.dir2neighbor(dir)
local height, pos2, liquidflag = mobkit.is_neighbor_node_reachable(self,neighbor)
if height and not liquidflag then
local fast = false
heightl = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,-1))
if heightl and abs(heightl-height)<0.001 then
heightr = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,1))
if heightr and abs(heightr-height)<0.001 then
fast = true
dir.y = 0
local dirn = vector.normalize(dir)
local npos = mobkit.get_node_pos(mobkit.pos_shift(pos,neighbors[neighbor]))
local factor = abs(dirn.x) > abs(dirn.z) and abs(npos.x-pos.x) or abs(npos.z-pos.z)
pos2=mobkit.pos_shift(pos,{x=dirn.x*factor,z=dirn.z*factor})
end
end
return height, pos2, fast
else
for i=1,4 do
-- scan left
height, pos2, liq = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,-i))
if height and not liq then return height,pos2 end
-- scan right
height, pos2, liq = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,i))
if height and not liq then return height,pos2 end
end
end
end
function mobkit.goto_next_waypoint(self,tpos)
local height, pos2 = mobkit.get_next_waypoint(self,tpos)
if not height then return false end
if height <= 0.01 then
local yaw = self.object:get_yaw()
local tyaw = minetest.dir_to_yaw(vector.direction(self.object:get_pos(),pos2))
if abs(tyaw-yaw) > 1 then
mobkit.lq_turn2pos(self,pos2)
end
mobkit.lq_dumbwalk(self,pos2)
else
mobkit.lq_turn2pos(self,pos2)
mobkit.lq_dumbjump(self,height)
end
return true
end
----------------------------
-- BEHAVIORS
----------------------------
-- LOW LEVEL QUEUE FUNCTIONS
----------------------------
function mobkit.lq_turn2pos(self,tpos)
local func=function(self)
local pos = self.object:get_pos()
return mobkit.turn2yaw(self,
minetest.dir_to_yaw(vector.direction(pos,tpos)))
end
mobkit.queue_low(self,func)
end
function mobkit.lq_idle(self,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_low(self,func)
end
function mobkit.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
if mobkit.is_there_yet2d(pos,minetest.yaw_to_dir(self.object:get_yaw()),dest) then
-- if mobkit.isnear2d(pos,dest,0.25) then
if not self.isonground or abs(dest.y-pos.y) > 0.1 then -- prevent uncontrolled fall when velocity too high
-- if abs(dest.y-pos.y) > 0.1 then -- isonground too slow for speeds > 4
self.object:set_velocity({x=0,y=y,z=0})
end
return true
end
if self.isonground then
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)
-- self.object:set_yaw(minetest.dir_to_yaw(dir))
mobkit.turn2yaw(self,minetest.dir_to_yaw(dir))
dir.y = y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
-- initial velocity for jump height h, v= a*sqrt(h*2/a) ,add 20%
function mobkit.lq_dumbjump(self,height,anim)
anim = anim or 'stand'
local jump = true
local func=function(self)
local yaw = self.object:get_yaw()
if self.isonground then
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
else
local dir = minetest.yaw_to_dir(yaw)
local vel = self.object:get_velocity()
if self.lastvelocity.y < 0.9 then
dir = vector.multiply(dir,3)
end
dir.y = vel.y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
function mobkit.lq_jumpout(self)
local phase = 1
local func=function(self)
local vel=self.object:get_velocity()
if phase == 1 then
vel.y=vel.y+5
self.object:set_velocity(vel)
phase = 2
else
if vel.y < 0 then return true end
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir.y=vel.y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
function mobkit.lq_freejump(self)
local phase = 1
local func=function(self)
local vel=self.object:get_velocity()
if phase == 1 then
vel.y=vel.y+6
self.object:set_velocity(vel)
phase = 2
else
if vel.y <= 0.01 then return true end
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir.y=vel.y
self.object:set_velocity(dir)
end
end
mobkit.queue_low(self,func)
end
function mobkit.lq_jumpattack(self,height,target)
local init=true
local timer=0.5
local tgtbox = target:get_properties().collisionbox
local func=function(self)
if not mobkit.is_alive(target) then return true end
if self.isonground then
if init then -- collision bug workaround
local vel = self.object:get_velocity()
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir=vector.multiply(dir,6)
dir.y = -mobkit.gravity*sqrt(height*2/-mobkit.gravity)
self.object:set_velocity(dir)
mobkit.make_sound(self,'charge')
init=false
else
mobkit.lq_idle(self,0.3)
return true
end
else
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)
-- bounce off
local vy = self.object:get_velocity().y
self.object:set_velocity({x=dir.x*-3,y=vy,z=dir.z*-3})
-- play attack sound if defined
mobkit.make_sound(self,'attack')
return true
end
end
end
mobkit.queue_low(self,func)
end
function mobkit.lq_fallover(self)
local zrot = 0
local init = true
local func=function(self)
if init then
local vel = self.object:get_velocity()
self.object:set_velocity(mobkit.pos_shift(vel,{y=1}))
mobkit.animate(self,'stand')
init = false
end
zrot=zrot+pi*0.05
local rot = self.object:get_rotation()
self.object:set_rotation({x=rot.x,y=rot.y,z=zrot})
if zrot >= pi*0.5 then return true end
end
mobkit.queue_low(self,func)
end
-----------------------------
-- HIGH LEVEL QUEUE FUNCTIONS
-----------------------------
function mobkit.dumbstep(self,height,tpos,speed_factor,idle_duration)
if height <= 0.001 then
mobkit.lq_turn2pos(self,tpos)
mobkit.lq_dumbwalk(self,tpos,speed_factor)
else
mobkit.lq_turn2pos(self,tpos)
mobkit.lq_dumbjump(self,height)
end
idle_duration = idle_duration or 6
mobkit.lq_idle(self,random(ceil(idle_duration*0.5),idle_duration))
end
function mobkit.hq_roam(self,prty)
local func=function(self)
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.3) end
end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_follow0(self,tgtobj) -- probably delete this one
local func = function(self)
if not 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) > 3 then
local neighbor = mobkit.dir2neighbor(vector.direction(pos,opos))
if not neighbor then return true end --temp debug
local height, tpos = mobkit.is_neighbor_node_reachable(self,neighbor)
if height then mobkit.dumbstep(self,height,tpos)
else
for i=1,4 do --scan left
height, tpos = mobkit.is_neighbor_node_reachable(self,(8+neighbor-i-1)%8+1)
if height then mobkit.dumbstep(self,height,tpos)
break
end --scan right
height, tpos = mobkit.is_neighbor_node_reachable(self,(neighbor+i-1)%8+1)
if height then mobkit.dumbstep(self,height,tpos)
break
end
end
end
else
mobkit.lq_idle(self,1)
end
end
end
mobkit.queue_high(self,func,0)
end
function mobkit.hq_follow(self,prty,tgtobj)
local func = function(self)
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) > 3 then
mobkit.goto_next_waypoint(self,opos)
else
mobkit.lq_idle(self,1)
end
end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_goto(self,prty,tpos)
local func = function(self)
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
if vector.distance(pos,tpos) > 3 then
mobkit.goto_next_waypoint(self,tpos)
else
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_runfrom(self,prty,tgtobj)
local init=true
local timer=6
local func = function(self)
if not mobkit.is_alive(tgtobj) 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*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
function mobkit.hq_hunt(self,prty,tgtobj)
local func = function(self)
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()
local dist = vector.distance(pos,opos)
if dist > self.view_range then
return true
elseif dist > 3 then
mobkit.goto_next_waypoint(self,opos)
else
mobkit.hq_attack(self,prty+1,tgtobj)
end
end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_warn(self,prty,tgtobj)
local timer=0
local tgttime = 0
local init = true
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if init then
mobkit.animate(self,'stand')
init = false
end
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
local dist = vector.distance(pos,opos)
if dist > 11 then
return true
elseif dist < 4 or timer > 12 then -- too close man
-- mobkit.clear_queue_high(self)
mobkit.remember(self,'hate',tgtobj:get_player_name())
mobkit.hq_hunt(self,prty+1,tgtobj) -- priority
else
timer = timer+self.dtime
if mobkit.is_queue_empty_low(self) then
mobkit.lq_turn2pos(self,opos)
end
-- make noise in random intervals
if timer > tgttime then
mobkit.make_sound(self,'warn')
-- if self.sounds and self.sounds.warn then
-- minetest.sound_play(self.sounds.warn, {object=self.object})
-- end
tgttime = timer + 1.1 + random()*1.5
end
end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_die(self)
local timer = 5
local start = true
local func = function(self)
if start then
mobkit.lq_fallover(self)
self.logic = function(self) end -- brain dead as well
start=false
end
timer = timer-self.dtime
if timer < 0 then self.object:remove() end
end
mobkit.queue_high(self,func,100)
end
function mobkit.hq_attack(self,prty,tgtobj)
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if mobkit.is_queue_empty_low(self) then
local pos = mobkit.get_stand_pos(self)
-- local tpos = tgtobj:get_pos()
local tpos = mobkit.get_stand_pos(tgtobj)
local dist = vector.distance(pos,tpos)
if dist > 3 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.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 mobkit.hq_liquid_recovery(self,prty) -- scan for nearest land
local radius = 1
local yaw = 0
local func = function(self)
if not self.isinliquid then return true end
local pos=self.object:get_pos()
local vec = minetest.yaw_to_dir(yaw)
local pos2 = mobkit.pos_shift(pos,vector.multiply(vec,radius))
local height, liquidflag = mobkit.get_terrain_height(pos2)
if height and not liquidflag then
mobkit.hq_swimto(self,prty,pos2)
return true
end
yaw=yaw+pi*0.25
if yaw>2*pi then
yaw = 0
radius=radius+1
if radius > self.view_range then
self.hp = 0
return true
end
end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_swimto(self,prty,tpos)
local box = self.object:get_properties().collisionbox
local cols = {}
local func = function(self)
if not self.isinliquid then
if self.isonground then return true end
return false
end
local pos = mobkit.get_stand_pos(self)
local y=self.object:get_velocity().y
local pos2d = {x=pos.x,y=tpos.y,z=pos.z}
local dir=vector.normalize(vector.direction(pos2d,tpos))
local yaw = minetest.dir_to_yaw(dir)
if mobkit.timer(self,1) then
cols = mobkit.get_box_displace_cols(pos,box,dir,1)
for _,p in ipairs(cols[1]) do
p.y=pos.y
local h,l = mobkit.get_terrain_height(p)
if h and h>pos.y and self.isinliquid then
mobkit.lq_freejump(self)
break
end
end
elseif mobkit.turn2yaw(self,yaw) then
dir.y = y
self.object:set_velocity(dir)
end
end
mobkit.queue_high(self,func,prty)
end
---------------------
-- AQUATIC
---------------------
-- MACROS
local function aqua_radar_dumb(pos,yaw,range,reverse)
range = range or 4
local function okpos(p)
local node = mobkit.nodeatpos(p)
if node then
if node.drawtype == 'liquid' then
local nodeu = mobkit.nodeatpos(mobkit.pos_shift(p,{y=1}))
local noded = mobkit.nodeatpos(mobkit.pos_shift(p,{y=-1}))
if (nodeu and nodeu.drawtype == 'liquid') or (noded and noded.drawtype == 'liquid') then
return true
else
return false
end
else
local h,l = mobkit.get_terrain_height(p)
if h then
local node2 = mobkit.nodeatpos({x=p.x,y=h+1.99,z=p.z})
if node2 and node2.drawtype == 'liquid' then return true, h end
else
return false
end
end
else
return false
end
end
local fpos = mobkit.pos_translate2d(pos,yaw,range)
local ok,h = okpos(fpos)
if not ok then
local ffrom, fto, fstep
if reverse then
ffrom, fto, fstep = 3,1,-1
else
ffrom, fto, fstep = 1,3,1
end
for i=ffrom, fto, fstep do
local ok,h = okpos(mobkit.pos_translate2d(pos,yaw+i,range))
if ok then return yaw+i,h end
ok,h = okpos(mobkit.pos_translate2d(pos,yaw-i,range))
if ok then return yaw-i,h end
end
return yaw+pi,h
else
return yaw, h
end
end
function mobkit.is_in_deep(target)
if not target then return false end
local nodepos = mobkit.get_stand_pos(target)
local node1 = mobkit.nodeatpos(nodepos)
nodepos.y=nodepos.y+1
local node2 = mobkit.nodeatpos(nodepos)
nodepos.y=nodepos.y-2
local node3 = mobkit.nodeatpos(nodepos)
if node1 and node2 and node3 and node1.drawtype=='liquid' and (node2.drawtype=='liquid' or node3.drawtype=='liquid') then
return true
end
end
-- HQ behaviors
function mobkit.hq_aqua_roam(self,prty,speed)
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,'def')
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 = aqua_radar_dumb(pos,yaw,speed,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) > 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)
-- local yaw = self.object:get_yaw()
mobkit.go_forward_horizontal(self,speed)
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_aqua_turn(self,prty,tyaw,speed)
local func = function(self)
local finished=mobkit.turn2yaw(self,tyaw)
-- local yaw = self.object:get_yaw()
mobkit.go_forward_horizontal(self,speed)
if finished then return true end
end
mobkit.queue_high(self,func,prty)
end
function mobkit.hq_aqua_attack(self,prty,tgtobj,speed)
local tyaw = 0
local prvscanpos = {x=0,y=0,z=0}
local init = true
local tgtbox = tgtobj:get_properties().collisionbox
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if init then
mobkit.animate(self,'fast')
mobkit.make_sound(self,'attack')
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 = aqua_radar_dumb(pos,yaw,speed*0.5)
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
local tpos = tgtobj:get_pos()
local tyaw=minetest.dir_to_yaw(vector.direction(pos,tpos))
mobkit.turn2yaw(self,tyaw,3)
local yaw = self.object:get_yaw()
if mobkit.timer(self,1) then
if not mobkit.is_in_deep(tgtobj) then return true end
local vel = self.object:get_velocity()
if tpos.y>pos.y+0.5 then self.object:set_velocity({x=vel.x,y=vel.y+0.5,z=vel.z})
elseif tpos.y<pos.y-0.5 then self.object:set_velocity({x=vel.x,y=vel.y-0.5,z=vel.z}) end
end
if mobkit.is_pos_in_box(mobkit.pos_translate2d(pos,yaw,self.attack.range),tpos,tgtbox) then --bite
tgtobj:punch(self.object,1,self.attack)
mobkit.hq_aqua_turn(self,prty,yaw-pi,speed)
return true
end
mobkit.go_forward_horizontal(self,speed)
end
mobkit.queue_high(self,func,prty)
end

View File

@ -0,0 +1,870 @@
-- yaw values:
-- x+ = -pi/2
-- x- = +pi/2
-- z+ = 0
-- z- = -pi
mobkit={}
mobkit.gravity = -9.8
mobkit.friction = 0.4 -- less is more
local abs = math.abs
local pi = math.pi
local floor = math.floor
local ceil = math.ceil
local random = math.random
local sqrt = math.sqrt
local max = math.max
local min = math.min
local tan = math.tan
local pow = math.pow
local sign = function(x)
return (x<0) and -1 or 1
end
mobkit.terminal_velocity = sqrt(2*-mobkit.gravity*20) -- 20 meter fall = dead
mobkit.safe_velocity = sqrt(2*-mobkit.gravity*5) -- 5 m safe fall
local abr = tonumber(minetest.get_mapgen_setting('active_block_range')) or 3
-- UTILITY FUNCTIONS
function mobkit.dot(v1,v2)
return v1.x*v2.x+v1.y*v2.y+v1.z*v2.z
end
function mobkit.minmax(v,m)
return min(abs(v),m)*sign(v)
end
function mobkit.pos_shift(pos,vec) -- vec components can be omitted e.g. vec={y=1}
vec.x=vec.x or 0
vec.y=vec.y or 0
vec.z=vec.z or 0
return {x=pos.x+vec.x,
y=pos.y+vec.y,
z=pos.z+vec.z}
end
function mobkit.pos_translate2d(pos,yaw,dist) -- translate pos dist distance in yaw direction
return vector.add(pos,vector.multiply(minetest.yaw_to_dir(yaw),dist))
end
function mobkit.is_pos_in_box(pos,bpos,box)
return pos.x > bpos.x+box[1] and pos.x < bpos.x+box[4] and
pos.y > bpos.y+box[2] and pos.y < bpos.y+box[5] and
pos.z > bpos.z+box[3] and pos.z < bpos.z+box[6]
end
-- call this instead if you want feet position.
--[[
function mobkit.get_stand_pos(thing) -- thing can be luaentity or objectref.
if type(thing) == 'table' then
return mobkit.pos_shift(thing.object:get_pos(),{y=thing.collisionbox[2]+0.01})
elseif type(thing) == 'userdata' then
local colbox = thing:get_properties().collisionbox
return mobkit.pos_shift(thing:get_pos(),{y=colbox[2]+0.01})
end
end --]]
function mobkit.get_stand_pos(thing) -- thing can be luaentity or objectref.
local pos = {}
local colbox = {}
if type(thing) == 'table' then
pos = thing.object:get_pos()
colbox = thing.object:get_properties().collisionbox
elseif type(thing) == 'userdata' then
pos = thing:get_pos()
colbox = thing:get_properties().collisionbox
else
return false
end
return mobkit.pos_shift(pos,{y=colbox[2]+0.01}), pos
end
function mobkit.set_acceleration(thing,vec,limit)
limit = limit or 100
if type(thing) == 'table' then thing=thing.object end
vec.x=mobkit.minmax(vec.x,limit)
vec.y=mobkit.minmax(vec.y,limit)
vec.z=mobkit.minmax(vec.z,limit)
thing:set_acceleration(vec)
end
function mobkit.nodeatpos(pos)
local node = minetest.get_node_or_nil(pos)
if node then return minetest.registered_nodes[node.name] end
end
function mobkit.get_nodename_off(pos,vec)
return minetest.get_node(mobkit.pos_shift(pos,vec)).name
end
function mobkit.get_node_pos(pos)
return {
x=floor(pos.x+0.5),
y=floor(pos.y+0.5),
z=floor(pos.z+0.5),
}
end
function mobkit.get_nodes_in_area(pos1,pos2,full)
local npos1=mobkit.get_node_pos(pos1)
local npos2=mobkit.get_node_pos(pos2)
local result = {}
local cnt = 0 -- safety
local sx = (pos2.x<pos1.x) and -1 or 1
local sz = (pos2.z<pos1.z) and -1 or 1
local sy = (pos2.y<pos1.y) and -1 or 1
local x=npos1.x-sx
local z=npos1.z-sz
local y=npos1.y-sy
repeat
x=x+sx
z=npos1.z-sz
repeat
z=z+sz
y=npos1.y-sy
repeat
y=y+sy
local pos = {x=x,y=y,z=z}
local node = mobkit.nodeatpos(pos)
if node then
if full==true then
result[pos] = node
else
result[node] = true
end
end
cnt=cnt+1
if cnt > 125 then
minetest.chat_send_all('get_nodes_in_area: area too big ')
return result
end
until y==npos2.y
until z==npos2.z
until x==npos2.x
return result
end
function mobkit.get_hitbox_bottom(self)
local y = self.collisionbox[2]
local pos = self.object:get_pos()
return {
{x=pos.x+self.collisionbox[1],y=pos.y+y,z=pos.z+self.collisionbox[3]},
{x=pos.x+self.collisionbox[1],y=pos.y+y,z=pos.z+self.collisionbox[6]},
{x=pos.x+self.collisionbox[4],y=pos.y+y,z=pos.z+self.collisionbox[3]},
{x=pos.x+self.collisionbox[4],y=pos.y+y,z=pos.z+self.collisionbox[6]},
}
end
function mobkit.get_node_height(pos)
local npos = mobkit.get_node_pos(pos)
local node = mobkit.nodeatpos(npos)
if node == nil then return nil end
if node.walkable then
if node.drawtype == 'nodebox' then
if node.node_box and node.node_box.type == 'fixed' then
if type(node.node_box.fixed[1]) == 'number' then
return npos.y + node.node_box.fixed[5] ,0, false
elseif type(node.node_box.fixed[1]) == 'table' then
return npos.y + node.node_box.fixed[1][5] ,0, false
else
return npos.y + 0.5,1, false -- todo handle table of boxes
end
elseif node.node_box and node.node_box.type == 'leveled' then
return minetest.get_node_level(pos)/64-0.5+mobkit.get_node_pos(pos).y, 0, false
else
return npos.y + 0.5,1, false -- the unforeseen
end
else
return npos.y+0.5,1, false -- full node
end
else
local liquidflag = false
if node.drawtype == 'liquid' then liquidflag = true end
return npos.y-0.5,-1,liquidflag
end
end
-- get_terrain_height
-- steps(optional) number of recursion steps; default=3
-- dir(optional) is 1=up, -1=down, 0=both; default=0
-- liquidflag(forbidden) never provide this parameter.
function mobkit.get_terrain_height(pos,steps,dir,liquidflag) --dir is 1=up, -1=down, 0=both
steps = steps or 3
dir = dir or 0
local h,f,l = mobkit.get_node_height(pos)
if h == nil then return nil end
if l then liquidflag = true end
if f==0 then
return h, liquidflag
end
if dir==0 or dir==f then
steps = steps - 1
if steps <=0 then return nil end
return mobkit.get_terrain_height(mobkit.pos_shift(pos,{y=f}),steps,f,liquidflag)
else
return h, liquidflag
end
end
function mobkit.get_spawn_pos_abr(dtime,intrvl,radius,chance,reduction)
dtime = min(dtime,0.1)
local plyrs = minetest.get_connected_players()
intrvl=1/intrvl
if random()<dtime*(intrvl*#plyrs) then
local plyr = plyrs[random(#plyrs)] -- choose random player
local vel = plyr:get_player_velocity()
local spd = vector.length(vel)
chance = (1-chance) * 1/(spd*0.75+1)
local yaw
if spd > 1 then
-- spawn in the front arc
yaw = minetest.dir_to_yaw(vel) + random()*0.35 - 0.75
else
-- random yaw
yaw = random()*pi*2 - pi
end
local pos = plyr:get_pos()
local dir = vector.multiply(minetest.yaw_to_dir(yaw),radius)
local pos2 = vector.add(pos,dir)
pos2.y=pos2.y-5
local height, liquidflag = mobkit.get_terrain_height(pos2,32)
if height then
local objs = minetest.get_objects_inside_radius(pos,radius*1.1)
for _,obj in ipairs(objs) do -- count mobs in abrange
if not obj:is_player() then
local lua = obj:get_luaentity()
if lua and lua.name ~= '__builtin:item' then
chance=chance + (1-chance)*reduction -- chance reduced for every mob in range
end
end
end
if chance < random() then
pos2.y = height
objs = minetest.get_objects_inside_radius(pos2,radius*0.95)
for _,obj in ipairs(objs) do -- do not spawn if another player around
if obj:is_player() then return end
end
return pos2, liquidflag
end
end
end
end
function mobkit.turn2yaw(self,tyaw,rate)
tyaw = tyaw or 0 --temp
rate = rate or 6
local yaw = self.object:get_yaw()
yaw = yaw+pi
tyaw=(tyaw+pi)%(pi*2)
local step=min(self.dtime*rate,abs(tyaw-yaw)%(pi*2))
local dir = abs(tyaw-yaw)>pi and -1 or 1
dir = tyaw>yaw and dir*1 or dir * -1
local nyaw = (yaw+step*dir)%(pi*2)
self.object:set_yaw(nyaw-pi)
if nyaw==tyaw then return true, nyaw-pi
else return false, nyaw-pi end
end
function mobkit.dir_to_rot(v,rot)
rot = rot or {x=0,y=0,z=0}
return {x = (v.x==0 and v.y==0 and v.z==0) and rot.x or math.atan2(v.y,vector.length({x=v.x,y=0,z=v.z})),
y = (v.x==0 and v.z==0) and rot.y or minetest.dir_to_yaw(v),
z=rot.z}
end
function mobkit.rot_to_dir(rot) -- keep rot within <-pi/2,pi/2>
local dir = minetest.yaw_to_dir(rot.y)
dir.y = dir.y+tan(rot.x)*vector.length(dir)
return vector.normalize(dir)
end
function mobkit.isnear2d(p1,p2,thresh)
if abs(p2.x-p1.x) < thresh and abs(p2.z-p1.z) < thresh then
return true
else
return false
end
end
-- object has reached the destination if dest is in the rear half plane.
function mobkit.is_there_yet2d(pos,dir,dest) -- obj positon; facing vector; destination position
local c = -dir.x*pos.x-dir.z*pos.z -- the constant
if dir.z > 0 then
return dest.z <= (-dir.x*dest.x - c)/dir.z -- line equation
elseif dir.z < 0 then
return dest.z >= (-dir.x*dest.x - c)/dir.z
elseif dir.x > 0 then
return dest.x <= (-dir.z*dest.z - c)/dir.x
elseif dir.x < 0 then
return dest.x >= (-dir.z*dest.z - c)/dir.x
else
return false
end
end
function mobkit.isnear3d(p1,p2,thresh)
if abs(p2.x-p1.x) < thresh and abs(p2.z-p1.z) < thresh and abs(p2.y-p1.y) < thresh then
return true
else
return false
end
end
function mobkit.get_box_intersect_cols(pos,box)
local pmin = {x=floor(pos.x+box[1]+0.5),z=floor(pos.z+box[3]+0.5)}
local pmax = {x=floor(pos.x+box[4]+0.5),z=floor(pos.z+box[6]+0.5)}
result= {}
for x=pmin.x,pmax.x do
for z=pmin.z,pmax.z do
table.insert(result,{x=x,z=z})
end
end
return result
end
function mobkit.get_box_displace_cols(pos,box,vec,dist)
local result = {{}}
-- front facing corner pos and neighbors
local fpos = {pos.y}
local xpos={pos.y}
local zpos={pos.y}
local xoff=nil
local zoff=nil
if vec.x < 0 then
fpos.x = pos.x+box[1] -- frontmost corner's x
xoff = box[4]-box[1] -- edge offset along x
else
fpos.x = pos.x+box[4]
xoff = box[1]-box[4]
end
if vec.z < 0 then
fpos.z = pos.z+box[3] -- frontmost corner's z
zoff = box[6]-box[3] -- edge offset along z
else
fpos.z = pos.z+box[6]
zoff = box[3]-box[6]
end
-- displacement vector
if dist then vec = vector.multiply(vector.normalize(vec),dist) end
-- traverse x
local xsgn = sign(vec.x)
local zsgn = sign(zoff)
local index=0
for x = floor(fpos.x+0.5)+xsgn*0.5, fpos.x+vec.x, xsgn do
index=index+1
if index > 50 then return result end
result[index] = result[index] or {}
local zcomp = vec.x == 0 and 0 or fpos.z + (x-fpos.x)*vec.z/vec.x -- z component at the intersection of x and node edge
for z = floor(zcomp+0.5), floor(zcomp+zoff+0.5), zsgn do
table.insert(result[index],{x=x+xsgn*0.5,z=z})
end
end
-- traverse z
local zsgn = sign(vec.z)
local xsgn = sign(xoff)
index=0
for z = floor(fpos.z + 0.5)+zsgn*0.5, fpos.z+vec.z, zsgn do
index=index+1
if index > 50 then return result end
result[index] = result[index] or {}
local xcomp = vec.z == 0 and 0 or fpos.x + (z-fpos.z)*vec.x/vec.z
for x = floor(xcomp+0.5), floor(xcomp+xoff+0.5), xsgn do
table.insert(result[index],{x=x,z=z+zsgn*0.5})
end
end
return result
end
function mobkit.get_box_height(thing)
if type(thing) == 'table' then thing = thing.object end
local colbox = thing:get_properties().collisionbox
local height
if colbox then height = colbox[5]-colbox[2]
else height = 0.1 end
return height > 0 and height or 0.1
end
function mobkit.is_alive(thing) -- thing can be luaentity or objectref.
-- if not thing then return false end
if not mobkit.exists(thing) then return false end
if type(thing) == 'table' then return thing.hp > 0 end
if thing:is_player() then return thing:get_hp() > 0
else
local lua = thing:get_luaentity()
local hp = lua and lua.hp or nil
return hp and hp > 0
end
end
function mobkit.exists(thing)
if not thing then return false end
if type(thing) == 'table' then thing=thing.object end
if type(thing) == 'userdata' then
if thing:is_player() then
if thing:get_look_horizontal() then return true end
else
if thing:get_yaw() then return true end
end
end
end
function mobkit.hurt(luaent,dmg)
if not luaent then return false end
if type(luaent) == 'table' then
luaent.hp = max((luaent.hp or 0) - dmg,0)
end
end
function mobkit.heal(luaent,dmg)
if not luaent then return false end
if type(luaent) == 'table' then
luaent.hp = min(luaent.max_hp,(luaent.hp or 0) + dmg)
end
end
function mobkit.animate(self,anim)
if self.animation and self.animation[anim] then
if self._anim == anim then return end
self._anim=anim
local aparms = {}
if #self.animation[anim] > 0 then
aparms = self.animation[anim][random(#self.animation[anim])]
else
aparms = self.animation[anim]
end
aparms.frame_blend = aparms.frame_blend or 0
self.object:set_animation(aparms.range,aparms.speed,aparms.frame_blend,aparms.loop)
else
self._anim = nil
end
end
function mobkit.make_sound(self, sound)
local spec = self.sounds and self.sounds[sound]
local param_table = {object=self.object}
if type(spec) == 'table' then
--pick random sound if it's a spec for random sounds
if #spec > 0 then spec = spec[random(#spec)] end
--returns value or a random value within the range [value[1], value[2])
local function in_range(value)
return type(value) == 'table' and value[1]+random()*(value[2]-value[1]) or value
end
--pick random values within a range if they're a table
param_table.gain = in_range(spec.gain)
param_table.fade = in_range(spec.fade)
param_table.pitch = in_range(spec.pitch)
return minetest.sound_play(spec.name, param_table)
end
return minetest.sound_play(spec, param_table)
end
function mobkit.go_forward_horizontal(self,speed) -- sets velocity in yaw direction, y component unaffected
local y = self.object:get_velocity().y
local yaw = self.object:get_yaw()
local vel = vector.multiply(minetest.yaw_to_dir(yaw),speed)
vel.y = y
self.object:set_velocity(vel)
end
function mobkit.drive_to_pos(self,tpos,speed,turn_rate,dist)
local pos=self.object:get_pos()
dist = dist or 0.2
if mobkit.isnear2d(pos,tpos,dist) then return true end
local tyaw = minetest.dir_to_yaw(vector.direction(pos,tpos))
mobkit.turn2yaw(self,tyaw,turn_rate)
mobkit.go_forward_horizontal(self,speed)
return false
end
function mobkit.timer(self,s) -- returns true approx every s seconds
local t1 = floor(self.time_total)
local t2 = floor(self.time_total+self.dtime)
if t2>t1 and t2%s==0 then return true end
end
-- Memory functions.
-- Stuff in memory is serialized, never try to remember objectrefs.
function mobkit.remember(self,key,val)
self.memory[key]=val
return val
end
function mobkit.forget(self,key)
self.memory[key] = nil
end
function mobkit.recall(self,key)
return self.memory[key]
end
-- Queue functions
function mobkit.queue_high(self,func,priority)
local maxprty = mobkit.get_queue_priority(self)
if priority > maxprty then
mobkit.clear_queue_low(self)
end
for i,f in ipairs(self.hqueue) do
if priority > f.prty then
table.insert(self.hqueue,i,{func=func,prty=priority})
return
end
end
table.insert(self.hqueue,{func=func,prty=priority})
end
function mobkit.queue_low(self,func)
table.insert(self.lqueue,func)
end
function mobkit.is_queue_empty_low(self)
if #self.lqueue == 0 then return true
else return false end
end
function mobkit.clear_queue_high(self)
self.hqueue = {}
end
function mobkit.clear_queue_low(self)
self.lqueue = {}
end
function mobkit.get_queue_priority(self)
if #self.hqueue > 0 then
return self.hqueue[1].prty
else return 0 end
end
function mobkit.is_queue_empty_high(self)
if #self.hqueue == 0 then return true
else return false end
end
function mobkit.get_nearby_player(self) -- returns random player if nearby or nil
for _,obj in ipairs(self.nearby_objects) do
if obj:is_player() and mobkit.is_alive(obj) then return obj end
end
return
end
function mobkit.get_nearby_entity(self,name) -- returns random nearby entity of name or nil
for _,obj in ipairs(self.nearby_objects) do
if mobkit.is_alive(obj) and not obj:is_player() and obj:get_luaentity().name == name then return obj end
end
return
end
function mobkit.get_closest_entity(self,name) -- returns closest entity of name or nil
local cobj = nil
local dist = abr*64
local pos = self.object:get_pos()
for _,obj in ipairs(self.nearby_objects) do
local luaent = obj:get_luaentity()
if mobkit.is_alive(obj) and not obj:is_player() and luaent and luaent.name == name then
local opos = obj:get_pos()
local odist = abs(opos.x-pos.x) + abs(opos.z-pos.z)
if odist < dist then
dist=odist
cobj=obj
end
end
end
return cobj
end
local function execute_queues(self)
--Execute hqueue
if #self.hqueue > 0 then
local func = self.hqueue[1].func
if func(self) then
table.remove(self.hqueue,1)
self.lqueue = {}
end
end
-- Execute lqueue
if #self.lqueue > 0 then
local func = self.lqueue[1]
if func(self) then
table.remove(self.lqueue,1)
end
end
end
local function sensors()
local timer = 2
local pulse = 1
return function(self)
timer=timer-self.dtime
if timer < 0 then
pulse = pulse + 1 -- do full range every third scan
local range = self.view_range
if pulse > 2 then
pulse = 1
else
range = self.view_range*0.5
end
local pos = self.object:get_pos()
--local tim = minetest.get_us_time()
self.nearby_objects = minetest.get_objects_inside_radius(pos, range)
--minetest.chat_send_all(minetest.get_us_time()-tim)
for i,obj in ipairs(self.nearby_objects) do
if obj == self.object then
table.remove(self.nearby_objects,i)
break
end
end
timer=2
end
end
end
------------
-- CALLBACKS
------------
function mobkit.default_brain(self)
if mobkit.is_queue_empty_high(self) then mobkit.hq_roam(self,0) end
end
function mobkit.physics(self)
local vel=self.object:get_velocity()
local vnew = vector.new(vel)
-- dumb friction
if self.isonground and not self.isinliquid then
vnew = {x= vel.x> 0.2 and vel.x*mobkit.friction or 0,
y=vel.y,
z=vel.z > 0.2 and vel.z*mobkit.friction or 0}
end
-- bounciness
if self.springiness and self.springiness > 0 then
if colinfo and colinfo.collides then
for _,c in ipairs(colinfo.collisions) do
if c.old_velocity[c.axis] > 0.1 then
vnew[c.axis] = vnew[c.axis] * self.springiness * -1
end
end
elseif not colinfo then -- MT 5.2 and earlier
for _,k in ipairs({'y','z','x'}) do
if vel[k]==0 and abs(self.lastvelocity[k])> 0.1 then
vnew[k]=-self.lastvelocity[k]*self.springiness
end
end
end
end
self.object:set_velocity(vnew)
-- buoyancy
local surface = nil
local surfnodename = nil
local spos = mobkit.get_stand_pos(self)
spos.y = spos.y+0.01
-- get surface height
local snodepos = mobkit.get_node_pos(spos)
local surfnode = mobkit.nodeatpos(spos)
while surfnode and surfnode.drawtype == 'liquid' do
surfnodename = surfnode.name
surface = snodepos.y+0.5
if surface > spos.y+self.height then break end
snodepos.y = snodepos.y+1
surfnode = mobkit.nodeatpos(snodepos)
end
self.isinliquid = surfnodename
if surface then -- standing in liquid
-- self.isinliquid = true
local submergence = min(surface-spos.y,self.height)/self.height
-- local balance = self.buoyancy*self.height
local buoyacc = mobkit.gravity*(self.buoyancy-submergence)
mobkit.set_acceleration(self.object,
{x=-vel.x*self.water_drag,y=buoyacc-vel.y*abs(vel.y)*0.4,z=-vel.z*self.water_drag})
else
-- self.isinliquid = false
self.object:set_acceleration({x=0,y=mobkit.gravity,z=0})
end
end
function mobkit.vitals(self)
-- vitals: fall damage
local vel = self.object:get_velocity()
local velocity_delta = abs(self.lastvelocity.y - vel.y)
if velocity_delta > mobkit.safe_velocity then
self.hp = self.hp - floor(self.max_hp * min(1, velocity_delta/mobkit.terminal_velocity))
end
-- vitals: oxygen
if self.lung_capacity then
local colbox = self.object:get_properties().collisionbox
local headnode = mobkit.nodeatpos(mobkit.pos_shift(self.object:get_pos(),{y=colbox[5]})) -- node at hitbox top
if headnode and headnode.drawtype == 'liquid' then
self.oxygen = self.oxygen - self.dtime
else
self.oxygen = self.lung_capacity
end
if self.oxygen <= 0 then self.hp=0 end -- drown
end
end
function mobkit.statfunc(self)
local tmptab={}
tmptab.memory = self.memory
tmptab.hp = self.hp
tmptab.texture_no = self.texture_no
return minetest.serialize(tmptab)
end
function mobkit.actfunc(self, staticdata, dtime_s)
self.logic = self.logic or self.brainfunc
self.physics = self.physics or mobkit.physics
self.lqueue = {}
self.hqueue = {}
self.nearby_objects = {}
self.nearby_players = {}
self.pos_history = {}
self.path_dir = 1
self.time_total = 0
self.water_drag = self.water_drag or 1
local sdata = minetest.deserialize(staticdata)
if sdata then
for k,v in pairs(sdata) do
self[k] = v
end
end
if self.textures==nil then
local prop_tex = self.object:get_properties().textures
if prop_tex then self.textures=prop_tex end
end
if not self.memory then -- this is the initial activation
self.memory = {}
-- texture variation
if #self.textures > 1 then self.texture_no = random(#self.textures) end
end
if self.timeout and ((self.timeout>0 and dtime_s > self.timeout and next(self.memory)==nil) or
(self.timeout<0 and dtime_s > abs(self.timeout))) then
self.object:remove()
end
-- apply texture
if self.textures and self.texture_no then
local props = {}
props.textures = {self.textures[self.texture_no]}
self.object:set_properties(props)
end
--hp
self.max_hp = self.max_hp or 10
self.hp = self.hp or self.max_hp
--armor
if type(self.armor_groups) ~= 'table' then
self.armor_groups={}
end
self.armor_groups.immortal = 1
self.object:set_armor_groups(self.armor_groups)
self.buoyancy = self.buoyancy or 0
self.oxygen = self.oxygen or self.lung_capacity
self.lastvelocity = {x=0,y=0,z=0}
self.sensefunc=sensors()
end
function mobkit.stepfunc(self,dtime,colinfo) -- not intended to be modified
self.dtime = min(dtime,0.2)
self.colinfo = colinfo
self.height = mobkit.get_box_height(self)
-- physics comes first
local vel = self.object:get_velocity()
if colinfo then
self.isonground = colinfo.touching_ground
else
if self.lastvelocity.y==0 and vel.y==0 then
self.isonground = true
else
self.isonground = false
end
end
self:physics()
if self.logic then
if self.view_range then self:sensefunc() end
self:logic()
execute_queues(self)
end
self.lastvelocity = self.object:get_velocity()
self.time_total=self.time_total+self.dtime
end
-- load example behaviors
dofile(minetest.get_modpath("mobkit") .. "/example_behaviors.lua")
minetest.register_on_mods_loaded(function()
local mbkfuns = ''
for n,f in pairs(mobkit) do
if type(f) == 'function' then
mbkfuns = mbkfuns .. n .. string.split(minetest.serialize(f),'.lua')[2] or ''
end
end
local crc = minetest.sha1(mbkfuns)
-- dbg(crc)
-- if crc ~= 'a061770008fe9ecf8e1042a227dc3beabd10e481' then
-- minetest.log("error","Mobkit namespace inconsistent, has been modified by other mods.")
-- end
end)

View File

@ -0,0 +1,560 @@
Contents
1 Concepts
1.1 Behavior functions
1.1.1 Low level functions
1.1.2 High level functions
1.1.2.1 Priority
1.1.3 Modifying built in behaviors
1.2 Logic function
1.3 Processing diagram
1.4 Entity definition
1.5 Exposed luaentity members
2 Reference
2.1 Utility functions
2.2 Built in behaviors
2.2.1 High level behaviors
2.2.2 Low level behaviors
2.3 Constants and member variables
-----------
1. Concepts
-----------
1.1 Behavior functions
These are the most fundamental units of code, every action entities can perform is a separate function.
There are two types of behaviors:
- low level, these govern physical actions and interactions (think moves)
- high level, these are logical structures governing low level behaviors in order to perform more complex tasks
Behaviors run for considerable amount of time, this means the functions are being called repeatedly on consecutive engine steps.
Therefore a need for preserving state between calls, this is why they are implemented as closures, see defining conventions for details.
Behavior functions are active until they finish the job, are removed from the queue or superseded by a higher priority behavior.
They signal finished state by returning true, therefore it's very important to carefully design the completion conditions
For a behavior to begin executing it has to be put on a queue. There are two separate queues, one for low and one for high level behaviors.
Queuing is covered by behavour defining conventions
Mobkit comes with some example behavior functions, which are located in /example_behaviors.lua
!!! In simplest scenarios there's no need to code behaviors, much can be achieved using only built-in stuff !!!
!!! To start using the api it's enough to learn defining mobs and writing brain functions !!!
1.1.1 Low level behavior functions
These are physical actions and interactions: steps, jumps, turns etc. here you'll set velocity, yaw, kick off animations and sounds.
Low level behavior definition:
function mobkit.lq_bhv1(self,[optional additional persistent parameters]) -- enclosing function
... -- optional definitions of additional persistent variables
local func=function(self) -- enclosed function, self is mandatory and the only allowed parameter
... -- actual function definition, remember to return true eventually
end
mobkit.queue_low(self,func) -- this will queue the behavior at the time of lq_bhv1 call
end
1.1.2 High level behavior functions
These are complex tasks like getting to a position, following other objects, hiding, patrolling an area etc.
Their job is tracking changes in the environment and managing low level behavior queue accordingly.
High level behavior definition:
function mobkit.hq_bhv1(self,priority,[optional additional persistent parameters]) -- enclosing function
... -- optional definitions of additional persistent variables
local func=function(self) -- enclosed function, self is mandatory and the only allowed parameter
... -- actual function definition, remember to return true eventually
end
mobkit.queue_high(self,func,priority) -- this will queue the behavior at the time of hq_bhv1 call
end
1.1.2.1 Priority
Unlike low level behaviors which are executed in FIFO order, high level behaviors support prioritization.
This concept is essential for making sure the right behavior is active at the right time.
Prioritization is what makes it possible to interrupt a task in order to perform a more important one
The currently executing behavior is always the first in the queue.
When a new behavior is placed onto the queue:
If the queue is not empty a new behavior is inserted before the first behavior of lower priority if such exists, or last.
If the new behavior supersedes the one currently executing, low level queue is purged immediately.
Common idioms:
hq_bhv1(self,prty):
...
hq_bhv2(self,prty) -- bhv1 kicks off bhv2 with equal priority
return true -- and ends,
-- bhv2 becomes active on the next engine step.
hq_bhv1(self,prty):
...
hq_bhv2(self,prty+1) -- bhv1 kicks off bhv2 with higher priority
-- bhv2 takes over and when it ends, bhv1 resumes.
Particular prioritization scheme is to be designed by the user according to specific mod requirements.
1.1.3 Modifying built in behaviors
Do not modify example_behaviors.lua directly, because functions defined there are meant to be shared between mods.
Instead, copy the contents of /behaviors2override.lua into your mod/game, changing every occurence of the string '[yournamespace]' to the name of a lua table representing your namespace of choice.
1.2 Logic function
------------------
Every mob must have one.
Its job is managing high level behavior queue in response to events which are not intercepted by callbacks.
Contrary to what the name suggests, these functions needn't necessarily be too complex thanks to their limited responsibilities.
Typical flow might look like this:
if mobkit.timer(self,1) then -- returns true approx every second
local prty = mobkit.get_queue_priority(self)
if prty < 20
if ... then
hq_do_important_stuff(self,20)
return
end
end
if prty < 10 then
if ... then
hq_do_something_else(self,10)
return
elseif ... then
hq_do_this_instead(self,10)
return
end
end
if mobkit.is_queue_empty_high(self) then
hq_fool_around(self,0)
end
end
1.3 Processing diagram
----------------------
---------------------------------------
| PHYSICS |
| |
| ----------------------- |
| | Logic Function | |
| ----------------------- |
| | |
| -----|----------------- |
| | V HL Queue| |
| | 1| 2| 3|... | |
| ----------------------- |
| | |
| -----|----------------- |
| | V LL Queue| |
| | 1| 2| 3|... | |
| ----------------------- |
| |
---------------------------------------
Order of execution during an engine step:
First comes physics: gravity, buoyancy, friction etc., then the logic function is called.
After that, the first behavior on the high level queue, if exists,
and the last, the first low level behavior if present.
1.4 Entity definition
---------------------
minetest.register_entity("mod:name",{
-- required minetest api props
initial_properties = {
physical = true,
collide_with_objects = true,
collisionbox = {...},
visual = "mesh",
mesh = "...",
textures = {...},
},
-- required mobkit props
timeout = [num], -- entities are removed after this many seconds inactive
-- 0 is never
-- mobs having memory entries are not affected
buoyancy = [num], -- (0,1) - portion of collisionbox submerged
-- = 1 - controlled buoyancy (fish, submarine)
-- > 1 - drowns
-- < 0 - MC like water trampolining
lung_capacity = [num], -- seconds
max_hp = [num],
on_step = mobkit.stepfunc,
on_activate = mobkit.actfunc,
get_staticdata = mobkit.statfunc,
logic = [function user defined], -- older 'brainfunc' name works as well.
-- optional mobkit props
-- or used by built in behaviors
physics = [function user defined] -- optional, overrides built in physics
animation = {
[name]={range={x=[num],y=[num]},speed=[num],loop=[bool]}, -- single
[name]={ -- variant, animations are chosen randomly.
{range={x=[num],y=[num]},speed=[num],loop=[bool]},
{range={x=[num],y=[num]},speed=[num],loop=[bool]},
...
}
...
}
sounds = {
[name] = [string filename], --single, simple,
[name] = { --single, more powerful. All fields but 'name' are optional
name = [string filename],
gain=[num or range], --range is a table of the format {left_bound, right_bound}
fade=[num or range],
pitch=[num or range],
},
[name] = { --variant, sound is chosen randomly
{
name = [string filename],
gain=[num or range],
fade=[num or range],
pitch=[num or range],
},
{
name = [string filename],
gain=[num or range],
fade=[num or range],
pitch=[num or range],
},
...
},
...
},
max_speed = [num], -- m/s
jump_height = [num], -- nodes/meters
view_range = [num], -- nodes/meters
attack={range=[num], -- range is distance between attacker's collision box center
damage_groups={fleshy=[num]}}, -- and the tip of the murder weapon in nodes/meters
armor_groups = {fleshy=[num]}
})
1.5 Exposed luaentity members
Some frequently used entity fields to be accessed directly for convenience
self.dtime -- max(dtime as passed to on_step,0.5) - limit of 0.05 to prevent jerkines on long steps.
self.hp -- hitpoints
self.isonground -- true if in collision with negative Y
self.isinliquid -- true if the node at foot level is drawtype=='liquid'
------------
2. Reference
------------
2.1 Utility Functions
function mobkit.minmax(v,m)
-- v,n: numbers
-- returns v trimmed to <-m,m> range
function mobkit.get_terrain_height(pos,steps)
-- recursively search for walkable surface at pos.
-- steps (optional) is how far from pos it gives up, expressed in nodes, default 3
-- Returns:
-- surface height at pos, or nil if not found
-- liquid flag: true if found surface is covered with liquid
function mobkit.turn2yaw(self,tyaw,rate)
-- gradually turns towards yaw
-- self: luaentity
-- tyaw: target yaw in radians
-- rate: turn rate in rads/s
--returns: true if facing tyaw; current yaw
function mobkit.timer(self,s)
-- returns true approx every s seconds
-- used to reduce execution of code that needn't necessarily be done on every engine step
function mobkit.pos_shift(pos,vec)
-- convenience function
-- returns pos shifted by vec
-- vec needn't have all three components given, absent components are assumed zero.
-- e.g pos_shift(pos,{y=1}) is valid
function mobkit.pos_translate2d(pos,yaw,dist)
-- returns pos translated in the yaw direction by dist
function mobkit.get_stand_pos(thing)
-- returns object pos projected onto the bottom collisionbox face
-- thing can be luaentity or objectref.
function mobkit.nodeatpos(pos)
-- convenience function
-- returns nodedef or nil if it's an ignore node
function mobkit.get_node_pos(pos)
-- returns center of the node that pos is inside
function mobkit.get_nodes_in_area(pos1,pos2,[full])
-- in basic mode returns a table of unique nodes within area indexed by node
-- in full=true mode returns a table of nodes indexed by pos
-- works for up to 125 nodes.
function mobkit.isnear2d(p1,p2,thresh)
-- returns true if pos p2 is within a square with center at pos p1 and radius thresh
-- y components are ignored
function mobkit.is_there_yet2d(pos,dir,dest) -- obj positon; facing vector; destination position
-- returns true if a position dest is behind position pos according to facing vector dir
-- (checks if dest is in the rear half plane as defined by pos and dir)
-- y components are ignored
function mobkit.isnear3d(p1,p2,thresh)
-- returns true if pos p2 is within a cube with center at pos p1 and radius thresh
function mobkit.get_box_intersect_cols(pos,box)
-- returns an array of {x=,z=} columns that the box intersects with.
function mobkit.get_box_displace_cols(pos,box,vec,dist)
-- returns an array of {x=,z=} columns that the box would pass by if moved by horizontal vector vec
-- if dist provided, vec gets normalized.
function mobkit.dir_to_rot(v,rot)
-- converts a 3d vector v to rotation like in set_rotation() object method
-- rot (optional) is current object rotation
function mobkit.rot_to_dir(rot)
-- converts minetest rotation vector (pitch,yaw,roll) to direction unit vector
function mobkit.is_alive(thing)
-- non essential, checks if thing exists in the world and is alive
-- makes an assumption that luaentities are considered dead when their hp < 100
-- thing can be luaentity or objectref.
-- used for stored luaentities and objectrefs
function mobkit.exists(thing)
-- checks if thing exists in the world
-- thing can be luaentity or objectref.
-- used for stored luaentities and objectrefs
function mobkit.hurt(luaent,dmg)
-- decrease luaent.hp by dmg
function mobkit.heal(luaent,dmg)
-- increase luaent.hp by dmg
function mobkit.get_spawn_pos_abr(dtime,intrvl,radius,chance,reduction)
-- returns a potential spawn position at random intervals
-- intrvl: avg spawn attempt interval for every player
-- radius: spawn distance in nodes, active_block_range*16 is recommended
-- chance: (0,1) chance to spawn a mob if there are no other objects in area
-- reduction: (0,1) spawn chance is reduced by this factor for every object in range.
--usage:
minetest.register_globalstep(function(dtime)
local spawnpos = mobkit.get_spawn_pos_abr(...)
if spawnpos then
... -- mod/game specific logic
end
end)
function mobkit.animate(self,anim)
-- makes an entity play an animation of name anim, or does nothing if not defined
-- anim is string, see entity definition
-- does nothing if the same animation is already running
function mobkit.make_sound(self,sound)
-- sound is string, see entity definition
-- makes an entity play sound, or does nothing if not defined
--returns sound handle
function mobkit.go_forward_horizontal(self,speed)
-- sets an entity's horizontal velocity in yaw direction. Vertical velocity unaffected.
function mobkit.drive_to_pos(self,tpos,speed,turn_rate,dist)
-- moves in facing direction while gradually turning towards tpos, returns true if in dist distance from tpos
-- tpos: target position
-- speed: in m/s
-- turn_rate: in rad/s
-- dist: in m.
-- Memory functions.
This represents mob long term memory
Warning: Stuff in memory is serialized, never try to remember objectrefs or tables referencing them
or the engine will crash.
function mobkit.remember(self,key,val)
-- premanently store a key, value pair
function mobkit.forget(self,key)
-- clears a memory entry
function mobkit.recall(self,key)
-- returns val associated with key
-- Queue functions
function mobkit.queue_high(self,func,priority)
-- only for use in behavior definitions, see 1.1.2
function mobkit.queue_low(self,func)
-- only for use in behavior definitions, see 1.1.1
function mobkit.clear_queue_high(self)
function mobkit.clear_queue_low(self)
function mobkit.is_queue_empty_high(self)
function mobkit.is_queue_empty_low(self)
function mobkit.get_queue_priority(self)
-- returns the priority of currently running behavior
-- this is also the highest of all queued behaviors
-- Use these inside logic functions --
function mobkit.vitals(self)
-- default drowning and fall damage, call it before hp check
function mobkit.get_nearby_player(self)
-- returns random player if nearby or nil
function mobkit.get_nearby_entity(self,name)
-- returns random nearby entity of name or nil
function mobkit.get_closest_entity(self,name)
-- returns closest entity of name or nil
-- Misc
Neighbors structure represents a node's horizontal neighbors
Not essential, used by some built in behaviors
Custom behaviors may not need it.
Neighbor #1 is offset {x=1,z=0}, subsequent numbers go clockwise
function mobkit.dir2neighbor(dir)
-- converts a 3d vector to neighbor number, y component ignored
function mobkit.neighbor_shift(neighbor,shift)
-- get another neighbor number relative to the given, shift: plus is clockwise, minus the opposite
-- 1,1 = 2; 1,-2 = 7
2.2 Built in behaviors
function mobkit.goto_next_waypoint(self,tpos)
-- this functions groups common operations making mobs move in a specific direction
-- not a behavior itself, but is used by some built in HL behaviors
-- which use node by node movement algorithm
2.2.1 High Level Behaviors --
function mobkit.hq_roam(self,prty)
-- slow random roaming
-- never returns
function mobkit.hq_follow(self,prty,tgtobj)
-- follow the tgtobj
-- returns if tgtobj becomes inactive
function mobkit.hq_goto(self,prty,tpos)
-- go to tpos position
-- returns on arrival
function mobkit.hq_runfrom(self,prty,tgtobj)
-- run away from tgtobj object
-- returns when tgtobj far enough
function mobkit.hq_hunt(self,prty,tgtobj)
-- follow tgtobj and when close enough, kick off hq_attack
-- returns when tgtobj too far
function mobkit.hq_warn(self,prty,tgtobj)
-- when a tgtobj close by, turn towards them and make the 'warn' sound
-- kick off hq_hunt if tgtobj too close or timer expired
-- returns when tgtobj moves away
function mobkit.hq_die(self)
-- default death, rotate and remove() after set time
function mobkit.hq_attack(self,prty,tgtobj)
-- default attack, turns towards tgtobj and leaps
-- returns when tgtobj out of range
function mobkit.hq_liquid_recovery(self,prty)
-- use when submerged in liquid, scan for nearest land
-- if land is found within view_range, kick off hq_swimto
-- otherwise die
function mobkit.hq_swimto(self,prty,tpos)
-- swim towards the position tpos, jump if necessary
-- returns if standing firmly on dry land
Aquatic behaviors:
Macros:
function aqua_radar_dumb(pos,yaw,range,reverse)
-- assumes a mob will avoid shallows
-- checks if a pos in front of a moving entity swimmable
-- otherwise returns new position
function mobkit.is_in_deep(target)
-- checks if an object is in water at least 2 nodes deep
Hq Behaviors:
function mobkit.hq_aqua_roam(self,prty,speed)
function mobkit.hq_aqua_attack(self,prty,tgtobj,speed)
function mobkit.hq_aqua_turn(self,prty,tyaw,speed)
-- used by both previous bhv
2.2.2 Low Level Behaviors --
function mobkit.lq_turn2pos(self,tpos)
-- gradually turn towards tpos position
-- returns when facing tpos
function mobkit.lq_idle(self,duration)
-- do nothing for duration seconds
-- set 'stand' animation
function mobkit.lq_dumbwalk(self,dest,speed_factor)
-- simply move towards dest
-- set 'walk' animation
function mobkit.lq_dumbjump(self,height)
-- if standing on the ground, jump in the facing direction
-- height is relative to feet level
-- set 'stand' animation
function mobkit.lq_freejump(self)
-- unconditional jump in the facing direction
-- useful e.g for getting out of water
-- returns when the apex has been reached
function mobkit.lq_jumpattack(self,height,target)
-- jump towards the target, punch if a hit
-- returns after punch or on the ground
function mobkit.lq_fallover(self)
-- gradually rotates Z = 0 to pi/2
2.3 Constants and member variables --
mobkit.gravity = -9.8
mobkit.friction = 0.4 -- inert entities will slow down when in contact with the ground
-- the smaller the number, the greater the effect
self.dtime -- for convenience, dtime as passed to currently executing on_step()
self.isonground -- true if y velocity is 0 for at least two succesive steps
self.isinliquid -- true if feet submerged in liquid type=source

View File

@ -0,0 +1,2 @@
name = mobkit
description = Entity API

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 TheTermos
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
mobkit

View File

@ -0,0 +1,303 @@
local abr = minetest.get_mapgen_setting('active_block_range')
local wildlife = {}
--wildlife.spawn_rate = 0.5 -- less is more
local min=math.min
local max=math.max
local spawn_rate = 1 - max(min(minetest.settings:get('wildlife_spawn_chance') or 0.2,1),0)
local spawn_reduction = minetest.settings:get('wildlife_spawn_reduction') or 0.5
local function node_dps_dmg(self)
local pos = self.object:get_pos()
local box = self.object:get_properties().collisionbox
local pos1 = {x = pos.x + box[1], y = pos.y + box[2], z = pos.z + box[3]}
local pos2 = {x = pos.x + box[4], y = pos.y + box[5], z = pos.z + box[6]}
local nodes_overlap = mobkit.get_nodes_in_area(pos1, pos2)
local total_damage = 0
for node_def, _ in pairs(nodes_overlap) do
local dps = node_def.damage_per_second
if dps then
total_damage = math.max(total_damage, dps)
end
end
if total_damage ~= 0 then
mobkit.hurt(self, total_damage)
end
end
local function predator_brain(self)
-- vitals should be checked every step
if mobkit.timer(self,1) then node_dps_dmg(self) end
mobkit.vitals(self)
-- if self.object:get_hp() <=100 then
if self.hp <= 0 then
mobkit.clear_queue_high(self) -- cease all activity
mobkit.hq_die(self) -- kick the bucket
return
end
if mobkit.timer(self,1) then -- decision making needn't happen every engine step
local prty = mobkit.get_queue_priority(self)
if prty < 20 and self.isinliquid then
mobkit.hq_liquid_recovery(self,20)
return
end
local pos=self.object:get_pos()
-- hunt
if prty < 10 then -- if not busy with anything important
local prey = mobkit.get_closest_entity(self,'wildlife:deer') -- look for prey
if prey then
mobkit.hq_hunt(self,10,prey) -- and chase it
end
end
if prty < 9 then
local plyr = mobkit.get_nearby_player(self)
if plyr and vector.distance(pos,plyr:get_pos()) < 10 then -- if player close
mobkit.hq_warn(self,9,plyr) -- try to repel them
end -- hq_warn will trigger subsequent bhaviors if needed
end
-- fool around
if mobkit.is_queue_empty_high(self) then
mobkit.hq_roam(self,0)
end
end
end
local function herbivore_brain(self)
if mobkit.timer(self,1) then node_dps_dmg(self) end
mobkit.vitals(self)
if self.hp <= 0 then
mobkit.clear_queue_high(self)
mobkit.hq_die(self)
return
end
if mobkit.timer(self,1) then
local prty = mobkit.get_queue_priority(self)
if prty < 20 and self.isinliquid then
mobkit.hq_liquid_recovery(self,20)
return
end
local pos = self.object:get_pos()
if prty < 11 then
local pred = mobkit.get_closest_entity(self,'wildlife:wolf')
if pred then
mobkit.hq_runfrom(self,11,pred)
return
end
end
if prty < 10 then
local plyr = mobkit.get_nearby_player(self)
if plyr and vector.distance(pos,plyr:get_pos()) < 8 then
mobkit.hq_runfrom(self,10,plyr)
return
end
end
if mobkit.is_queue_empty_high(self) then
mobkit.hq_roam(self,0)
end
end
end
-- spawning is too specific to be included in the api, this is an example.
-- a modder will want to refer to specific names according to games/mods they're using
-- in order for mobs not to spawn on treetops, certain biomes etc.
local function spawnstep(dtime)
for _,plyr in ipairs(minetest.get_connected_players()) do
if math.random()<dtime*0.2 then -- each player gets a spawn chance every 5s on average
local vel = plyr:get_player_velocity()
local spd = vector.length(vel)
local chance = spawn_rate * 1/(spd*0.75+1) -- chance is quadrupled for speed=4
local yaw
if spd > 1 then
-- spawn in the front arc
yaw = plyr:get_look_horizontal() + math.random()*0.35 - 0.75
else
-- random yaw
yaw = math.random()*math.pi*2 - math.pi
end
local pos = plyr:get_pos()
local dir = vector.multiply(minetest.yaw_to_dir(yaw),abr*16)
local pos2 = vector.add(pos,dir)
pos2.y=pos2.y-5
local height, liquidflag = mobkit.get_terrain_height(pos2,32)
if height and height >= 0 and not liquidflag -- and math.abs(height-pos2.y) <= 30 testin
and mobkit.nodeatpos({x=pos2.x,y=height-0.01,z=pos2.z}).is_ground_content then
local objs = minetest.get_objects_inside_radius(pos,abr*16+5)
local wcnt=0
local dcnt=0
for _,obj in ipairs(objs) do -- count mobs in abrange
if not obj:is_player() then
local luaent = obj:get_luaentity()
if luaent and luaent.name:find('wildlife:') then
chance=chance + (1-chance)*spawn_reduction -- chance reduced for every mob in range
if luaent.name == 'wildlife:wolf' then wcnt=wcnt+1
elseif luaent.name=='wildlife:deer' then dcnt=dcnt+1 end
end
end
end
--minetest.chat_send_all('chance '.. chance)
if chance < math.random() then
-- if no wolves and at least one deer spawn wolf, else deer
-- local mobname = (wcnt==0 and dcnt > 0) and 'wildlife:wolf' or 'wildlife:deer'
local mobname = dcnt>wcnt+1 and 'wildlife:wolf' or 'wildlife:deer'
pos2.y = height+0.5
objs = minetest.get_objects_inside_radius(pos2,abr*16-2)
for _,obj in ipairs(objs) do -- do not spawn if another player around
if obj:is_player() then return end
end
--minetest.chat_send_all('spawnin '.. mobname ..' #deer:' .. dcnt)
minetest.add_entity(pos2,mobname) -- ok spawn it already damnit
end
end
end
end
end
minetest.register_globalstep(spawnstep)
minetest.register_entity("wildlife:wolf",{
-- common props
physical = true,
stepheight = 0.1, --EVIL!
collide_with_objects = true,
collisionbox = {-0.3, -0.00, -0.3, 0.3, 0.85, 0.3},
visual = "mesh",
mesh = "mobs_mc_wolf.b3d",
textures = {"mobs_mc_wolf.png"},
visual_size = {x = 1.3, y = 1.3},
static_save = true,
makes_footstep_sound = true,
on_step = mobkit.stepfunc, -- required
on_activate = mobkit.actfunc, -- required
get_staticdata = mobkit.statfunc,
-- api props
springiness=0,
buoyancy = 0.75, -- portion of hitbox submerged
max_speed = 5,
jump_height = 1.26,
view_range = 24,
lung_capacity = 10, -- seconds
max_hp = 14,
timeout=600,
attack={range=0.5,damage_groups={fleshy=7}},
sounds = {
attack = "mobs_mc_wolf_bark",
war_cry = "mobs_mc_wolf_growl",
warn = "mobs_mc_wolf_growl",
damage = {name = "mobs_mc_wolf_hurt", gain=0.6},
death = {name = "mobs_mc_wolf_death", gain=0.6},
eat = "mobs_mc_animal_eat_generic",
distance = 16,
},
animation = {
walk={range={x=10,y=29},speed=30,loop=true},
stand={range={x=1,y=5},speed=1,loop=true},
},
brainfunc = predator_brain,
on_punch=function(self, puncher, time_from_last_punch, tool_capabilities, dir)
if mobkit.is_alive(self) then
local hvel = vector.multiply(vector.normalize({x=dir.x,y=0,z=dir.z}),4)
self.object:set_velocity({x=hvel.x,y=2,z=hvel.z})
mobkit.hurt(self,tool_capabilities.damage_groups.fleshy or 1)
if type(puncher)=='userdata' and puncher:is_player() then -- if hit by a player
mobkit.clear_queue_high(self) -- abandon whatever they've been doing
mobkit.hq_hunt(self,10,puncher) -- get revenge
end
end
end
})
minetest.register_entity("wildlife:deer",{
-- common props
physical = true,
stepheight = 0.1, --EVIL!
collide_with_objects = true,
hp_min = 10,
hp_max = 10,
xp_min = 1,
xp_max = 3,
rotate = 270,
collisionbox = {-0.45, -0.01, -0.45, 0.45, 1.39, 0.45},
visual = "mesh",
mesh = "mobs_mc_cow.b3d",
textures = {
"mobs_mc_cow.png",
},
visual_size = {x=2.8, y=2.8},
static_save = true,
makes_footstep_sound = true,
on_step = mobkit.stepfunc, -- required
on_activate = mobkit.actfunc, -- required
get_staticdata = mobkit.statfunc,
-- api props
springiness=0,
buoyancy = 0.9,
max_speed = 5,
jump_height = 1.26,
view_range = 24,
lung_capacity = 10, -- seconds
max_hp = 10,
timeout = 600,
attack={range=0.5,damage_groups={fleshy=3}},
sounds = {
random = "mobs_mc_cow",
scared = "mobs_mc_cow_hurt",
hurt = "mobs_mc_cow_hurt",
eat = "mobs_mc_animal_eat_generic",
distance = 16,
},
animation = {
walk={range={x=10,y=29},speed=30,loop=true},
stand={range={x=1,y=5},speed=1,loop=true},
},
brainfunc = herbivore_brain,
on_punch=function(self, puncher, time_from_last_punch, tool_capabilities, dir)
local hvel = vector.multiply(vector.normalize({x=dir.x,y=0,z=dir.z}),4)
self.object:set_velocity({x=hvel.x,y=2,z=hvel.z})
mobkit.make_sound(self,'hurt')
mobkit.hurt(self,tool_capabilities.damage_groups.fleshy or 1)
end,
})
-- minetest.register_on_chat_message(
-- function(name, message)
-- if message == 'doit' then
-- local plyr=minetest.get_player_by_name(name)
-- local pos=mobkit.get_stand_pos(plyr)
-- local nodes = mobkit.get_nodes_in_area(pos,mobkit.pos_shift(pos,{x=-1,z=-1,y=-1}))
-- for p,n in pairs(nodes) do
-- minetest.chat_send_all(p.name ..' '.. dump(n))
-- end
-- end
-- end
-- )

View File

@ -0,0 +1,2 @@
name = wildlife
depends = mobkit

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -0,0 +1,15 @@
# This file contains settings of wildlife that can be changed in
# minetest.conf
# Chance of spawning a mob when there are no other
# mobs in active_block_range around player
# must be float from 0 to 1
# 1 is always, 0 is never
wildlife_spawn_chance (Spawn chance) float 0.3
# Base spawn chance is reduced by this factor
# for each mob within active_block_range
# must be float from 0 to 1
# greater number is greater reduction
wildlife_spawn_reduction (Spawn chance reduction) float 0.2

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
dog sounds by Mike Koenig
http://soundbible.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.