forked from VoxeLibre/VoxeLibre
Compare commits
2 Commits
master
...
mobs-study
Author | SHA1 | Date |
---|---|---|
cora | bdad4362b0 | |
cora | ff5fc89885 |
|
@ -222,7 +222,7 @@ function mobs:spawn_specific(name, dimension, type_of_spawning, biomes, min_ligh
|
||||||
--print(dump(biomes))
|
--print(dump(biomes))
|
||||||
|
|
||||||
-- Do mobs spawn at all?
|
-- 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
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
||||||
|
name = mobkit
|
||||||
|
description = Entity API
|
Binary file not shown.
After Width: | Height: | Size: 80 KiB |
|
@ -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.
|
|
@ -0,0 +1 @@
|
||||||
|
mobkit
|
|
@ -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
|
||||||
|
-- )
|
|
@ -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 |
|
@ -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.
Binary file not shown.
Binary file not shown.
|
@ -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.
Loading…
Reference in New Issue