Merge pull request 'Mob animation/head/jumping improvments/fixes' (#2865) from mob_head_code_improvments into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/2865
Reviewed-by: cora <cora@noreply.git.minetest.land>
This commit is contained in:
cora 2022-10-27 02:19:18 +00:00
commit 4f191754b8
2 changed files with 217 additions and 59 deletions

View File

@ -414,21 +414,20 @@ local set_yaw = function(self, yaw, delay, dtime)
if self.noyaw then return end if self.noyaw then return end
if self._kb_turn then
self._turn_to = yaw self._turn_to = yaw
end
--clamp our yaw to a 360 range --clamp our yaw to a 360 range
if math.deg(self.object:get_yaw()) > 360 then if math.deg(self.object:get_yaw()) > 360 then
self.object:set_yaw(math.rad(10)) self.object:set_yaw(math.rad(1))
elseif math.deg(self.object:get_yaw()) < 0 then elseif math.deg(self.object:get_yaw()) < 0 then
self.object:set_yaw(math.rad(350)) self.object:set_yaw(math.rad(359))
end end
--calculate the shortest way to turn to find our target --calculate the shortest way to turn to find our target
local target_shortest_path = shortest_term_of_yaw_rotatoin(self, self.object:get_yaw(), yaw, true) local target_shortest_path = shortest_term_of_yaw_rotatoin(self, self.object:get_yaw(), yaw, true)
--turn in the shortest path possible toward our target. if we are attacking, don't dance. --turn in the shortest path possible toward our target. if we are attacking, don't dance.
if math.abs(target_shortest_path) > 100 and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then if (math.abs(target_shortest_path) > 50 and not self._kb_turn) and (self.attack and self.attack:get_pos() or self.following and self.following:get_pos()) then
if self.following then if self.following then
target_shortest_path = shortest_term_of_yaw_rotatoin(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true) target_shortest_path = shortest_term_of_yaw_rotatoin(self, self.object:get_yaw(), minetest.dir_to_yaw(vector.direction(self.object:get_pos(), self.following:get_pos())), true)
else else
@ -1075,17 +1074,87 @@ local function within_limits(pos, radius)
return true return true
end end
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
local can_jump_cliff = function(self)
local yaw = self.object:get_yaw()
local pos = self.object:get_pos()
local v = self.object:get_velocity()
local v2 = abs(v.x)+abs(v.z)*.833
local jump_c_multiplier = 1
if v2/self.walk_velocity/2>1 then
jump_c_multiplier = v2/self.walk_velocity/2
end
-- where is front
local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6
local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6
--is there nothing under the block in front? if so jump the gap.
local nodLow = node_ok({
x = pos.x + dir_x-0.6,
y = pos.y - 0.5,
z = pos.z + dir_z-0.6
}, "air")
local nodFar = node_ok({
x = pos.x + dir_x*2,
y = pos.y - 0.5,
z = pos.z + dir_z*2
}, "air")
local nodFar2 = node_ok({
x = pos.x + dir_x*2.5,
y = pos.y - 0.5,
z = pos.z + dir_z*2.5
}, "air")
if minetest.registered_nodes[nodLow.name]
and minetest.registered_nodes[nodLow.name].walkable ~= true
and (minetest.registered_nodes[nodFar.name]
and minetest.registered_nodes[nodFar.name].walkable == true
or minetest.registered_nodes[nodFar2.name]
and minetest.registered_nodes[nodFar2.name].walkable == true)
then
--disable fear heigh while we make our jump
self._jumping_cliff = true
minetest.after(1, function()
if self and self.object then
self._jumping_cliff = false
end
end)
return true
else
return false
end
end
-- is mob facing a cliff or danger -- is mob facing a cliff or danger
local is_at_cliff_or_danger = function(self) local is_at_cliff_or_danger = function(self)
if self.fear_height == 0 then -- 0 for no falling protection! if self.fear_height == 0 or can_jump_cliff(self) or self._jumping_cliff or not self.object:get_luaentity() then -- 0 for no falling protection!
return false return false
end end
if not self.object:get_luaentity() then
return false
end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
@ -1118,7 +1187,7 @@ end
local is_at_water_danger = function(self) local is_at_water_danger = function(self)
if not self.object:get_luaentity() then if not self.object:get_luaentity() or can_jump_cliff(self) or self._jumping_cliff then
return false return false
end end
local yaw = self.object:get_yaw() local yaw = self.object:get_yaw()
@ -1152,20 +1221,6 @@ local is_at_water_danger = function(self)
end end
-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)
fallback = fallback or mcl_mobs.fallback_node
local node = minetest.get_node_or_nil(pos)
if node and minetest.registered_nodes[node.name] then
return node
end
return minetest.registered_nodes[fallback]
end
-- environmental damage (water, lava, fire, light etc.) -- environmental damage (water, lava, fire, light etc.)
local do_env_damage = function(self) local do_env_damage = function(self)
@ -1221,6 +1276,7 @@ local do_env_damage = function(self)
end end
local _, dim = mcl_worlds.y_to_layer(pos.y) local _, dim = mcl_worlds.y_to_layer(pos.y)
if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then if (self.sunlight_damage ~= 0 or self.ignited_by_sunlight) and (sunlight or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
if self.armor_list and not self.armor_list.helmet or not self.armor_list or self.armor_list and self.armor_list.helmet and self.armor_list.helmet == "" then
if self.ignited_by_sunlight then if self.ignited_by_sunlight then
mcl_burning.set_on_fire(self.object, 10) mcl_burning.set_on_fire(self.object, 10)
else else
@ -1228,6 +1284,7 @@ local do_env_damage = function(self)
return true return true
end end
end end
end
local y_level = self.collisionbox[2] local y_level = self.collisionbox[2]
@ -1431,8 +1488,8 @@ local do_jump = function(self)
end end
-- where is front -- where is front
local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+.4 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6
local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+.4 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)*jump_c_multiplier+0.6
-- what is in front of mob? -- what is in front of mob?
nod = node_ok({ nod = node_ok({
@ -1449,8 +1506,9 @@ local do_jump = function(self)
z = pos.z + dir_z z = pos.z + dir_z
}, "air") }, "air")
-- we don't attempt to jump if there's a stack of blocks blocking -- we don't attempt to jump if there's a stack of blocks blocking
if minetest.registered_nodes[nodTop.name].walkable == true then if minetest.registered_nodes[nodTop.name].walkable == true and not (self.attack and self.state == "attack") then
return false return false
end end
@ -1460,7 +1518,7 @@ local do_jump = function(self)
end end
local ndef = minetest.registered_nodes[nod.name] local ndef = minetest.registered_nodes[nod.name]
if self.walk_chance == 0 or ndef and ndef.walkable then if self.walk_chance == 0 or ndef and ndef.walkable or can_jump_cliff(self) then
if minetest.get_item_group(nod.name, "fence") == 0 if minetest.get_item_group(nod.name, "fence") == 0
and minetest.get_item_group(nod.name, "fence_gate") == 0 and minetest.get_item_group(nod.name, "fence_gate") == 0
@ -1470,6 +1528,10 @@ local do_jump = function(self)
v.y = self.jump_height + 0.1 * 3 v.y = self.jump_height + 0.1 * 3
if can_jump_cliff(self) then
v=vector.multiply(v, vector.new(2.8,1,2.8))
end
set_animation(self, "jump") -- only when defined set_animation(self, "jump") -- only when defined
self.object:set_velocity(v) self.object:set_velocity(v)
@ -2472,7 +2534,7 @@ local function go_to_pos(entity,b)
local v = { x = b.x - s.x, z = b.z - s.z } local v = { x = b.x - s.x, z = b.z - s.z }
local yaw = (atann(v.z / v.x) + pi / 2) - entity.rotate local yaw = (atann(v.z / v.x) + pi / 2) - entity.rotate
if b.x > s.x then yaw = yaw + pi end if b.x > s.x then yaw = yaw + pi end
entity.object:set_yaw(yaw) --entity.object:set_yaw(yaw)
set_velocity(entity,entity.follow_velocity) set_velocity(entity,entity.follow_velocity)
mcl_mobs:set_animation(entity, "walk") mcl_mobs:set_animation(entity, "walk")
end end
@ -3118,7 +3180,7 @@ local do_states = function(self, dtime)
if self.shoot_interval if self.shoot_interval
and self.timer > self.shoot_interval and self.timer > self.shoot_interval
and not minetest.raycast(p, self.attack:get_pos(), false, false):next() and not minetest.raycast(vector.add(p, vector.new(0,self.shoot_offset,0)), vector.add(self.attack:get_pos(), vector.new(0,1.5,0)), false, false):next()
and random(1, 100) <= 60 then and random(1, 100) <= 60 then
self.timer = 0 self.timer = 0
@ -3247,12 +3309,83 @@ local function player_near(pos)
end end
end end
local function get_armor_texture(armor_name)
if armor_name == "" then
return ""
end
if armor_name=="blank.png" then
return "blank.png"
end
local seperator = string.find(armor_name, ":")
return "mcl_armor_"..string.sub(armor_name, seperator+1, -1)..".png^"
end
local function set_armor_texture(self)
if self.armor_list then
local chestplate=minetest.registered_items[self.armor_list.chestplate] or {name=""}
local boots=minetest.registered_items[self.armor_list.boots] or {name=""}
local leggings=minetest.registered_items[self.armor_list.leggings] or {name=""}
local helmet=minetest.registered_items[self.armor_list.helmet] or {name=""}
if helmet.name=="" and chestplate.name=="" and leggings.name=="" and boots.name=="" then
helmet={name="blank.png"}
end
local texture = get_armor_texture(chestplate.name)..get_armor_texture(helmet.name)..get_armor_texture(boots.name)..get_armor_texture(leggings.name)
if string.sub(texture, -1,-1) == "^" then
texture=string.sub(texture,1,-2)
end
if self.textures[self.wears_armor] then
self.textures[self.wears_armor]=texture
end
self.object:set_properties({textures=self.textures})
local armor_
if type(self.armor) == "table" then
armor_ = table.copy(self.armor)
armor_.immortal = 1
else
armor_ = {immortal=1, fleshy = self.armor}
end
for _,item in pairs(self.armor_list) do
if not item then return end
if type(minetest.get_item_group(item, "mcl_armor_points")) == "number" then
armor_.fleshy=armor_.fleshy-(minetest.get_item_group(item, "mcl_armor_points")*3.5)
end
end
self.object:set_armor_groups(armor_)
end
end
local function check_item_pickup(self) local function check_item_pickup(self)
if self.pick_up and #self.pick_up > 0 then if self.pick_up and #self.pick_up > 0 or self.wears_armor then
local p = self.object:get_pos() local p = self.object:get_pos()
for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do for _,o in pairs(minetest.get_objects_inside_radius(p,2)) do
local l=o:get_luaentity() local l=o:get_luaentity()
if l and l.name == "__builtin:item" then if l and l.name == "__builtin:item" then
if not player_near(p) and l.itemstring:find("mcl_armor") and self.wears_armor then
local armor_type
if l.itemstring:find("chestplate") then
armor_type = "chestplate"
elseif l.itemstring:find("boots") then
armor_type = "boots"
elseif l.itemstring:find("leggings") then
armor_type = "leggings"
elseif l.itemstring:find("helmet") then
armor_type = "helmet"
end
if not armor_type then
return
end
if not self.armor_list then
self.armor_list={helmet="",chestplate="",boots="",leggings=""}
elseif self.armor_list[armor_type] and self.armor_list[armor_type] ~= "" then
return
end
self.armor_list[armor_type]=ItemStack(l.itemstring):get_name()
o:remove()
end
if self.pick_up then
for k,v in pairs(self.pick_up) do for k,v in pairs(self.pick_up) do
if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then if not player_near(p) and self.on_pick_up and l.itemstring:find(v) then
local r = self.on_pick_up(self,l) local r = self.on_pick_up(self,l)
@ -3267,6 +3400,7 @@ local function check_item_pickup(self)
end end
end end
end end
end
local check_herd_timer = 0 local check_herd_timer = 0
local function check_herd(self,dtime) local function check_herd(self,dtime)
@ -3620,7 +3754,7 @@ local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
elseif luaentity and luaentity._knockback then elseif luaentity and luaentity._knockback then
kb = kb + luaentity._knockback kb = kb + luaentity._knockback
end end
--self._kb_turn = false self._kb_turn = true
self._turn_to=self.object:get_yaw()-1.57 self._turn_to=self.object:get_yaw()-1.57
self.frame_speed_multiplier=2.3 self.frame_speed_multiplier=2.3
if self.animation.run_end then if self.animation.run_end then
@ -3631,7 +3765,7 @@ local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
minetest.after(0.2, function() minetest.after(0.2, function()
if self and self.object then if self and self.object then
self.frame_speed_multiplier=1 self.frame_speed_multiplier=1
self._kb_turn = true self._kb_turn = false
end end
end) end)
self.object:add_velocity({ self.object:add_velocity({
@ -3936,9 +4070,16 @@ local mob_activate = function(self, staticdata, def, dtime)
self.on_spawn_run = true -- if true, set flag to run once only self.on_spawn_run = true -- if true, set flag to run once only
end end
end end
if not self._run_armor_init then
self.armor_list={helmet="",chestplate="",boots="",leggings=""}
set_armor_texture(self)
self._run_armor_init = true
end
-- run after_activate -- run after_activate
if def.after_activate then if def.after_activate then
def.after_activate(self, staticdata, def, dtime) def.after_activate(self, staticdata, def, dtime)
end end
end end
@ -4053,7 +4194,11 @@ local mob_step = function(self, dtime)
if not self.animation.walk_speed then if not self.animation.walk_speed then
self.animation.walk_speed = 25 self.animation.walk_speed = 25
end end
if abs(v.x)+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.object:set_animation_frame_speed((v2/math.max(1,self.run_velocity))*self.animation.walk_speed*self.frame_speed_multiplier)
else
self.object:set_animation_frame_speed(25)
end
end end
--set_speed --set_speed
@ -4111,12 +4256,13 @@ local mob_step = function(self, dtime)
-- end rotation -- end rotation
if self.head_swivel and type(self.head_swivel) == "string" then if self.head_swivel and type(self.head_swivel) == "string" then
local final_rotation = vector.new(0,0,0)
local oldp,oldr = self.object:get_bone_position(self.head_swivel) local oldp,oldr = self.object:get_bone_position(self.head_swivel)
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do for _, obj in pairs(minetest.get_objects_inside_radius(pos, 10)) do
if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then if obj:is_player() and not self.attack or obj:get_luaentity() and obj:get_luaentity().name == self.name and self ~= obj:get_luaentity() then
if not self._locked_object then if not self._locked_object then
if math.random(5000/self.curiosity) == 1 then if math.random(5000/self.curiosity) == 1 or vector.distance(pos,obj:get_pos())<4 and obj:is_player() then
self._locked_object = obj self._locked_object = obj
end end
else else
@ -4127,8 +4273,8 @@ local mob_step = function(self, dtime)
end end
end end
if self.attack then if self.attack or self.following then
self._locked_object = self.attack self._locked_object = self.attack or self.following
end end
if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then if self._locked_object and (self._locked_object:is_player() or self._locked_object:get_luaentity()) and self._locked_object:get_hp() > 0 then
@ -4148,30 +4294,36 @@ local mob_step = function(self, dtime)
local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0))) local direction_player = vector.direction(vector.add(self.object:get_pos(), vector.new(0, self.head_eye_height*.7, 0)), vector.add(player_pos, vector.new(0, _locked_object_eye_height, 0)))
local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset local mob_yaw = math.deg(-(-(self_rot.y)-(-minetest.dir_to_yaw(direction_player))))+self.head_yaw_offset
local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier local mob_pitch = math.deg(-dir_to_pitch(direction_player))*self.head_pitch_multiplier
if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.type == "monster") then
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.multiply(oldr, 0.9)) if (mob_yaw < -60 or mob_yaw > 60) and not (self.attack and self.state == "attack" and not self.runaway) then
elseif self.attack and self.type == "monster" then final_rotation = vector.multiply(oldr, 0.9)
elseif self.attack and self.state == "attack" and not self.runaway then
if self.head_yaw == "y" then if self.head_yaw == "y" then
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.new(mob_pitch, mob_yaw, 0)) final_rotation = vector.new(mob_pitch, mob_yaw, 0)
elseif self.head_yaw == "z" then elseif self.head_yaw == "z" then
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.new(mob_pitch, 0, -mob_yaw)) final_rotation = vector.new(mob_pitch, 0, -mob_yaw)
end end
else else
if self.head_yaw == "y" then if self.head_yaw == "y" then
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0)) final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, ((mob_yaw-oldr.y)*.3)+oldr.y, 0)
elseif self.head_yaw == "z" then elseif self.head_yaw == "z" then
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3)) final_rotation = vector.new(((mob_pitch-oldr.x)*.3)+oldr.x, 0, -(((mob_yaw-oldr.y)*.3)+oldr.y)*3)
end end
end end
end end
elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then elseif not self._locked_object and math.abs(oldr.y) > 3 and math.abs(oldr.x) < 3 then
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.multiply(oldr, 0.9)) final_rotation = vector.multiply(oldr, 0.9)
else else
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), vector.new(0,0,0)) final_rotation = vector.new(0,0,0)
end end
mcl_util.set_bone_position(self.object,self.head_swivel, vector.new(0,self.bone_eye_height,self.horrizonatal_head_height), final_rotation)
end end
-- run custom function (defined in mob lua file) -- run custom function (defined in mob lua file)
if self.do_custom then if self.do_custom then
@ -4243,6 +4395,8 @@ local mob_step = function(self, dtime)
do_jump(self) do_jump(self)
set_armor_texture(self)
runaway_from(self) runaway_from(self)
if is_at_water_danger(self) and self.state ~= "attack" then if is_at_water_danger(self) and self.state ~= "attack" then
@ -4399,6 +4553,7 @@ minetest.register_entity(name, {
curiosity = def.curiosity or 1, -- how often mob will look at player on idle curiosity = def.curiosity or 1, -- how often mob will look at player on idle
head_yaw = def.head_yaw or "y", -- axis to rotate head on head_yaw = def.head_yaw or "y", -- axis to rotate head on
horrizonatal_head_height = def.horrizonatal_head_height or 0, horrizonatal_head_height = def.horrizonatal_head_height or 0,
wears_armor = def.wears_armor, -- a number value used to index texture slot for armor
stepheight = def.stepheight or 0.6, stepheight = def.stepheight or 0.6,
name = name, name = name,
description = def.description, description = def.description,
@ -4452,6 +4607,7 @@ minetest.register_entity(name, {
nofollow = def.nofollow, nofollow = def.nofollow,
can_open_doors = def.can_open_doors, can_open_doors = def.can_open_doors,
jump = def.jump ~= false, jump = def.jump ~= false,
automatic_face_movement_max_rotation_per_sec = 300,
walk_chance = def.walk_chance or 50, walk_chance = def.walk_chance or 50,
attacks_monsters = def.attacks_monsters or false, attacks_monsters = def.attacks_monsters or false,
group_attack = def.group_attack or false, group_attack = def.group_attack or false,
@ -4561,6 +4717,7 @@ minetest.register_entity(name, {
self.object:set_properties({ self.object:set_properties({
collide_with_objects = false, collide_with_objects = false,
}) })
return mob_activate(self, staticdata, def, dtime) return mob_activate(self, staticdata, def, dtime)
end, end,

View File

@ -59,6 +59,7 @@ local zombie = {
curiosity = 7, curiosity = 7,
head_pitch_multiplier=-1, head_pitch_multiplier=-1,
breath_max = -1, breath_max = -1,
wears_armor = 1,
armor = {undead = 90, fleshy = 90}, armor = {undead = 90, fleshy = 90},
collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.8, 0.3}, collisionbox = {-0.3, -0.01, -0.3, 0.3, 1.8, 0.3},
visual = "mesh", visual = "mesh",