Movement improvements

This commit is contained in:
kno10 2024-09-28 02:23:44 +02:00
parent 3e214793aa
commit 26210439b0
11 changed files with 400 additions and 612 deletions

View File

@ -203,8 +203,12 @@ function mob_class:mob_activate(staticdata, def, dtime)
self.collisionbox = colbox
self.selectionbox = selbox
self.visual_size = vis_size
self.standing_in = "ignore"
self.standing_on = "ignore"
self.standing_in = NODE_IGNORE
self.standing_on = NODE_IGNORE
self.standing_under = NODE_IGNORE
self.standing_body = NODE_IGNORE
self.standing_depth = 0
self.state = self.state or "stand"
self.jump_sound_cooloff = 0 -- used to prevent jump sound from being played too often in short time
self.opinion_sound_cooloff = 0 -- used to prevent sound spam of particular sound types
@ -284,13 +288,10 @@ function mob_class:outside_limits()
if posx > MAPGEN_LIMIT or posy > MAPGEN_LIMIT or posz > MAPGEN_LIMIT then
minetest.log("action", "Warning mob past limits of worldgen: " .. minetest.pos_to_string(pos))
else
if self.state ~= "stand" then
minetest.log("action", "Warning mob close to limits of worldgen: " .. minetest.pos_to_string(pos))
self.state = "stand"
self:set_animation("stand")
self.object:set_acceleration(vector.zero())
self.object:set_velocity(vector.zero())
end
self:turn_in_direction(-posx, -posz, 1) -- turn to world spawn
self.state = "walk"
self:set_animation("walk")
self:set_velocity(self.walk_velocity)
end
return true
end
@ -302,25 +303,19 @@ local function on_step_work(self, dtime, moveresult)
if self:check_despawn(pos, dtime) then return true end
if self:outside_limits() then return end
-- Start: Death/damage processing
-- All damage needs to be undertaken at the start. We need to exit processing if the mob dies.
if self:check_death_and_slow_mob() then
--minetest.log("action", "Mob is dying: ".. tostring(self.name))
-- Do we abandon out of here now?
end
if self:falling(pos, moveresult) then return end
-- Update what we know of the mobs environment for physics and movement
self:update_standing()
local player_in_active_range = self:player_in_active_range()
-- The following functions return true when the mob died and we should stop processing
if self:check_suspend(player_in_active_range) then return end
if self:gravity_and_floating(pos, dtime, moveresult) then return end
if self:step_damage(dtime, pos) then return end
self:check_water_flow()
if self.state == "die" then return end
-- End: Death/damage processing
local player_in_active_range = self:player_in_active_range()
self:check_suspend(player_in_active_range)
self:check_water_flow()
self._can_jump_cliff = not self._jumping_cliff and self:can_jump_cliff()
self:flop()
self:check_smooth_rotation(dtime)
--self:flop()
self:smooth_rotation(dtime)
if player_in_active_range then
self:set_animation_speed() -- set animation speed relative to velocity
@ -350,8 +345,11 @@ local function on_step_work(self, dtime, moveresult)
self:check_aggro(dtime)
self:check_particlespawners(dtime)
if self.do_custom and self.do_custom(self, dtime) == false then return end
if self.do_custom and self:do_custom(dtime) == false then return end
if self:do_states(dtime, player_in_active_range) then return end
self:smooth_acceleration(dtime)
local cx, cz = self:collision()
self.object:add_velocity(vector.new(cx, 0, cz))
if mobs_debug then self:update_tag() end
if not self.object:get_luaentity() then return false end
end

View File

@ -58,6 +58,7 @@ functions needed for the mob to work properly which contains the following:
that are not liquids
'runaway' if true causes animals to turn and run away when hit.
'view_range' how many nodes in distance the mob can see a player.
'see_through_opaque' override whether the mob can see through opaque nodes
'damage' how many health points the mob does to a player or another
mob when melee attacking.
'knock_back' when true has mobs falling backwards when hit, the greater

View File

@ -54,10 +54,6 @@ function mob_class:feed_tame(clicker, feed_count, breed, tame, notake)
if self.health < self.hp_max and not consume_food then
consume_food = true
self.health = math.min(self.health + 4, self.hp_max)
if self.htimer < 1 then
self.htimer = 5
end
self.object:set_hp(self.health)
end

View File

@ -21,6 +21,7 @@ local abs = math.abs
local cos = math.cos
local sin = math.sin
local atan2 = math.atan2
local sqrt = math.sqrt
local vector_offset = vector.offset
local vector_new = vector.new
local vector_copy = vector.copy
@ -88,7 +89,7 @@ function mob_class:smart_mobs(s, p, dist, dtime)
self.path.lastpos = vector_copy(s)
local use_pathfind = false
local has_lineofsight = minetest.line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0), .2)
local has_lineofsight = self:line_of_sight(vector_offset(s, 0, .5, 0), vector_offset(target_pos, 0, 1.5, 0), self.see_through_opaque or mobs_see_through, false)
-- im stuck, search for path
if not has_lineofsight then
@ -177,9 +178,8 @@ function mob_class:smart_mobs(s, p, dist, dtime)
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 = mcl_mobs.fallback_node})
if self.standing_in.buildable_to or self.standing_in.groups.liquid then
minetest.set_node(s, {name = mcl_mobs.fallback_node})
end
end
@ -629,9 +629,9 @@ function mob_class:on_punch(hitter, tflp, tool_capabilities, dir)
kb = kb + 9 * mcl_enchanting.get_enchantment(wielditem, "knockback")
-- add player velocity to mob knockback
local hv = hitter:get_velocity()
local dir_dot = (hv.x * dir.x) + (hv.z * dir.z)
local player_mag = ((hv.x * hv.x) + (hv.z * hv.z))^0.5
local mob_mag = ((v.x * v.x) + (v.z * v.z))^0.5
local dir_dot = hv.x * dir.x + hv.z * dir.z
local player_mag = sqrt(hv.x * hv.x + hv.z * hv.z)
local mob_mag = sqrt(v.x * v.x + v.z * v.z)
if dir_dot > 0 and mob_mag <= player_mag * 0.625 then
kb = kb + (abs(hv.x) + abs(hv.z)) * r
end
@ -999,7 +999,7 @@ function mob_class:do_states_attack(dtime)
or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
or (self.attack_type == "dogshoot" and (dist > self.reach or dist < self.avoid_distance and self.shooter_avoid_enemy) and self:dogswitch() == 0) then
local vec = vector_new(p.x - s.x, p.y - s.y - 1, p.z - s.z)
local dist = (vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)^0.5
local dist = sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)
self:turn_in_direction(vec.x, vec.z, 10)
if self.strafes then
@ -1007,11 +1007,11 @@ function mob_class:do_states_attack(dtime)
if random(40) == 1 then self.strafe_direction = self.strafe_direction * -1 end
local dir = -atan2(p.x - s.x, p.z - s.z)
self.acc = vector_new(-sin(dir + self.strafe_direction) * 0.8, 0, cos(dir + self.strafe_direction) * 0.8)
self.object:add_velocity(vector_new(-sin(dir + self.strafe_direction) * 0.8, 0, cos(dir + self.strafe_direction) * 0.8))
--stay away from player so as to shoot them
if self.avoid_distance and dist < self.avoid_distance and self.shooter_avoid_enemy then
local f = 0.3 * (self.avoid_distance - dist) / self.avoid_distance
self.acc.x, self.acc.z = self.acc.x - sin(dir) * f, self.acc.z + cos(dir) * f
self.object:add_velocity(-sin(dir) * f, 0, cos(dir) * f)
end
else
self:set_velocity(0)

