diff --git a/mods/ENTITIES/mcl_mobs/api/api.lua b/mods/ENTITIES/mcl_mobs/api/api.lua index b2d5ba162f..e5e9d32f48 100644 --- a/mods/ENTITIES/mcl_mobs/api/api.lua +++ b/mods/ENTITIES/mcl_mobs/api/api.lua @@ -120,7 +120,7 @@ end -- creative check function mobs.is_creative(name) - return minetest.is_creative_enabled(name) + return minetest_is_creative_enabled(name) end @@ -159,6 +159,7 @@ dofile(api_path .. "movement.lua") dofile(api_path .. "set_up.lua") dofile(api_path .. "attack_type_instructions.lua") dofile(api_path .. "sound_handling.lua") +dofile(api_path .. "death_logic.lua") mobs.spawning_mobs = {} @@ -326,6 +327,7 @@ function mobs:register_mob(name, def) visual_size_origin = def.visual_size or {x = 1, y = 1, z = 1}, punch_timer_cooloff = def.punch_timer_cooloff or 0.5, projectile_cooldown = def.projectile_cooldown or 2, + death_animation_timer = 0, --end j4i stuff -- MCL2 extensions diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua index 11c61f20f4..3a0dda1171 100644 --- a/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/ai.lua @@ -610,13 +610,33 @@ mobs.mob_step = function(self, dtime) return false end + --do death logic (animation, poof, explosion, etc) + if self.health <= 0 then + + mobs.death_logic(self, dtime) + + --this is here because the mob must continue to move + --while stunned before coming to a complete halt even during + --the death tilt + if self.pause_timer > 0 then + self.pause_timer = self.pause_timer - dtime + --perfectly reset pause_timer + if self.pause_timer < 0 then + self.pause_timer = 0 + end + end + + return + end + local attacking = nil + --scan for players within eyesight if self.hostile then --true for line_of_sight is debug attacking = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height) - --go get the closest player ROAR >:O + --go get the closest player if attacking then --set initial punch timer @@ -650,28 +670,42 @@ mobs.mob_step = function(self, dtime) end end + --mob is stunned after being hit + if self.pause_timer > 0 then + self.pause_timer = self.pause_timer - dtime + --don't break eye contact + if self.hostile and self.attacking then + mobs.set_yaw_while_attacking(self) + end - --jump only (like slimes) - if self.jump_only then - jump_state_switch(self, dtime) - jump_state_execution(self, dtime) - --swimming - elseif self.swim then - swim_state_switch(self, dtime) - swim_state_execution(self, dtime) - --flying - elseif self.fly then - fly_state_switch(self, dtime) - fly_state_execution(self,dtime) - --regular mobs that walk around + --perfectly reset pause_timer + if self.pause_timer < 0 then + self.pause_timer = 0 + end + + return -- don't allow collision detection + --do normal ai else - land_state_switch(self, dtime) - land_state_execution(self,dtime) + --jump only (like slimes) + if self.jump_only then + jump_state_switch(self, dtime) + jump_state_execution(self, dtime) + --swimming + elseif self.swim then + swim_state_switch(self, dtime) + swim_state_execution(self, dtime) + --flying + elseif self.fly then + fly_state_switch(self, dtime) + fly_state_execution(self,dtime) + --regular mobs that walk around + else + land_state_switch(self, dtime) + land_state_execution(self,dtime) + end end - - -- can mob be pushed, if so calculate direction -- do this last (overrides everything) if self.pushable then mobs.collision(self) diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua index b7b8fa5748..00a33e9573 100644 --- a/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/animation.lua @@ -175,10 +175,9 @@ mobs.set_static_pitch = function(self) local current_rotation = self.object:get_rotation() current_rotation.x = 0 - current_rotation.z = 0 self.object:set_rotation(current_rotation) - self.pitch_switchfdas = "static" + self.pitch_switch = "static" end --this is a helper function for mobs explosion animation diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua index 802201688e..76a319d334 100644 --- a/mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/backup_code_api.lua @@ -971,84 +971,7 @@ local update_tag = function(self) update_roll(self) end --- drop items -local item_drop = function(self, cooked, looting_level) - -- no drops if disabled by setting - if not mobs_drop_items then return end - - looting_level = looting_level or 0 - - -- no drops for child mobs (except monster) - if (self.child and self.type ~= "monster") then - return - end - - local obj, item, num - local pos = self.object:get_pos() - - self.drops = self.drops or {} -- nil check - - for n = 1, #self.drops do - local dropdef = self.drops[n] - local chance = 1 / dropdef.chance - local looting_type = dropdef.looting - - if looting_level > 0 then - local chance_function = dropdef.looting_chance_function - if chance_function then - chance = chance_function(looting_level) - elseif looting_type == "rare" then - chance = chance + (dropdef.looting_factor or 0.01) * looting_level - end - end - - local num = 0 - local do_common_looting = (looting_level > 0 and looting_type == "common") - if math_random() < chance then - num = math_random(dropdef.min or 1, dropdef.max or 1) - elseif not dropdef.looting_ignore_chance then - do_common_looting = false - end - - if do_common_looting then - num = num + math_floor(math_random(0, looting_level) + 0.5) - end - - if num > 0 then - item = dropdef.name - - -- cook items when true - if cooked then - - local output = minetest_get_craft_result({ - method = "cooking", width = 1, items = {item}}) - - if output and output.item and not output.item:is_empty() then - item = output.item:get_name() - end - end - - -- add item if it exists - for x = 1, num do - obj = minetest_add_item(pos, ItemStack(item .. " " .. 1)) - end - - if obj and obj:get_luaentity() then - - obj:set_velocity({ - x = math_random(-10, 10) / 9, - y = 6, - z = math_random(-10, 10) / 9, - }) - elseif obj then - obj:remove() -- item does not exist - end - end - end - - self.drops = {} -end -- check if mob is dead or only hurt diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/death_logic.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/death_logic.lua new file mode 100644 index 0000000000..53d517a584 --- /dev/null +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/death_logic.lua @@ -0,0 +1,165 @@ +local minetest_add_item = minetest.add_item +local minetest_add_particlespawner = minetest.add_particlespawner +local minetest_sound_play = minetest.sound_play + +local math_pi = math.pi +local math_random = math.random +local math_floor = math.floor +local HALF_PI = math_pi / 2 + +local vector_new = vector.new + + +local death_effect = function(self) + + local pos = self.object:get_pos() + local yaw = self.object:get_yaw() + local collisionbox = self.object:get_properties().collisionbox + + 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]} + end + + minetest_add_particlespawner({ + amount = 50, + time = 0.001, + minpos = vector.add(pos, min), + maxpos = vector.add(pos, max), + minvel = vector.new(-0.5,0.5,-0.5), + maxvel = vector.new(0.5,1,0.5), + minexptime = 1.1, + maxexptime = 1.5, + minsize = 1, + maxsize = 2, + collisiondetection = false, + vertical = false, + texture = "mcl_particles_mob_death.png", -- this particle looks strange + }) +end + + +-- drop items +local item_drop = function(self, cooked, looting_level) + + looting_level = looting_level or 0 + + -- no drops for child mobs (except monster) + if (self.child and self.type ~= "monster") then + return + end + + local obj, item, num + local pos = self.object:get_pos() + + self.drops = self.drops or {} -- nil check + + for n = 1, #self.drops do + local dropdef = self.drops[n] + local chance = 1 / dropdef.chance + local looting_type = dropdef.looting + + if looting_level > 0 then + local chance_function = dropdef.looting_chance_function + if chance_function then + chance = chance_function(looting_level) + elseif looting_type == "rare" then + chance = chance + (dropdef.looting_factor or 0.01) * looting_level + end + end + + local num = 0 + local do_common_looting = (looting_level > 0 and looting_type == "common") + if math_random() < chance then + num = math_random(dropdef.min or 1, dropdef.max or 1) + elseif not dropdef.looting_ignore_chance then + do_common_looting = false + end + + if do_common_looting then + num = num + math_floor(math_random(0, looting_level) + 0.5) + end + + if num > 0 then + item = dropdef.name + + -- cook items when true + if cooked then + + local output = minetest_get_craft_result({ + method = "cooking", width = 1, items = {item}}) + + if output and output.item and not output.item:is_empty() then + item = output.item:get_name() + end + end + + -- add item if it exists + for x = 1, num do + obj = minetest_add_item(pos, ItemStack(item .. " " .. 1)) + end + + if obj and obj:get_luaentity() then + + obj:set_velocity({ + x = math_random(-10, 10) / 9, + y = 6, + z = math_random(-10, 10) / 9, + }) + elseif obj then + obj:remove() -- item does not exist + end + end + end + + self.drops = {} +end + + +mobs.death_logic = function(self, dtime) + self.death_animation_timer = self.death_animation_timer + dtime + + + --the final POOF of a mob despawning + if self.death_animation_timer >= 1.25 then + + item_drop(self,false,1) + + death_effect(self) + + self.object:remove() + + return + end + + --I'm sure there's a more efficient way to do this + --but this is the easiest, easier to work with 1 variable synced + --this is also not smooth + local death_animation_roll = self.death_animation_timer * 2 -- * 2 to make it faster + if death_animation_roll > 1 then + death_animation_roll = 1 + end + + local rot = self.object:get_rotation() --(no pun intended) + + rot.z = death_animation_roll * HALF_PI + + self.object:set_rotation(rot) + + mobs.set_mob_animation(self,"stand", true) + + + --flying and swimming mobs just fall down + if self.fly or self.swim then + if self.object:get_acceleration().y ~= -self.gravity then + self.object:set_acceleration(vector_new(0,-self.gravity,0)) + end + end + + --when landing allow mob to slow down and just fall if in air + if self.pause_timer <= 0 then + mobs.set_velocity(self,0) + end +end \ No newline at end of file diff --git a/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua b/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua index 7cbcd9a706..32011aafe8 100644 --- a/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua +++ b/mods/ENTITIES/mcl_mobs/api/mob_functions/interaction.lua @@ -1,5 +1,12 @@ -local math_floor = math.floor +local minetest_after = minetest.after +local minetest_sound_play = minetest.sound_play + +local math_floor = math.floor +local math_min = math.min +local math_random = math.random + local vector_direction = vector.direction +local vector_multiply = vector.multiply mobs.feed_tame = function(self) return nil @@ -36,6 +43,10 @@ end -- I have no idea what this does mobs.create_mob_on_rightclick = function(on_rightclick) return function(self, clicker) + --don't allow rightclicking dead mobs + if self.health <= 0 then + return + end local stop = on_rightclick_prefix(self, clicker) if (not stop) and (on_rightclick) then on_rightclick(self, clicker) @@ -47,6 +58,11 @@ end -- deal damage and effects when mob punched mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) + --don't do anything if the mob is already dead + if self.health <= 0 then + return + end + --neutral passive mobs switch to neutral hostile if self.neutral then @@ -65,37 +81,33 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) end - --[[ -- custom punch function if self.do_punch then - -- when false skip going any further if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then return end end + --don't do damage until pause timer resets + if self.pause_timer > 0 then + return + end + + -- error checking when mod profiling is enabled if not tool_capabilities then - minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") + minetest.log("warning", "[mobs_mc] Mod profiling enabled, damage not enabled") return end + local is_player = hitter:is_player() - if is_player then - -- is mob protected? - if self.protected and minetest_is_protected(self.object:get_pos(), hitter:get_player_name()) then - return - end - - -- set/update 'drop xp' timestamp if hitted by player - self.xp_timestamp = minetest_get_us_time() - end - -- punch interval local weapon = hitter:get_wielded_item() + local punch_interval = 1.4 -- exhaust attacker @@ -108,28 +120,9 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) local armor = self.object:get_armor_groups() or {} local tmp - -- quick error check incase it ends up 0 (serialize.h check test) - if tflp == 0 then - tflp = 0.2 - end - - if use_cmi then - damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir) - else - - for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do - - tmp = tflp / (tool_capabilities.full_punch_interval or 1.4) - - if tmp < 0 then - tmp = 0.0 - elseif tmp > 1 then - tmp = 1.0 - end - - damage = damage + (tool_capabilities.damage_groups[group] or 0) - * tmp * ((armor[group] or 0) / 100.0) - end + --calculate damage groups + for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do + damage = damage + (tool_capabilities.damage_groups[group] or 0) * ((armor[group] or 0) / 100.0) end if weapon then @@ -141,9 +134,7 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) -- check for tool immunity or special damage for n = 1, #self.immune_to do - if self.immune_to[n][1] == weapon:get_name() then - damage = self.immune_to[n][2] or 0 break end @@ -155,21 +146,14 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) return end - if use_cmi then - - local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) - - if cancel then return end - end - if tool_capabilities then punch_interval = tool_capabilities.full_punch_interval or 1.4 end -- add weapon wear manually -- Required because we have custom health handling ("health" property) - if minetest_is_creative_enabled("") ~= true - and tool_capabilities then + --minetest_is_creative_enabled("") ~= true --removed for now + if tool_capabilities then if tool_capabilities.punch_attack_uses then -- Without this delay, the wear does not work. Quite hacky ... minetest_after(0, function(name) @@ -207,65 +191,73 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) }, true) end - damage_effect(self, damage) + --damage_effect(self, damage) -- do damage self.health = self.health - damage -- skip future functions if dead, except alerting others - if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then - die = true + --if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then + -- die = true + --end + + -- knock back effect + local velocity = self.object:get_velocity() + + --2d direction + local pos1 = self.object:get_pos() + pos1.y = 0 + local pos2 = hitter:get_pos() + pos2.y = 0 + + local dir = vector.direction(pos2,pos1) + + local up = 3 + + -- if already in air then dont go up anymore when hit + if velocity.y ~= 0 then + up = 0 end - -- knock back effect (only on full punch) - if not die - and self.knock_back - and tflp >= punch_interval then - local v = self.object:get_velocity() - local r = 1.4 - math_min(punch_interval, 1.4) - local kb = r * 2.0 - local up = 2 + --0.75 for perfect distance to not be too easy, and not be too hard + local multiplier = 0.75 - -- if already in air then dont go up anymore when hit - if v.y ~= 0 - or self.fly then - up = 0 - end - - -- direction error check - dir = dir or {x = 0, y = 0, z = 0} - - -- check if tool already has specific knockback value - if tool_capabilities.damage_groups["knockback"] then - kb = tool_capabilities.damage_groups["knockback"] - else - kb = kb * 1.5 - end - - - local luaentity - if hitter then - luaentity = hitter:get_luaentity() - end - if hitter and is_player then - local wielditem = hitter:get_wielded_item() - kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback") - elseif luaentity and luaentity._knockback then - kb = kb + luaentity._knockback - end - - self.object:set_velocity({ - x = dir.x * kb, - y = dir.y * kb + up * 2, - z = dir.z * kb - }) - - self.pause_timer = 0.25 + -- check if tool already has specific knockback value + local knockback_enchant = mcl_enchanting.get_enchantment(hitter:get_wielded_item(), "knockback") + if knockback_enchant and knockback_enchant > 0 then + multiplier = knockback_enchant + 1 --(starts from 1, 1 would be no change) end - end -- END if damage + + + local luaentity + + --[[ --why does this multiply it again??? + if hitter then + luaentity = hitter:get_luaentity() + end + if hitter and is_player then + local wielditem = hitter:get_wielded_item() + kb = kb + 3 * mcl_enchanting.get_enchantment(wielditem, "knockback") + elseif luaentity and luaentity._knockback then + kb = kb + luaentity._knockback + end + ]]-- + + dir = vector_multiply(dir,multiplier) + + dir.y = up + + --add velocity breaks momentum - use set velocity + self.object:set_velocity(dir) + + --0.4 seconds until you can hurt the mob again + self.pause_timer = 0.4 + end + -- END if damage -- if skittish then run away + --[[ if not die and self.runaway == true and self.state ~= "flop" then local lp = hitter:get_pos() @@ -288,54 +280,6 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) self.following = nil end - local name = hitter:get_player_name() or "" - - -- attack puncher and call other mobs for help - if self.passive == false - and self.state ~= "flop" - and (self.child == false or self.type == "monster") - and hitter:get_player_name() ~= self.owner - and not mobs.invis[ name ] then - - if not die then - -- attack whoever punched mob - self.state = "" - do_attack(self, hitter) - end - - -- alert others to the attack - local objs = minetest_get_objects_inside_radius(hitter:get_pos(), self.view_range) - local obj = nil - - for n = 1, #objs do - - obj = objs[n]:get_luaentity() - - if obj then - - -- only alert members of same mob or friends - if obj.group_attack - and obj.state ~= "attack" - and obj.owner ~= name then - if obj.name == self.name then - do_attack(obj, hitter) - elseif type(obj.group_attack) == "table" then - for i=1, #obj.group_attack do - if obj.name == obj.group_attack[i] then - do_attack(obj, hitter) - break - end - end - end - end - - -- have owned mobs attack player threat - if obj.owner == name and obj.owner_loyal then - do_attack(obj, self.object) - end - end - end - end ]]-- end