-- Imported and modified API functions from Mobs_Redo -- WIP; pathfinding is not functional -- Localize math functions local pi = math.pi local square = math.sqrt local sin = math.sin local cos = math.cos local abs = math.abs local min = math.min local max = math.max local atann = math.atan local random = math.random local floor = math.floor local atan = function(x) if not x or x ~= x then --error("atan bassed NaN") return 0 else return atann(x) end end local helper = adaptive_ai.helper local set_yaw = helper.mobs.set_yaw local do_jump = helper.mobs.do_jump local set_velocity = helper.mobs.set_velocity local is_at_cliff = helper.mobs.is_at_cliff local flight_check = helper.mobs.flight_check local get_distance = helper.mobs.get_distance local mob_sound = helper.mobs.mob_sound local do_env_damage = helper.mobs.do_env_damage local replace = helper.mobs.replace local flop = helper.mobs.flop local runaway_from = helper.mobs.runaway_from local set_animation = helper.mobs.set_animation local mobs_griefing = helper.mobs.mobs_griefing local los_switcher = false local height_switcher = false local chat_log = adaptive_ai.chat_log -- Edited from mob_redo's smart_mobs pathfinding function -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 local ai_pathfinding = function(self, s, p, dist, dtime) chat_log("pathfinding tete") local s1 = self.path.lastpos local target_pos = self.target.pos -- is it becoming stuck? if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then self.path.stuck_timer = self.path.stuck_timer + dtime else self.path.stuck_timer = 0 end self.path.lastpos = {x = s.x, y = s.y, z = s.z} local use_pathfind = false local has_lineofsight = minetest.line_of_sight( {x = s.x, y = (s.y) + .5, z = s.z}, {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2) -- im stuck, search for path if not has_lineofsight then if los_switcher == true then use_pathfind = true los_switcher = false end -- cannot see target! else if los_switcher == false then los_switcher = true use_pathfind = false minetest.after(1, function(self) if has_lineofsight then self.path.following = false end end, self) end -- can see target! end if (self.path.stuck_timer > stuck_timeout and not self.path.following) then use_pathfind = true self.path.stuck_timer = 0 minetest.after(1, function(self) if has_lineofsight then self.path.following = false end end, self) end if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then use_pathfind = true self.path.stuck_timer = 0 minetest.after(1, function(self) if has_lineofsight then self.path.following = false end end, self) end if abs(vector.subtract(s,target_pos).y) > self.stepheight then if height_switcher then use_pathfind = true height_switcher = false end else if not height_switcher then use_pathfind = false height_switcher = true end end if use_pathfind then -- lets try find a path, first take care of positions -- since pathfinder is very sensitive local sheight = self.collisionbox[5] - self.collisionbox[2] -- round position to center of node to avoid stuck in walls -- also adjust height for player models! s.x = floor(s.x + 0.5) -- s.y = floor(s.y + 0.5) - sheight s.z = floor(s.z + 0.5) local ssight, sground = minetest.line_of_sight(s, {x = s.x, y = s.y - 4, z = s.z}, 1) -- determine node above ground if not ssight then s.y = sground.y + 1 end local p1 = self.target.pos p1.x, p1.y, p1.z = floor(p1.x + 0.5), floor(p1.y + 0.5), floor(p1.z + 0.5) local dropheight = 6 if self.fear_height ~= 0 then dropheight = self.fear_height end self.path.way = minetest.find_path(s, p1, self.view_range, self.stepheight, dropheight, "A*") self.state = "move" self.target.dir = "towards" -- no path found, try something else if not self.path.way then self.path.following = false -- lets make way by digging/building if not accessible if self.pathfinding == 2 and mobs_griefing then -- is player higher than mob? if s.y < p1.y then -- build upwards if not minetest.is_protected(s, "") then local ndef1 = minetest.registered_nodes[self.standing_in] if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then minetest.set_node(s, {name = mobs.fallback_node}) end end local sheight = math.ceil(self.collisionbox[5]) + 1 -- assume mob is 2 blocks high so it digs above its head s.y = s.y + sheight -- remove one block above to make room to jump if not minetest.is_protected(s, "") then local node1 = node_ok(s, "air").name local ndef1 = minetest.registered_nodes[node1] if node1 ~= "air" and node1 ~= "ignore" and ndef1 and not ndef1.groups.level and not ndef1.groups.unbreakable and not ndef1.groups.liquid then minetest.set_node(s, {name = "air"}) minetest.add_item(s, ItemStack(node1)) end end s.y = s.y - sheight self.object:setpos({x = s.x, y = s.y + 2, z = s.z}) else -- dig 2 blocks to make door toward player direction local yaw1 = self.object:get_yaw() + pi / 2 local p1 = {x = s.x + cos(yaw1), y = s.y, z = s.z + sin(yaw1)} if not minetest.is_protected(p1, "") then local node1 = node_ok(p1, "air").name local ndef1 = minetest.registered_nodes[node1] if node1 ~= "air" and node1 ~= "ignore" and ndef1 and not ndef1.groups.level and not ndef1.groups.unbreakable and not ndef1.groups.liquid then minetest.add_item(p1, ItemStack(node1)) minetest.set_node(p1, {name = "air"}) end p1.y = p1.y + 1 node1 = node_ok(p1, "air").name ndef1 = minetest.registered_nodes[node1] if node1 ~= "air" and node1 ~= "ignore" and ndef1 and not ndef1.groups.level and not ndef1.groups.unbreakable and not ndef1.groups.liquid then minetest.add_item(p1, ItemStack(node1)) minetest.set_node(p1, {name = "air"}) end end end end -- will try again in 2 second self.path.stuck_timer = stuck_timeout - 2 -- frustration! cant find the damn path :( mob_sound(self, self.sounds.random) else -- yay I found path mob_sound(self, self.sounds.war_cry) set_velocity(self, self.walk_velocity) -- follow path now that I have it self.path.following = true end end end -- Pieced together from mob_redo's state management functions -- self.target.pos -- self.target.dir = "towards" or "away" local ai_manager = function(self, dtime) if self.action_managers then -- there's a manager function per action if self.action then --chat_log("action managers in action") self.action_managers[self.action](self, dtime) end end --chat_log(self.state == "manager on") if self.target and self.target.obj then self.target.pos = self.target.obj:get_pos() end local yaw = self.object:get_yaw() or 0 local s = self.object:get_pos() local lp = nil if self.state == "stand" then --chat_log("I am idle standing") if random(1, 4) == 1 then local objs = minetest.get_objects_inside_radius(s, 3) for n = 1, #objs do if objs[n]:is_player() then lp = objs[n]:get_pos() break end end -- look at any players nearby, otherwise turn randomly if lp then local vec = {x = lp.x - s.x, z = lp.z - s.z} yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end else yaw = yaw + random(-0.5, 0.5) end yaw = set_yaw(self, yaw, 8) end if self.walk_chance ~= 0 and self.facing_fence ~= true and random(1, 100) <= self.walk_chance and is_at_cliff(self) == false then set_velocity(self, self.walk_velocity) self.state = "wander" set_animation(self, "walk") else set_velocity(self, 0) set_animation(self, "stand") end elseif self.state == "wander" then --chat_log("I am idle wandering") -- is there something I need to avoid? local avoid = {} if self.water_damage > 0 then table.insert(avoid, "group:water") end if self.lava_damage > 0 then table.insert(avoid, "group:lava") end if #avoid > 0 then lp = minetest.find_node_near(s, 1, avoid) end if lp then -- if mob in water or lava then look for land if (self.lava_damage and minetest.registered_nodes[self.standing_in].groups.lava) or (self.water_damage and minetest.registered_nodes[self.standing_in].groups.water) then lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone", "group:sand", node_ice, node_snowblock}) -- did we find land? if lp then local vec = {x = lp.x - s.x, z = lp.z - s.z} yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end -- look towards land and jump/move in that direction yaw = set_yaw(self, yaw, 6) do_jump(self) set_velocity(self, self.walk_velocity) else yaw = yaw + random(-0.5, 0.5) end else local vec = {x = lp.x - s.x, z = lp.z - s.z} yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end end yaw = set_yaw(self, yaw, 8) -- otherwise randomly turn elseif random(1, 100) <= 30 then yaw = yaw + random(-0.5, 0.5) yaw = set_yaw(self, yaw, 8) end -- stand for great fall in front local temp_is_cliff = is_at_cliff(self) if self.facing_fence == true or temp_is_cliff or random(1, 100) <= 30 then set_velocity(self, 0) self.state = "stand" set_animation(self, "stand") else set_velocity(self, self.walk_velocity) if flight_check(self) and self.animation and self.animation.fly_start and self.animation.fly_end then set_animation(self, "fly") else set_animation(self, "walk") end end elseif self.state == "runaway" then --chat_log("I am moving away") self.runaway_timer = self.runaway_timer + 1 -- stop after 5 seconds or when at cliff if self.runaway_timer > 5 or is_at_cliff(self) then self.runaway_timer = 0 set_velocity(self, 0) self.state = "stand" set_animation(self, "stand") self.runaway_timer = 0 else set_velocity(self, self.run_velocity) set_animation(self, "walk") end elseif self.state == "move" then -- is there something I need to avoid? if self.water_damage > 0 and self.lava_damage > 0 then lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"}) elseif self.water_damage > 0 then lp = minetest.find_node_near(s, 1, {"group:water"}) elseif self.lava_damage > 0 then lp = minetest.find_node_near(s, 1, {"group:lava"}) end if lp then -- if mob in water or lava then look for land if (self.lava_damage and minetest.registered_nodes[self.standing_in].groups.lava) or (self.water_damage and minetest.registered_nodes[self.standing_in].groups.water) then lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone", "group:sand", node_ice, node_snowblock}) -- did we find land? if lp then local vec = { x = lp.x - s.x, z = lp.z - s.z } yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end -- look towards land and jump/move in that direction yaw = set_yaw(self, yaw, 6) local jumped = do_jump(self) if jumped then chat_log("Jumping while moving") end set_velocity(self, self.walk_velocity) else yaw = yaw + random(-0.5, 0.5) end else local vec = { x = lp.x - s.x, z = lp.z - s.z } yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate if lp.x > s.x then yaw = yaw + pi end end yaw = set_yaw(self, yaw, 8) end -- stand for great fall in front local temp_is_cliff = is_at_cliff(self) if self.facing_fence == true or temp_is_cliff then set_velocity(self, 0) self.state = "stand" set_animation(self, "stand") else set_velocity(self, self.walk_velocity) if flight_check(self) and self.animation and self.animation.fly_start and self.animation.fly_end then set_animation(self, "fly") else set_animation(self, "walk") end end end -- end ai_manager end local ai_on_step = function(self, dtime) -- To do: add rest of mob_redo functionality, like: -- Attacking -- Follow -- Etc -- mob plays random sound at times if random(1, 100) == 1 then mob_sound(self, self.sounds.random) end -- environmental damage timer (every 1 second) self.env_damage_timer = self.env_damage_timer + dtime if (self.env_damage_timer > 1) then self.env_damage_timer = 0 -- check for environmental damage (water, fire, lava etc.) do_env_damage(self) -- node replace check (cow eats grass etc.) replace(self, pos) end --flop(self) do_jump(self) runaway_from(self) end adaptive_ai.ai_pathfinding = ai_pathfinding adaptive_ai.ai_manager = ai_manager adaptive_ai.ai_on_step = ai_on_step