View File

@ -18,51 +18,31 @@ if player_transfer_distance == 0 then player_transfer_distance = math.huge end
-- custom particle effects
function mcl_mobs.effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, go_down)
radius = radius or 2
min_size = min_size or 0.5
max_size = max_size or 1
gravity = gravity or DEFAULT_FALL_SPEED
glow = glow or 0
go_down = go_down or false
local ym
if go_down then
ym = 0
else
ym = -radius
end
minetest.add_particlespawner({
amount = amount,
time = 0.25,
minpos = pos,
maxpos = pos,
minvel = {x = -radius, y = ym, z = -radius},
maxvel = {x = radius, y = radius, z = radius},
minacc = {x = 0, y = gravity, z = 0},
maxacc = {x = 0, y = gravity, z = 0},
minvel = vector.new(-radius, go_down and 0 or -radius, -radius),
maxvel = vector.new(radius, radius, radius),
minacc = vector.new(0, gravity or DEFAULT_FALL_SPEED, 0),
maxacc = vector.new(0, gravity or DEFAULT_FALL_SPEED, 0),
minexptime = 0.1,
maxexptime = 1,
minsize = min_size,
maxsize = max_size,
minsize = min_size or 0.5,
maxsize = max_size or 1,
texture = texture,
glow = glow,
glow = glow or 0,
})
end
function mcl_mobs.death_effect(pos, yaw, collisionbox, rotate)
local min, max
if collisionbox then
min = {x=collisionbox[1], y=collisionbox[2], z=collisionbox[3]}
max = {x=collisionbox[4], y=collisionbox[5], z=collisionbox[6]}
else
min = { x = -0.5, y = 0, z = -0.5 }
max = { x = 0.5, y = 0.5, z = 0.5 }
end
local min = collisionbox and vector.new(collisionbox[1], collisionbox[2], collisionbox[3]) or vector.new(-0.5, 0, -0.5)
local max = collisionbox and vector.new(collisionbox[4], collisionbox[5], collisionbox[6]) or vector.new(0.5, 0.5, 0.5)
if rotate then
min = vector.rotate(min, {x=0, y=yaw, z=math.pi/2})
max = vector.rotate(max, {x=0, y=yaw, z=math.pi/2})
min = vector.rotate(min, vector.new(0, yaw, math.pi/2))
max = vector.rotate(max, vector.new(0, yaw, math.pi/2))
min, max = vector.sort(min, max)
min = vector.multiply(min, 0.5)
max = vector.multiply(max, 0.5)
@ -94,57 +74,35 @@ end
-- play sound
function mob_class:mob_sound(soundname, is_opinion, fixed_pitch)
local soundinfo
if self.sounds_child and self.child then
soundinfo = self.sounds_child
elseif self.sounds then
soundinfo = self.sounds
end
if not soundinfo then
return
end
local soundinfo = self.child and self.sounds_child or self.sounds
if not soundinfo then return end
local sound = soundinfo[soundname]
if sound then
if is_opinion and self.opinion_sound_cooloff > 0 then
return
end
local pitch
if not fixed_pitch then
local base_pitch = soundinfo.base_pitch
if not base_pitch then
base_pitch = 1
end
if self.child and (not self.sounds_child) then
-- Children have higher pitch
pitch = base_pitch * 1.5
else
pitch = base_pitch
end
-- randomize the pitch a bit
pitch = pitch + math.random(-10, 10) * 0.005
end
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = self.sounds.distance,
pitch = pitch,
}, true)
self.opinion_sound_cooloff = 1
if not sound then return end
if is_opinion and self.opinion_sound_cooloff > 0 then return end
local pitch = fixed_pitch
if not fixed_pitch then
pitch = soundinfo.base_pitch or 1
if self.child and not self.sounds_child then pitch = pitch * 1.5 end
pitch = pitch + (math.random() - 0.5) * 0.2
end
-- Should be 0.1 to 0.2 for mobs. Cow and zombie farms loud. At least have cool down.
minetest.sound_play(sound, {
object = self.object,
gain = 1.0,
max_hear_distance = self.sounds.distance,
pitch = pitch,
}, true)
self.opinion_sound_cooloff = 1
end
function mob_class:step_opinion_sound(dtime)
if self.state ~= "attack" and self.state ~= PATHFINDING then
if self.opinion_sound_cooloff > 0 then
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
end
-- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous
if math.random(1, 70) == 1 then
self:mob_sound("random", true)
end
if self.state == "attack" or self.state == PATHFINDING then return end
if self.opinion_sound_cooloff > 0 then
self.opinion_sound_cooloff = self.opinion_sound_cooloff - dtime
end
-- mob plays random sound at times. Should be 120. Zombie and mob farms are ridiculous
if math.random(1, 70) == 1 then
self:mob_sound("random", true)
end
end
@ -181,26 +139,22 @@ function mob_class:remove_texture_mod(mod)
end
function mob_class:damage_effect(damage)
-- damage particles
if (not disable_blood) and damage > 0 then
if disable_blood or damage <= 0 then return end
local amount_large = math.floor(damage / 2)
local amount_small = damage % 2
local amount_large = math.floor(damage / 2)
local amount_small = damage % 2
local pos = self.object:get_pos()
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
local pos = self.object:get_pos()
pos.y = pos.y + (self.collisionbox[5] - self.collisionbox[2]) * .5
local texture = "mobs_blood.png"
-- full heart damage (one particle for each 2 HP damage)
if amount_large > 0 then
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
end
-- half heart damage (one additional particle if damage is an odd number)
if amount_small > 0 then
-- TODO: Use "half heart"
mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
end
local texture = "mobs_blood.png"
-- full heart damage (one particle for each 2 HP damage)
if amount_large > 0 then
mcl_mobs.effect(pos, amount_large, texture, 2, 2, 1.75, 0, nil, true)
end
-- half heart damage (one additional particle if damage is an odd number)
if amount_small > 0 then
-- TODO: Use "half heart"
mcl_mobs.effect(pos, amount_small, texture, 1, 1, 1.75, 0, nil, true)
end
end
@ -397,23 +351,20 @@ end
function mob_class:set_animation_speed()
local v = self.object:get_velocity()
if v then
local v = self:get_velocity()
if v > 0 then
if self.frame_speed_multiplier then
local v2 = math.abs(v.x)+math.abs(v.z)*.833
if not self.animation.walk_speed then
self.animation.walk_speed = 25
end
if math.abs(v.x)+math.abs(v.z) > 0.5 then
self.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
self.animation.walk_speed = self.animation.walk_speed or 25 -- TODO: move to initialization
if v > 0.5 then
self.object:set_animation_frame_speed((v/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
else
self.object:set_animation_frame_speed(25)
end
end
--set_speed
if validate_vector(self.acc) then
self.object:add_velocity(self.acc)
end
--if validate_vector(self.acc) then
-- self.object:add_velocity(self.acc)
--end
end
end

View File

@ -9,7 +9,7 @@ mcl_mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core
-- used by the libaries below.
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
local function node_ok(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
@ -19,6 +19,26 @@ local node_ok = function(pos, fallback)
end
mcl_mobs.node_ok = node_ok
local function line_of_sight(origin, target, see_through_opaque, liquids)
local raycast = minetest.raycast(origin, target, false, liquids or false)
for hitpoint in raycast do
if hitpoint.type == "node" then
local node = minetest.get_node(minetest.get_pointed_thing_position(hitpoint))
if node.name ~= "air" then
local nodef = minetest.registered_nodes[node.name]
if nodef and nodef.walkable and not (see_through_opaque and not nodef.groups.opaque) then
return false
end
end
end
--TODO type object could block vision, for example chests
end
return true
end
mcl_mobs.line_of_sight = line_of_sight
local NODE_IGNORE = { name = "ignore", groups = {} } -- fallback for unknown nodes
--api and helpers
-- effects: sounds and particles mostly
dofile(path .. "/effects.lua")
@ -226,7 +246,6 @@ function mcl_mobs.register_mob(name, def)
health = 0,
frame_speed_multiplier = 1,
reach = def.reach or 3,
htimer = 0,
texture_list = def.textures,
child_texture = def.child_texture,
docile_by_day = def.docile_by_day or false,

View File

@ -10,8 +10,9 @@ local PATHFINDING = "gowp"
local node_snow = "mcl_core:snow"
local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
local logging = minetest.settings:get_bool("mcl_logging_mobs_movement", true)
local mobs_griefing = minetest.settings:get_bool("mobs_griefing", true)
local mobs_see_through_opaque = minetest.settings:get_bool("mobs_see_through_opaque", false)
local random = math.random
local sin = math.sin
@ -29,7 +30,8 @@ local vector_copy = vector.copy
local vector_offset = vector.offset
local vector_distance = vector.distance
local registered_fallback_node = minetest.registered_nodes[mcl_mobs.fallback_node]
local node_ok = mcl_mobs.node_ok
local see_through_opaque = mcl_mobs.see_through_opaque
-- Stop movement and stand
function mob_class:stand()
@ -38,49 +40,35 @@ function mob_class:stand()
self:set_animation("stand")
end
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then return node end
return fallback and minetest.registered_nodes[fallback] or registered_fallback_node
-- Turn towards a (nearby) target, primarily for path following
function mob_class:go_to_pos(b, speed)
if not self then return end
if not b then return end
local s = self.object:get_pos()
if vector_distance(b,s) < .4 then return true end
if b.y > s.y + 0.2 then self:do_jump() end
self:turn_in_direction(b.x - s.x, b.z - s.z, 2)
speed = speed or self.walk_velocity
self:set_velocity(speed)
self:set_animation(speed <= self.walk_velocity and "walk" or "run")
end
-- Returns true is node can deal damage to self
-- Returns true is node can deal damage to self, except water damage
function mob_class:is_node_dangerous(nodename)
local ndef = minetest.registered_nodes[nodename]
return ndef and (
(self.lava_damage > 0 and ndef.groups.lava)
or (self.fire_damage > 0 and ndef.groups.fire)
or ((ndef.damage_per_second or 0) > 0))
return ndef
and ((self.lava_damage > 0 and (ndef.groups.lava or 0) > 0)
or (self.fire_damage > 0 and (ndef.groups.fire or 0) > 0)
or ((ndef.damage_per_second or 0) > 0))
end
-- Returns true if node is a water hazard
function mob_class:is_node_waterhazard(nodename)
local ndef = minetest.registered_nodes[nodename]
return ndef and ndef.groups.water and (
(self.water_damage > 0)
or (not self.breathes_in_water and self.breath_max ~= -1 and (ndef.drowning or 0) > 0))
end
local function raycast_line_of_sight(origin, target)
local raycast = minetest.raycast(origin, target, false, true)
local los_blocked = false
for hitpoint in raycast do
if hitpoint.type == "node" then
--TODO type object could block vision, for example chests
local node = minetest.get_node(minetest.get_pointed_thing_position(hitpoint))
if node.name ~= "air" then
local nodef = minetest.registered_nodes[node.name]
if nodef and nodef.walkable then
los_blocked = true
break
end
end
end
end
return not los_blocked
return ndef and ndef.groups.water
and (self.water_damage > 0
or (not self.breathes_in_water and self.breath_max ~= -1 and (ndef.drowning or 0) > 0))
end
function mob_class:target_visible(origin)
@ -96,12 +84,14 @@ function mob_class:target_visible(origin)
local targ_head_height, targ_feet_height
local cbox = self.collisionbox
-- TODO also worth testing midway between feet and head?
-- to top of entity
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[5], 0), mobs_see_through_opaque, true) then return true end
-- to feed of entity
if self.attack:is_player() then
targ_head_height = vector_offset(target_pos, 0, cbox[5], 0)
targ_feet_height = target_pos -- Cbox would put feet under ground which interferes with ray
if line_of_sight(origin_eye_pos, target_pos, mobs_see_through_opaque, true) then return true end -- Cbox would put feet under ground which interferes with ray
else
targ_head_height = vector_offset(target_pos, 0, cbox[5], 0)
targ_feet_height = vector_offset(target_pos, 0, cbox[2], 0)
if line_of_sight(origin_eye_pos, vector_offset(target_pos, 0, cbox[2], 0), mobs_see_through_opaque, true) then return true end
end
--minetest.log("start targ_head_height: " .. dump(targ_head_height))
@ -119,101 +109,37 @@ function mob_class:target_visible(origin)
return false
end
-- check line of sight (BrunoMine)
-- check line of sight
function mob_class:line_of_sight(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 = vector_copy(pos1)
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 = vector_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 do
-- Check if you can still move forward
if td < ad + stepsize then return true end -- Reached the target
-- Moves the analyzed pos
local d = vector_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
return line_of_sight(pos1, pos2, mobs_see_through_opaque, true)
end
function mob_class:can_jump_cliff()
local yaw = self.object:get_yaw()
local pos = self.object:get_pos()
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox
-- where is front
local dir_x = -sin(yaw) * (cbox[4] + 0.5)
local dir_z = cos(yaw) * (cbox[4] + 0.5)
--is there nothing under the block in front? if so jump the gap.
local node_low = node_ok(vector_offset(pos, dir_x * 0.6, -0.5, dir_z * 0.6), "air")
local dir_x, dir_z = -sin(yaw) * (cbox[4] + 0.5), cos(yaw) * (cbox[4] + 0.5)
-- below next:
local node_low = minetest.get_node(vector_offset(pos, dir_x * 0.6, -0.5, dir_z * 0.6)).name
local ndef_low = minetest.registered_nodes[node_low]
-- next is solid, no need to jump
if minetest.registered_nodes[node_low.name] and minetest.registered_nodes[node_low.name].walkable == true then
if ndef_low and ndef_low.walkable then
self._jumping_cliff = false
return false
end
local node_far = node_ok(vector_offset(pos, dir_x * 1.6, -0.5, dir_z * 1.6), "air")
local node_far2 = node_ok(vector_offset(pos, dir_x * 2.5, -0.5, dir_z * 2.5), "air")
local node_far = minetest.get_node(vector_offset(pos, dir_x * 1.6, -0.5, dir_z * 1.6)).name
local node_far2 = minetest.get_node(vector_offset(pos, dir_x * 2.5, -0.5, dir_z * 2.5)).name
local ndef_far = minetest.registered_nodes[node_far]
local ndef_far2 = minetest.registered_nodes[node_far2]
-- TODO: also check there is air above these nodes?
-- some place to land on
if (minetest.registered_nodes[node_far.name] and minetest.registered_nodes[node_far.name].walkable == true)
or (minetest.registered_nodes[node_far2.name] and minetest.registered_nodes[node_far2.name].walkable == true)
then
if (ndef_far and ndef_far.walkable) or (ndef_far2 and ndef_far2.walkable) then
--disable fear height while we make our jump
self._jumping_cliff = true
--minetest.log("Jumping cliff: " .. self.name .. " nodes " .. node_low.name .. " - " .. node_far.name .. " - " .. node_far2.name)
minetest.after(.1, function()
if self and self.object then
self._jumping_cliff = false
end
end)
minetest.after(.1, function() if self and self.object then self._jumping_cliff = false end end)
return true
else
self._jumping_cliff = false
@ -227,15 +153,12 @@ function mob_class:is_at_cliff_or_danger()
if self.fear_height == 0 or self._jumping_cliff or self._can_jump_cliff or not self.object:get_luaentity() then -- 0 for no falling protection!
return false
end
if self.fly then -- also avoids checking fish
return false
end
local yaw = self.object:get_yaw()
if self.fly then return false end -- also avoids checking fish
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox
local dir_x = -sin(yaw) * (cbox[4] + 0.5)
local dir_z = cos(yaw) * (cbox[4] + 0.5)
local pos = self.object:get_pos()
local ypos = pos.y + cbox[2] + 0.1 -- just above floor
local free_fall, blocker = minetest.line_of_sight(
@ -255,7 +178,7 @@ function mob_class:is_at_cliff_or_danger()
end
local bnode = minetest.get_node(blocker)
-- minetest.log("At cliff: " .. self.name .. " below " .. bnode.name .. " height "..height)
if self:is_node_dangerous(self.standing_in) or self:is_node_waterhazard(self.standing_in) then
if self:is_node_dangerous(self.standing_in.name) or self:is_node_waterhazard(self.standing_in.name) then
return false -- allow to get out of the immediate danger
end
if self:is_node_dangerous(bnode.name) or self:is_node_waterhazard(bnode.name) then
@ -275,19 +198,12 @@ function mob_class:is_at_water_danger()
return false
end
local in_water_danger = self:is_node_waterhazard(self.standing_in) or self:is_node_waterhazard(self.standing_on)
local in_water_danger = self:is_node_waterhazard(self.standing_in.name) or self:is_node_waterhazard(self.standing_on.name)
if in_water_danger then return false end -- If you're in trouble, do not stop
if not self.object:get_luaentity() or self._jumping_cliff or self._can_jump_cliff then
return false
end
local yaw = self.object:get_yaw()
local pos = self.object:get_pos()
if not yaw or not pos then
return false
end
if not self.object:get_luaentity() or self._jumping_cliff or self._can_jump_cliff then return false end
local pos, yaw = self.object:get_pos(), self.object:get_yaw()
local cbox = self.collisionbox
local dir_x = -sin(yaw) * (cbox[4] + 0.5)
local dir_z = cos(yaw) * (cbox[4] + 0.5)
@ -346,40 +262,39 @@ function mob_class:do_jump()
self._jumping_cliff = false
-- something stopping us while moving?
if self.state ~= "stand" and self:get_velocity() > 0.5 and self.object:get_velocity().y ~= 0 then return false end
local v = self.object:get_velocity()
--if self.state ~= "stand" and self:get_velocity() > 0.5 and v.y ~= 0 then return false end
local in_water = self.standing_in.groups.water or self.standing_in.groups.lava -- todo: liquid?
-- allow jumping in water, and when on ground
if not in_water and self.standing_on and not self.standing_on.walkable then return false end
if not in_water and v.y ~= 0 then return false end
if self.standing_under and self.standing_under.walkable then return false end
local pos = self.object:get_pos()
local v = self.object:get_velocity()
local cbox = self.collisionbox
local in_water = minetest.get_item_group(node_ok(pos).name, "water") > 0
-- what is mob standing on?
pos.y = pos.y + cbox[2]
local node_below = node_ok(vector_offset(pos, 0, -0.2, 0))
local nbdef = minetest.registered_nodes[node_below.name]
if nbdef and nbdef.walkable == false and not in_water then return false end
local yaw = self.object:get_yaw()
local cbox = self.collisionbox
pos.y = pos.y + cbox[2]
-- where is front
local dir_x = -sin(yaw) * (cbox[4] + 0.5) + v.x * 0.25
local dir_z = cos(yaw) * (cbox[4] + 0.5) + v.z * 0.25
-- what is in front of mob?
local nod = node_ok(vector_offset(pos, dir_x, 0.5, dir_z))
local nod = minetest.get_node(vector_offset(pos, dir_x, 0.5, dir_z)).name
local ndef = minetest.registered_nodes[nod.name]
-- thin blocks that do not need to be jumped
if nod.name == node_snow or (ndef and ndef.groups.carpet or 0) > 0 then return false end
-- this is used to detect if there's a block on top of the block in front of the mob.
-- If there is, there is no point in jumping as we won't manage.
local node_top = node_ok(vector_offset(pos, dir_x, 1.5, dir_z), "air")
local node_top = minetest.get_node(vector_offset(pos, dir_x, 1.5, dir_z)).name
-- TODO: also check above the mob itself?
-- we don't attempt to jump if there's a stack of blocks blocking, unless attacking
local ntdef = minetest.registered_nodes[node_top.name]
local ntdef = minetest.registered_nodes[node_top]
if ntdef and ntdef.walkable == true and not (self.attack and self.state == "attack") then return false end
if self.walk_chance ~= 0 and not (ndef and ndef.walkable) and not self._can_jump_cliff then return false end
@ -389,26 +304,18 @@ function mob_class:do_jump()
return false
end
v.y = self.jump_height + 0.3
if in_water then
v.x, v.y, v.z = v.x * 1.2, v.y * 1.5, v.z * 1.2
elseif self._can_jump_cliff then
v.x, v.y, v.z = v.x * 2.5, v.y * 1.1, v.z * 2.5
end
v.y = math.min(v.y, 0) + self.jump_height -- + 0.3
--if in_water then
-- v.x, v.y, v.z = v.x * 1.2, v.y + self.jump_height * 0.5, v.z * 1.2
--elseif self._can_jump_cliff then
-- v.x, v.y, v.z = v.x * 2.5, v.y + self.jump_height * 0.1, v.z * 2.5
--end
if in_water or self._cam_jump_cliff then v.y = v.y + self.jump_height * 0.25 end
v.y = math.min(-self.fall_speed, math.max(v.y, self.fall_speed))
self:set_animation("jump") -- only when defined
self.object:set_velocity(v)
-- when in air move forward
local forward = function(self, v)
if not self.object or not self.object:get_luaentity() or self.state == "die" then return end
self.object:set_acceleration(vector_new(v.x * 2, DEFAULT_FALL_SPEED, v.z * 2))
end
-- trying multiple time helps mobs jump
minetest.after(0.1, forward, self, v)
minetest.after(0.2, forward, self, v)
minetest.after(0.3, forward, self, v)
if self.jump_sound_cooloff <= 0 then
self:mob_sound("jump")
self.jump_sound_cooloff = 0.5
@ -547,8 +454,7 @@ function mob_class:check_runaway_from()
sp = s
dist = vector_distance(p, s)
-- choose closest player/mpb to runaway from
if dist < min_dist
and self:line_of_sight(vector_offset(sp, 0, 1, 0), vector_offset(p, 0, 1, 0), 2) == true then
if dist < min_dist and line_of_sight(vector_offset(sp, 0, 1, 0), vector_offset(p, 0, 1, 0), mobs_see_through_opaque, false) then
-- aim higher to make looking up hills more realistic
min_dist = dist
min_player = player
@ -617,8 +523,7 @@ function mob_class:check_follow()
self:set_animation("run")
end
else
self:set_velocity(0)
self:set_animation("stand")
self:stand()
end
return
end
@ -645,28 +550,13 @@ function mob_class:flop()
end
end
self:set_animation( "stand", true)
return
self:set_animation("stand", true)
elseif self.state == "flop" then
self.state = "stand"
self.object:set_acceleration(vector_zero())
self:set_velocity(0)
self:stand()
end
end
end
function mob_class:go_to_pos(b, speed)
if not self then return end
if not b then return end
local s = self.object:get_pos()
if vector_distance(b,s) < .4 then return true end
if b.y > s.y + 0.2 then self:do_jump() end
self:turn_in_direction(b.x - s.x, b.z - s.z, 2)
speed = speed or self.walk_velocity
self:set_velocity(speed)
self:set_animation(speed <= self.walk_velocity and "walk" or "run")
end
local check_herd_timer = 0
function mob_class:check_herd(dtime)
local pos = self.object:get_pos()
@ -690,7 +580,7 @@ function mob_class:check_herd(dtime)
end
function mob_class:teleport(target)
if self.do_teleport then return self.do_teleport(self, target) end
if self.do_teleport then return self:do_teleport(target) end
end
function mob_class:animate_walk_or_fly()
@ -706,8 +596,8 @@ function mob_class:do_states_walk()
local s = self.object:get_pos()
-- If mob in or on dangerous block, look for land
if self:is_node_dangerous(self.standing_in) or self:is_node_waterhazard(self.standing_in)
or not self.fly and (self:is_node_dangerous(self.standing_on) or self:is_node_waterhazard(self.standing_on)) then
if self:is_node_dangerous(self.standing_in.name) or self:is_node_waterhazard(self.standing_in.name)
or not self.fly and (self:is_node_dangerous(self.standing_on.name) or self:is_node_waterhazard(self.standing_on.name)) then
-- Better way to find shore - copied from upstream
local lp = minetest.find_nodes_in_area_under_air(vector_offset(s, -5, -0.5, -5), vector_offset(s, 5, 1, 5), {"group:solid"})
if #lp == 0 then
@ -736,14 +626,12 @@ function mob_class:do_states_walk()
-- facing wall? then turn
local facing_wall = false
local cbox = self.collisionbox
local dir_x = -sin(yaw - QUARTERPI) * (cbox[4] + 0.5)
local dir_z = cos(yaw - QUARTERPI) * (cbox[4] + 0.5)
local nodface = node_ok(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z))
if minetest.registered_nodes[nodface.name] and minetest.registered_nodes[nodface.name].walkable == true then
dir_x = -sin(yaw + QUARTERPI) * (cbox[4] + 0.5)
dir_z = cos(yaw + QUARTERPI) * (cbox[4] + 0.5)
nodface = node_ok(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z))
if minetest.registered_nodes[nodface.name] and minetest.registered_nodes[nodface.name].walkable == true then
local dir_x, dir_z = -sin(yaw - QUARTERPI) * (cbox[4] + 0.5), cos(yaw - QUARTERPI) * (cbox[4] + 0.5)
local nodface = minetest.registered_nodes[minetest.get_node(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z)).name]
if nodface and nodface.walkable then
dir_x, dir_z = -sin(yaw + QUARTERPI) * (cbox[4] + 0.5), cos(yaw + QUARTERPI) * (cbox[4] + 0.5)
nodface = minetest.registered_nodes[minetest.get_node(vector_offset(s, dir_x, cbox[5] - cbox[2], dir_z)).name]
if nodface and nodface.walkable then
facing_wall = true
end
end
@ -811,7 +699,7 @@ function mob_class:do_states_stand(player_in_active_range)
else
self:set_velocity(self.walk_velocity)
self.state = "walk"
self:set_animation( "walk")
self:set_animation("walk")
end
end
end
@ -827,8 +715,8 @@ function mob_class:do_states_runaway()
self:stand()
self:turn_by(PI * (random() + 0.5), 8)
else
self:set_velocity( self.run_velocity)
self:set_animation( "run")
self:set_velocity(self.run_velocity)
self:set_animation("run")
end
end

