VoxeLibre/mods/ENTITIES/mcl_mobs/physics.lua
GuyLiner 4d922d9edf Added conditional based on existing fire damage conditional that will apply fire damage to a
mob standing on top of a magma node that belongs to the "fire" group. The magma node
has also been added to the fire group, making it so that standing on the node will
do damage to the mob.

In official Minecraft, magma blocks just do fire damage without the fire animation.
Source: https://minecraft.fandom.com/wiki/Magma_Block
2023-02-06 00:10:52 +00:00

1022 lines
26 KiB
Lua

local math, vector, minetest, mcl_mobs = math, vector, minetest, mcl_mobs
local mob_class = mcl_mobs.mob_class
local ENTITY_CRAMMING_MAX = 24
local CRAMMING_DAMAGE = 3
local DEATH_DELAY = 0.5
local DEFAULT_FALL_SPEED = -9.81*1.5
local PATHFINDING = "gowp"
local mobs_debug = minetest.settings:get_bool("mobs_debug", false)
local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
local mob_active_range = tonumber(minetest.settings:get("mcl_mob_active_range")) or 48
local show_health = false
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_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
-- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius)
local wmin, wmax = -30912, 30928
if mcl_vars then
if mcl_vars.mapgen_edge_min and mcl_vars.mapgen_edge_max then
wmin, wmax = mcl_vars.mapgen_edge_min, mcl_vars.mapgen_edge_max
end
end
if radius then
wmin = wmin - radius
wmax = wmax + radius
end
if not pos then return true end
for _,v in pairs(pos) do
if v < wmin or v > wmax then return false end
end
return true
end
function mob_class:player_in_active_range()
for _,p in pairs(minetest.get_connected_players()) do
local pos = self.object:get_pos()
if pos and vector.distance(pos, p:get_pos()) <= mob_active_range then return true end
-- slightly larger than the mc 32 since mobs spawn on that circle and easily stand still immediately right after spawning.
end
end
-- Return true if object is in view_range
function mob_class:object_in_range(object)
if not object then
return false
end
local factor
-- Apply view range reduction for special player armor
if object:is_player() then
local factors = mcl_armor.player_view_range_factors[object]
factor = factors and factors[self.name]
end
-- Distance check
local dist
if factor and factor == 0 then
return false
elseif factor then
dist = self.view_range * factor
else
dist = self.view_range
end
local p1, p2 = self.object:get_pos(), object:get_pos()
return p1 and p2 and (vector.distance(p1, p2) <= dist)
end
function mob_class:item_drop(cooked, looting_level)
if not mobs_drop_items then return end
looting_level = looting_level or 0
if (self.child and self.type ~= "monster") then
return
end
local obj, item, num
local pos = self.object:get_pos()
self.drops = self.drops or {}
for n = 1, #self.drops do
local dropdef = self.drops[n]
local chance = 1 / dropdef.chance
local looting_type = dropdef.looting
if looting_level > 0 then
local chance_function = dropdef.looting_chance_function
if chance_function then
chance = chance_function(looting_level)
elseif looting_type == "rare" then
chance = chance + (dropdef.looting_factor or 0.01) * looting_level
end
end
local num = 0
local do_common_looting = (looting_level > 0 and looting_type == "common")
if math.random() < chance then
num = math.random(dropdef.min or 1, dropdef.max or 1)
elseif not dropdef.looting_ignore_chance then
do_common_looting = false
end
if do_common_looting then
num = num + math.floor(math.random(0, looting_level) + 0.5)
end
if num > 0 then
item = dropdef.name
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
for x = 1, num do
obj = minetest.add_item(pos, ItemStack(item .. " " .. 1))
end
if obj and obj:get_luaentity() then
obj:set_velocity({
x = math.random(-10, 10) / 9,
y = 6,
z = math.random(-10, 10) / 9,
})
elseif obj then
obj:remove() -- item does not exist
end
end
end
self.drops = {}
end
-- collision function borrowed amended from jordan4ibanez open_ai mod
function mob_class:collision()
local pos = self.object:get_pos()
if not pos then return {0,0} end
local vel = self.object:get_velocity()
local x = 0
local z = 0
local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
for _,object in pairs(minetest.get_objects_inside_radius(pos, width)) do
local ent = object:get_luaentity()
if object:is_player() or (ent and ent.is_mob and object ~= self.object) then
if object:is_player() and mcl_burning.is_burning(self.object) then
mcl_burning.set_on_fire(object, 4)
end
local pos2 = object:get_pos()
local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
local force = (width + 0.5) - vector.distance(
{x = pos.x, y = 0, z = pos.z},
{x = pos2.x, y = 0, z = pos2.z})
x = x + (vec.x * force)
z = z + (vec.z * force)
end
end
return({x,z})
end
function mob_class:check_death_and_slow_mob()
local d = 0.85
local dying = self:check_dying()
if dying then d = 0.92 end
local v = self.object:get_velocity()
if v then
--diffuse object velocity
self.object:set_velocity({x = v.x*d, y = v.y, z = v.z*d})
end
return dying
end
-- move mob in facing direction
function mob_class:set_velocity(v)
local c_x, c_y = 0, 0
-- can mob be pushed, if so calculate direction
if self.pushable then
c_x, c_y = unpack(self:collision())
end
-- halt mob if it has been ordered to stay
if self.order == "stand" or self.order == "sit" then
self.acc=vector.new(0,0,0)
return
end
local yaw = (self.object:get_yaw() or 0) + self.rotate
local vv = self.object:get_velocity()
if vv then
self.acc={
x = ((math.sin(yaw) * -v) + c_x)*.27,
y = 0,
z = ((math.cos(yaw) * v) + c_y)*.27,
}
end
end
-- calculate mob velocity
function mob_class:get_velocity()
local v = self.object:get_velocity()
if v then
return (v.x * v.x + v.z * v.z) ^ 0.5
end
return 0
end
function mob_class:update_roll()
local is_Fleckenstein = self.nametag == "Fleckenstein"
local was_Fleckenstein = false
local rot = self.object:get_rotation()
rot.z = is_Fleckenstein and pi or 0
self.object:set_rotation(rot)
local cbox = table.copy(self.collisionbox)
local acbox = self.object:get_properties().collisionbox
if math.abs(cbox[2] - acbox[2]) > 0.1 then
was_Fleckenstein = true
end
if is_Fleckenstein ~= was_Fleckenstein then
local pos = self.object:get_pos()
pos.y = pos.y + (acbox[2] + acbox[5])
self.object:set_pos(pos)
end
if is_Fleckenstein then
cbox[2], cbox[5] = -cbox[5], -cbox[2]
self.object:set_properties({collisionbox = cbox})
-- This leads to child mobs having the wrong collisionbox
-- and seeing as it seems to be nothing but an easter egg
-- i've put it inside the if. Which just makes it be upside
-- down lol.
end
end
local function shortest_term_of_yaw_rotation(self, rot_origin, rot_target, nums)
if not rot_origin or not rot_target then
return
end
rot_origin = math.deg(rot_origin)
rot_target = math.deg(rot_target)
if rot_origin < rot_target then
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return 1
end
else
if nums then
return -(rot_origin-(rot_target-360))
else
return -1
end
end
else
if math.abs(rot_origin-rot_target)<180 then
if nums then
return rot_target-rot_origin
else
return -1
end
else
if nums then
return (rot_target-(rot_origin-360))
else
return 1
end
end
end
end
-- set and return valid yaw
function mob_class:set_yaw(yaw, delay, dtime)
if self.noyaw then return end
if self.state ~= PATHFINDING then
self._turn_to = yaw
end
--mcl_log("Yaw is: \t\t" .. tostring(math.deg(yaw)))
--mcl_log("self.object:get_yaw() is: \t" .. tostring(math.deg(self.object:get_yaw())))
--clamp our yaw to a 360 range
if math.deg(self.object:get_yaw()) > 360 then
self.object:set_yaw(math.rad(0))
elseif math.deg(self.object:get_yaw()) < 0 then
self.object:set_yaw(math.rad(360))
end
if math.deg(yaw) > 360 then
yaw=yaw%360
elseif math.deg(yaw) < 0 then
yaw=((360*5)-yaw)%360
end
--calculate the shortest way to turn to find our target
local target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, false)
local target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), yaw, true)
--turn in the shortest path possible toward our target. if we are attacking, don't dance.
if (math.abs(target_shortest_path) > 50 and not self._kb_turn) and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then
if self.following then
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), false)
else
target_shortest_path = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), true)
target_shortest_path_nums = shortest_term_of_yaw_rotation(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.attack:get_pos())), false)
end
end
local ddtime = 0.05 --set_tick_rate
if dtime then
ddtime = dtime
end
if math.abs(target_shortest_path_nums) > 5 then
self.object:set_yaw(self.object:get_yaw()+(target_shortest_path*(3.6*ddtime)))
if self.acc then
self.acc=vector.rotate_around_axis(self.acc,vector.new(0,1,0), target_shortest_path*(3.6*ddtime))
end
end
delay = delay or 0
yaw = self.object:get_yaw()
if delay == 0 then
if self.shaking and dtime then
yaw = yaw + (math.random() * 2 - 1) * 5 * dtime
end
--self:update_roll()
return yaw
end
self.target_yaw = yaw
self.delay = delay
return self.target_yaw
end
-- global function to set mob yaw
function mcl_mobs.yaw(self, yaw, delay, dtime)
return mob_class.set_yaw(self, yaw, delay, dtime)
end
-- are we flying in what we are suppose to? (taikedz)
function mob_class:flight_check()
local nod = self.standing_in
local def = minetest.registered_nodes[nod]
if not def then return false end -- nil check
local fly_in
if type(self.fly_in) == "string" then
fly_in = { self.fly_in }
elseif type(self.fly_in) == "table" then
fly_in = self.fly_in
else
return false
end
for _,checknode in pairs(fly_in) do
if nod == checknode or nod == "ignore" then
return true
end
end
return false
end
-- check if mob is dead or only hurt
function mob_class:check_for_death(cause, cmi_cause)
if self.state == "die" then
return true
end
-- has health actually changed?
if self.health == self.old_health and self.health > 0 then
return false
end
local damaged = self.health < self.old_health
self.old_health = self.health
-- still got some health?
if self.health > 0 then
-- make sure health isn't higher than max
if self.health > self.hp_max then
self.health = self.hp_max
end
-- play damage sound if health was reduced and make mob flash red.
if damaged then
self:add_texture_mod("^[colorize:#d42222:175")
minetest.after(1, function(self)
if self and self.object then
self:remove_texture_mod("^[colorize:#d42222:175")
end
end, self)
self:mob_sound("damage")
end
-- backup nametag so we can show health stats
if not self.nametag2 then
self.nametag2 = self.nametag or ""
end
if show_health
and (cmi_cause and cmi_cause.type == "punch") then
self.htimer = 2
self.nametag = "" .. self.health .. " / " .. self.hp_max
self:update_tag()
end
return false
end
self:mob_sound("death")
local function death_handle(self)
-- dropped cooked item if mob died in fire or lava
if cause == "lava" or cause == "fire" then
self:item_drop(true, 0)
else
local wielditem = ItemStack()
if cause == "hit" then
local puncher = cmi_cause.puncher
if puncher then
wielditem = puncher:get_wielded_item()
end
end
local cooked = mcl_burning.is_burning(self.object) or mcl_enchanting.has_enchantment(wielditem, "fire_aspect")
local looting = mcl_enchanting.get_enchantment(wielditem, "looting")
self:item_drop(cooked, looting)
if ((not self.child) or self.type ~= "animal") and (minetest.get_us_time() - self.xp_timestamp <= math.huge) then
mcl_experience.throw_xp(self.object:get_pos(), math.random(self.xp_min, self.xp_max))
end
end
end
-- execute custom death function
if self.on_die then
local pos = self.object:get_pos()
local on_die_exit = self.on_die(self, pos, cmi_cause)
if on_die_exit ~= true then
death_handle(self)
end
if on_die_exit == true then
self.state = "die"
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
end
local collisionbox
if self.collisionbox then
collisionbox = table.copy(self.collisionbox)
end
self.state = "die"
self.attack = nil
self.v_start = false
self.fall_speed = DEFAULT_FALL_SPEED
self.timer = 0
self.blinktimer = 0
self:remove_texture_mod("^[colorize:#FF000040")
self:remove_texture_mod("^[brighten")
self.passive = true
self.object:set_properties({
pointable = false,
collide_with_objects = false,
})
self:set_velocity(0)
local acc = self.object:get_acceleration()
if acc then
acc.x, acc.y, acc.z = 0, DEFAULT_FALL_SPEED, 0
self.object:set_acceleration(acc)
end
local length
-- default death function and die animation (if defined)
if self.instant_death then
length = 0
elseif self.animation and self.animation.die_start and self.animation.die_end then
local frames = self.animation.die_end - self.animation.die_start
local speed = self.animation.die_speed or 15
length = math.max(frames / speed, 0) + DEATH_DELAY
self:set_animation( "die")
else
length = 1 + DEATH_DELAY
self:set_animation( "stand", true)
end
-- Remove body after a few seconds and drop stuff
local kill = function(self)
if not self.object:get_luaentity() then
return
end
death_handle(self)
local dpos = self.object:get_pos()
local cbox = self.collisionbox
local yaw = self.object:get_rotation().y
mcl_burning.extinguish(self.object)
self.object:remove()
mcl_mobs.death_effect(dpos, yaw, cbox, not self.instant_death)
end
if length <= 0 then
kill(self)
else
minetest.after(length, kill, self)
end
return true
end
-- Deal light damage to mob, returns true if mob died
function mob_class:deal_light_damage(pos, damage)
if not ((mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
self.health = self.health - damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("light", {type = "light"}) then
return true
end
end
end
-- environmental damage (water, lava, fire, light etc.)
function mob_class:do_env_damage()
-- feed/tame text timer (so mob 'full' messages dont spam chat)
if self.htimer > 0 then
self.htimer = self.htimer - 1
end
-- reset nametag after showing health stats
if self.htimer < 1 and self.nametag2 then
self.nametag = self.nametag2
self.nametag2 = nil
self:update_tag()
end
local pos = self.object:get_pos()
if not pos then return end
self.time_of_day = minetest.get_timeofday()
-- remove mob if beyond map limits
if not within_limits(pos, 0) then
mcl_burning.extinguish(self.object)
self.object:remove()
return true
end
local sunlight = minetest.get_natural_light(pos, self.time_of_day)
-- bright light harms mob
if self.light_damage ~= 0 and (sunlight or 0) > 12 then
if self:deal_light_damage(pos, self.light_damage) then
return true
end
end
local _, dim = mcl_worlds.y_to_layer(pos.y)
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then
if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10)
else
self:deal_light_damage(pos, self.sunlight_damage)
return true
end
end
end
local y_level = self.collisionbox[2]
if self.child then
y_level = self.collisionbox[2] * 0.5
end
-- what is mob standing in?
pos.y = pos.y + y_level + 0.25 -- foot level
local pos2 = {x=pos.x, y=pos.y-1, z=pos.z}
self.standing_in = node_ok(pos, "air").name
self.standing_on = node_ok(pos2, "air").name
-- don't fall when on ignore, just stand still
if self.standing_in == "ignore" then
self.object:set_velocity({x = 0, y = 0, z = 0})
end
local nodef = minetest.registered_nodes[self.standing_in]
local nodef2 = minetest.registered_nodes[self.standing_on]
-- rain
if self.rain_damage > 0 then
if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
self.health = self.health - self.rain_damage
if self:check_for_death("rain", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
end
pos.y = pos.y + 1 -- for particle effect position
-- water damage
if self.water_damage > 0
and nodef.groups.water then
if self.water_damage ~= 0 then
self.health = self.health - self.water_damage
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png", nil, nil, 1, nil)
if self:check_for_death("water", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- magma damage
elseif self.fire_damage > 0
and (nodef2.groups.fire) then
if self.fire_damage ~= 0 then
self.health = self.health - self.fire_damage
if self:check_for_death("fire", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- lava damage
elseif self.lava_damage > 0
and (nodef.groups.lava) then
if self.lava_damage ~= 0 then
self.health = self.health - self.lava_damage
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 10)
if self:check_for_death("lava", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- fire damage
elseif self.fire_damage > 0
and (nodef.groups.fire) then
if self.fire_damage ~= 0 then
self.health = self.health - self.fire_damage
mcl_mobs.effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
mcl_burning.set_on_fire(self.object, 5)
if self:check_for_death("fire", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- damage_per_second node check
elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then
self.health = self.health - nodef.damage_per_second
mcl_mobs.effect(pos, 5, "mcl_particles_smoke.png")
if self:check_for_death("dps", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
-- Drowning damage
if self.breath_max ~= -1 then
local drowning = false
if self.breathes_in_water then
if minetest.get_item_group(self.standing_in, "water") == 0 then
drowning = true
end
elseif nodef.drowning > 0 then
drowning = true
end
if drowning then
self.breath = math.max(0, self.breath - 1)
mcl_mobs.effect(pos, 2, "bubble.png", nil, nil, 1, nil)
if self.breath <= 0 then
local dmg
if nodef.drowning > 0 then
dmg = nodef.drowning
else
dmg = 4
end
self:damage_effect(dmg)
self.health = self.health - dmg
end
if self:check_for_death("drowning", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
else
self.breath = math.min(self.breath_max, self.breath + 1)
end
end
--- suffocation inside solid node
-- FIXME: Redundant with mcl_playerplus
if (self.suffocation == true)
and (nodef.walkable == nil or nodef.walkable == true)
and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
and (nodef.node_box == nil or nodef.node_box.type == "regular")
and (nodef.groups.disable_suffocation ~= 1)
and (nodef.groups.opaque == 1) then
-- Short grace period before starting to take suffocation damage.
-- This is different from players, who take damage instantly.
-- This has been done because mobs might briefly be inside solid nodes
-- when e.g. climbing up stairs.
-- This is a bit hacky because it assumes that do_env_damage
-- is called roughly every second only.
self.suffocation_timer = self.suffocation_timer + 1
if self.suffocation_timer >= 3 then
-- 2 damage per second
-- TODO: Deal this damage once every 1/2 second
self.health = self.health - 2
if self:check_for_death("suffocation", {type = "environment",
pos = pos, node = self.standing_in}) then
return true
end
end
else
self.suffocation_timer = 0
end
return self:check_for_death("", {type = "unknown"})
end
function mob_class:env_damage (dtime, pos)
-- environmental damage timer (every 1 second)
self.env_damage_timer = self.env_damage_timer + dtime
if (self.state == "attack" and self.env_damage_timer > 1)
or self.state ~= "attack" then
self:check_entity_cramming()
self.env_damage_timer = 0
-- check for environmental damage (water, fire, lava etc.)
if self:do_env_damage() then
return true
end
-- node replace check (cow eats grass etc.)
self:replace(pos)
end
end
function mob_class:damage_mob(reason,damage)
if not self.health then return end
damage = math.floor(damage)
if damage > 0 then
self.health = self.health - damage
mcl_mobs.effect(self.object:get_pos(), 5, "mcl_particles_smoke.png", 1, 2, 2, nil)
if self:check_for_death(reason, {type = reason}) then
return true
end
end
end
function mob_class:check_entity_cramming()
local p = self.object:get_pos()
if not p then return end
local oo = minetest.get_objects_inside_radius(p,1)
local mobs = {}
for _,o in pairs(oo) do
local l = o:get_luaentity()
if l and l.is_mob and l.health > 0 then table.insert(mobs,l) end
end
local clear = #mobs < ENTITY_CRAMMING_MAX
local ncram = {}
for _,l in pairs(mobs) do
if l then
if clear then
l.cram = nil
elseif l.cram == nil and not self.child then
table.insert(ncram,l)
elseif l.cram then
l:damage_mob("cramming",CRAMMING_DAMAGE)
end
end
end
for i,l in pairs(ncram) do
if i > ENTITY_CRAMMING_MAX then
l.cram = true
else
l.cram = nil
end
end
end
-- falling and fall damage
-- returns true if mob died
function mob_class:falling(pos)
if self.fly and self.state ~= "die" then
return
end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end
if mcl_portals ~= nil then
if mcl_portals.nether_portal_cooloff(self.object) then
return false -- mob has teleported through Nether portal - it's 99% not falling
end
end
-- floating in water (or falling)
local v = self.object:get_velocity()
if v then
if v.y > 0 then
-- apply gravity when moving up
self.object:set_acceleration({
x = 0,
y = DEFAULT_FALL_SPEED,
z = 0
})
elseif v.y <= 0 and v.y > self.fall_speed then
-- fall downwards at set speed
self.object:set_acceleration({
x = 0,
y = self.fall_speed,
z = 0
})
else
-- stop accelerating once max fall speed hit
self.object:set_acceleration({x = 0, y = 0, z = 0})
end
end
local acc = self.object:get_acceleration()
if minetest.registered_nodes[node_ok(pos).name].groups.lava then
if acc and self.floats_on_lava == 1 then
self.object:set_acceleration({
x = 0,
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
z = 0
})
end
end
-- in water then float up
if minetest.registered_nodes[node_ok(pos).name].groups.water then
if acc and self.floats == 1 then
self.object:set_acceleration({
x = 0,
y = -self.fall_speed / (math.max(1, v.y) ^ 2),
z = 0
})
end
else
-- fall damage onto solid ground
if self.fall_damage == 1 and self.object:get_velocity().y == 0 then
local n = node_ok(vector.offset(pos,0,-1,0)).name
local d = (self.old_y or 0) - self.object:get_pos().y
if d > 5 and n ~= "air" and n ~= "ignore" then
local add = minetest.get_item_group(self.standing_on, "fall_damage_add_percent")
local damage = d - 5
if add ~= 0 then
damage = damage + damage * (add/100)
end
self:damage_mob("fall",damage)
end
self.old_y = self.object:get_pos().y
end
end
end
function mob_class:check_water_flow()
-- Add water flowing for mobs from mcl_item_entity
local p, node, nn, def
p = self.object:get_pos()
node = minetest.get_node_or_nil(p)
if node then
nn = node.name
def = minetest.registered_nodes[nn]
end
-- Move item around on flowing liquids
if def and def.liquidtype == "flowing" then
--[[ Get flowing direction (function call from flowlib), if there's a liquid.
NOTE: According to Qwertymine, flowlib.quickflow is only reliable for liquids with a flowing distance of 7.
Luckily, this is exactly what we need if we only care about water, which has this flowing distance. ]]
local vec = flowlib.quick_flow(p, node)
-- Just to make sure we don't manipulate the speed for no reason
if vec.x ~= 0 or vec.y ~= 0 or vec.z ~= 0 then
-- Minecraft Wiki: Flowing speed is "about 1.39 meters per second"
local f = 1.39
-- Set new item moving speed into the direciton of the liquid
local newv = vector.multiply(vec, f)
self.object:set_acceleration({x = 0, y = 0, z = 0})
self.object:set_velocity({x = newv.x, y = -0.22, z = newv.z})
self.physical_state = true
self._flowing = true
self.object:set_properties({
physical = true
})
return
end
elseif self._flowing == true then
-- Disable flowing physics if not on/in flowing liquid
self._flowing = false
return
end
end
function mob_class:check_dying()
if ((self.state and self.state=="die") or self:check_for_death()) and not self.animation.die_end then
local rot = self.object:get_rotation()
if rot then
rot.z = ((math.pi/2-rot.z)*.2)+rot.z
self.object:set_rotation(rot)
end
return true
end
end
function mob_class:check_suspend()
local pos = self.object:get_pos()
if pos and not self:player_in_active_range() then
local node_under = node_ok(vector.offset(pos,0,-1,0)).name
self:set_animation( "stand", true)
local acc = self.object:get_acceleration()
if acc then
if acc.y > 0 or node_under ~= "air" then
self.object:set_acceleration(vector.new(0,0,0))
self.object:set_velocity(vector.new(0,0,0))
end
if acc.y == 0 and node_under == "air" then
self:falling(pos)
end
end
return true
end
end