Merge pull request 'Add ability to fight and kill mobs' (#1603) from jordan4ibanez/MineClone2:mineclone5 into mineclone5

Reviewed-on: MineClone2/MineClone2#1603
This commit is contained in:
jordan4ibanez 2021-04-23 01:38:12 +00:00
commit 53d3de7dd3
6 changed files with 307 additions and 240 deletions

View File

@ -120,7 +120,7 @@ end
-- creative check -- creative check
function mobs.is_creative(name) function mobs.is_creative(name)
return minetest.is_creative_enabled(name) return minetest_is_creative_enabled(name)
end end
@ -159,6 +159,7 @@ dofile(api_path .. "movement.lua")
dofile(api_path .. "set_up.lua") dofile(api_path .. "set_up.lua")
dofile(api_path .. "attack_type_instructions.lua") dofile(api_path .. "attack_type_instructions.lua")
dofile(api_path .. "sound_handling.lua") dofile(api_path .. "sound_handling.lua")
dofile(api_path .. "death_logic.lua")
mobs.spawning_mobs = {} 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}, visual_size_origin = def.visual_size or {x = 1, y = 1, z = 1},
punch_timer_cooloff = def.punch_timer_cooloff or 0.5, punch_timer_cooloff = def.punch_timer_cooloff or 0.5,
projectile_cooldown = def.projectile_cooldown or 2, projectile_cooldown = def.projectile_cooldown or 2,
death_animation_timer = 0,
--end j4i stuff --end j4i stuff
-- MCL2 extensions -- MCL2 extensions

View File

@ -610,13 +610,33 @@ mobs.mob_step = function(self, dtime)
return false return false
end 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 local attacking = nil
--scan for players within eyesight
if self.hostile then if self.hostile then
--true for line_of_sight is debug --true for line_of_sight is debug
attacking = mobs.detect_closest_player_within_radius(self,true,self.view_range,self.eye_height) 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 if attacking then
--set initial punch timer --set initial punch timer
@ -650,28 +670,42 @@ mobs.mob_step = function(self, dtime)
end end
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) --perfectly reset pause_timer
if self.jump_only then if self.pause_timer < 0 then
jump_state_switch(self, dtime) self.pause_timer = 0
jump_state_execution(self, dtime) end
--swimming
elseif self.swim then return -- don't allow collision detection
swim_state_switch(self, dtime) --do normal ai
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 else
land_state_switch(self, dtime) --jump only (like slimes)
land_state_execution(self,dtime) 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 end
-- can mob be pushed, if so calculate direction -- do this last (overrides everything) -- can mob be pushed, if so calculate direction -- do this last (overrides everything)
if self.pushable then if self.pushable then
mobs.collision(self) mobs.collision(self)

View File

@ -175,10 +175,9 @@ mobs.set_static_pitch = function(self)
local current_rotation = self.object:get_rotation() local current_rotation = self.object:get_rotation()
current_rotation.x = 0 current_rotation.x = 0
current_rotation.z = 0
self.object:set_rotation(current_rotation) self.object:set_rotation(current_rotation)
self.pitch_switchfdas = "static" self.pitch_switch = "static"
end end
--this is a helper function for mobs explosion animation --this is a helper function for mobs explosion animation

View File

@ -971,84 +971,7 @@ local update_tag = function(self)
update_roll(self) update_roll(self)
end 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 -- check if mob is dead or only hurt

View File

@ -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

View File