View File

@ -20,10 +20,8 @@ local cos = math.cos
local node_ok = mcl_mobs.node_ok
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
-- check if within physical map limits (-30911 to 30927)
local function within_limits(pos, radius)
@ -44,6 +42,23 @@ local function within_limits(pos, radius)
return true
end
-- Function that update some helpful variables on the mobs position:
-- standing_in: node the feet of the mob are in
-- standing_height: approx. "head height" with respect to that node
-- standing_on: node below
-- standing_under: node above
-- these may be "nil" (= ignore) and are otherwise already resolved via minetest.registered_nodes
function mob_class:update_standing()
local pos = self.object:get_pos()
local temp_pos = vector.offset(pos, 0, self.collisionbox[2] + 0.5, 0) -- foot level
self.standing_in = minetest.registered_nodes[minetest.get_node(temp_pos).name] or NODE_IGNORE
temp_pos.y = temp_pos.y - 1.5 -- below
self.standing_on = minetest.registered_nodes[minetest.get_node(temp_pos).name] or NODE_IGNORE
self.standing_height = pos.y - math.ceil(temp_pos.y + 0.5) + self.head_eye_height * 0.75
temp_pos.y = temp_pos.y + 2 -- at +1 = above
self.standing_under = minetest.registered_nodes[minetest.get_node(temp_pos).name] or NODE_IGNORE
end
function mob_class:player_in_active_range()
for _,p in pairs(minetest.get_connected_players()) do
local pos = self.object:get_pos()
@ -127,7 +142,7 @@ function mob_class:item_drop(cooked, looting_level)
end
for x = 1, num do
obj = minetest.add_item(pos, ItemStack(item .. " " .. 1))
obj = minetest.add_item(pos, ItemStack(item .. " 1"))
if obj and obj:get_luaentity() then
obj:set_velocity(vector.new((random() - 0.5) * 1.5, 6, (random() - 0.5) * 1.5))
@ -184,37 +199,52 @@ end
-- move mob in facing direction
function mob_class:set_velocity(v)
local c_x, c_z = 0, 0
-- can mob be pushed, if so calculate direction
if self.pushable then
c_x, c_z = self:collision()
end
if v > 0 then
local yaw = (self.object:get_yaw() or 0) + self.rotate
local x = ((-sin(yaw) * v) + c_x) * .4
local z = (( cos(yaw) * v) + c_z) * .4
if not self.acc then
self.acc = vector.new(x, 0, z)
else
self.acc.x, self.acc.y, self.acc.z = x, 0, z
end
else -- allow standing mobs to be pushed
if not self.acc then
self.acc = vector.new(c_x * .2, 0, c_z * .2)
else
self.acc.x, self.acc.y, self.acc.z = c_x * .2, 0, c_z * .2
end
end
self.target_vel = v
end
-- calculate mob velocity (2d)
function mob_class:get_velocity()
function mob_class:get_velocity_xz()
local v = self.object:get_velocity()
if not v then return 0 end
return (v.x*v.x + v.z*v.z)^0.5
end
-- legacy API
mob_class.get_velocity = mob_class.get_velocity_xz
function mob_class:update_roll()
-- Relative turn, primarily for random turning
-- @param angle number: realative angle, in radians
-- @param dtime deprecated: ignored now, because of smooth rotations
-- @param dtime deprecated: ignored now, because of smooth rotations
-- @return target angle
function mob_class:turn_by(angle, delay, dtime)
return self:set_yaw((self.object:get_yaw() or 0) + angle, delay, dtime)
end
-- Turn into a direction (e.g., to the player, or away)
-- @param dx number: delta in x axis to target
-- @param dz number: delta in z axis to target
-- @param delay number: time needed to turn
-- @param dtime deprecated: ignored now, because of smooth rotations
-- @return target angle
function mob_class:turn_in_direction(dx, dz, delay, dtime)
if abs(dx) == 0 and abs(dz) == 0 then return self.object:get_yaw() + self.rotate end
return self:set_yaw(-atan2(dx, dz) - self.rotate, delay, dtime) + self.rotate
end
-- Absolute turn into a particular direction
-- @param yaw number: angle in radians
-- @param delay number: time needed to turn
-- @param dtime deprecated: ignored now, because of smooth rotations
-- @return target angle
function mob_class:set_yaw(yaw, delay, dtime)
if self.noyaw then return end
if self._kb_turn then return yaw end -- knockback in effect
if not self.object:get_yaw() or not self.object:get_pos() then return end
self.delay = delay or 0
self.target_yaw = yaw % TWOPI
return self.target_yaw
end
-- name tag easter egg, test engine capabilities for rolling
local function update_roll()
local is_Fleckenstein = self.nametag == "Fleckenstein"
if not is_Fleckenstein and not self.is_Fleckenstein then return end
@ -237,30 +267,9 @@ function mob_class:update_roll()
self.is_Fleckenstein = is_Fleckenstein
end
-- Relative turn, primarily for random turning
-- @param dtime deprecated: ignored now, because of smooth rotations
function mob_class:turn_by(angle, delay, dtime)
return self:set_yaw((self.object:get_yaw() or 0) + angle, delay, dtime)
end
-- Turn into a direction (e.g., to the player, or away)
-- @param dtime deprecated: ignored now, because of smooth rotations
function mob_class:turn_in_direction(dx, dz, delay, dtime)
if abs(dx) == 0 and abs(dz) == 0 then return self.object:get_yaw() + self.rotate end
return self:set_yaw(-atan2(dx, dz) - self.rotate, delay, dtime) + self.rotate
end
-- set and return valid yaw
-- @param dtime deprecated: ignored now, because of smooth rotations
function mob_class:set_yaw(yaw, delay, dtime)
if self.noyaw then return end
if self._kb_turn then return yaw end -- knockback in effect
if not self.object:get_yaw() or not self.object:get_pos() then return end
self.delay = delay or 0
self.target_yaw = yaw % TWOPI
return self.target_yaw
end
-- improved smooth rotation
function mob_class:check_smooth_rotation(dtime)
-- Improved smooth rotation
-- @param dtime number: timestep length
function mob_class:smooth_rotation(dtime)
if not self.target_yaw then return end
local delay = self.delay
@ -277,22 +286,28 @@ function mob_class:check_smooth_rotation(dtime)
if self.shaking then
yaw = yaw + (random() * 2 - 1) / 72 * dtime
end
--[[ needed? if self.acc then
local change = yaw - initial_yaw
local si, co = sin(change), cos(change)
self.acc.x, self.acc.y = co * self.acc.x - si * self.acc.y, si * self.acc.x + co * self.acc.y
end ]]--
self.object:set_yaw(yaw)
self:update_roll()
if yaw ~= initial_yaw then self.object:set_yaw(yaw) end
--update_roll() -- Fleckenstein easter egg
end
-- Handling of intentional acceleration by the mob
-- its best to place environmental effects afterwards
-- TODO: have mobs that act faster and that act slower?
-- @param dtime number: timestep length
function mob_class:smooth_acceleration(dtime)
local yaw = self.target_yaw or (self.object:get_yaw() or 0) + self.rotate
local vel = self.target_vel or 0
local x, z = -sin(yaw) * vel, cos(yaw) * vel
local v = self.object:get_velocity()
local w = min(1, dtime * 10)
v.x, v.z = v.x + w * (x - v.x), v.z + w * (z - v.z)
self.object:set_velocity(v)
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
if not self.standin_in or not self.standing_on then return true end -- nil check
if not self.fly_in then return false end
local fly_in
if type(self.fly_in) == "string" then
@ -345,21 +360,6 @@ function mob_class:check_for_death(cause, cmi_cause)
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
@ -449,13 +449,7 @@ function mob_class:check_for_death(cause, cmi_cause)
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)
@ -495,6 +489,15 @@ function mob_class:check_for_death(cause, cmi_cause)
return true
end
function mob_class:damage_mob(damage, reason, cmi_cause)
if not self.health then return false end
damage = floor(damage)
if damage <= 0 then return false end
self.health = self.health - damage
mcl_mobs.effect(self.object:get_pos(), 5, "mcl_particles_smoke.png", 1, 2, 2, nil)
return self:check_for_death(reason, cmi_cause)
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
@ -509,21 +512,8 @@ function mob_class:deal_light_damage(pos, damage)
end
-- environmental damage (water, lava, fire, light etc.)
-- called about once per second
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
@ -572,20 +562,9 @@ function mob_class:do_env_damage()
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 = vector.new(pos.x, pos.y-1, pos.z)
self.standing_in = node_ok(pos, "air").name
self.standing_on = node_ok(pos2, "air").name
local pos3 = vector.offset(pos, 0, 1, 0)
self.standing_under = node_ok(pos3, "air").name
-- don't fall when on ignore, just stand still
if self.standing_in == "ignore" then
self.object:set_velocity(vector.zero())
local standin = self.standing_in
-- wither rose effect
elseif self.standing_in == "mcl_flowers:wither_rose" then
if standin.name == "mcl_flowers:wither_rose" then
mcl_potions.give_effect_by_level("withering", self.object, 2, 2)
end
@ -596,59 +575,51 @@ function mob_class:do_env_damage()
-- rain
if self.rain_damage > 0 and 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
if self:check_for_death("rain", {type = "environment", pos = pos, node = standin.name}) then
return true
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 and standin.groups.water 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
mcl_mobs.effect(vector.offset(pos, 0, 1, 0), 5, "mcl_particles_smoke.png", nil, nil, 1, nil)
if self:check_for_death("water", {type = "environment", pos = pos, node = standin.name}) then
return true
end
elseif self.lava_damage > 0 and (nodef.groups.lava) then
-- lava damage
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
if self.lava_damage > 0 and standin.groups.lava then
self.health = self.health - self.lava_damage
mcl_mobs.effect(vector.offset(pos, 0, 1, 0), 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 = standin.name}) then
return true
end
elseif self.fire_damage > 0 and (nodef2.groups.fire) then
-- magma damage
end
if self.fire_damage > 0 and self.standing_on.groups.fire then -- magma damage
self.health = self.health - self.fire_damage
if self:check_for_death("fire", {type = "environment", pos = pos, node = self.standing_in}) then
if self:check_for_death("fire", {type = "environment", pos = pos, node = standin.name}) then
return true
end
elseif self.fire_damage > 0 and (nodef.groups.fire) then
-- fire damage
end
if self.fire_damage > 0 and standin.groups.fire 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
if self:check_for_death("fire", {type = "environment", pos = pos, node = standin.name}) then
return true
end
elseif nodef.damage_per_second ~= 0 and not nodef.groups.lava and not nodef.groups.fire then
-- damage_per_second node check
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
end
if standin.damage_per_second ~= 0 and not (standin.groups.lava or standin.groups.fire) then
self.health = self.health - standin.damage_per_second
mcl_mobs.effect(vector.offset(pos, 0, 1, 0), 5, "mcl_particles_smoke.png")
if self:check_for_death("dps", {type = "environment", pos = pos, node = standin.name}) then
return true
end
end
-- Cactus damage
if self.standing_on == "mcl_core:cactus" or self.standing_in == "mcl_core:cactus" or self.standing_under == "mcl_core:cactus" then
self:damage_mob("cactus", 2)
if self:check_for_death("cactus", {type = "environment", pos = pos, node = self.standing_in}) then
if self.standing_on.name == "mcl_core:cactus" or standin.name == "mcl_core:cactus" or self.standing_under.name == "mcl_core:cactus" then
if self:damage_mob(2, "cactus", {type = "environment", pos = pos, node = standin.name}) then
return true
end
else
@ -680,8 +651,7 @@ function mob_class:do_env_damage()
threshold = 1.25
end
if dist < threshold then
self:damage_mob("cactus", 2)
if self:check_for_death("cactus", {type = "environment", pos = pos, node = self.standing_in}) then
if self:damage_mob(2, "cactus", {type = "environment", pos = pos, node = standin.name}) then
return true
end
end
@ -693,10 +663,8 @@ function mob_class:do_env_damage()
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 and nodef3.drowning > 0 then
if not standin.groups.water then drowning = true end
elseif standin.drowning > 0 and self.standing_under.drowning > 0 then
drowning = true
end
@ -704,17 +672,11 @@ function mob_class:do_env_damage()
self.breath = 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
local dmg = standin.drowning > 0 and standin.drowning or 4
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
if self:check_for_death("drowning", {type = "environment", pos = pos, node = standin.name}) then
return true
end
else
@ -724,13 +686,12 @@ function mob_class:do_env_damage()
--- 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
if self.suffocation
and (standin.walkable == nil or standin.walkable)
and (standin.collision_box == nil or standin.collision_box.type == "regular")
and (standin.node_box == nil or standin.node_box.type == "regular")
and (standin.groups.disable_suffocation ~= 1)
and (standin.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
@ -743,8 +704,7 @@ function mob_class:do_env_damage()
-- 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
if self:check_for_death("suffocation", {type = "environment", pos = pos, node = standin.name}) then
return true
end
end
@ -767,127 +727,109 @@ function mob_class:step_damage (dtime, pos)
-- 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
self:check_entity_cramming()
-- check for environmental damage (water, fire, lava etc.)
if self:do_env_damage() then
return true
end
if self:do_env_damage() then return true end
self:replace_node(pos) -- (sheep eats grass etc.)
end
end
function mob_class:damage_mob(reason,damage)
if not self.health then return end
damage = 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()
for i = 1,#oo do
local l = oo[i]: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
for i = 1,#mobs do
local l = mobs[i]
if l then
if clear then
l.cram = nil
elseif l.cram == nil and not self.child then
table.insert(ncram,l)
elseif not l.cram and not self.child then
ncram[#ncram] = l
elseif l.cram then
l:damage_mob("cramming",CRAMMING_DAMAGE)
l:damage_mob(CRAMMING_DAMAGE, "cramming", { type = "cramming" })
end
end
end
for i,l in pairs(ncram) do
if i > ENTITY_CRAMMING_MAX then
l.cram = true
else
l.cram = nil
end
l.cram = i > ENTITY_CRAMMING_MAX or nil
end
end
-- falling and fall damage
-- returns true if mob died
function mob_class:falling(pos, moveresult)
-- Handle gravity, floating, falling and fall damage
-- @param pos vector: Position
-- @param dtime number: timestep length
-- @param moveresult table: minetest engine movement result (collisions)
-- @return true if mob died
function mob_class:gravity_and_floating(pos, dtime, moveresult)
if self.fly and self.state ~= "die" then return end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end
if not self.fall_speed then self.fall_speed = DEFAULT_FALL_SPEED end -- TODO: move to initialization code only?
if self.standing_in == NODE_IGNORE then -- not emerged yet, do not fall
self.object:set_velocity(vector.zero())
return false
end
self.object:set_properties({ nametag = "on: "..self.standing_on.name.."\nin: "..self.standing_in.name.."\n "..tostring(self.standing_height) })
-- Gravity
local v = self.object:get_velocity()
if v then
if v.y > 0 or (v.y <= 0 and v.y > self.fall_speed) then
-- fall downwards at set speed
if moveresult and moveresult.touching_ground then
-- when touching ground, retain a minimal gravity to keep the touching_ground flag
-- but also to not get upwards acceleration with large dtime when on bouncy ground
self.object:set_acceleration(vector.new(0, self.fall_speed * 0.01, 0))
else
self.object:set_acceleration(vector.new(0, self.fall_speed, 0))
end
else
-- stop accelerating once max fall speed hit
self.object:set_acceleration(vector.zero())
--local acc_y = self.fall_speed
local acc_y = moveresult and moveresult.touching_ground and 0 or self.fall_speed
local visc = 1
local vel = self.object:get_velocity() or vector.zero()
local standbody = self.standing_in
if standbody.groups.water then
visc = 0.8
if self.floats > 0 then --and minetest.registered_nodes[node_ok(vector.offset(pos, 0, self.collisionbox[5] - 0.25, 0)).name].groups.water then
local w = self.standing_under.groups.water and 0 or self.standing_height -- 0 is submerged, 1 is out
acc_y = self.fall_speed * max(-1, min(1, 2 * w - 1)) -- -1 to +1
--self.object:set_acceleration(vector.new(0, -self.fall_speed * 0.5, 0))
end
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
local registered_node = minetest.registered_nodes[node_ok(pos).name]
if registered_node.groups.lava then
if self.floats_on_lava == 1 then
self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0))
end
end
-- in water then float up
if registered_node.groups.water then
if self.floats == 1 and minetest.registered_nodes[node_ok(vector.offset(pos,0,self.collisionbox[5] -0.25,0)).name].groups.water then
self.object:set_acceleration(vector.new(0, -self.fall_speed / max(1, v.y^2), 0))
elseif standbody.groups.lava then
visc = 0.7
if self.floats_on_lava > 0 then
local w = self.standing_under.groups.water and 0 or self.standing_height -- 0 is submerged, 1 is out
acc_y = self.fall_speed * max(-1, min(1, 2 * w - 1)) -- -1 to +1
--self.object:set_acceleration(vector.new(0, -self.fall_speed * 0.5, 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)
-- fall damage onto solid ground (bouncy ground will yield vel.y > 0)
if self.fall_damage == 1 and vel.y == 0 then
local d = self.start_fall_y and (self.start_fall_y - self.object:get_pos().y) or 0
if d > 5 then
local ndef_on = self.standing_on
if ndef_on and ndef_on.walkable then
local damage = d - 5
local add = ndef_on.fall_damage_add_percent
if add then
damage = damage + damage * (add/100)
end
if self:damage_mob(damage, "falling", {type = "environment"}) then
return true
end
self.start_fall_y = nil
end
self:damage_mob("fall",damage)
else
self.start_fall_y = self.object:get_pos().y
end
self.old_y = self.object:get_pos().y
end
end
--vel.x, vel.y, vel.z = vel.x * visc, (vel.y + acc_y * dtime) * visc, vel.z * visc
vel.y = max(min(vel.y, -self.fall_speed), self.fall_speed)
-- Cap dtime to reduce bopping on water (hence we also do not use minetest acceleration)
-- but the minetest engine already applied the current velocity on the full timestep
dtime = min(dtime, 0.01)
-- ideally, we could use: self.object:set_acceleration(vector.new(0, acc_y * visc, 0))
vel.y = vel.y + acc_y * dtime -- apply acceleration in LUA, with limited dtime
vel.y = vel.y * visc
self.object:set_velocity(vel)
end
function mob_class:check_water_flow()
@ -909,8 +851,8 @@ function mob_class:check_water_flow()
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(vector.zero())
self.object:set_velocity(vector.new(newv.x, -0.22, newv.z))
--self.object:set_acceleration(vector.zero())
self.object:add_velocity(vector.new(newv.x, -0.22, newv.z))
self.physical_state = true
self._flowing = true
@ -937,18 +879,10 @@ end
function mob_class:check_suspend(player_in_active_range)
local pos = self.object:get_pos()
if pos and not 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.zero())
self.object:set_velocity(vector.zero())
end
self:set_animation("stand", true)
if self.object:get_velocity() then
self.object:set_velocity(vector.zero())
end
return true
end

View File

@ -82,7 +82,7 @@ local cod = {
do_custom = function(self)
--[[ this is supposed to make them jump out the water but doesn't appear to work very well
self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0))
if minetest.get_item_group(self.standing_in, "water") ~= 0 then
if self.standing_in.groups.water then
if self.object:get_velocity().y < 5 then
self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 })
end

View File

@ -84,7 +84,7 @@ local dolphin = {
--[[ this is supposed to make them jump out the water but doesn't appear to work very well
do_custom = function(self,dtime)
self.object:set_bone_position("body", vector.new(0,1,0), vector.new(degrees(dir_to_pitch(self.object:get_velocity())) * -1 + 90,0,0))
if minetest.get_item_group(self.standing_in, "water") ~= 0 then
if self.standing_in.groups.water then
if self.object:get_velocity().y < 5 then
self.object:add_velocity({ x = 0 , y = math.random()*.014-.007, z = 0 })
end

View File

@ -104,6 +104,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", {
explosion_damage_radius = 3.5,
explosiontimer_reset_radius = 3,
reach = 3,
see_through_opaque = false,
explosion_timer = 1.5,
allow_fuse_reset = true,
stop_to_explode = true,