955 lines
23 KiB
Lua
955 lines
23 KiB
Lua
|
|
local helper = adaptive_ai.helper
|
|
|
|
local chat_log = function(s)
|
|
--minetest.chat_send_player("singleplayer", s)
|
|
end
|
|
|
|
adaptive_ai.chat_log = chat_log
|
|
|
|
local function get_node_group(name, group)
|
|
if type(group) == "table" then
|
|
local check = false
|
|
for _,g in ipairs(group) do
|
|
check = check or get_node_group(name, g)
|
|
end
|
|
return check
|
|
else
|
|
if not minetest.registered_nodes[name] or not minetest.registered_nodes[name].groups[group] then
|
|
return nil
|
|
end
|
|
return minetest.registered_nodes[name].groups[group]
|
|
end
|
|
end
|
|
|
|
local function is_airlike(name, airlike_groups)
|
|
airlike_groups = airlike_groups or {}
|
|
if name == "air" then return true end
|
|
|
|
return get_node_group(name, airlike_groups)
|
|
end
|
|
|
|
local function search_surface(pos, airlike_groups)
|
|
local top = minetest.get_node_or_nil(pos)
|
|
if not top then return nil end
|
|
|
|
local top_pos = {x=pos.x, y=pos.y, z=pos.z}
|
|
top = minetest.get_node_or_nil(top_pos)
|
|
--chat_log("Found "..top.name.."...")
|
|
|
|
if not is_airlike(top.name) then
|
|
repeat
|
|
top_pos.y = top_pos.y + 1
|
|
top = minetest.get_node_or_nil(top_pos)
|
|
--chat_log("Going up - found "..top.name.."...")
|
|
until not top or is_airlike(top.name)
|
|
top_pos = not top and nil or top_pos
|
|
else
|
|
repeat
|
|
top_pos.y = top_pos.y - 1
|
|
top = minetest.get_node(top_pos)
|
|
--chat_log("Going down - found "..top.name.."...")
|
|
until not top or not is_airlike(top.name)
|
|
top_pos = not top and nil or top_pos
|
|
|
|
top_pos.y = top_pos.y + 1
|
|
end
|
|
--chat_log("Found!")
|
|
|
|
return top_pos
|
|
end
|
|
|
|
helper.get_node_group = get_node_group
|
|
helper.search_surface = search_surface
|
|
|
|
----------------------------------------------
|
|
-- Auxiliar functions copied from mobs_redo --
|
|
----------------------------------------------
|
|
|
|
-- 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 floor = math.floor
|
|
local random = math.random
|
|
local atan = function(x)
|
|
if not x or x ~= x then
|
|
--error("atan bassed NaN")
|
|
return 0
|
|
else
|
|
return atann(x)
|
|
end
|
|
end
|
|
|
|
-- config values
|
|
local show_health = minetest.settings:get_bool("mob_show_health") ~= false
|
|
local use_cmi = minetest.global_exists("cmi")
|
|
local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
|
|
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
|
|
|
|
-- default nodes
|
|
local node_fire = "fire:basic_flame"
|
|
local node_permanent_flame = "fire:permanent_flame"
|
|
local node_ice = "default:ice"
|
|
local node_snowblock = "default:snowblock"
|
|
local node_snow = "default:snow"
|
|
|
|
helper.mobs = {}
|
|
|
|
local get_distance = function(a, b)
|
|
local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
|
|
return square(x * x + y * y + z * z)
|
|
end
|
|
|
|
local set_animation = function(ent, anim)
|
|
mobs:set_animation(ent,anim)
|
|
end
|
|
|
|
local mob_sound = function(ent, sound)
|
|
if sound then
|
|
minetest.sound_play(sound, {
|
|
object = ent.object,
|
|
gain = 1.0,
|
|
max_hear_distance = ent.sounds.distance
|
|
})
|
|
end
|
|
end
|
|
|
|
local set_velocity = function(ent, v)
|
|
-- do not move if mob has stopped
|
|
if ent.state == "stand" then
|
|
ent.object:setvelocity({x = 0, y = 0, z = 0})
|
|
return
|
|
end
|
|
|
|
local yaw = (ent.object:get_yaw() or 0) + ent.rotate
|
|
ent.object:setvelocity({
|
|
x = sin(yaw) * -v,
|
|
y = ent.object:getvelocity().y,
|
|
z = cos(yaw) * v
|
|
})
|
|
end
|
|
|
|
local get_velocity = function(ent)
|
|
local v = ent.object:getvelocity()
|
|
return (v.x * v.x + v.z * v.z) ^ 0.5
|
|
end
|
|
|
|
local node_ok = function(pos, fallback)
|
|
fallback = fallback or mobs.fallback_node
|
|
local node = minetest.get_node_or_nil(pos)
|
|
if node and minetest.registered_nodes[node.name] then
|
|
return node
|
|
end
|
|
return minetest.registered_nodes[fallback]
|
|
end
|
|
|
|
local set_yaw = function(ent, yaw, delay)
|
|
mobs:yaw(ent, yaw, delay)
|
|
return ent.target_yaw
|
|
end
|
|
|
|
local do_jump = function(self)
|
|
if not self.jump
|
|
or self.jump_height == 0
|
|
or self.fly
|
|
or self.child
|
|
or self.order == "stand" then
|
|
return false
|
|
end
|
|
|
|
self.facing_fence = false
|
|
-- something stopping us while moving?
|
|
if self.state ~= "stand"
|
|
and get_velocity(self) > 0.5
|
|
and self.object:getvelocity().y ~= 0 then
|
|
return false
|
|
end
|
|
|
|
local pos = self.object:get_pos()
|
|
local yaw = self.object:get_yaw()
|
|
|
|
-- what is mob standing on?
|
|
pos.y = pos.y + self.collisionbox[2] - 0.2
|
|
|
|
local nod = node_ok(pos)
|
|
if minetest.registered_nodes[nod.name].walkable == false then
|
|
return false
|
|
end
|
|
|
|
-- where is front
|
|
local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
|
|
local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
|
|
|
|
-- what is in front of mob?
|
|
local nod = node_ok({
|
|
x = pos.x + dir_x,
|
|
y = pos.y + 0.5,
|
|
z = pos.z + dir_z
|
|
})
|
|
|
|
-- thin blocks that do not need to be jumped
|
|
if nod.name == node_snow then
|
|
return false
|
|
end
|
|
|
|
if minetest.registered_items[nod.name].walkable then
|
|
if not nod.name:find("fence")
|
|
and not nod.name:find("gate") then
|
|
local v = self.object:getvelocity()
|
|
v.y = self.jump_height
|
|
set_animation(self, "jump") -- only when defined
|
|
self.object:setvelocity(v)
|
|
|
|
-- when in air move forward
|
|
minetest.after(0.3, function(self, v)
|
|
if self.object:get_luaentity() then
|
|
self.object:set_acceleration({
|
|
x = v.x * 2,--1.5,
|
|
y = 0,
|
|
z = v.z * 2,--1.5
|
|
})
|
|
end
|
|
end, self, v)
|
|
|
|
if get_velocity(self) > 0 then
|
|
mob_sound(self, self.sounds.jump)
|
|
end
|
|
else
|
|
self.facing_fence = true
|
|
end
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local is_at_cliff = function(ent)
|
|
if ent.fear_height == 0 then -- 0 for no falling protection!
|
|
return false
|
|
end
|
|
|
|
local yaw = ent.object:get_yaw()
|
|
local dir_x = -sin(yaw) * (ent.collisionbox[4] + 0.5)
|
|
local dir_z = cos(yaw) * (ent.collisionbox[4] + 0.5)
|
|
local pos = ent.object:get_pos()
|
|
local ypos = pos.y + ent.collisionbox[2] -- just above floor
|
|
|
|
if minetest.line_of_sight(
|
|
{x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
|
|
{x = pos.x + dir_x, y = ypos - ent.fear_height, z = pos.z + dir_z}
|
|
, 1) then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local within_limits = function(pos, radius)
|
|
if (pos.x - radius) > -30913
|
|
and (pos.x + radius) < 30928
|
|
and (pos.y - radius) > -30913
|
|
and (pos.y + radius) < 30928
|
|
and (pos.z - radius) > -30913
|
|
and (pos.z + radius) < 30928 then
|
|
return true -- within limits
|
|
end
|
|
|
|
return false -- beyond limits
|
|
end
|
|
|
|
-- update nametag colour
|
|
local update_tag = function(ent)
|
|
local col = "#00FF00"
|
|
local qua = ent.hp_max / 4
|
|
|
|
if ent.health <= floor(qua) then
|
|
col = "#FF0000"
|
|
elseif ent.health <= floor(qua * 2) then
|
|
col = "#FF6600"
|
|
elseif ent.health <= floor(qua * 3) then
|
|
col = "#FFFF00"
|
|
end
|
|
|
|
ent.object:set_properties({nametag = ent.nametag, nametag_color = col})
|
|
end
|
|
|
|
-- check line of sight (BrunoMine)
|
|
local line_of_sight = function(ent, pos1, pos2, stepsize)
|
|
|
|
stepsize = stepsize or 1
|
|
|
|
local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
|
|
|
|
-- normal walking and flying mobs can see you through air
|
|
if s == true then
|
|
return true
|
|
end
|
|
|
|
-- New pos1 to be analyzed
|
|
local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
|
|
|
|
local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
|
|
|
|
-- Checks the return
|
|
if r == true then return true end
|
|
|
|
-- Nodename found
|
|
local nn = minetest.get_node(pos).name
|
|
|
|
-- Target Distance (td) to travel
|
|
local td = get_distance(pos1, pos2)
|
|
|
|
-- Actual Distance (ad) traveled
|
|
local ad = 0
|
|
|
|
-- It continues to advance in the line of sight in search of a real
|
|
-- obstruction which counts as 'normal' nodebox.
|
|
while minetest.registered_nodes[nn]
|
|
and (minetest.registered_nodes[nn].walkable == false
|
|
or minetest.registered_nodes[nn].drawtype == "nodebox") do
|
|
|
|
-- Check if you can still move forward
|
|
if td < ad + stepsize then
|
|
return true -- Reached the target
|
|
end
|
|
|
|
-- Moves the analyzed pos
|
|
local d = get_distance(pos1, pos2)
|
|
|
|
npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
|
|
npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
|
|
npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
|
|
|
|
-- NaN checks
|
|
if d == 0
|
|
or npos1.x ~= npos1.x
|
|
or npos1.y ~= npos1.y
|
|
or npos1.z ~= npos1.z then
|
|
return false
|
|
end
|
|
|
|
ad = ad + stepsize
|
|
|
|
-- scan again
|
|
r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
|
|
|
|
if r == true then return true end
|
|
|
|
-- New Nodename found
|
|
nn = minetest.get_node(pos).name
|
|
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- are we flying in what we are suppose to? (taikedz)
|
|
local flight_check = function(ent, pos_w)
|
|
local nod = ent.standing_in
|
|
local def = minetest.registered_nodes[nod]
|
|
|
|
if not def then return false end -- nil check
|
|
|
|
if type(ent.fly_in) == "string"
|
|
and nod == ent.fly_in then
|
|
return true
|
|
elseif type(ent.fly_in) == "table" then
|
|
for _,fly_in in pairs(ent.fly_in) do
|
|
if nod == fly_in then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- stops mobs getting stuck inside stairs and plantlike nodes
|
|
if def.drawtype ~= "airlike"
|
|
and def.drawtype ~= "liquid"
|
|
and def.drawtype ~= "flowingliquid" then
|
|
return true
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
|
|
radius = radius or 2
|
|
min_size = min_size or 0.5
|
|
max_size = max_size or 1
|
|
gravity = gravity or -10
|
|
glow = glow or 0
|
|
|
|
minetest.add_particlespawner({
|
|
amount = amount,
|
|
time = 0.25,
|
|
minpos = pos,
|
|
maxpos = pos,
|
|
minvel = {x = -radius, y = -radius, z = -radius},
|
|
maxvel = {x = radius, y = radius, z = radius},
|
|
minacc = {x = 0, y = gravity, z = 0},
|
|
maxacc = {x = 0, y = gravity, z = 0},
|
|
minexptime = 0.1,
|
|
maxexptime = 1,
|
|
minsize = min_size,
|
|
maxsize = max_size,
|
|
texture = texture,
|
|
glow = glow,
|
|
})
|
|
end
|
|
|
|
-- drop items
|
|
local item_drop = function(ent, cooked)
|
|
-- no drops if disabled by setting
|
|
if not mobs_drop_items then return end
|
|
|
|
-- no drops for child mobs
|
|
if ent.child then return end
|
|
|
|
local obj, item, num
|
|
local pos = ent.object:get_pos()
|
|
ent.drops = ent.drops or {} -- nil check
|
|
|
|
for n = 1, #ent.drops do
|
|
if random(1, ent.drops[n].chance) == 1 then
|
|
num = random(ent.drops[n].min or 1, ent.drops[n].max or 1)
|
|
item = ent.drops[n].name
|
|
|
|
-- cook items when true
|
|
if cooked then
|
|
local output = minetest.get_craft_result({
|
|
method = "cooking", width = 1, items = {item}})
|
|
|
|
if output and output.item and not output.item:is_empty() then
|
|
item = output.item:get_name()
|
|
end
|
|
end
|
|
|
|
-- add item if it exists
|
|
obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
|
|
|
|
if obj and obj:get_luaentity() then
|
|
obj:setvelocity({
|
|
x = random(-10, 10) / 9,
|
|
y = 6,
|
|
z = random(-10, 10) / 9,
|
|
})
|
|
elseif obj then
|
|
obj:remove() -- item does not exist
|
|
end
|
|
end
|
|
end
|
|
|
|
ent.drops = {}
|
|
end
|
|
|
|
|
|
-- check if mob is dead or only hurt
|
|
local check_for_death = function(ent, cause, cmi_cause)
|
|
-- has health actually changed?
|
|
if ent.health == ent.old_health and ent.health > 0 then
|
|
return
|
|
end
|
|
|
|
ent.old_health = ent.health
|
|
-- still got some health? play hurt sound
|
|
if ent.health > 0 then
|
|
mob_sound(ent, ent.sounds.damage)
|
|
-- make sure health isn't higher than max
|
|
if ent.health > ent.hp_max then
|
|
ent.health = ent.hp_max
|
|
end
|
|
|
|
-- backup nametag so we can show health stats
|
|
if not ent.nametag2 then
|
|
ent.nametag2 = ent.nametag or ""
|
|
end
|
|
|
|
if show_health and (cmi_cause and cmi_cause.type == "punch") then
|
|
ent.htimer = 2
|
|
ent.nametag = "♥ " .. ent.health .. " / " .. ent.hp_max
|
|
update_tag(ent)
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- dropped cooked item if mob died in lava
|
|
if cause == "lava" then
|
|
item_drop(ent, true)
|
|
else
|
|
item_drop(ent, nil)
|
|
end
|
|
|
|
mob_sound(ent, ent.sounds.death)
|
|
local pos = ent.object:get_pos()
|
|
|
|
-- execute custom death function
|
|
if ent.on_die then
|
|
ent.on_die(ent, pos)
|
|
|
|
if use_cmi then
|
|
cmi.notify_die(ent.object, cmi_cause)
|
|
end
|
|
|
|
ent.object:remove()
|
|
|
|
return true
|
|
end
|
|
|
|
-- default death function and die animation (if defined)
|
|
if ent.animation and ent.animation.die_start and ent.animation.die_end then
|
|
local frames = ent.animation.die_end - ent.animation.die_start
|
|
local speed = ent.animation.die_speed or 15
|
|
local length = max(frames / speed, 0)
|
|
|
|
ent.attack = nil
|
|
ent.v_start = false
|
|
ent.timer = 0
|
|
ent.blinktimer = 0
|
|
ent.passive = true
|
|
ent.state = "die"
|
|
set_velocity(ent, 0)
|
|
set_animation(ent, "die")
|
|
|
|
minetest.after(length, function(ent)
|
|
if use_cmi then
|
|
cmi.notify_die(ent.object, cmi_cause)
|
|
end
|
|
|
|
ent.object:remove()
|
|
end, ent)
|
|
else
|
|
if use_cmi then
|
|
cmi.notify_die(ent.object, cmi_cause)
|
|
end
|
|
|
|
ent.object:remove()
|
|
end
|
|
|
|
effect(pos, 20, "tnt_smoke.png")
|
|
return true
|
|
end
|
|
|
|
local do_env_damage = function(ent)
|
|
-- feed/tame text timer (so mob 'full' messages dont spam chat)
|
|
if ent.htimer > 0 then
|
|
ent.htimer = ent.htimer - 1
|
|
end
|
|
|
|
-- reset nametag after showing health stats
|
|
if ent.htimer < 1 and ent.nametag2 then
|
|
|
|
ent.nametag = ent.nametag2
|
|
ent.nametag2 = nil
|
|
|
|
update_tag(ent)
|
|
end
|
|
|
|
local pos = ent.object:get_pos()
|
|
ent.time_of_day = minetest.get_timeofday()
|
|
|
|
-- remove mob if beyond map limits
|
|
if not within_limits(pos, 0) then
|
|
ent.object:remove()
|
|
return
|
|
end
|
|
|
|
-- bright light harms mob
|
|
if ent.light_damage ~= 0
|
|
-- and pos.y > 0
|
|
-- and ent.time_of_day > 0.2
|
|
-- and ent.time_of_day < 0.8
|
|
and (minetest.get_node_light(pos) or 0) > 12 then
|
|
ent.health = ent.health - ent.light_damage
|
|
effect(pos, 5, "tnt_smoke.png")
|
|
if check_for_death(ent, "light", {type = "light"}) then return end
|
|
end
|
|
|
|
local y_level = ent.collisionbox[2]
|
|
|
|
if ent.child then
|
|
y_level = ent.collisionbox[2] * 0.5
|
|
end
|
|
|
|
-- what is mob standing in?
|
|
pos.y = pos.y + y_level + 0.25 -- foot level
|
|
ent.standing_in = node_ok(pos, "air").name
|
|
-- print ("standing in " .. ent.standing_in)
|
|
|
|
-- don't fall when on ignore, just stand still
|
|
if ent.standing_in == "ignore" then
|
|
ent.object:setvelocity({x = 0, y = 0, z = 0})
|
|
end
|
|
|
|
local nodef = minetest.registered_nodes[ent.standing_in]
|
|
pos.y = pos.y + 1 -- for particle effect position
|
|
-- water
|
|
if ent.water_damage and nodef.groups.water then
|
|
if ent.water_damage ~= 0 then
|
|
ent.health = ent.health - ent.water_damage
|
|
effect(pos, 5, "bubble.png", nil, nil, 1, nil)
|
|
if check_for_death(ent, "water", {type = "environment",
|
|
pos = pos, node = ent.standing_in}) then return end
|
|
end
|
|
|
|
-- lava or fire
|
|
elseif ent.lava_damage
|
|
and (nodef.groups.lava
|
|
or ent.standing_in == node_fire
|
|
or ent.standing_in == node_permanent_flame) then
|
|
if ent.lava_damage ~= 0 then
|
|
ent.health = ent.health - ent.lava_damage
|
|
effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
|
|
if check_for_death(ent, "lava", {type = "environment",
|
|
pos = pos, node = ent.standing_in}) then return end
|
|
end
|
|
|
|
-- damage_per_second node check
|
|
elseif nodef.damage_per_second ~= 0 then
|
|
ent.health = ent.health - nodef.damage_per_second
|
|
effect(pos, 5, "tnt_smoke.png")
|
|
if check_for_death(ent, "dps", {type = "environment",
|
|
pos = pos, node = ent.standing_in}) then return end
|
|
end
|
|
check_for_death(ent, "", {type = "unknown"})
|
|
end
|
|
|
|
-- find and replace what mob is looking for (grass, wheat etc.)
|
|
local replace = function(ent, pos)
|
|
if not mobs_griefing
|
|
or not ent.replace_rate
|
|
or not ent.replace_what
|
|
or ent.child == true
|
|
or ent.object:getvelocity().y ~= 0
|
|
or random(1, ent.replace_rate) > 1 then
|
|
return
|
|
end
|
|
|
|
local what, with, y_offset
|
|
if type(ent.replace_what[1]) == "table" then
|
|
local num = random(#ent.replace_what)
|
|
what = ent.replace_what[num][1] or ""
|
|
with = ent.replace_what[num][2] or ""
|
|
y_offset = ent.replace_what[num][3] or 0
|
|
else
|
|
what = ent.replace_what
|
|
with = ent.replace_with or ""
|
|
y_offset = ent.replace_offset or 0
|
|
end
|
|
|
|
pos.y = pos.y + y_offset
|
|
if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
|
|
local oldnode = {name = what}
|
|
local newnode = {name = with}
|
|
local on_replace_return
|
|
|
|
if ent.on_replace then
|
|
on_replace_return = ent.on_replace(ent, pos, oldnode, newnode)
|
|
end
|
|
|
|
if on_replace_return ~= false then
|
|
minetest.set_node(pos, {name = with})
|
|
|
|
-- when cow/sheep eats grass, replace wool and milk
|
|
if ent.gotten == true then
|
|
ent.gotten = false
|
|
ent.object:set_properties(ent)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- specific runaway
|
|
local specific_runaway = function(list, what)
|
|
|
|
-- no list so do not run
|
|
if list == nil then
|
|
return false
|
|
end
|
|
|
|
-- found entity on list to attack?
|
|
for no = 1, #list do
|
|
|
|
if list[no] == what then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- find someone to runaway from
|
|
local runaway_from = function(ent)
|
|
|
|
if not ent.runaway_from then
|
|
return
|
|
end
|
|
|
|
local s = ent.object:get_pos()
|
|
local p, sp, dist
|
|
local player, obj, min_player
|
|
local type, name = "", ""
|
|
local min_dist = ent.view_range + 1
|
|
local objs = minetest.get_objects_inside_radius(s, ent.view_range)
|
|
|
|
for n = 1, #objs do
|
|
|
|
if objs[n]:is_player() then
|
|
|
|
if mobs.invis[ objs[n]:get_player_name() ]
|
|
or ent.owner == objs[n]:get_player_name() then
|
|
|
|
type = ""
|
|
else
|
|
player = objs[n]
|
|
type = "player"
|
|
name = "player"
|
|
end
|
|
else
|
|
obj = objs[n]:get_luaentity()
|
|
|
|
if obj then
|
|
player = obj.object
|
|
type = obj.type
|
|
name = obj.name or ""
|
|
end
|
|
end
|
|
|
|
-- find specific mob to runaway from
|
|
if name ~= "" and name ~= ent.name
|
|
and specific_runaway(ent.runaway_from, name) then
|
|
|
|
p = player:get_pos()
|
|
sp = s
|
|
|
|
-- aim higher to make looking up hills more realistic
|
|
p.y = p.y + 1
|
|
sp.y = sp.y + 1
|
|
|
|
dist = get_distance(p, s)
|
|
|
|
|
|
-- choose closest player/mpb to runaway from
|
|
if dist < min_dist
|
|
and line_of_sight(ent, sp, p, 2) == true then
|
|
min_dist = dist
|
|
min_player = player
|
|
end
|
|
end
|
|
end
|
|
|
|
if min_player then
|
|
|
|
local lp = player:get_pos()
|
|
local vec = {
|
|
x = lp.x - s.x,
|
|
y = lp.y - s.y,
|
|
z = lp.z - s.z
|
|
}
|
|
|
|
local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - ent.rotate
|
|
|
|
if lp.x > s.x then
|
|
yaw = yaw + pi
|
|
end
|
|
|
|
yaw = set_yaw(ent, yaw, 4)
|
|
ent.state = "runaway"
|
|
ent.runaway_timer = 3
|
|
end
|
|
end
|
|
|
|
local flop = function(ent)
|
|
-- swimmers flop when out of their element, and swim again when back in
|
|
if ent.fly then
|
|
local s = ent.object:get_pos()
|
|
if not flight_check(ent, s) then
|
|
|
|
ent.state = "flop"
|
|
ent.object:setvelocity({x = 0, y = -5, z = 0})
|
|
|
|
set_animation(ent, "stand")
|
|
|
|
return
|
|
elseif ent.state == "flop" then
|
|
ent.state = "stand"
|
|
end
|
|
end
|
|
end
|
|
|
|
local do_stand = function(ent, dtime)
|
|
local yaw = self.object:get_yaw() or 0
|
|
|
|
if random(1, 4) == 1 then
|
|
local lp = nil
|
|
local s = ent.object:get_pos()
|
|
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) - ent.rotate
|
|
if lp.x > s.x then yaw = yaw + pi end
|
|
else
|
|
yaw = yaw + random(-0.5, 0.5)
|
|
end
|
|
yaw = set_yaw(ent, yaw, 8)
|
|
end
|
|
|
|
set_velocity(ent, 0)
|
|
set_animation(ent, "stand")
|
|
|
|
-- npc's ordered to stand stay standing
|
|
if ent.type ~= "npc"
|
|
or ent.order ~= "stand" then
|
|
|
|
if ent.walk_chance ~= 0
|
|
and ent.facing_fence ~= true
|
|
and random(1, 100) <= ent.walk_chance
|
|
and is_at_cliff(ent) == false then
|
|
|
|
set_velocity(ent, ent.walk_velocity)
|
|
ent.state = "walk"
|
|
set_animation(ent, "walk")
|
|
|
|
--[[ fly up/down randomly for flying mobs
|
|
if ent.fly and random(1, 100) <= ent.walk_chance then
|
|
|
|
local v = ent.object:getvelocity()
|
|
local ud = random(-1, 2) / 9
|
|
|
|
ent.object:setvelocity({x = v.x, y = ud, z = v.z})
|
|
end--]]
|
|
end
|
|
end
|
|
end
|
|
|
|
local do_walk = function(ent, dtime)
|
|
local yaw = self.object:get_yaw() or 0
|
|
|
|
local s = self.object:get_pos()
|
|
local lp = nil
|
|
|
|
-- 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)
|
|
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
|
|
end
|
|
|
|
-- Config values
|
|
helper.mobs.show_health = show_health
|
|
helper.mobs.use_cmi = use_cmi
|
|
helper.mobs.mobs_griefing = mobs_griefing
|
|
|
|
-- Auxiliar functions
|
|
helper.mobs.get_distance = get_distance
|
|
helper.mobs.set_animation = set_animation
|
|
helper.mobs.mob_sound = mob_sound
|
|
helper.mobs.node_ok = node_ok
|
|
helper.mobs.set_velocity = set_velocity
|
|
helper.mobs.get_velocity = get_velocity
|
|
helper.mobs.is_at_cliff = is_at_cliff
|
|
helper.mobs.within_limits = within_limits
|
|
helper.mobs.update_tag = update_tag
|
|
helper.mobs.flight_check = flight_check
|
|
helper.mobs.effect = effect
|
|
helper.mobs.item_drop = item_drop
|
|
helper.mobs.check_for_death = check_for_death
|
|
helper.mobs.do_env_damage = do_env_damage
|
|
helper.mobs.replace = replace
|
|
helper.mobs.flop = flop
|
|
helper.mobs.runaway_from = runaway_from
|
|
helper.mobs.do_jump = do_jump
|
|
helper.mobs.set_yaw = set_yaw
|
|
helper.mobs.do_state_stand = do_state_stand
|
|
helper.mobs.update_tag = update_tag
|
|
|
|
adaptive_ai.helper = helper
|