@ -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_direction = vector.direction
local vector_multiply = vector.multiply
mobs.feed_tame = function(self) mobs.feed_tame = function(self)
return nil return nil
@ -36,6 +43,10 @@ end
-- I have no idea what this does -- I have no idea what this does
mobs.create_mob_on_rightclick = function(on_rightclick) mobs.create_mob_on_rightclick = function(on_rightclick)
return function(self, clicker) return function(self, clicker)
--don't allow rightclicking dead mobs
if self.health <= 0 then
return
end
local stop = on_rightclick_prefix(self, clicker) local stop = on_rightclick_prefix(self, clicker)
if (not stop) and (on_rightclick) then if (not stop) and (on_rightclick) then
on_rightclick(self, clicker) on_rightclick(self, clicker)
@ -47,6 +58,11 @@ end
-- deal damage and effects when mob punched -- deal damage and effects when mob punched
mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir) 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 --neutral passive mobs switch to neutral hostile
if self.neutral then if self.neutral then
@ -65,37 +81,33 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
end end
--[[
-- custom punch function -- custom punch function
if self.do_punch then if self.do_punch then
-- when false skip going any further -- when false skip going any further
if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
return return
end end
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 -- error checking when mod profiling is enabled
if not tool_capabilities then 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 return
end end
local is_player = hitter:is_player() 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 -- punch interval
local weapon = hitter:get_wielded_item() local weapon = hitter:get_wielded_item()
local punch_interval = 1.4 local punch_interval = 1.4
-- exhaust attacker -- 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 armor = self.object:get_armor_groups() or {}
local tmp local tmp
-- quick error check incase it ends up 0 (serialize.h check test) --calculate damage groups
if tflp == 0 then for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
tflp = 0.2 damage = damage + (tool_capabilities.damage_groups[group] or 0) * ((armor[group] or 0) / 100.0)
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
end end
if weapon then if weapon then
@ -141,9 +134,7 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
-- check for tool immunity or special damage -- check for tool immunity or special damage
for n = 1, #self.immune_to do for n = 1, #self.immune_to do
if self.immune_to[n][1] == weapon:get_name() then if self.immune_to[n][1] == weapon:get_name() then
damage = self.immune_to[n][2] or 0 damage = self.immune_to[n][2] or 0
break break
end end
@ -155,21 +146,14 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
return return
end 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 if tool_capabilities then
punch_interval = tool_capabilities.full_punch_interval or 1.4 punch_interval = tool_capabilities.full_punch_interval or 1.4
end end
-- add weapon wear manually -- add weapon wear manually
-- Required because we have custom health handling ("health" property) -- Required because we have custom health handling ("health" property)
if minetest_is_creative_enabled("") ~= true --minetest_is_creative_enabled("") ~= true --removed for now
and tool_capabilities then if tool_capabilities then
if tool_capabilities.punch_attack_uses then if tool_capabilities.punch_attack_uses then
-- Without this delay, the wear does not work. Quite hacky ... -- Without this delay, the wear does not work. Quite hacky ...
minetest_after(0, function(name) minetest_after(0, function(name)
@ -207,65 +191,73 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
}, true) }, true)
end end
damage_effect(self, damage) --damage_effect(self, damage)
-- do damage -- do damage
self.health = self.health - damage self.health = self.health - damage
-- skip future functions if dead, except alerting others -- skip future functions if dead, except alerting others
if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then --if check_for_death(self, "hit", {type = "punch", puncher = hitter}) then
die = true -- 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 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() --0.75 for perfect distance to not be too easy, and not be too hard
local r = 1.4 - math_min(punch_interval, 1.4) local multiplier = 0.75
local kb = r * 2.0
local up = 2
-- if already in air then dont go up anymore when hit -- check if tool already has specific knockback value
if v.y ~= 0 local knockback_enchant = mcl_enchanting.get_enchantment(hitter:get_wielded_item(), "knockback")
or self.fly then if knockback_enchant and knockback_enchant > 0 then
up = 0 multiplier = knockback_enchant + 1 --(starts from 1, 1 would be no change)
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
end 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 skittish then run away
--[[
if not die and self.runaway == true and self.state ~= "flop" then if not die and self.runaway == true and self.state ~= "flop" then
local lp = hitter:get_pos() local lp = hitter:get_pos()
@ -288,54 +280,6 @@ mobs.mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
self.following = nil self.following = nil
end 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 end