mob_core/hq_lq.lua

1351 lines
46 KiB
Lua

------------------------------
-- Mob Core HQ/LQ Functions --
------------------------------
---------- Ver 0.1 -----------
------------
-- Locals --
------------
local random = math.random
local pi = math.pi
local abs = math.abs
local ceil = math.ceil
local vec_dist = vector.distance
local abr = minetest.get_mapgen_setting('active_block_range')
local legacy_jump = minetest.settings:get_bool("legacy_jump")
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}
}
---------------------
-- Quick Callbacks --
---------------------
-- Current Collisionbox --
function mob_core.get_hitbox(object)
if type(object) == "table" then
object = object.object
end
return object:get_properties().collisionbox
end
local hitbox = mob_core.get_hitbox -- Recommended use for cleaner code
local function dist_2d(pos1, pos2)
local a = vector.new(pos1.x, 0, pos1.z)
local b = vector.new(pos2.x, 0, pos2.z)
return vec_dist(a, b)
end
--------------------
-- Object Control --
--------------------
-- Set Vertical Velocity --
local function set_lift(self, val)
local vel = self.object:get_velocity()
vel.y = val
self.object:set_velocity(vel)
end
-------------
-- Sensors --
-------------
local function index_collisions(self, pos, no_air)
local width = self.object:get_properties().collisionbox[4] + 1
local pos1 = vector.subtract(pos, width)
local pos2 = vector.add(pos, width)
local collisions = {}
for x = pos1.x, pos2.x do
for y = pos1.y, pos2.y do
for z = pos1.z, pos2.z do
local npos = vector.new(x, y, z)
local name = minetest.get_node(npos).name
if minetest.registered_nodes[name].walkable or
(no_air and name == "air") then
table.insert(collisions, npos)
end
end
end
end
return collisions
end
-- Can Fit --
function mob_core.can_fit(self, pos, no_air)
local width = hitbox(self)[4] + 1
local height = self.height * 0.5
local pos1 = vector.new(pos.x - width, pos.y - height, pos.z - width)
local pos2 = vector.new(pos.x + width, pos.y + height, pos.z + width)
for x = pos1.x, pos2.x do
for y = pos1.y, pos2.y do
for z = pos1.z, pos2.z do
local npos = vector.new(x, y, z)
local name = minetest.get_node(npos).name
if minetest.registered_nodes[name].walkable or
(no_air and name == "air") then
return false
end
end
end
end
return true
end
-- Obstacle Avoidance Calculation --
local function find_closest_pos(tbl, pos)
local iter = 2
if #tbl < 2 then return end
local closest = tbl[1]
while iter < #tbl do
if vec_dist(pos, closest) < vec_dist(pos, tbl[iter + 1]) then
iter = iter + 1
else
closest = tbl[iter]
iter = iter + 1
end
end
if iter >= #tbl and closest then return closest end
end
function mob_core.collision_avoidance(self)
local box = hitbox(self)
local width = abs(box[3]) + abs(box[6])
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local outset = self.obstacle_avoidance_range or 1
local ahead = vector.add(pos, vector.multiply(minetest.yaw_to_dir(yaw),
width * outset))
local can_fit = mob_core.can_fit(self, ahead)
local collisions = index_collisions(self, ahead)
local obstacle = find_closest_pos(collisions, pos)
if not can_fit and obstacle then
local avoidance_path =
vector.normalize((vector.subtract(pos, obstacle)))
local magnitude = (width * 2) - vec_dist(pos, obstacle)
return avoidance_path, magnitude
end
end
-- Find Water Surface --
local function sensor_surface(self, range)
local pos = self.object:get_pos()
local node = minetest.get_node(pos)
local dist = 0
while node.name == self.isinliquid and dist <= range do
pos.y = pos.y + 1
node = minetest.get_node(pos)
dist = dist + 1
end
if node.name ~= self.isinliquid then return dist end
return range
end
-- Find First Solid Node Above Object --
local function sensor_ceil(self, range)
local pos = self.object:get_pos()
local node = minetest.get_node(pos)
local dist = 0
while not minetest.registered_nodes[node.name].walkable and dist <= range do
pos.y = pos.y + 1
node = minetest.get_node(pos)
dist = dist + 1
end
if minetest.registered_nodes[node.name].walkable then return dist end
return range
end
-- Find First Solid/Liquid Node Below Object --
local function sensor_floor(self, range, water)
water = water or false
local pos = self.object:get_pos()
local node = minetest.get_node(pos)
local dist = 0
while not minetest.registered_nodes[node.name].walkable and abs(dist) <=
range do
pos.y = pos.y - 1
node = minetest.get_node(pos)
dist = dist - 1
end
if water then
if minetest.registered_nodes[node.name].walkable or
minetest.registered_nodes[node.name].drawtype == "liquid" then
return abs(dist)
end
else
if minetest.registered_nodes[node.name].walkable then
return abs(dist)
end
end
return range
end
-- Find Nearest Node From Input Table --
function mob_core.find_node_expanding(self, input)
input = input or mob_core.walkable_nodes
local pos = self.object:get_pos()
local radius = 0
local height = 0
local input_index = {}
while radius <= self.view_range do
radius = radius + 1
height = height + 0.25
input_index = minetest.find_nodes_in_area_under_air(
vector.new(pos.x - radius, pos.y - 1, pos.z - radius),
vector.new(pos.x + radius, pos.y + height,
pos.z + radius), input)
end
if #input_index > 1 then return input_index[1] end
end
---------------------
-- Basic Functions --
---------------------
-- Check for a Step --
local function step_check(self) -- This function is only used to find a step for pre 5.3 mobs
if not legacy_jump then return end
local pos = mobkit.get_stand_pos(self)
local width = hitbox(self)[4] + 0.3
local pos1 = {x = pos.x + width, y = pos.y + 1.1, z = pos.z + width}
local pos2 = {x = pos.x - width, y = pos.y, z = pos.z - width}
local area = minetest.find_nodes_in_area_under_air(pos1, pos2,
mob_core.walkable_nodes)
if #area < 1 then return end
for i = 1, #area do
local yaw = self.object:get_yaw()
local yaw_to_node = minetest.dir_to_yaw(vector.direction(pos, area[i]))
if abs(yaw - yaw_to_node) <= 1.5 then
local center = self.object:get_pos()
center.y = center.y + 1.1
center = vector.add(center,
vector.multiply(minetest.yaw_to_dir(yaw), 0.25))
self.object:set_pos(center)
break
end
end
end
-- Check for Fall --
local function line_of_sight(pos1, pos2) -- from mobs_redo, by Astrobe
local ray = minetest.raycast(pos1, pos2, true, true)
local thing = ray:next()
while thing do
if thing.type == "node" then
local name = minetest.get_node(thing.under).name
if minetest.registered_items[name] and
(minetest.registered_items[name].walkable or
minetest.registered_items[name].groups.liquid) then
return false
end
end
thing = ray:next()
end
return true
end
function mob_core.fall_check(self, pos, height) -- Partially taken from mobs_redo
if not mobkit.is_alive(self) then return false end
if height == 0 then return false end
local yaw = self.object:get_yaw()
local dir_x = -math.sin(yaw) * (self.collisionbox[4] + 0.5)
local dir_z = math.cos(yaw) * (self.collisionbox[4] + 0.5)
pos = pos or self.object:get_pos()
local ypos = pos.y + self.collisionbox[2]
if line_of_sight({x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
{x = pos.x + dir_x, y = ypos - height, z = pos.z + dir_z}) then
return true
end
return false
end
------------------------
-- API Object Control --
------------------------
function mob_core.knockback(self, target)
if not self.knockback then return end
local pos = mobkit.get_stand_pos(self)
local pos2 = target:get_pos()
if not pos2 then return end
local dir = vector.direction(pos, pos2)
if target:is_player() then
local vel = vector.multiply(dir, self.knockback)
vel.y = self.knockback * 0.5
target:add_player_velocity(vel)
else
local vel = vector.multiply(dir, self.knockback)
vel.y = self.knockback * 0.5
target:add_velocity(vel)
end
end
-- Punch Timer --
function mob_core.punch_timer(self, new_val)
self.punch_timer = self.punch_timer or 0
if new_val and new_val > 0 then self.punch_timer = new_val end
if self.punch_timer > 0 then
self.punch_timer = self.punch_timer - self.dtime
else
self.punch_timer = 0
end
self.punch_timer = mobkit.remember(self, "punch_timer", self.punch_timer)
end
------------------
-- LQ Functions --
------------------
function mob_core.lq_dumb_punch(self, target)
local func = function(self)
local vel = self.object:get_velocity()
self.object:set_velocity({x = 0, y = vel.y, z = 0})
local pos = self.object:get_pos()
local yaw = self.object:get_yaw()
local tpos = target:get_pos()
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
if abs(tyaw - yaw) > 0.1 then mobkit.turn2yaw(self, tyaw, 4) end
local dist = dist_2d(pos, tpos) - hitbox(target)[4]
if dist < hitbox(self)[4] + self.reach and self.punch_timer <=
0 then
mobkit.animate(self, "punch")
target:punch(self.object, 1.0, {
full_punch_interval = 0.1,
damage_groups = {fleshy = self.damage}
}, nil)
mob_core.punch_timer(self, self.punch_cooldown)
mob_core.knockback(self, target)
self.custom_punch_target = target
if self.custom_punch and self.custom_punch_target then
self.custom_punch(self)
end
mobkit.clear_queue_low(self)
return true
else
return true
end
end
mobkit.queue_low(self, func)
end
------------
-- Aerial --
------------
-- Takeoff --
function mob_core.hq_takeoff(self, prty, lift_force)
lift_force = lift_force or 2
local tyaw = 0
local lift = 0
local init = false
local func = function(self)
if not init then
mobkit.animate(self, "stand")
init = true
end
local yaw = self.object:get_yaw()
local vel = self.object:get_velocity()
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local ceiling = sensor_ceil(self, self.view_range)
local floor = sensor_floor(self, self.view_range, true)
if vel.y > 0 then mobkit.animate(self, "fly") end
if self.isonground or self.isinliquid then
if steer_to then
tyaw = minetest.dir_to_yaw(steer_to)
else
local dir = minetest.yaw_to_dir(yaw)
dir.y = dir.y + self.height
self.object:set_velocity(vector.multiply(dir, lift_force))
end
end
if ceiling > self.height then lift = lift_force end
if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end
if ceiling < self.height and floor < self.height then
mob_core.hq_land(self, prty + 1)
return false
end
if floor >= self.soar_height then
mob_core.hq_aerial_roam(self, prty + 1, 1)
return true
else
lift = lift_force
end
self.object:set_acceleration({x = 0, y = 0, z = 0})
if not turn_intensity or turn_intensity < 1 then
turn_intensity = 1
end
mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed)
end
mobkit.queue_high(self, func, prty)
end
-- Roam --
function mob_core.hq_aerial_roam(self, prty, speed_factor)
local tyaw = 0
local lift = 0
local center = self.object:get_pos()
local init = false
local func = function(self)
if not init then
mobkit.animate(self, 'fly')
init = true
end
local pos = mobkit.get_stand_pos(self)
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local ceiling = sensor_ceil(self, self.view_range)
local floor = sensor_floor(self, self.view_range, true)
if floor and floor <= self.soar_height then
if lift < 1 then lift = lift + 0.2 end
end
if ceiling and ceiling <= math.abs(self.view_range / 4) then
if lift > -1 then lift = lift - 0.2 end
end
if mobkit.timer(self, 1) then
if vec_dist(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
if floor and floor > self.soar_height then lift = 0.1 end
end
if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end
if mobkit.timer(self, random(1, 16)) then
if floor and floor > self.soar_height then lift = -0.2 end
end
self.object:set_acceleration({x = 0, y = 0, z = 0})
if not turn_intensity or turn_intensity < 1 then
turn_intensity = 1
end
mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed * speed_factor)
end
mobkit.queue_high(self, func, prty)
end
-- Land --
function mob_core.hq_land(self, prty, pos2)
local init = false
local func = function(self)
if not init then
mobkit.animate(self, 'fly')
init = true
end
local floor = sensor_floor(self, self.view_range, true)
self.object:set_acceleration{x = 0, y = 0, z = 0}
if pos2 then
local pos = self.object:get_pos()
if vec_dist(pos, pos2) > self.height + 1 then
mobkit.turn2yaw(self, minetest.dir_to_yaw(
vector.direction(pos, pos2)))
set_lift(self, -self.max_speed / 2)
mobkit.go_forward_horizontal(self, self.max_speed)
end
if floor <= self.height + 1 then
mobkit.animate(self, "land")
mobkit.hq_roam(self, prty + 1)
return true
end
else
if floor > self.height + 1 then
mobkit.turn2yaw(self,
minetest.dir_to_yaw(self.object:get_velocity()))
set_lift(self, -self.max_speed / 2)
end
if floor <= self.height + 1 then
mobkit.animate(self, "land")
mobkit.hq_roam(self, prty + 1)
return true
end
end
end
mobkit.queue_high(self, func, prty)
end
-- Follow Holding --
function mob_core.hq_aerial_follow_holding(self, prty, player) -- Follow Player
local tyaw = 0
local lift = 0
local init = false
if not player then return end
if not mob_core.follow_holding(self, player) then return end
local func = function(self)
if mobkit.is_queue_empty_low(self) then
if mob_core.follow_holding(self, player) then
if not init then
mobkit.animate(self, "fast" or "fly")
end
self.status = mobkit.remember(self, "status", "following")
local pos = mobkit.get_stand_pos(self)
local tpos = player:get_pos()
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local ceiling = sensor_ceil(self, self.view_range)
local floor = sensor_floor(self, self.view_range, true)
local dir = vector.direction(pos, tpos)
lift = dir.y
if floor and floor <= self.soar_height then
if lift < 1 then lift = lift + 0.2 end
end
if ceiling and ceiling <= math.abs(self.view_range / 4) then
if lift > -1 then lift = lift - 0.2 end
end
tyaw = minetest.dir_to_yaw(dir)
if steer_to then
tyaw = minetest.dir_to_yaw(steer_to)
end
if lift < 0 then
self.object:set_acceleration({x = 0, y = 0.1, z = 0})
else
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
if not turn_intensity or turn_intensity < 1 then
turn_intensity = 1
end
mobkit.turn2yaw(self, tyaw,
(self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed)
end
end
if (self.status == "following" and
not mob_core.follow_holding(self, player)) then
self.status = mobkit.remember(self, "status", "")
return true
end
end
mobkit.queue_high(self, func, prty)
end
-------------
-- Aquatic --
-------------
local function aqua_radar_dumb(pos, yaw, range, reverse) -- Ported from mobkit
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 = 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
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 mob_core.hq_aqua_roam(self, prty, speed_factor)
local tyaw = 0
local lift = 0
local center = self.object:get_pos()
local init = false
local func = function(self)
if not self.isinliquid then return true end
if not init then
mobkit.animate(self, "swim")
init = true
end
local pos = self.object:get_pos()
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local surface = sensor_surface(self, self.view_range)
local floor = sensor_floor(self, self.view_range)
if floor <= self.floor_avoidance_range then
if lift < 1 then lift = lift + 0.2 end
end
if surface <= self.surface_avoidance_range then
if lift > -1 then lift = lift - 0.2 end
end
if mobkit.timer(self, 1) then
if vec_dist(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
if floor > self.height then
if math.abs(lift) > 0.5 then lift = 0.1 end
end
end
if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end
if mobkit.timer(self, random(3, 6)) then -- Ocassionally go down
if floor > self.floor_avoidance_range and surface >
self.surface_avoidance_range then
if math.random(1, 2) == 1 then
lift = -0.5
else
lift = 0.5
end
end
end
if lift < 0 then
self.object:set_acceleration({x = 0, y = 0.1, z = 0})
else
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
if not turn_intensity or turn_intensity < 1 then
turn_intensity = 1
end
mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed * speed_factor)
end
mobkit.queue_high(self, func, prty)
end
-- Aquatic Attack --
function mob_core.hq_aqua_attack(self, prty, target)
local tyaw = 0
local lift = 0
local init = false
local func = function(self)
if not self.isinliquid or not mobkit.is_alive(target) or
not mob_core.can_fit(self, target:get_pos(), true) then
return true
end
if not init then
mobkit.animate(self, "swim")
init = true
end
local pos = self.object:get_pos()
local tpos = target:get_pos()
local yaw = self.object:get_yaw()
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local surface = sensor_surface(self, self.view_range)
local floor = sensor_floor(self, self.view_range)
local dir = vector.direction(pos, tpos)
lift = dir.y
if floor <= self.floor_avoidance_range then
if lift < 1 then lift = lift + 0.2 end
end
if surface <= self.surface_avoidance_range then
if lift > -1 then lift = lift - 0.2 end
end
tyaw = minetest.dir_to_yaw(dir)
if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end
if lift < 0 then
self.object:set_acceleration({x = 0, y = 0.1, z = 0})
else
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
local target_side = abs(target:get_properties().collisionbox[4])
if vec_dist(pos, tpos) < self.reach + target_side then
target:punch(self.object, 1.0, {
full_punch_interval = 0.1,
damage_groups = {fleshy = self.damage}
}, nil)
mobkit.animate(self, "punch_swim" or "punch")
mob_core.knockback(self, target)
self.custom_punch_target = target
if self.custom_punch and self.custom_punch_target then
self.custom_punch(self)
end
mobkit.hq_aqua_turn(self, prty, yaw - pi, self.max_speed)
return true
end
if not turn_intensity or turn_intensity < 1 then
turn_intensity = 1
end
mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed)
end
mobkit.queue_high(self, func, prty)
end
-- Swim from/Runaway --
function mob_core.hq_swimfrom(self, prty, target, speed)
local init = false
local timer = 6
local func = function(self)
if not mobkit.is_alive(target) then return true end
if not self.isinliquid then return true end
if not init then
timer = timer - self.dtime
if timer <= 0 or vec_dist(self.object:get_pos(), target:get_pos()) <
8 then
mobkit.make_sound(self, 'scared')
init = true
end
return
end
local pos = mobkit.get_stand_pos(self)
local chase_pos = target:get_pos()
local dir = vector.direction(pos, chase_pos)
local yaw = minetest.dir_to_yaw(dir) - (pi / 2)
local dist = vec_dist(pos, chase_pos)
if (dist / 1.5) < self.view_range then
local swimto, height = aqua_radar_dumb(pos, yaw, 3)
if height and height > pos.y then
local vel = self.object:get_velocity()
vel.y = vel.y + 0.1
self.object:set_velocity(vel)
end
mobkit.hq_aqua_turn(self, prty + 1, swimto, speed)
else
return true
end
timer = timer - 1
end
mobkit.queue_high(self, func, prty)
end
-- Aquatic Follow --
function mob_core.hq_aqua_follow_holding(self, prty, player) -- Follow Player
local tyaw = 0
local lift = 0
local init = false
if not player then return end
if not mob_core.follow_holding(self, player) then return end
local func = function(self)
if mobkit.is_queue_empty_low(self) then
if mob_core.follow_holding(self, player) then
if not init then
mobkit.animate(self, "fast" or "swim")
end
local pos = mobkit.get_stand_pos(self)
local tpos = player:get_pos()
self.status = mobkit.remember(self, "status", "following")
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local surface = sensor_surface(self, self.view_range)
local floor = sensor_floor(self, self.view_range)
local dir = vector.direction(pos, tpos)
lift = dir.y
if floor <= self.floor_avoidance_range then
if lift < 1 then lift = lift + 0.2 end
end
if surface <= self.surface_avoidance_range then
if lift > -1 then lift = lift - 0.2 end
end
tyaw = minetest.dir_to_yaw(dir)
if steer_to then
tyaw = minetest.dir_to_yaw(steer_to)
end
if lift < 0 then
self.object:set_acceleration({x = 0, y = 0.1, z = 0})
else
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
if not turn_intensity or turn_intensity < 1 then
turn_intensity = 1
end
mobkit.turn2yaw(self, tyaw,
(self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed)
end
end
if (self.status == "following" and
not mob_core.follow_holding(self, player)) then
self.status = mobkit.remember(self, "status", "")
return true
end
end
mobkit.queue_high(self, func, prty)
end
-----------
-- Basic --
-----------
function mob_core.hq_follow_holding(self, prty, player, stop_threshold) -- Follow Player
if not player then return end
if not mob_core.follow_holding(self, player) then return end
stop_threshold = stop_threshold or 2.5
local func = function(self)
if mobkit.is_queue_empty_low(self) then
if mob_core.follow_holding(self, player) then
local pos = mobkit.get_stand_pos(self)
local tpos = player:get_pos()
if vec_dist(pos, tpos) <= self.collisionbox[4] + stop_threshold then
mobkit.lq_idle(self, 0.1, "stand")
else
if self.animation["run"] then
mobkit.animate(self, "run")
else
mobkit.animate(self, "walk")
end
self.status = mobkit.remember(self, "status", "following")
mobkit.clear_queue_low(self)
mob_core.goto_next_waypoint(self, tpos)
end
end
end
if (self.status == "following" and
not mob_core.follow_holding(self, player)) then
self.status = mobkit.remember(self, "status", "")
mobkit.lq_idle(self, 1, "stand")
return true
end
end
mobkit.queue_high(self, func, prty)
end
-------------------------------
-- Modified Mobkit Functions --
-------------------------------
-- Is Neighbor Node Reachable -- Modified to add variable to ignore liquidflag
function mob_core.is_neighbor_node_reachable(self, neighbor)
local fall = self.max_fall or self.jump_height
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(fall) + 1
local height, liquidflag = mobkit.get_terrain_height(tpos, recursteps)
if height and abs(height - pos.y) <= fall then
tpos.y = height
height = height - pos.y
if neighbor % 2 == 0 then
local n2 = neighbor - 1
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
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
if tpos.y + self.height - pos.y > 1 then
local snpos = mobkit.get_node_pos(pos)
local pos1 = {x = pos.x, y = snpos.y + 1, z = pos.z}
local pos2 = {x = tpos.x, y = tpos.y + self.height, z = tpos.z}
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
if self.ignore_liquidflag then liquidflag = false end
return height, tpos, liquidflag
else
if self.ignore_liquidflag and not mob_core.fall_check(self, pos, fall) then
return 0.1, tpos, false
end
return
end
end
-- Get next Waypoint -- Modified to make use of mob_core.is_neighbor_node_reachable()
function mob_core.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 = mob_core.is_neighbor_node_reachable(self,
neighbor)
if height and not liquidflag and
not (nogopos and mobkit.isnear2d(pos2, nogopos, 0.1)) then
local heightl = mob_core.is_neighbor_node_reachable(self,
mobkit.neighbor_shift(
neighbor, -1))
if heightl and abs(heightl - height) < 0.001 then
local heightr = mob_core.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
local height, pos2, liq = mob_core.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
height, pos2, liq = mob_core.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
height, pos2, liquidflag = mob_core.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
table.remove(self.pos_history, 2)
self.path_dir = self.path_dir * -1
end
------------------------
-- Built-in behaviors --
------------------------
function mob_core.goto_next_waypoint(self, tpos, speed_factor)
speed_factor = speed_factor or 1
local _, pos2 = mob_core.get_next_waypoint(self, tpos)
if pos2 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, speed_factor)
step_check(self)
return true
end
end
-- Dumbstep -- Modified to use new jump mechanic
function mob_core.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
and not self.isinliquid)
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
or self.isinliquid 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
function mob_core.dumbstep(self, tpos, speed_factor, idle_duration)
mobkit.lq_turn2pos(self, tpos)
mob_core.lq_dumbwalk(self, tpos, speed_factor)
step_check(self)
idle_duration = idle_duration or 6
mobkit.lq_idle(self, random(ceil(idle_duration * 0.5), idle_duration))
end
-- Roam -- not finished
function mob_core.hq_roam(self, prty)
local func = function(self)
local fall = self.max_fall or self.jump_height
self.status = mobkit.remember(self, "status", "")
local pos = mobkit.get_stand_pos(self)
if mobkit.is_queue_empty_low(self) and
(self.isonground or not mob_core.fall_check(self, pos, fall)) then
local neighbor = random(8)
local _, tpos, liquidflag = mob_core.is_neighbor_node_reachable(
self, neighbor)
if tpos and not mob_core.fall_check(self, tpos, fall) and
not liquidflag then
mob_core.dumbstep(self, tpos, 0.3, random(4, 8))
end
end
end
mobkit.queue_high(self, func, prty)
end
------------------------------
-- Recoded Mobkit Functions --
------------------------------
-- Run From --
function mob_core.hq_runfrom(self, prty, tgtobj)
local init = false
local timer = 6
local func = function(self)
if not mobkit.is_alive(tgtobj) then return true end
if not init then
timer = timer - self.dtime
if timer <= 0 or vec_dist(self.object:get_pos(), tgtobj:get_pos()) <
8 then
mobkit.make_sound(self, 'scared')
init = true
end
end
mobkit.animate(self, "run")
self.status = mobkit.remember(self, "status", "fleeing")
if mobkit.is_queue_empty_low(self) and self.isonground then
local pos = mobkit.get_stand_pos(self)
local opos = tgtobj:get_pos()
if vec_dist(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
}
mob_core.goto_next_waypoint(self, tpos)
else
mobkit.lq_idle(self, 1)
self.object:set_velocity({x = 0, y = 0, z = 0})
return true
end
end
end
mobkit.queue_high(self, func, prty)
end
-- Liquid Recovery -- Recoded to be smoother and more reliably find land
function mob_core.hq_liquid_recovery(self, prty, anim)
local tpos
local init = false
anim = anim or "walk"
local func = function(self)
if not init then
mobkit.animate(self, anim)
init = true
end
if self.isonground and not self.isinliquid then
mobkit.lq_idle(self, 0.1)
return true
end
local pos = mobkit.get_stand_pos(self)
local scan = mob_core.find_node_expanding(self)
if not tpos then
tpos = scan
elseif (scan and tpos) and (scan.y < tpos.y) then -- This eliminates issue of mobs swimming to walls
tpos = scan
end
if tpos then
local dist = vec_dist(pos, tpos)
mobkit.drive_to_pos(self, tpos, self.max_speed * 0.75, 1,
self.collisionbox[4] * 1.25)
if dist < self.collisionbox[4] * 1.75 then
mobkit.clear_queue_low(self)
mobkit.lq_turn2pos(self, tpos)
local vel = self.object:get_velocity()
vel.y = self.jump_height
self.object:set_velocity(vel)
minetest.after(0.3, function(self, vel)
if self.object:get_luaentity() then
self.object:set_acceleration(
{x = vel.x * 2, y = 0, z = vel.z * 2})
end
end, self, vel)
return true
end
end
end
mobkit.queue_high(self, func, prty)
end
-- Hunt -- Recoded to use various Mob Core functions
function mob_core.hq_hunt(self, prty, target)
local scan_pos = target:get_pos()
scan_pos.y = scan_pos.y + 1
if not line_of_sight(self.object:get_pos(), scan_pos) then return true end
local func = function(self)
if not mobkit.is_alive(target) then
mobkit.clear_queue_high(self)
return true
end
local pos = mobkit.get_stand_pos(self)
local tpos = target:get_pos()
mob_core.punch_timer(self)
if mobkit.is_queue_empty_low(self) then
self.status = mobkit.remember(self, "status", "hunting")
local dist = vec_dist(pos, tpos)
local yaw = self.object:get_yaw()
local tyaw = minetest.dir_to_yaw(vector.direction(pos, tpos))
if abs(tyaw - yaw) > 0.1 then
mobkit.lq_turn2pos(self, tpos)
end
if dist > self.view_range then
self.status = mobkit.remember(self, "status", "")
return true
end
local target_side = abs(target:get_properties().collisionbox[4])
mob_core.goto_next_waypoint(self, tpos)
if vec_dist(pos, tpos) < self.reach + target_side then
self.status = mobkit.remember(self, "status", "")
mob_core.lq_dumb_punch(self, target, "stand")
end
end
end
mobkit.queue_high(self, func, prty)
end
------------------------
-- Built-in Behaviors --
------------------------
function mob_core.fly_to_next_waypoint(self, pos2, speed_factor)
speed_factor = speed_factor or 0.75
local lift
local tyaw
mobkit.animate(self, "fly")
local pos = mobkit.get_stand_pos(self)
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local ceiling = sensor_ceil(self, self.view_range)
-- Basic Movement
if self.isonground or self.isinliquid then return end
lift = 0
tyaw = minetest.dir_to_yaw(vector.direction(pos, pos2))
if ceiling <= self.height * 2 then
if lift > -1 then
lift = lift - 0.2
end
end
if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end
local dir = vector.direction(pos, pos2)
if dir.y > 0 then
lift = dir.y * self.max_speed
else
lift = dir.y * self.max_speed
end
local dist = vec_dist(pos, pos2)
if dist < self.collisionbox[4] then return true end
if not turn_intensity or turn_intensity < 1 then turn_intensity = 1 end
mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed * speed_factor)
end
function mob_core.swim_to_next_waypoint(self, pos2, speed_factor)
speed_factor = speed_factor or 0.75
local lift
local tyaw
mobkit.animate(self, "swim")
local pos = mobkit.get_stand_pos(self)
local steer_to, turn_intensity = mob_core.collision_avoidance(self)
local ceiling = sensor_ceil(self, self.view_range)
-- Basic Movement
if self.isonground or not self.isinliquid then return end
lift = 0
tyaw = minetest.dir_to_yaw(vector.direction(pos, pos2))
if ceiling <= self.height * 2 then
if lift > -1 then
lift = lift - 0.2
end
end
if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end
local dir = vector.direction(pos, pos2)
if dir.y > 0 then
lift = dir.y * self.max_speed
else
lift = dir.y * self.max_speed
end
local dist = vec_dist(pos, pos2)
if dist < self.collisionbox[4] then return true end
if not turn_intensity or turn_intensity < 1 then turn_intensity = 1 end
mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity)
set_lift(self, lift)
mobkit.go_forward_horizontal(self, self.max_speed * speed_factor)
end