adaptive-ai/api_mobs.lua
2018-06-26 19:20:49 +02:00

476 lines
13 KiB
Lua

-- 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