Add files via upload

master
TheTermos 2019-09-14 17:26:56 +02:00 committed by GitHub
parent d7288f6c2b
commit 04ae1483e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 3910 additions and 0 deletions

570
behaviors.lua Normal file
View File

@ -0,0 +1,570 @@
-- node by node land movement macros
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)
--minetest.chat_send_all('pos2 ' .. minetest.serialize(pos2))
--minetest.chat_send_all('nogopos ' .. minetest.serialize(nogopos))
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, liq = mobkit.is_neighbor_node_reachable(self,mobkit.neighbor_shift(neighbor,4))
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
-- 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
function mobkit.dumbstep(self,height,tpos,speed_factor)
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
mobkit.lq_idle(self,random(1,6))
end
----------------------------
-- 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,yaw)
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 phase=1
local func=function(self)
if not mobkit.is_alive(target) then return true end
if self.isonground then
if phase==1 then -- collision bug workaround
local vel = self.object:get_velocity()
vel.y = -mobkit.gravity*sqrt(height*2/-mobkit.gravity)
self.object:set_velocity(vel)
mobkit.make_sound(self,'charge')
phase=2
else
return true
end
elseif phase==2 then
local dir = minetest.yaw_to_dir(self.object:get_yaw())
local vy = self.object:get_velocity().y
dir=vector.multiply(dir,6)
dir.y=vy
self.object:set_velocity(dir)
phase=3
elseif phase==3 then -- in air
local twidth = target:get_properties().collisionbox[1]
local pos = self.object:get_pos()
-- calculate attack spot
local dir = minetest.yaw_to_dir(self.object:get_yaw())
dir2 = vector.add(dir,self.attack.range+twidth)
local apos = vector.add(pos,dir2)
-- local tpos = mobkit.get_stand_pos(target) --test
-- tpos.y = tpos.y+height
if mobkit.isnear2d(apos,target:get_pos(),0.25) 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')
phase=4
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.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)
timer=0
tgttime = 0
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true 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.brainfunc = 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.8 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 func = function(self)
-- if not self.isinliquid and mobkit.is_queue_empty_low(self) then return true end
if not self.isinliquid and self.isonground then return true end
-- local pos = self.object:get_pos()
local pos = mobkit.get_stand_pos(self)
local y=self.object:get_velocity().y
local pos2d = {x=pos.x,y=0,z=pos.z}
local dir=vector.normalize(vector.direction(pos2d,tpos))
local yaw = minetest.dir_to_yaw(dir)
if mobkit.timer(self,1) then
--perpendicular vectors: {-z,x};{z,-x}
local offset=self.collisionbox[1]
local pos1 = mobkit.pos_shift(mobkit.pos_shift(pos,{x=-dir.z*offset,z=dir.x*offset}),dir)
local h,l = mobkit.get_terrain_height(pos1)
if h and h>pos.y then
mobkit.lq_freejump(self)
else
local pos2 = mobkit.pos_shift(mobkit.pos_shift(pos,{x=dir.z*offset,z=-dir.x*offset}),dir)
local h,l = mobkit.get_terrain_height(pos2)
if h and h>pos.y then
mobkit.lq_freejump(self)
end
end
elseif mobkit.turn2yaw(self,yaw) then
dir.y = y
self.object:set_velocity(dir)
end
end
mobkit.queue_high(self,func,prty)
end

227
core.lua Normal file
View File

