Movement improvements
This commit is contained in:
parent
3e214793aa
commit
26210439b0
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue