------------------------------ -- Mob Core HQ/LQ Functions -- ------------------------------ ---------- Ver 0.1 ----------- ------------ -- Math -- ------------ local random = math.random local pi = math.pi local abs = math.abs local ceil = math.ceil local min = math.min local function diff(a, b) -- Get difference between 2 angles return math.atan2(math.sin(b - a), math.cos(b - a)) end local function clamp(num, min_, max_) if num < min_ then num = min_ elseif num > max_ then num = max_ end return num end local function lerp(a, b, w) if abs(a - b) > pi then if a < b then return (a + (b - a) * 1) + (pi * 2) elseif a > b then return (a + (b - a) * 1) - (pi * 2) end end return a + (b - a) * w end local vec_dist = vector.distance local function vec_cross(a, b) return { x = a.y * b.z - a.z * b.y, y = a.z * b.x - a.x * b.z, z = a.x * b.y - a.y * b.x } end -------------- -- Settings -- -------------- local abr = minetest.get_mapgen_setting('active_block_range') local legacy_jump = minetest.settings:get_bool("legacy_jump") --------------------- -- 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 -- 240805 RJK: Crash fix if not x or not y or not z then ocutil.log ("IE HQLQ #0001") self.object:remove() return {} end local npos = vector.new (x, y, z) if not npos then ocutil.log ("IE HQLQ #0002") self.object:remove() return {} end local gn = minetest.get_node (npos) if not gn then ocutil.log ("IE HQLQ #0003") self.object:remove() return {} end local name = gn.name if not name then ocutil.log ("IE HQLQ #0004") self.object:remove() return {} end local rnode = minetest.registered_nodes [name] if not rnode then ocutil.log ("IE HQLQ #0005") self.object:remove() return {} end if rnode.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 = mobkit.get_stand_pos(self) pos.y = pos.y + (self.height * 0.5) -- center of hitbox local yaw = self.object:get_yaw() local outset = self.obstacle_avoidance_range or width 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, false 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 sensor_floor = mob_core.sensor_floor --------------------- -- Basic Functions -- --------------------- -- 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 kb = self.knockback if kb - hitbox(target)[4] > 1 then kb = kb - hitbox(target)[4] else kb = 1 end local dir = vector.direction({x = pos.x, y = 0, z = pos.z}, {x = pos2.x, y = 0, z = pos2.z}) local vel = vector.multiply(dir, kb) vel.y = kb * 0.2 target:add_velocity(vel) end -- Punch Timer -- function mob_core.punch_timer(self, new_val) 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, anim) anim = anim or "punch" local length = 0.1 if self.animation[anim] then length = (self.animation[anim].range.y - self.animation[anim].range.x) / self.animation[anim].speed end local func = function(self) if not mobkit.exists(target) then return true end 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 mobkit.animate(self, anim or "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 length = length - self.dtime if length <= 0 then return true end end mobkit.queue_low(self, func) end function mob_core.lq_follow_path(self, path, speed_factor, anim) anim = anim or "walk" local timer = #path local func = function(self) if #path <= 1 then return true end local speed = speed_factor or 1 local path_iter = 1 local width = ceil(hitbox(self)[4]) if #path >= width then path_iter = width end local pos = mobkit.get_stand_pos(self) local tpos = path[path_iter] local dir = vector.direction(pos, tpos) local total_dist = vec_dist(pos, path[#path]) if total_dist <= width + 0.5 then return true end if not self.isonground then speed = speed * 0.5 end if vec_dist(pos, tpos) <= width + 0.5 or (path[path_iter + 1] and vec_dist(pos, path[path_iter + 1]) <= width + 0.5) then table.remove(path, 1) timer = timer - 1 end local turn_rate = self.turn_rate or 8 if vector.distance(pos, tpos) < width + 2 then turn_rate = turn_rate + 2 end timer = timer - self.dtime if timer <= 0 then return true end mobkit.turn2yaw(self, minetest.dir_to_yaw(dir), turn_rate) mobkit.go_forward_horizontal(self, self.max_speed * speed) mobkit.animate(self, anim) end mobkit.queue_low(self, func) end ------------ -- Aerial -- ------------ function mob_core.tilt_to_yaw(self, tyaw, rate) self._tyaw = tyaw tyaw = tyaw or 0 -- temp rate = rate or 6 local rot = self.object:get_rotation() 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) local nroll = vec_cross(minetest.yaw_to_dir(yaw), minetest.yaw_to_dir(tyaw)).y local roll = lerp(rot.z, nroll, 0.1) self.object:set_rotation({x = rot.x, y = nyaw - pi, z = roll}) if nyaw == tyaw then return true, nyaw - pi else return false, nyaw - pi end end function mob_core.hq_takeoff_and_fly_continuously(self, prty, lift_force) lift_force = lift_force or 1 local stage = 1 local center = self.object:get_pos() local func = function(self) local pos = mobkit.get_stand_pos(self) local tyaw = minetest.dir_to_yaw(vector.direction(pos, center)) -- Mob will spiral around takeoff point local steer_to, turn_intensity = mob_core.collision_avoidance(self) local dist_to_ground = sensor_floor(self, self.view_range, true) if dist_to_ground < (self.floor_avoidance_range or 16) then local lift = (self.max_speed * 0.5) * lift_force if steer_to then tyaw = minetest.dir_to_yaw(steer_to) turn_intensity = abs(turn_intensity * 3.33) else turn_intensity = 0 end local rot = self.object:get_rotation() self.object:set_rotation({ x = lerp(rot.x, (lift * (math.pi * 0.01)), 0.2), y = rot.y, z = rot.z }) mob_core.tilt_to_yaw(self, tyaw, (self.turn_rate or 4) + turn_intensity) set_lift(self, rot.x * 10) local accel = self.object:get_acceleration() accel.y = accel.y + lift self.object:set_acceleration(accel) mobkit.animate(self, "fly") mobkit.go_forward_horizontal(self, self.max_speed) else mob_core.hq_aerial_roam(self, prty, 1) return true end end mobkit.queue_high(self, func, prty) end mob_core.hq_takeoff = mob_core.hq_takeoff_and_fly_continuously function mob_core.hq_takeoff_and_soar(self, prty, lift_force) lift_force = lift_force or 1 local stage = 1 local center = self.object:get_pos() local func = function(self) local pos = mobkit.get_stand_pos(self) local tyaw = minetest.dir_to_yaw(vector.direction(pos, center)) -- Mob will spiral around takeoff point local steer_to, turn_intensity = mob_core.collision_avoidance(self) local dist_to_ground = sensor_floor(self, self.view_range, true) if dist_to_ground < (self.floor_avoidance_range or 16) then local lift = (self.max_speed * 0.5) * lift_force if steer_to then tyaw = minetest.dir_to_yaw(steer_to) turn_intensity = abs(turn_intensity * 3.33) else turn_intensity = 0 end local rot = self.object:get_rotation() self.object:set_rotation({ x = lerp(rot.x, (lift * (math.pi * 0.01)), 0.2), y = rot.y, z = rot.z }) mob_core.tilt_to_yaw(self, tyaw, (self.turn_rate or 4) + turn_intensity) set_lift(self, rot.x * 10) local accel = self.object:get_acceleration() accel.y = accel.y + lift self.object:set_acceleration(accel) mobkit.animate(self, "fly") mobkit.go_forward_horizontal(self, self.max_speed) else mob_core.hq_fly_and_soar(self, prty) return true end end mobkit.queue_high(self, func, prty) end -- Roam -- function mob_core.hq_fly_continuously(self, prty, speed_factor) speed_factor = speed_factor or 1 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 rot = self.object:get_rotation() local steer_to, turn_intensity = mob_core.collision_avoidance(self) local ceiling = sensor_ceil(self, self.view_range) or self.view_range local floor = sensor_floor(self, self.view_range, true) or self.view_range local max_lift = (self.max_speed / 2) * speed_factor 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 end if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end if mobkit.timer(self, 6) then if floor > self.height + 1 and ceiling > self.height + 1 then lift = random(-max_lift, max_lift) end end if ceiling <= math.abs(self.view_range / 4) then lift = -max_lift end if floor <= (self.floor_avoidance_range or math.ceil(hitbox(self)[4]) * 2) or self.isinliquid then lift = max_lift end self.object:set_acceleration({x = 0, y = 0, z = 0}) if not turn_intensity then turn_intensity = 0 else turn_intensity = abs(turn_intensity * 3.33) end -- Set pitch self.object:set_rotation({ x = lerp(rot.x, (lift * (math.pi * 0.01)), 0.2), y = rot.y, z = rot.z }) -- Set yaw/roll mob_core.tilt_to_yaw(self, tyaw, (self.turn_rate or 4) + turn_intensity) -- Set velocity set_lift(self, rot.x * 10) local accel = self.object:get_acceleration() accel.y = accel.y + lift self.object:set_acceleration(accel) if lift > 0 then mobkit.animate(self, "fly") else mobkit.animate(self, "soar") end mobkit.go_forward_horizontal(self, self.max_speed * speed_factor) end mobkit.queue_high(self, func, prty) end mob_core.hq_aerial_roam = mob_core.hq_fly_continuously function mob_core.hq_fly_and_soar(self, prty, speed_factor) speed_factor = speed_factor or 1 local tyaw = 0 local lift = 0 local center = self.object:get_pos() local func = function(self) local pos = mobkit.get_stand_pos(self) local yaw = self.object:get_yaw() local rot = self.object:get_rotation() local steer_to, turn_intensity = mob_core.collision_avoidance(self) local ceiling = sensor_ceil(self, self.view_range) or self.view_range local floor = sensor_floor(self, self.view_range, true) or self.view_range local max_lift = (self.max_speed) * speed_factor -- Check distance from center every second if mobkit.timer(self, 1) then if vec_dist(pos, center) > abr * 16 * 0.5 then local dir = vector.direction(pos, { x = center.x + random() * 10 - 5, y = center.y, z = center.z + random() * 10 - 5 }) tyaw = minetest.dir_to_yaw(dir) lift = clamp(dir.y, -max_lift, max_lift) else if abs(tyaw - yaw) < 0.2 then tyaw = tyaw + random() * pi - pi * 0.5 if floor > self.height + 1 and ceiling > self.height + 1 then lift = random(-max_lift, max_lift) end end end end -- Steer from obstacles if steer_to then tyaw = minetest.dir_to_yaw(steer_to) end -- Avoid ground if floor <= (self.floor_avoidance_range or math.ceil(hitbox(self)[4]) * 2) then lift = max_lift end -- Avoid ceilings if ceiling <= math.abs(self.view_range / 4) then lift = -max_lift end -- If there is no obstacle, turn at 1 rad/s if not turn_intensity or turn_intensity < 1 then turn_intensity = 1 end -- Set pitch self.object:set_rotation({ x = lerp(rot.x, (lift * (math.pi * 0.01)), 0.2), y = rot.y, z = rot.z }) -- Set yaw/roll mob_core.tilt_to_yaw(self, tyaw, turn_intensity) -- Set velocity set_lift(self, rot.x * 10) local accel = self.object:get_acceleration() accel.y = accel.y + lift self.object:set_acceleration(accel) if lift > 0 then mobkit.animate(self, "fly") else mobkit.animate(self, "soar") end mobkit.go_forward_horizontal(self, self.max_speed * speed_factor) end mobkit.queue_high(self, func, prty) end -- Land -- function mob_core.hq_land_and_roam(self, prty, pos2) local init = false local func = function(self) if not init then mobkit.animate(self, 'fly') init = true end local rot = self.object:get_rotation() local floor = sensor_floor(self, self.view_range, true) self.object:set_acceleration{x = 0, y = 0, z = 0} local pitch = rot.x + ((-self.max_speed / 2) - rot.x) * 0.05 self.object:set_rotation({x = pitch * 0.31, y = rot.y, z = rot.z}) if pos2 then local pos = self.object:get_pos() if vec_dist(pos, pos2) > self.height + 1 then mob_core.tilt_to_yaw(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 self.object:set_rotation( {x = 0, y = self.object:get_yaw(), z = 0}) mobkit.animate(self, "land") mobkit.hq_roam(self, prty + 1) return true end end end mobkit.queue_high(self, func, prty) end mob_core.hq_land = mob_core.hq_land_and_roam -- 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.floor_avoidance_range or math.ceil(hitbox(self)[4]) * 2) 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 reset_timer = 6 local center = self.object:get_pos() local init = false local func = function(self) if not self.isinliquid then return true end if not init or self._anim ~= "swim" 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 or math.ceil(hitbox(self)[4]) * 2) 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 reset_timer <= 0 then center = self.object:get_pos() end if steer_to then tyaw = minetest.dir_to_yaw(steer_to) reset_timer = reset_timer - self.dtime else reset_timer = 6 end if mobkit.timer(self, random(3, 6)) then -- Ocassionally go down if floor > (self.floor_avoidance_range or math.ceil(hitbox(self)[4]) * 2) 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 function mob_core.hq_swim_and_idle(self, prty, speed_factor) local next_pos = self.object:get_pos() local func = function(self) if not self.isinliquid then return true end local pos = self.object:get_pos() if mobkit.timer(self, random(3, 4)) then next_pos = { x = pos.x + random() * 10 - 5, y = pos.y + random(-5, 5), z = pos.z + random() * 10 - 5 } end if minetest.get_node(next_pos).name ~= self.isinliquid then next_pos = { x = pos.x + random() * 10 - 5, y = pos.y + random(-5, 5), z = pos.z + random() * 10 - 5 } end if mobkit.is_queue_empty_low(self) then if vector.distance(pos, next_pos) > hitbox(self)[4] then local dir = vector.direction(pos, next_pos) local tyaw = minetest.dir_to_yaw(dir) local steer_to, turn_intensity = mob_core.collision_avoidance(self) if steer_to then tyaw = minetest.dir_to_yaw(steer_to) else turn_intensity = 1 end mobkit.turn2yaw(self, tyaw, (self.turn_rate or 2) * turn_intensity) set_lift(self, dir.y * self.max_speed) mobkit.go_forward_horizontal(self, self.max_speed * speed_factor) mobkit.animate(self, "swim") else mobkit.lq_idle(self, random(1, 2), "swim_idle") end end 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 or math.ceil(hitbox(self)[4]) * 2) 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 or math.ceil(hitbox(self)[4]) * 2) 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 local function is_object_at_pos(self, pos) local objects = minetest.get_objects_inside_radius(pos, 0.5) if #objects < 1 then return false end for i = 1, #objects do if objects[i] ~= self.object and not (objects[i]:get_luaentity() and objects[i]:get_luaentity().collide_with_objects) then return true end end return false end local function moveable(self, pos) local width = mob_core.get_hitbox(self)[4] local height = self.height local pos1 = vector.new(pos.x - width, pos.y, 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 p2 = vector.new(x, y, z) local node = minetest.get_node(p2) local def = minetest.registered_nodes[node.name] if def and def.walkable and mobkit.get_node_height(p2) > 0 then local p3 = vector.new(p2.x, p2.y + 1, p2.z) local node2 = minetest.get_node(p3) local def2 = minetest.registered_nodes[node2.name] if def2 and def2.walkable and mobkit.get_node_height(p3) > 0 then return false end elseif is_object_at_pos(self, p2) then return false end end end end return true end 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 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)) mob_core.lq_dumbwalk(self, pos2, speed_factor) return true end end -- Dumbstep -- Modified to use new jump mechanic function mob_core.lq_dumbwalk(self, tpos, speed_factor, anim) anim = anim or "walk" local timer = 2 local width = mob_core.get_hitbox(self)[4] local func = function(self) local speed = speed_factor or 1 local pos = mobkit.get_stand_pos(self) local dir = vector.direction(pos, tpos) local dist = vec_dist(pos, tpos) if mobkit.isnear2d(pos, tpos, clamp(width * 0.3125, 0.25, 1.5)) then return true end if not self.isonground then speed = speed * 0.5 end local turn_rate = self.turn_rate or 8 if dist < 2.5 then turn_rate = turn_rate + 2 end timer = timer - self.dtime if timer <= 0 then return true end mobkit.turn2yaw(self, minetest.dir_to_yaw(dir), turn_rate) mobkit.go_forward_horizontal(self, self.max_speed * speed) mobkit.animate(self, anim) end mobkit.queue_low(self, func) end --[[function mob_core.lq_dumbwalk(self, dest, speed_factor, anim) local timer = 3 -- failsafe local width = mob_core.get_hitbox(self)[4] speed_factor = speed_factor or 1 anim = anim or "walk" local func = function(self) mobkit.animate(self, anim) timer = timer - self.dtime if timer < 0 then return true end local s_fctr = speed_factor local pos = mobkit.get_stand_pos(self) local dir = vector.direction({x = pos.x, y = 0, z = pos.z}, {x = dest.x, y = 0, z = dest.z}) local y = self.object:get_velocity().y local yaw = self.object:get_yaw() local tyaw = minetest.dir_to_yaw(dir) if not self.isonground then s_fctr = 0.2 end local yaw_diff = diff(yaw, tyaw) if abs(yaw_diff) > 0.1 then mobkit.turn2yaw(self, tyaw, (self.turn_rate or 6) + abs(clamp(yaw_diff, -1, 1))) end if mobkit.isnear2d(pos, dest, clamp(width * 0.3125, 0.25, 1.5)) and abs(dest.y - pos.y) < 1 then if (not self.isonground and not self.isinliquid) or abs(dest.y - pos.y) > 0.1 then self.object:set_velocity({x = 0, y = y, z = 0}) end return true end if self.isonground or self.isinliquid then mobkit.go_forward_horizontal(self, self.max_speed * speed_factor) 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) 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, "stand") return true end local pos = mobkit.get_stand_pos(self) if not tpos then local pos1 = vector.new(pos.x - self.view_range, pos.y - 1, pos.z - self.view_range) local pos2 = vector.new(pos.x + self.view_range, pos.y, pos.z + self.view_range) local walkable_nodes = minetest.find_nodes_in_area_under_air(pos1, pos2, mob_core.walkable_nodes) tpos = find_closest_pos(walkable_nodes, pos) 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 mob_core.tilt_to_yaw(self, tyaw, (self.turn_rate or 6) * 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