@ -0,0 +1,227 @@
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.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.lqueue = {}
self.hqueue = {}
self.nearby_objects = {}
self.nearby_players = {}
self.pos_history = {}
self.path_dir = 1
self.time_total = 0
local sdata = minetest.deserialize(staticdata)
if sdata then
for k,v in pairs(sdata) do
self[k] = v
end
end
if self.timeout and self.timeout>0 and dtime_s > self.timeout and next(self.memory)==nil then
self.object:remove()
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
-- apply texture
if self.texture_no then
local props = {}
props.textures = {self.textures[self.texture_no]}
self.object:set_properties(props)
end
--hp
self.hp = self.hp or (self.max_hp or 10)
--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.oxygen = self.oxygen or self.lung_capacity
self.lastvelocity = {x=0,y=0,z=0}
self.height = self.collisionbox[5] - self.collisionbox[2]
self.sensefunc=sensors()
end
function mobkit.stepfunc(self,dtime) -- not intended to be modified
self.dtime = dtime
-- physics comes first
-- self.object:set_acceleration({x=0,y=mobkit.gravity,z=0})
local vel = self.object:get_velocity()
-- if self.lastvelocity.y == vel.y then
if abs(self.lastvelocity.y-vel.y)<0.001 then
self.isonground = true
else
self.isonground = false
end
-- dumb friction
if self.isonground then
self.object:set_velocity({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
local vnew = vector.new(vel)
if not self.collided then -- ugly workaround for inconsistent collisions
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
if not vector.equals(vel,vnew) then
self.collided = true
else
if self.collided then
vnew = vector.new(self.lastvelocity)
end
self.collided = false
end
self.object:set_velocity(vnew)
end
-- buoyancy
local spos = mobkit.get_stand_pos(self)
spos.y = spos.y+0.01
-- get surface height
-- local surface = mobkit.get_node_pos(spos).y+0.5
local surface = nil
local snodepos = mobkit.get_node_pos(spos)
local surfnode = mobkit.nodeatpos(spos)
while surfnode and surfnode.drawtype == 'liquid' do
surface = snodepos.y+0.5
if surface > spos.y+self.height then break end
snodepos.y = snodepos.y+1
surfnode = mobkit.nodeatpos(snodepos)
end
if surface then -- standing in liquid
self.isinliquid = true
local submergence = min(surface-spos.y,self.height)
local balance = self.buoyancy*self.height
local buoyacc = mobkit.gravity*((balance - submergence)^2/balance^2*sign(balance - submergence))
self.object:set_acceleration({x=-vel.x,y=buoyacc-vel.y*abs(vel.y)*0.7,z=-vel.z})
else
self.isinliquid = false
self.object:set_acceleration({x=0,y=mobkit.gravity,z=0})
end
-- local footnode = mobkit.nodeatpos(spos)
-- local headnode
-- if footnode and footnode.drawtype == 'liquid' then
-- vel = self.object:get_velocity()
-- headnode = mobkit.nodeatpos(mobkit.pos_shift(spos,{y=self.height or 0})) -- TODO: height may be nil
-- local submergence = headnode.drawtype=='liquid'
-- and self.buoyancy-1
-- or (self.buoyancy*self.height-(1-(spos.y+0.5)%1))^2/(self.buoyancy*self.height)^2*sign(self.buoyancy*self.height-(1-(spos.y+0.5)%1))
-- local buoyacc = submergence * mobkit.gravity
-- self.object:set_acceleration({x=-vel.x,y=buoyacc-vel.y*abs(vel.y)*0.5,z=-vel.z})
-- end
if self.brainfunc then
-- vitals: fall damage
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-100) * min(1, velocity_delta/mobkit.terminal_velocity))
end
-- vitals: oxygen
local headnode = mobkit.nodeatpos(mobkit.pos_shift(self.object:get_pos(),{y=self.collisionbox[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
self:sensefunc()
self:brainfunc()
execute_queues(self)
end
self.lastvelocity = self.object:get_velocity()
self.time_total=self.time_total+self.dtime
end

1331
init.lua Normal file

File diff suppressed because it is too large Load Diff

477
mobkit_api.txt Normal file
View File

@ -0,0 +1,477 @@
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.2 Brain 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
!!! 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.2 Brain 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 |
| |
| ----------------------- |
| | Brain 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 brain 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
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,
brainfunc = [function user defined],
-- optional mobkit props
-- or used by built in behaviors
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],
...
}
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 - dtime as passed to on_step
self.hp - hitpoints
self.isonground - true if pos.y remains unchanged for 2 consecutive steps
self.isinliquid - true if the node at foot level is drawtype=='liquid'
------------
2. Reference
------------
2.1 Utility Functions
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.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.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.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.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
-- 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 brain functions --
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
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

1305
utility.lua Normal file

File diff suppressed because it is too large Load Diff