From 19c18631008204f4e2eb5bcdd871676449d6357d Mon Sep 17 00:00:00 2001 From: kno10 Date: Sat, 28 Sep 2024 02:23:44 +0200 Subject: [PATCH] Movement improvements --- mods/ENTITIES/mcl_mobs/api.lua | 48 ++- mods/ENTITIES/mcl_mobs/api.txt | 1 + mods/ENTITIES/mcl_mobs/breeding.lua | 4 - mods/ENTITIES/mcl_mobs/combat.lua | 20 +- mods/ENTITIES/mcl_mobs/effects.lua | 163 ++++------ mods/ENTITIES/mcl_mobs/init.lua | 23 +- mods/ENTITIES/mcl_mobs/movement.lua | 290 ++++++------------ mods/ENTITIES/mcl_mobs/physics.lua | 458 ++++++++++++---------------- mods/ENTITIES/mobs_mc/cod.lua | 2 +- mods/ENTITIES/mobs_mc/dolphin.lua | 2 +- mods/ENTITIES/mobs_mc/stalker.lua | 14 +- 11 files changed, 407 insertions(+), 618 deletions(-) diff --git a/mods/ENTITIES/mcl_mobs/api.lua b/mods/ENTITIES/mcl_mobs/api.lua index 5f7b07a36..bd9ff445a 100644 --- a/mods/ENTITIES/mcl_mobs/api.lua +++ b/mods/ENTITIES/mcl_mobs/api.lua @@ -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 diff --git a/mods/ENTITIES/mcl_mobs/api.txt b/mods/ENTITIES/mcl_mobs/api.txt index 29cdc2c48..b3df78b36 100644 --- a/mods/ENTITIES/mcl_mobs/api.txt +++ b/mods/ENTITIES/mcl_mobs/api.txt @@ -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 diff --git a/mods/ENTITIES/mcl_mobs/breeding.lua b/mods/ENTITIES/mcl_mobs/breeding.lua index 426353b5a..0541c9e66 100644 --- a/mods/ENTITIES/mcl_mobs/breeding.lua +++ b/mods/ENTITIES/mcl_mobs/breeding.lua @@ -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 diff --git a/mods/ENTITIES/mcl_mobs/combat.lua b/mods/ENTITIES/mcl_mobs/combat.lua index 54e45b71b..b0aee5a25 100644 --- a/mods/ENTITIES/mcl_mobs/combat.lua +++ b/mods/ENTITIES/mcl_mobs/combat.lua @@ -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, 1) 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) diff --git a/mods/ENTITIES/mcl_mobs/effects.lua b/mods/ENTITIES/mcl_mobs/effects.lua index 22434be31..54e8d3362 100644 --- a/mods/ENTITIES/mcl_mobs/effects.lua +++ b/mods/ENTITIES/mcl_mobs/effects.lua @@ -14,51 +14,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) @@ -90,57 +70,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 @@ -177,26 +135,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 @@ -389,23 +343,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 diff --git a/mods/ENTITIES/mcl_mobs/init.lua b/mods/ENTITIES/mcl_mobs/init.lua index b7029c8fc..e1cb45ef1 100644 --- a/mods/ENTITIES/mcl_mobs/init.lua +++ b/mods/ENTITIES/mcl_mobs/init.lua @@ -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") @@ -225,7 +245,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, diff --git a/mods/ENTITIES/mcl_mobs/movement.lua b/mods/ENTITIES/mcl_mobs/movement.lua index e30c010d2..479e16e4c 100644 --- a/mods/ENTITIES/mcl_mobs/movement.lua +++ b/mods/ENTITIES/mcl_mobs/movement.lua @@ -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 diff --git a/mods/ENTITIES/mcl_mobs/physics.lua b/mods/ENTITIES/mcl_mobs/physics.lua index cb9b13614..96f312ba6 100644 --- a/mods/ENTITIES/mcl_mobs/physics.lua +++ b/mods/ENTITIES/mcl_mobs/physics.lua @@ -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 diff --git a/mods/ENTITIES/mobs_mc/cod.lua b/mods/ENTITIES/mobs_mc/cod.lua index fba769694..3a349ffb4 100644 --- a/mods/ENTITIES/mobs_mc/cod.lua +++ b/mods/ENTITIES/mobs_mc/cod.lua @@ -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 diff --git a/mods/ENTITIES/mobs_mc/dolphin.lua b/mods/ENTITIES/mobs_mc/dolphin.lua index 45753ff7f..c74e9a83a 100644 --- a/mods/ENTITIES/mobs_mc/dolphin.lua +++ b/mods/ENTITIES/mobs_mc/dolphin.lua @@ -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 diff --git a/mods/ENTITIES/mobs_mc/stalker.lua b/mods/ENTITIES/mobs_mc/stalker.lua index 962fbdea9..a2557af55 100644 --- a/mods/ENTITIES/mobs_mc/stalker.lua +++ b/mods/ENTITIES/mobs_mc/stalker.lua @@ -7,12 +7,12 @@ local S = minetest.get_translator("mobs_mc") --################### -local function get_texture(self) - local on_name = self.standing_on +local function get_texture(self, prev) + local standing_on = self.standing_on local texture local texture_suff = "" - if on_name and on_name ~= "air" then - local tiles = minetest.registered_nodes[on_name].tiles + if standing_on and standing_on.groups.solid then + local tiles = standing_on.tiles if tiles then local tile = tiles[1] local color @@ -25,7 +25,7 @@ local function get_texture(self) texture = tile end if not color then - color = minetest.colorspec_to_colorstring(minetest.registered_nodes[on_name].color) + color = minetest.colorspec_to_colorstring(standing_on.color) end if color then texture_suff = "^[multiply:" .. color .. "^[hsl:0:0:20" @@ -33,6 +33,7 @@ local function get_texture(self) end end if not texture or texture == "" then + if prev then return prev end texture = "vl_stalker_default.png" end texture = texture:gsub("([\\^:\\[])","\\%1") -- escape texture modifiers @@ -98,6 +99,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, @@ -132,7 +134,7 @@ mcl_mobs.register_mob("mobs_mc:stalker", { self:boom(mcl_util.get_object_center(self.object), self.explosion_strength) end end - local new_texture = get_texture(self) + local new_texture = get_texture(self, self._stalker_texture) if self._stalker_texture ~= new_texture then self.object:set_properties({textures={new_texture, "mobs_mc_empty.png"}}) self._stalker_texture = new_texture