2017-05-16 04:48:28 +02:00
2015-09-01 17:15:24 +02:00
-- Mobs Api (9th August 2015)
2017-05-16 04:48:28 +02:00
2019-08-28 17:31:41 +02:00
local S = minetest.get_translator ( " mobs " )
2015-09-01 17:15:24 +02:00
mobs = { }
mobs.mod = " redo "
-- Initial settings check
2017-05-16 04:48:28 +02:00
2017-06-27 21:13:30 +02:00
local damage_enabled = minetest.settings : get_bool ( " enable_damage " ) or false
local peaceful_only = minetest.settings : get_bool ( " only_peaceful_mobs " ) or false
local enable_blood = minetest.settings : get_bool ( " mobs_enable_blood " ) or false
2017-05-12 05:24:40 +02:00
2017-06-27 21:13:30 +02:00
mobs.protected = tonumber ( minetest.settings : get ( " mobs_spawn_protected " ) ) or 0
2019-09-03 21:30:19 +02:00
mobs.remove = false
2015-09-01 17:15:24 +02:00
2015-10-24 20:36:31 +02:00
local function is_too_near_spawn ( pos )
2017-06-27 21:13:30 +02:00
if minetest.is_singleplayer ( ) or not minetest.settings : get_bool ( " mobs_safe_spawn " ) then
2015-10-25 22:18:54 +01:00
return false
end
2017-05-12 04:29:55 +02:00
local sp = minetest.setting_get_pos ( " static_spawnpoint " ) or { x = 0 , y = 0 , z = 0 }
2017-06-27 21:13:30 +02:00
local rad = minetest.settings : get ( " static_spawn_radius " ) or 256
2015-10-24 20:36:31 +02:00
return ( vector.distance ( pos , sp ) < rad )
end
2019-08-29 15:00:35 +02:00
-- particle effects
local function effect ( pos , amount , texture , max_size )
minetest.add_particlespawner (
{
amount = amount ,
time = 0.25 ,
minpos = pos ,
maxpos = pos ,
minvel = { x = - 0 , y = 2 , z = - 0 } ,
maxvel = { x = 2 , y = 6 , z = 2 } ,
minacc = { x = - 4 , y = - 16 , z = - 4 } ,
maxacc = { x = 4 , y = - 4 , z = 4 } ,
minexptime = 0.1 ,
maxexptime = 1 ,
minsize = 1 ,
maxsize = ( max_size or 2 ) ,
texture = texture ,
} )
end
2019-09-22 13:22:39 +02:00
local function mob_sound ( self , sound , keep_pitch )
local pitch
2019-09-22 13:37:00 +02:00
if not keep_pitch then
if self.child then
pitch = 1.5
else
pitch = 1.0
end
pitch = pitch + 0.0025 * math.random ( - 10 , 10 )
2019-09-22 13:22:39 +02:00
end
minetest.sound_play (
sound ,
{
pitch = pitch ,
object = self.object ,
max_hear_distance = self.sounds . distance
2020-04-07 01:00:55 +02:00
} , true )
2019-09-22 13:22:39 +02:00
end
2019-09-04 17:35:43 +02:00
-- Handle death (does not remove mob)
2019-09-03 19:56:47 +02:00
local function die_handler ( self , killer )
2019-08-29 15:00:35 +02:00
local pos = self.object : get_pos ( )
2019-09-03 19:56:47 +02:00
minetest.log ( " action " , " [mobs] " .. self.name .. " dies at " .. minetest.pos_to_string ( vector.round ( pos ) ) )
2019-08-29 15:00:35 +02:00
local obj = nil
2019-09-03 19:56:47 +02:00
local drops_food = false
2019-08-29 15:00:35 +02:00
for _ , drop in ipairs ( self.drops ) do
2019-09-03 19:56:47 +02:00
if minetest.get_item_group ( drop.name , " food " ) ~= 0 then
drops_food = true
end
2019-08-29 15:00:35 +02:00
if math.random ( 1 , drop.chance ) == 1 then
obj = minetest.add_item ( pos , ItemStack ( drop.name .. " " .. math.random ( drop.min , drop.max ) ) )
if obj then
obj : set_velocity (
{
x = math.random ( - 1 , 1 ) ,
y = 5 ,
z = math.random ( - 1 , 1 )
} )
end
end
end
if self.sounds . death ~= nil then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . death )
2019-08-29 15:00:35 +02:00
end
2019-09-03 19:56:47 +02:00
-- Hunter achievement: If mob is a food-dropping animal, it counts.
if killer ~= nil and self.type == " animal " and drops_food then
achievements.trigger_achievement ( killer , " hunter " )
end
2019-08-29 15:00:35 +02:00
if self.on_die then
2019-09-03 19:56:47 +02:00
self.on_die ( self , pos , killer )
end
end
-- on mob death drop items
2019-09-22 13:55:08 +02:00
local function check_for_death ( self , hitter , damage )
if damage == nil then
damage = 0
end
local hp = self.object : get_hp ( ) - damage
2019-09-03 19:56:47 +02:00
if hp > 0 then
self.health = hp
if self.sounds . damage ~= nil then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . damage )
2019-09-03 19:56:47 +02:00
end
return false
else
die_handler ( self , hitter )
2019-09-04 17:35:43 +02:00
self.object : remove ( )
2019-09-03 19:56:47 +02:00
return true
2019-08-29 15:00:35 +02:00
end
end
-- from TNT mod
local function calc_velocity ( pos1 , pos2 , old_vel , power )
local vel = vector.direction ( pos1 , pos2 )
vel = vector.normalize ( vel )
vel = vector.multiply ( vel , power )
local dist = vector.distance ( pos1 , pos2 )
dist = math.max ( dist , 1 )
vel = vector.divide ( vel , dist )
vel = vector.add ( vel , old_vel )
return vel
end
-- modified from TNT mod
local function entity_physics ( pos , radius )
radius = radius * 2
local objs = minetest.get_objects_inside_radius ( pos , radius )
local obj_pos , obj_vel , dist
for _ , obj in pairs ( objs ) do
obj_pos = obj : get_pos ( )
obj_vel = obj : get_velocity ( )
dist = math.max ( 1 , vector.distance ( pos , obj_pos ) )
if obj_vel ~= nil then
obj : set_velocity ( calc_velocity ( pos , obj_pos , obj_vel , radius * 10 ) )
end
local damage = ( 4 / dist ) * radius
obj : set_hp ( obj : get_hp ( ) - damage )
end
end
2015-09-01 17:15:24 +02:00
function mobs : register_mob ( name , def )
2017-05-12 04:29:55 +02:00
minetest.register_entity (
2015-09-01 17:15:24 +02:00
name ,
{
2019-09-26 18:13:36 +02:00
_cmi_is_mob = true ,
2015-09-01 17:15:24 +02:00
stepheight = def.stepheight or 0.6 ,
name = name ,
fly = def.fly ,
fly_in = def.fly_in or " air " ,
owner = def.owner or " " ,
2019-09-22 14:17:33 +02:00
last_feeder = " " ,
2015-09-01 17:15:24 +02:00
order = def.order or " " ,
on_die = def.on_die ,
do_custom = def.do_custom ,
jump_height = def.jump_height or 6 ,
jump_chance = def.jump_chance or 0 ,
rotate = math.rad ( def.rotate or 0 ) , -- 0=front, 90=side, 180=back, 270=side2
lifetimer = def.lifetimer or 180 , -- 3 minutes
hp_min = def.hp_min or 5 ,
hp_max = def.hp_max or 10 ,
2019-09-25 13:43:57 +02:00
breath_max = def.breath_max or - 1 , -- how many seconds the mob can breathe in drowning nodes. -1 = no drowning damage
breath = def.breath_max ,
2015-09-01 17:15:24 +02:00
physical = true ,
collides_with_objects = def.collides_with_objects or true ,
collisionbox = def.collisionbox ,
visual = def.visual ,
visual_size = def.visual_size or { x = 1 , y = 1 } ,
mesh = def.mesh ,
makes_footstep_sound = def.makes_footstep_sound or false ,
view_range = def.view_range or 5 ,
walk_velocity = def.walk_velocity or 1 ,
run_velocity = def.run_velocity or 2 ,
damage = def.damage ,
light_damage = def.light_damage or 0 ,
water_damage = def.water_damage or 0 ,
lava_damage = def.lava_damage or 0 ,
2019-09-22 23:58:16 +02:00
takes_node_damage = def.takes_node_damage or true ,
2015-09-01 17:15:24 +02:00
fall_damage = def.fall_damage or 1 ,
fall_speed = def.fall_speed or - 10 , -- must be lower than -2 (default: -10)
drops = def.drops or { } ,
armor = def.armor ,
on_rightclick = def.on_rightclick ,
type = def.type ,
explode_radius = def.explode_radius or 3 ,
attack_type = def.attack_type ,
arrow = def.arrow ,
shoot_interval = def.shoot_interval ,
sounds = def.sounds or { } ,
animation = def.animation ,
follow = def.follow or " " ,
jump = def.jump or true ,
walk_chance = def.walk_chance or 50 ,
attacks_monsters = def.attacks_monsters or false ,
group_attack = def.group_attack or false ,
--fov = def.fov or 120,
passive = def.passive or false ,
recovery_time = def.recovery_time or 0.5 ,
knock_back = def.knock_back or 3 ,
blood_amount = def.blood_amount or 5 ,
blood_texture = def.blood_texture or " mobs_blood.png " ,
shoot_offset = def.shoot_offset or 0 ,
floats = def.floats or 1 , -- floats in water by default
replace_rate = def.replace_rate ,
replace_what = def.replace_what ,
replace_with = def.replace_with ,
replace_offset = def.replace_offset or 0 ,
timer = 0 ,
env_damage_timer = 0 , -- only if state = "attack"
attack = { player = nil , dist = nil } ,
state = " stand " ,
tamed = false ,
pause_timer = 0 ,
horny = false ,
hornytimer = 0 ,
child = false ,
gotten = false ,
health = 0 ,
on_replace = def.on_replace or nil ,
on_spawn = def.on_spawn or nil ,
2017-05-16 04:48:28 +02:00
check_fed = function ( self , name , feed_count , breed ) -- Check if a mob is fed
self.food = ( self.food or 0 ) + 1
-- Make children grow quicker
if self.child == true then
self.hornytimer = self.hornytimer + 20
return true
end
-- Feed and tame
if self.food >= feed_count then
self.food = 0
if breed and self.hornytimer == 0 then
self.horny = true
end
2019-09-22 14:17:33 +02:00
self.last_feeder = name
2017-05-16 04:48:28 +02:00
self.gotten = false
if ( not self.tamed ) and name ~= nil
and ( ( not self.owner ) or self.owner == " " ) then
self.owner = name
self.tamed = true
achievements.trigger_achievement (
minetest.get_player_by_name ( name ) ,
" best_friends_forever "
)
end
-- Make sound when fed so many times
if self.sounds . random then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . random )
2017-05-16 04:48:28 +02:00
end
end
end ,
2017-05-12 18:07:18 +02:00
do_attack = function ( self , player , dist )
2019-03-20 15:04:20 +01:00
if is_too_near_spawn ( self.object : get_pos ( ) ) then return end
2017-05-16 04:48:28 +02:00
if self.state ~= " attack " then
if math.random ( 0 , 100 ) < 90
and self.sounds . war_cry then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . war_cry )
2017-05-16 04:48:28 +02:00
end
self.state = " attack "
self.attack . player = player
self.attack . dist = dist
end
end ,
2017-05-12 18:07:18 +02:00
set_velocity = function ( self , v )
2017-05-16 04:48:28 +02:00
v = ( v or 0 )
if def.drawtype
and def.drawtype == " side " then
self.rotate = math.rad ( 90 )
end
2019-03-20 15:04:20 +01:00
local tmpyaw = self.object : get_yaw ( )
2020-04-07 14:54:15 +02:00
if not tmpyaw then
return
end
2017-05-16 04:48:28 +02:00
local yaw = self.rotate
2019-09-14 09:37:09 +02:00
if tmpyaw ~= tmpyaw then -- It's a NaN value
minetest.log ( " warning " , " [mobs] object:get_yaw(): NaN returned (pos= " .. minetest.pos_to_string ( vector.round ( self.object_get_pos ( ) ) ) .. " ; entitystring= " .. self.name .. " ) " )
2017-05-16 04:48:28 +02:00
else
yaw = yaw + tmpyaw
end
local x = math.sin ( yaw ) * - v
local z = math.cos ( yaw ) * v
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( { x = x , y = self.object : get_velocity ( ) . y , z = z } )
2017-05-16 04:48:28 +02:00
end ,
2015-09-01 17:15:24 +02:00
get_velocity = function ( self )
2019-03-20 15:04:20 +01:00
local v = self.object : get_velocity ( )
2017-05-16 04:48:28 +02:00
return ( v.x ^ 2 + v.z ^ 2 ) ^ ( 0.5 )
end ,
in_fov = function ( self , pos )
-- checks if POS is in self's FOV
2019-03-20 15:04:20 +01:00
local yaw = self.object : get_yaw ( ) + self.rotate
2017-05-16 04:48:28 +02:00
local vx = math.sin ( yaw )
local vz = math.cos ( yaw )
local ds = math.sqrt ( vx ^ 2 + vz ^ 2 )
local ps = math.sqrt ( pos.x ^ 2 + pos.z ^ 2 )
local d = { x = vx / ds , z = vz / ds }
local p = { x = pos.x / ps , z = pos.z / ps }
local an = ( d.x * p.x ) + ( d.z * p.z )
a = math.deg ( math.acos ( an ) )
if a > ( self.fov / 2 ) then
return false
else
return true
end
end ,
set_animation = function ( self , type )
if not self.animation then
return
end
if not self.animation . current then
self.animation . current = " "
end
if type == " stand "
and self.animation . current ~= " stand " then
if self.animation . stand_start
and self.animation . stand_end
and self.animation . speed_normal then
self.object : set_animation ( {
x = self.animation . stand_start ,
y = self.animation . stand_end } ,
self.animation . speed_normal , 0 )
self.animation . current = " stand "
end
elseif type == " walk "
and self.animation . current ~= " walk " then
if self.animation . walk_start
and self.animation . walk_end
and self.animation . speed_normal then
self.object : set_animation ( {
x = self.animation . walk_start ,
y = self.animation . walk_end } ,
self.animation . speed_normal , 0 )
self.animation . current = " walk "
end
elseif type == " run "
and self.animation . current ~= " run " then
if self.animation . run_start
and self.animation . run_end
and self.animation . speed_run then
self.object : set_animation ( {
x = self.animation . run_start ,
y = self.animation . run_end } ,
self.animation . speed_run , 0 )
self.animation . current = " run "
end
elseif type == " punch "
and self.animation . current ~= " punch " then
if self.animation . punch_start
and self.animation . punch_end
and self.animation . speed_normal then
self.object : set_animation ( {
x = self.animation . punch_start ,
y = self.animation . punch_end } ,
self.animation . speed_normal , 0 )
self.animation . current = " punch "
end
end
end ,
on_step = function ( self , dtime )
if self.type == " monster "
and peaceful_only then
2019-09-03 21:07:55 +02:00
minetest.log ( " action " , " [mobs] Hostile mob " .. self.name .. " removed at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
2017-05-16 04:48:28 +02:00
self.object : remove ( )
return
end
-- if lifetimer run out and not npc; tamed or attacking then remove mob
if self.type ~= " npc "
and not self.tamed then
self.lifetimer = self.lifetimer - dtime
if self.lifetimer <= 0
and self.state ~= " attack " then
2019-09-25 11:39:02 +02:00
-- Only despawn if away from player
local objs = minetest.get_objects_inside_radius ( self.object : get_pos ( ) , 15 )
for n = 1 , # objs do
if objs [ n ] : is_player ( ) then
self.lifetimer = 20
return
end
end
minetest.log ( " action " , " [mobs] Mob lifetimer expired, removed " .. self.name .. " at " .. minetest.pos_to_string ( self.object : get_pos ( ) ) )
2019-03-20 15:04:20 +01:00
effect ( self.object : get_pos ( ) , 15 , " tnt_smoke.png " )
2017-05-16 04:48:28 +02:00
self.object : remove ( )
return
end
end
-- check for mob drop/replace (used for chicken egg and sheep eating grass/wheat)
if self.replace_rate
and self.child == false
and math.random ( 1 , self.replace_rate ) == 1 then
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
pos.y = pos.y + self.replace_offset
2019-03-20 15:04:20 +01:00
if self.replace_what and self.object : get_velocity ( ) . y == 0 and # minetest.find_nodes_in_area ( pos , pos , self.replace_what ) > 0 then
2017-05-16 04:48:28 +02:00
--and self.state == "stand" then
if self.on_replace ~= nil then
self.on_replace ( self , pos )
else
minetest.set_node ( pos , { name = self.replace_with } )
end
end
end
local yaw = 0
if not self.fly then
-- floating in water (or falling)
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
local nod = minetest.get_node_or_nil ( pos )
if nod then nod = nod.name else nod = " default:dirt " end
local nodef = minetest.registered_nodes [ nod ]
2019-03-20 15:04:20 +01:00
local v = self.object : get_velocity ( )
2017-05-16 04:48:28 +02:00
if v.y > 0.1 then
2019-03-20 15:04:20 +01:00
self.object : set_acceleration ( {
2017-05-16 04:48:28 +02:00
x = 0 ,
y = self.fall_speed ,
z = 0
} )
end
if nodef.groups . water then
if self.floats == 1 then
2019-03-20 15:04:20 +01:00
self.object : set_acceleration (
2017-05-16 04:48:28 +02:00
{
x = 0 ,
y = - self.fall_speed / ( math.max ( 1 , v.y ) ^ 2 ) ,
z = 0
} )
end
else
2019-03-20 15:04:20 +01:00
self.object : set_acceleration (
2017-05-16 04:48:28 +02:00
{
x = 0 ,
y = self.fall_speed ,
z = 0
} )
-- fall damage
if self.fall_damage == 1
2019-03-20 15:04:20 +01:00
and self.object : get_velocity ( ) . y == 0 then
local d = ( self.old_y or 0 ) - self.object : get_pos ( ) . y
2017-05-16 04:48:28 +02:00
if d > 5 then
self.object : set_hp ( self.object : get_hp ( ) - math.floor ( d - 5 ) )
2019-03-20 15:04:20 +01:00
effect ( self.object : get_pos ( ) , 5 , " tnt_smoke.png " )
2020-04-09 14:02:26 +02:00
if check_for_death ( self ) then return true end
2017-05-16 04:48:28 +02:00
end
2019-03-20 15:04:20 +01:00
self.old_y = self.object : get_pos ( ) . y
2017-05-16 04:48:28 +02:00
end
end
end
-- knockback timer
if self.pause_timer > 0 then
self.pause_timer = self.pause_timer - dtime
if self.pause_timer < 1 then
self.pause_timer = 0
end
return
end
-- attack timer
self.timer = self.timer + dtime
if self.state ~= " attack " then
if self.timer < 1 then
return
end
self.timer = 0
end
if self.sounds . random
and math.random ( 1 , 100 ) <= 1 then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . random )
2017-05-16 04:48:28 +02:00
end
2020-04-09 14:02:26 +02:00
-- Environment damage handling
-- Returns true if mob died
2017-05-16 04:48:28 +02:00
local do_env_damage = function ( self )
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
local tod = minetest.get_timeofday ( )
-- daylight above ground
if self.light_damage ~= 0
and pos.y > 0
and tod > 0.2
and tod < 0.8
and ( minetest.get_node_light ( pos ) or 0 ) > 12 then
self.object : set_hp ( self.object : get_hp ( ) - self.light_damage )
effect ( pos , 5 , " tnt_smoke.png " )
2020-04-09 14:02:26 +02:00
if check_for_death ( self ) then return true end
2017-05-16 04:48:28 +02:00
end
pos.y = pos.y + self.collisionbox [ 2 ] -- foot level
local nod = minetest.get_node_or_nil ( pos )
2019-08-28 17:54:05 +02:00
if not nod then return end
2017-05-16 04:48:28 +02:00
local nodef = minetest.registered_nodes [ nod.name ]
pos.y = pos.y + 1
2019-09-25 13:43:57 +02:00
-- node damage
if self.takes_node_damage == true
and nodef.damage_per_second > 0 then
self.object : set_hp ( self.object : get_hp ( ) - nodef.damage_per_second )
if enable_blood then
effect ( pos , self.blood_amount , self.blood_texture )
else
effect ( pos , self.blood_amount , " mobs_damage.png " )
end
2020-04-09 14:02:26 +02:00
if check_for_death ( self ) then return true end
2019-09-25 13:43:57 +02:00
end
-- drowning damage
if self.breath_max ~= - 1 then
if not self.breath then
self.breath = self.breath_max
end
if nodef.drowning > 0 then
self.breath = self.breath - 1
if self.breath < 0 then
self.breath = 0
self.object : set_hp ( self.object : get_hp ( ) - nodef.drowning )
effect ( pos , 5 , " bubble.png " )
end
2020-04-09 14:02:26 +02:00
if check_for_death ( self ) then return true end
2019-09-25 13:43:57 +02:00
else
self.breath = self.breath + 1
if self.breath > self.breath_max then
self.breath = self.breath_max
end
end
end
-- water damage
2017-05-16 04:48:28 +02:00
if self.water_damage ~= 0
and nodef.groups . water then
self.object : set_hp ( self.object : get_hp ( ) - self.water_damage )
effect ( pos , 5 , " bubble.png " )
2020-04-09 14:02:26 +02:00
if check_for_death ( self ) then return true end
2017-05-16 04:48:28 +02:00
end
2019-09-25 13:43:57 +02:00
-- lava damage
2017-05-16 04:48:28 +02:00
if self.lava_damage ~= 0
and nodef.groups . lava then
self.object : set_hp ( self.object : get_hp ( ) - self.lava_damage )
effect ( pos , 5 , " mobs_flame.png " , 8 )
2020-04-09 14:02:26 +02:00
if check_for_death ( self ) then return true end
2017-05-16 04:48:28 +02:00
end
end
local do_jump = function ( self )
if self.fly then
return
end
self.jumptimer = ( self.jumptimer or 0 ) + 1
if self.jumptimer < 3 then
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
pos.y = ( pos.y + self.collisionbox [ 2 ] ) - 0.2
local nod = minetest.get_node ( pos )
if not nod
or not minetest.registered_nodes [ nod.name ]
or minetest.registered_nodes [ nod.name ] . walkable == false then
return
end
if self.direction then
pos.y = pos.y + 0.5
local nod = minetest.get_node_or_nil (
{
x = pos.x + self.direction . x ,
y = pos.y ,
z = pos.z + self.direction . z
} )
if nod and nod.name and
( nod.name ~= " air "
or self.walk_chance == 0 ) then
local def = minetest.registered_items [ nod.name ]
if ( def
and def.walkable
and not nod.name : find ( " fence " ) )
or self.walk_chance == 0 then
2019-03-20 15:04:20 +01:00
local v = self.object : get_velocity ( )
2017-05-16 04:48:28 +02:00
v.y = self.jump_height + 1
v.x = v.x * 2.2
v.z = v.z * 2.2
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( v )
2017-05-16 04:48:28 +02:00
if self.sounds . jump then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . jump , false )
2017-05-16 04:48:28 +02:00
end
end
end
end
else
self.jumptimer = 0
end
end
-- environmental damage timer
self.env_damage_timer = self.env_damage_timer + dtime
if self.state == " attack "
and self.env_damage_timer > 1 then
self.env_damage_timer = 0
2020-04-09 14:02:26 +02:00
if do_env_damage ( self ) then return end
2017-05-16 04:48:28 +02:00
elseif self.state ~= " attack " then
2020-04-09 14:02:26 +02:00
if do_env_damage ( self ) then return end
2017-05-16 04:48:28 +02:00
end
-- find someone to attack
if self.type == " monster "
and damage_enabled
and self.state ~= " attack " then
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
local p , sp , dist
local player = nil
local type = nil
local obj = nil
local min_dist = self.view_range + 1
local min_player = nil
for _ , oir in ipairs ( minetest.get_objects_inside_radius ( s , self.view_range ) ) do
if oir : is_player ( ) then
player = oir
type = " player "
else
obj = oir : get_luaentity ( )
if obj then
player = obj.object
type = obj.type
end
end
if type == " player "
or type == " npc " then
2019-03-20 15:04:20 +01:00
s = self.object : get_pos ( )
p = player : get_pos ( )
2017-05-16 04:48:28 +02:00
sp = s
p.y = p.y + 1
sp.y = sp.y + 1 -- aim higher to make looking up hills more realistic
dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
if dist < self.view_range then
-- and self.in_fov(self,p) then
-- choose closest player to attack
if minetest.line_of_sight ( sp , p , 2 ) == true
and dist < min_dist then
min_dist = dist
min_player = player
end
end
end
end
-- attack player
if min_player then
self.do_attack ( self , min_player , min_dist )
end
end
-- npc, find closest monster to attack
local min_dist = self.view_range + 1
local min_player = nil
if self.type == " npc "
and self.attacks_monsters
and self.state ~= " attack " then
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
local obj = nil
for _ , oir in pairs ( minetest.get_objects_inside_radius ( s , self.view_range ) ) do
obj = oir : get_luaentity ( )
if obj
and obj.type == " monster " then
-- attack monster
2019-08-29 01:47:17 +02:00
local p = obj.object : get_pos ( )
local dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
2017-05-16 04:48:28 +02:00
if dist < min_dist then
min_dist = dist
min_player = obj.object
end
end
end
if min_player then
self.do_attack ( self , min_player , min_dist )
end
end
-- horny animal can mate for 40 seconds, afterwards horny animal cannot mate again for 200 seconds
if self.horny == true
and self.hornytimer < 240
and self.child == false then
self.hornytimer = self.hornytimer + 1
if self.hornytimer >= 240 then
self.hornytimer = 0
self.horny = false
end
end
-- if animal is child take 240 seconds before growing into adult
if self.child == true then
self.hornytimer = self.hornytimer + 1
if self.hornytimer > 240 then
self.child = false
self.hornytimer = 0
2021-08-08 14:48:48 +02:00
local cpos = self.object : get_pos ( )
cpos.y = cpos.y + ( ( self.base_colbox [ 5 ] - self.base_colbox [ 2 ] ) / 2 )
self.object : set_pos ( cpos )
2017-05-16 04:48:28 +02:00
self.object : set_properties (
{
textures = self.base_texture ,
mesh = self.base_mesh ,
visual_size = self.base_size ,
collisionbox = self.base_colbox ,
} )
end
end
-- if animal is horny, find another same animal who is horny and mate
if self.horny == true
and self.hornytimer <= 40 then
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
effect ( { x = pos.x , y = pos.y + 0.5 , z = pos.z } , 4 , " heart.png " )
local ents = minetest.get_objects_inside_radius ( pos , self.view_range )
local num = 0
local ent = nil
for i , obj in ipairs ( ents ) do
ent = obj : get_luaentity ( )
-- check for same animal with different colour
local canmate = false
if ent then
if ent.name == self.name then
canmate = true
else
local entname = string.split ( ent.name , " : " )
local selfname = string.split ( self.name , " : " )
if entname [ 1 ] == selfname [ 1 ] then
entname = string.split ( entname [ 2 ] , " _ " )
selfname = string.split ( selfname [ 2 ] , " _ " )
if entname [ 1 ] == selfname [ 1 ] then
canmate = true
end
end
end
end
if ent
and canmate == true
and ent.horny == true
and ent.hornytimer <= 40 then
num = num + 1
end
if num > 1 then
self.hornytimer = 41
ent.hornytimer = 41
2019-09-22 14:17:33 +02:00
local feeder
if self.last_feeder and self.last_feeder ~= " " and self.last_feeder == ent.last_feeder then
feeder = self.last_feeder
end
2017-05-16 04:48:28 +02:00
minetest.after (
7 ,
function ( dtime )
local mob = minetest.add_entity ( pos , self.name )
local ent2 = mob : get_luaentity ( )
2019-09-22 14:17:33 +02:00
if feeder then
local pfeeder = minetest.get_player_by_name ( feeder )
if pfeeder : is_player ( ) then
2019-09-24 00:24:15 +02:00
achievements.trigger_achievement ( pfeeder , " wonder_of_life " )
2019-09-22 14:17:33 +02:00
end
end
2017-05-16 04:48:28 +02:00
local textures = self.base_texture
if def.child_texture then
textures = def.child_texture [ 1 ]
end
mob : set_properties ( {
textures = textures ,
visual_size = {
x = self.base_size . x / 2 ,
y = self.base_size . y / 2
} ,
collisionbox = {
self.base_colbox [ 1 ] / 2 ,
self.base_colbox [ 2 ] / 2 ,
self.base_colbox [ 3 ] / 2 ,
self.base_colbox [ 4 ] / 2 ,
self.base_colbox [ 5 ] / 2 ,
self.base_colbox [ 6 ] / 2
} ,
} )
ent2.child = true
ent2.tamed = true
--ent2.following = ent -- follow mother
2019-09-22 14:17:33 +02:00
end , feeder )
2017-05-16 04:48:28 +02:00
num = 0
break
end
end
end
-- find player to follow
if ( self.follow ~= " " or self.order == " follow " ) and not self.following and self.state ~= " attack " then
local s , p , dist
for _ , player in pairs ( minetest.get_connected_players ( ) ) do
2019-03-20 15:04:20 +01:00
s = self.object : get_pos ( )
p = player : get_pos ( )
2017-05-16 04:48:28 +02:00
dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
if dist < self.view_range then
self.following = player
break
end
end
end
-- custom function (defined in mob lua file)
if self.do_custom then
self.do_custom ( self )
end
if self.type == " npc "
and self.order == " follow "
and self.state ~= " attack " then
-- npc stop following player if not owner
if self.following
and self.type == " npc "
and self.owner
and self.owner ~= self.following : get_player_name ( ) then
self.following = nil
end
else
-- stop following player if not holding specific item
if self.following
and self.following . is_player
and self.following : get_wielded_item ( ) : get_name ( ) ~= self.follow then
self.following = nil
end
end
-- follow player or mob
if self.following then
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
local p
if self.following . is_player
and self.following : is_player ( ) then
2019-03-20 15:04:20 +01:00
p = self.following : get_pos ( )
2017-05-16 04:48:28 +02:00
elseif self.following . object then
2019-03-20 15:04:20 +01:00
p = self.following . object : get_pos ( )
2017-05-16 04:48:28 +02:00
end
if p then
local dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
if dist > self.view_range then
self.following = nil
else
local vec = { x = p.x - s.x , y = p.y - s.y , z = p.z - s.z }
yaw = ( math.atan ( vec.z / vec.x ) + math.pi / 2 ) - self.rotate
if p.x > s.x then
yaw = yaw + math.pi
end
2019-09-14 09:37:09 +02:00
if yaw == yaw then -- NaN check
self.object : set_yaw ( yaw )
end
2017-05-16 04:48:28 +02:00
-- anyone but standing npc's can move along
if dist > 2 and self.order ~= " stand " then
2019-03-20 15:04:20 +01:00
if ( self.jump and self.get_velocity ( self ) <= 0.5 and self.object : get_velocity ( ) . y == 0 ) or ( self.object : get_velocity ( ) . y == 0 and self.jump_chance > 0 ) then
2017-05-16 04:48:28 +02:00
self.direction = {
x = math.sin ( yaw ) * - 1 ,
y = - 20 ,
z = math.cos ( yaw )
}
do_jump ( self )
end
self.set_velocity ( self , self.walk_velocity )
if self.walk_chance ~= 0 then
self : set_animation ( " walk " )
end
else
self.set_velocity ( self , 0 )
self : set_animation ( " stand " )
end
return
end
end
else
self : set_animation ( " stand " )
end
if self.state == " stand " then
-- randomly turn
if math.random ( 1 , 4 ) == 1 then
-- if there is a player nearby look at them
local lp = nil
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
if self.type == " npc " then
2019-03-20 15:04:20 +01:00
local o = minetest.get_objects_inside_radius ( self.object : get_pos ( ) , 3 )
2017-05-16 04:48:28 +02:00
local yaw = 0
for _ , o in ipairs ( o ) do
if o : is_player ( ) then
2019-03-20 15:04:20 +01:00
lp = o : get_pos ( )
2017-05-16 04:48:28 +02:00
break
end
end
end
if lp ~= nil then
local vec = { x = lp.x - s.x , y = lp.y - s.y , z = lp.z - s.z }
yaw = ( math.atan ( vec.z / vec.x ) + math.pi / 2 ) - self.rotate
if lp.x > s.x then
yaw = yaw + math.pi
end
else
2019-03-20 15:04:20 +01:00
yaw = self.object : get_yaw ( ) + ( ( math.random ( 0 , 360 ) - 180 ) / 180 * math.pi )
2017-05-16 04:48:28 +02:00
end
2019-09-14 09:37:09 +02:00
if yaw == yaw then -- NaN check
self.object : set_yaw ( yaw )
end
2017-05-16 04:48:28 +02:00
end
self.set_velocity ( self , 0 )
self.set_animation ( self , " stand " )
-- npc's ordered to stand stay standing
if self.type == " npc "
and self.order == " stand " then
self.set_velocity ( self , 0 )
self.state = " stand "
self : set_animation ( " stand " )
else
if self.walk_chance ~= 0 and math.random ( 1 , 100 ) <= self.walk_chance then
self.set_velocity ( self , self.walk_velocity )
self.state = " walk "
self.set_animation ( self , " walk " )
end
-- jumping mobs only
-- if self.jump and math.random(1, 100) <= self.jump_chance then
-- self.direction = {x = 0, y = 0, z = 0}
-- do_jump(self)
-- self.set_velocity(self, self.walk_velocity)
-- end
end
elseif self.state == " walk " then
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
local lp = minetest.find_node_near ( s , 1 , { " group:water " } )
-- water swimmers cannot move out of water
if self.fly and self.fly_in == " default:water_source " and not lp then
self.set_velocity ( self , 0 )
self.state = " flop " -- change to undefined state so nothing more happens
self : set_animation ( " stand " )
return
end
-- if water nearby then turn away
if lp then
local vec = { x = lp.x - s.x , y = lp.y - s.y , z = lp.z - s.z }
yaw = math.atan ( vec.z / vec.x ) + 3 * math.pi / 2 - self.rotate
2019-09-14 09:37:09 +02:00
if yaw == yaw then -- NaN check
if lp.x > s.x then
yaw = yaw + math.pi
end
self.object : set_yaw ( yaw )
2017-05-16 04:48:28 +02:00
end
-- otherwise randomly turn
elseif math.random ( 1 , 100 ) <= 30 then
2019-03-20 15:04:20 +01:00
self.object : set_yaw ( self.object : get_yaw ( ) + ( ( math.random ( 0 , 360 ) - 180 ) / 180 * math.pi ) )
2017-05-16 04:48:28 +02:00
end
2019-03-20 15:04:20 +01:00
if self.jump and self.get_velocity ( self ) <= 0.5 and self.object : get_velocity ( ) . y == 0 then
2017-05-16 04:48:28 +02:00
self.direction = {
x = math.sin ( yaw ) * - 1 ,
y = - 20 ,
z = math.cos ( yaw )
}
do_jump ( self )
end
self : set_animation ( " walk " )
self.set_velocity ( self , self.walk_velocity )
if math.random ( 1 , 100 ) <= 30 then
self.set_velocity ( self , 0 )
self.state = " stand "
self : set_animation ( " stand " )
end
-- exploding mobs
elseif self.state == " attack " and self.attack_type == " explode " then
if not self.attack . player
or not self.attack . player : is_player ( ) then
self.state = " stand "
self : set_animation ( " stand " )
self.timer = 0
self.blinktimer = 0
return
end
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
local p = self.attack . player : get_pos ( )
2017-05-16 04:48:28 +02:00
local dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
if dist > self.view_range or self.attack . player : get_hp ( ) <= 0 then
self.state = " stand "
self.v_start = false
self.set_velocity ( self , 0 )
self.timer = 0
self.blinktimer = 0
self.attack = { player = nil , dist = nil }
self : set_animation ( " stand " )
return
else
self : set_animation ( " walk " )
self.attack . dist = dist
end
local vec = { x = p.x - s.x , y = p.y - s.y , z = p.z - s.z }
yaw = math.atan ( vec.z / vec.x ) + math.pi / 2 - self.rotate
if p.x > s.x then
yaw = yaw + math.pi
end
2019-09-14 09:37:09 +02:00
if yaw == yaw then -- NaN check
self.object : set_yaw ( yaw )
end
2017-05-16 04:48:28 +02:00
if self.attack . dist > 3 then
if not self.v_start then
self.v_start = true
self.set_velocity ( self , self.run_velocity )
self.timer = 0
self.blinktimer = 0
else
self.timer = 0
self.blinktimer = 0
2019-03-20 15:04:20 +01:00
if self.get_velocity ( self ) <= 0.5 and self.object : get_velocity ( ) . y == 0 then
local v = self.object : get_velocity ( )
2017-05-16 04:48:28 +02:00
v.y = 5
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( v )
2017-05-16 04:48:28 +02:00
end
self.set_velocity ( self , self.run_velocity )
end
self : set_animation ( " run " )
else
self.set_velocity ( self , 0 )
self.timer = self.timer + dtime
self.blinktimer = ( self.blinktimer or 0 ) + dtime
if self.blinktimer > 0.2 then
self.blinktimer = 0
if self.blinkstatus then
2021-02-24 03:22:05 +01:00
self.object : set_texture_mod ( " " )
2017-05-16 04:48:28 +02:00
else
2021-02-24 03:22:05 +01:00
self.object : set_texture_mod ( " ^[brighten " )
2017-05-16 04:48:28 +02:00
end
self.blinkstatus = not self.blinkstatus
end
if self.timer > 3 then
2019-03-20 15:04:20 +01:00
local pos = vector.round ( self.object : get_pos ( ) )
2017-05-16 04:48:28 +02:00
entity_physics ( pos , 3 ) -- hurt player/mobs caught in blast area
if minetest.find_node_near ( pos , 1 , { " group:water " } ) or minetest.is_protected ( pos , " " ) then
self.object : remove ( )
if self.sounds . explode ~= " " then
minetest.sound_play ( self.sounds . explode , {
pos = pos ,
gain = 1.0 ,
max_hear_distance = 16
2020-04-07 01:00:55 +02:00
} , true )
2017-05-16 04:48:28 +02:00
end
effect ( pos , 15 , " tnt_smoke.png " , 5 )
2020-04-09 13:20:01 +02:00
minetest.log ( " action " , " [mobs] Mob " .. self.name .. " exploded at " .. minetest.pos_to_string ( vector.round ( pos ) ) )
2017-05-16 04:48:28 +02:00
return
end
self.object : remove ( )
2020-04-09 13:20:01 +02:00
minetest.log ( " action " , " [mobs] Mob " .. self.name .. " exploded at " .. minetest.pos_to_string ( vector.round ( pos ) ) )
2017-05-16 04:48:28 +02:00
pos.y = pos.y - 1
tnt.explode ( pos , self.explode_radius , self.sounds . explode )
2020-04-07 14:57:19 +02:00
return
2017-05-16 04:48:28 +02:00
end
end
-- end of exploding mobs
elseif self.state == " attack "
and self.attack_type == " dogfight " then
2019-03-20 15:04:20 +01:00
if not self.attack . player or not self.attack . player : get_pos ( ) then
2017-05-16 04:48:28 +02:00
self.state = " stand "
self : set_animation ( " stand " )
return
end
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
local p = self.attack . player : get_pos ( )
2017-05-16 04:48:28 +02:00
local dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
-- fly bit modified from BlockMens creatures mod
if self.fly
and dist > 2 then
local nod = minetest.get_node_or_nil ( s )
local p1 = s
local me_y = math.floor ( p1.y )
local p2 = p
local p_y = math.floor ( p2.y + 1 )
2019-03-20 15:04:20 +01:00
local v = self.object : get_velocity ( )
2017-05-16 04:48:28 +02:00
if nod
and nod.name == self.fly_in then
if me_y < p_y then
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( {
2017-05-16 04:48:28 +02:00
x = v.x ,
y = 1 * self.walk_velocity ,
z = v.z
} )
elseif me_y > p_y then
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( {
2017-05-16 04:48:28 +02:00
x = v.x ,
y = - 1 * self.walk_velocity ,
z = v.z
} )
end
else
if me_y < p_y then
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( {
2017-05-16 04:48:28 +02:00
x = v.x ,
y = 0.01 ,
z = v.z
} )
elseif me_y > p_y then
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( {
2017-05-16 04:48:28 +02:00
x = v.x ,
y = - 0.01 ,
z = v.z
} )
end
end
end
-- end fly bit
if dist > self.view_range or self.attack . player : get_hp ( ) <= 0 then
self.state = " stand "
self.set_velocity ( self , 0 )
self.attack = { player = nil , dist = nil }
self : set_animation ( " stand " )
return
else
self.attack . dist = dist
end
local vec = { x = p.x - s.x , y = p.y - s.y , z = p.z - s.z }
yaw = ( math.atan ( vec.z / vec.x ) + math.pi / 2 ) - self.rotate
if p.x > s.x then
yaw = yaw + math.pi
end
2019-09-14 09:37:09 +02:00
if yaw == yaw then -- NaN check
self.object : set_yaw ( yaw )
end
2017-05-16 04:48:28 +02:00
-- attack distance is 2 + half of mob width so the bigger mobs can attack (like slimes)
if self.attack . dist > ( ( - self.collisionbox [ 1 ] + self.collisionbox [ 4 ] ) / 2 ) + 2 then
-- jump attack
2019-03-20 15:04:20 +01:00
if ( self.jump and self.get_velocity ( self ) <= 0.5 and self.object : get_velocity ( ) . y == 0 ) or ( self.object : get_velocity ( ) . y == 0 and self.jump_chance > 0 ) then
2017-05-16 04:48:28 +02:00
self.direction = {
x = math.sin ( yaw ) * - 1 ,
y = - 20 ,
z = math.cos ( yaw )
}
do_jump ( self )
end
self.set_velocity ( self , self.run_velocity )
self : set_animation ( " run " )
else
self.set_velocity ( self , 0 )
self : set_animation ( " punch " )
if self.timer > 1 then
self.timer = 0
local p2 = p
local s2 = s
p2.y = p2.y + 1.5
s2.y = s2.y + 1.5
if minetest.line_of_sight ( p2 , s2 ) == true then
if self.sounds . attack then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . attack )
2017-05-16 04:48:28 +02:00
end
self.attack . player : punch (
self.object ,
1.0 ,
{
full_punch_interval = 1.0 ,
damage_groups = { fleshy = self.damage }
} , vec )
if self.attack . player : get_hp ( ) <= 0 then
self.state = " stand "
self : set_animation ( " stand " )
end
end
end
end
elseif self.state == " attack "
and self.attack_type == " shoot " then
2019-03-20 15:04:20 +01:00
local s = self.object : get_pos ( )
local p = self.attack . player : get_pos ( )
2017-05-16 04:48:28 +02:00
if not p then
self.state = " stand "
return
end
p.y = p.y - .5
s.y = s.y + .5
local dist = ( ( p.x - s.x ) ^ 2 + ( p.y - s.y ) ^ 2 + ( p.z - s.z ) ^ 2 ) ^ 0.5
if dist > self.view_range or self.attack . player : get_hp ( ) <= 0 then
self.state = " stand "
self.set_velocity ( self , 0 )
self : set_animation ( " stand " )
return
else
self.attack . dist = dist
end
local vec = { x = p.x - s.x , y = p.y - s.y , z = p.z - s.z }
yaw = ( math.atan ( vec.z / vec.x ) + math.pi / 2 ) - self.rotate
if p.x > s.x then
yaw = yaw + math.pi
end
2019-09-14 09:37:09 +02:00
if yaw == yaw then -- NaN check
self.object : set_yaw ( yaw )
end
2017-05-16 04:48:28 +02:00
self.set_velocity ( self , 0 )
if self.shoot_interval and self.timer > self.shoot_interval and math.random ( 1 , 100 ) <= 60 then
self.timer = 0
self : set_animation ( " punch " )
if self.sounds . attack then
2019-09-22 13:22:39 +02:00
mob_sound ( self , self.sounds . attack )
2017-05-16 04:48:28 +02:00
end
2019-03-20 15:04:20 +01:00
local p = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
p.y = p.y + ( self.collisionbox [ 2 ] + self.collisionbox [ 5 ] ) / 2
local obj = minetest.add_entity ( p , self.arrow )
local amount = ( vec.x ^ 2 + vec.y ^ 2 + vec.z ^ 2 ) ^ 0.5
local v = obj : get_luaentity ( ) . velocity
vec.y = vec.y + self.shoot_offset -- this makes shoot aim accurate
vec.x = vec.x * v / amount
vec.y = vec.y * v / amount
vec.z = vec.z * v / amount
2019-03-20 15:04:20 +01:00
obj : set_velocity ( vec )
2017-05-16 04:48:28 +02:00
end
end
end ,
on_activate = function ( self , staticdata , dtime_s )
if self.type == " monster "
and peaceful_only then
2019-09-03 21:07:55 +02:00
minetest.log ( " action " , " [mobs] Hostile mob " .. self.name .. " removed at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
2017-05-16 04:48:28 +02:00
self.object : remove ( )
2020-04-07 14:57:19 +02:00
return
2017-05-16 04:48:28 +02:00
end
-- load entity variables
if staticdata then
local tmp = minetest.deserialize ( staticdata )
if tmp then
for _ , stat in pairs ( tmp ) do
self [ _ ] = stat
end
end
end
-- select random texture, set model and size
if not self.base_texture then
self.base_texture = def.textures [ math.random ( 1 , # def.textures ) ]
self.base_mesh = def.mesh
self.base_size = self.visual_size
self.base_colbox = self.collisionbox
end
-- set texture, model and size
local textures = self.base_texture
local mesh = self.base_mesh
local vis_size = self.base_size
local colbox = self.base_colbox
-- specific texture if gotten
if self.gotten == true
and def.gotten_texture then
textures = def.gotten_texture
end
-- specific mesh if gotten
if self.gotten == true
and def.gotten_mesh then
mesh = def.gotten_mesh
end
-- if object is child then set half size
if self.child == true then
vis_size = {
x = self.base_size . x / 2 ,
y = self.base_size . y / 2
}
if def.child_texture then
textures = def.child_texture [ 1 ]
end
colbox = {
self.base_colbox [ 1 ] / 2 ,
self.base_colbox [ 2 ] / 2 ,
self.base_colbox [ 3 ] / 2 ,
self.base_colbox [ 4 ] / 2 ,
self.base_colbox [ 5 ] / 2 ,
self.base_colbox [ 6 ] / 2
}
end
if self.health == 0 then
self.health = math.random ( self.hp_min , self.hp_max )
end
self.object : set_hp ( self.health )
self.object : set_armor_groups ( { fleshy = self.armor } )
self.state = " stand "
self.order = " stand "
self.following = nil
2019-03-20 15:04:20 +01:00
self.old_y = self.object : get_pos ( ) . y
self.object : set_yaw ( math.random ( 1 , 360 ) / 180 * math.pi )
2017-05-16 04:48:28 +02:00
self.sounds . distance = ( self.sounds . distance or 10 )
self.textures = textures
self.mesh = mesh
self.collisionbox = colbox
self.visual_size = vis_size
-- set anything changed above
self.object : set_properties ( self )
if self.on_spawn then
self : on_spawn ( )
end
end ,
get_staticdata = function ( self )
-- remove mob when out of range unless tamed
if mobs.remove == true and self.remove_ok and not self.tamed then
2019-08-28 17:54:05 +02:00
minetest.log ( " action " , " [mobs] Mob removed (out of range): " .. tostring ( self.remove_ok ) .. " " .. self.name )
2017-05-16 04:48:28 +02:00
self.object : remove ( )
2020-04-07 14:57:19 +02:00
return
2017-05-16 04:48:28 +02:00
end
self.remove_ok = true
self.attack = nil
local tmp = { }
for _ , stat in pairs ( self ) do
local t = type ( stat )
if t ~= ' function '
and t ~= ' nil '
and t ~= ' userdata ' then
tmp [ _ ] = self [ _ ]
end
end
return minetest.serialize ( tmp )
end ,
2019-09-22 13:55:08 +02:00
on_punch = function ( self , hitter , tflp , tool_capabilities , dir , damage )
2017-05-16 04:48:28 +02:00
-- weapon wear
local weapon = hitter : get_wielded_item ( )
if weapon : get_definition ( ) . tool_capabilities ~= nil then
local wear = ( ( weapon : get_definition ( ) . tool_capabilities.full_punch_interval or 1.4 ) / 75 ) * 9000
2019-08-31 00:07:26 +02:00
if not minetest.settings : get_bool ( " creative_mode " ) then
weapon : add_wear ( wear )
end
2017-05-16 04:48:28 +02:00
hitter : set_wielded_item ( weapon )
end
2019-09-21 17:22:41 +02:00
-- punch sounds
minetest.sound_play ( " default_punch " , {
object = hitter ,
max_hear_distance = 5
2020-04-07 01:00:55 +02:00
} , true )
2017-05-16 04:48:28 +02:00
-- exit here if dead
2019-09-22 13:55:08 +02:00
if check_for_death ( self , hitter , damage ) then
2017-05-16 04:48:28 +02:00
return
end
-- blood_particles
if self.blood_amount > 0 then
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
2017-05-16 04:48:28 +02:00
pos.y = pos.y + ( - self.collisionbox [ 2 ] + self.collisionbox [ 5 ] ) / 3
if enable_blood then
effect ( pos , self.blood_amount , self.blood_texture )
else
2019-09-24 01:19:21 +02:00
effect ( pos , self.blood_amount , " mobs_damage.png " )
2017-05-16 04:48:28 +02:00
end
end
-- knock back effect
if self.knock_back > 0 then
local kb = self.knock_back
local r = self.recovery_time
2019-03-20 15:04:20 +01:00
local v = self.object : get_velocity ( )
2017-05-16 04:48:28 +02:00
if tflp < tool_capabilities.full_punch_interval then
if kb > 0 then
kb = kb * ( tflp / tool_capabilities.full_punch_interval )
end
r = r * ( tflp / tool_capabilities.full_punch_interval )
end
2019-03-20 15:04:20 +01:00
self.object : set_velocity ( { x = dir.x * kb , y = 0 , z = dir.z * kb } )
2017-05-16 04:48:28 +02:00
self.pause_timer = r
end
-- attack puncher and call other mobs for help
if self.passive == false
and not self.tamed then
if self.state ~= " attack " then
self.do_attack ( self , hitter , 1 )
end
-- alert others to the attack
local obj = nil
2019-03-20 15:04:20 +01:00
for _ , oir in pairs ( minetest.get_objects_inside_radius ( hitter : get_pos ( ) , 5 ) ) do
2017-05-16 04:48:28 +02:00
obj = oir : get_luaentity ( )
if obj then
if obj.group_attack == true
and obj.state ~= " attack " then
obj.do_attack ( obj , hitter , 1 )
end
end
end
end
end ,
2019-09-03 19:56:47 +02:00
on_death = function ( self , killer )
die_handler ( self , killer )
end ,
2017-05-16 04:48:28 +02:00
} )
2015-09-01 17:15:24 +02:00
end
mobs.spawning_mobs = { }
function mobs : spawn_specific ( name , nodes , neighbors , min_light , max_light , interval , chance , active_object_count , min_height , max_height )
mobs.spawning_mobs [ name ] = true
2017-05-12 04:29:55 +02:00
minetest.register_abm (
2015-09-01 17:15:24 +02:00
{
2017-05-19 23:04:11 +02:00
label = " Mob spawn ( " .. name .. " ) " ,
2017-05-16 04:48:28 +02:00
nodenames = nodes ,
neighbors = neighbors ,
interval = interval ,
chance = chance ,
action = function ( pos , node , _ , active_object_count_wider )
-- do not spawn if too many active entities in area
if active_object_count_wider > active_object_count or not mobs.spawning_mobs [ name ] then
--or not pos then
return
end
-- spawn above node
pos.y = pos.y + 1
-- mobs cannot spawn inside protected areas if enabled
if mobs.protected == 1
and minetest.is_protected ( pos , " " ) then
return
end
-- check if light and height levels are ok to spawn
local light = minetest.get_node_light ( pos )
if not light
or light > max_light
or light < min_light
or pos.y > max_height
or pos.y < min_height then
return
end
-- are we spawning inside a solid node?
local nod = minetest.get_node_or_nil ( pos )
if not nod
or not nod.name
or not minetest.registered_nodes [ nod.name ]
or minetest.registered_nodes [ nod.name ] . walkable == true then
return
end
pos.y = pos.y + 1
nod = minetest.get_node_or_nil ( pos )
if not nod
or not nod.name
or not minetest.registered_nodes [ nod.name ]
or minetest.registered_nodes [ nod.name ] . walkable == true then
return
end
2017-06-27 21:13:30 +02:00
if minetest.settings : get_bool ( " display_mob_spawn " ) then
2019-08-28 17:31:41 +02:00
minetest.chat_send_all ( " [mobs] " .. S ( " Spawned @1 at @2 " , name , minetest.pos_to_string ( pos ) ) )
2017-05-16 04:48:28 +02:00
end
-- spawn mob half block higher
pos.y = pos.y - 0.5
minetest.add_entity ( pos , name )
2019-08-28 17:54:05 +02:00
minetest.log ( " action " , " [mobs] Spawned " .. name .. " at " .. minetest.pos_to_string ( pos ) .. " on " .. node.name .. " near " .. neighbors [ 1 ] )
2017-05-16 04:48:28 +02:00
end
} )
2015-09-01 17:15:24 +02:00
end
-- compatibility with older mob registration
function mobs : register_spawn ( name , nodes , max_light , min_light , chance , active_object_count , max_height )
mobs : spawn_specific ( name , nodes , { " air " } , min_light , max_light , 30 , chance , active_object_count , - 31000 , max_height )
end
-- register arrow for shoot attack
function mobs : register_arrow ( name , def )
if not name or not def then return end -- errorcheck
2017-05-12 04:29:55 +02:00
minetest.register_entity (
2015-09-01 17:15:24 +02:00
name ,
{
2017-05-16 04:48:28 +02:00
physical = false ,
visual = def.visual ,
visual_size = def.visual_size ,
textures = def.textures ,
velocity = def.velocity ,
hit_player = def.hit_player ,
hit_node = def.hit_node ,
hit_mob = def.hit_mob ,
drop = def.drop or false ,
collisionbox = { 0 , 0 , 0 , 0 , 0 , 0 } , -- remove box around arrows
on_step = function ( self , dtime )
self.timer = ( self.timer or 0 ) + 1
2020-04-09 13:20:01 +02:00
if self.timer > 150 then
minetest.log ( " info " , " [mobs] Arrow " .. self.name .. " removed due to timeout at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
self.object : remove ( )
return
end
2017-05-16 04:48:28 +02:00
local engage = 10 - ( self.velocity / 2 ) -- clear entity before arrow becomes active
2019-03-20 15:04:20 +01:00
local pos = self.object : get_pos ( )
local node = minetest.get_node_or_nil ( self.object : get_pos ( ) )
2017-05-16 04:48:28 +02:00
if node then node = node.name else node = " air " end
if self.hit_node
and minetest.registered_nodes [ node ]
and minetest.registered_nodes [ node ] . walkable then
self.hit_node ( self , pos , node )
if self.drop == true then
pos.y = pos.y + 1
self.lastpos = ( self.lastpos or pos )
minetest.add_item ( self.lastpos , self.object : get_luaentity ( ) . name )
end
2020-04-09 13:20:01 +02:00
minetest.log ( " info " , " [mobs] Arrow " .. self.name .. " hit node at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
2019-08-28 17:54:05 +02:00
self.object : remove ( )
2017-05-16 04:48:28 +02:00
return
end
if ( self.hit_player or self.hit_mob )
and self.timer > engage then
for _ , player in pairs ( minetest.get_objects_inside_radius ( pos , 1.0 ) ) do
if self.hit_player
and player : is_player ( ) then
self.hit_player ( self , player )
2020-04-09 13:20:01 +02:00
minetest.log ( " info " , " [mobs] Arrow " .. self.name .. " hit player at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
2019-08-28 17:54:05 +02:00
self.object : remove ( )
2017-05-16 04:48:28 +02:00
return
end
if self.hit_mob
and player : get_luaentity ( ) . name ~= self.object : get_luaentity ( ) . name
and player : get_luaentity ( ) . name ~= " __builtin:item " then
self.hit_mob ( self , player )
2020-04-09 13:20:01 +02:00
minetest.log ( " info " , " [mobs] Arrow " .. self.name .. " hit mob at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
2019-08-28 17:54:05 +02:00
self.object : remove ( )
2017-05-16 04:48:28 +02:00
return
end
end
end
self.lastpos = pos
end
} )
2015-09-01 17:15:24 +02:00
end
-- Spawn Egg
function mobs : register_egg ( mob , desc , background )
local invimg = background
2019-09-03 21:39:29 +02:00
local place
if minetest.registered_entities [ mob ] . type == " monster " and peaceful_only then
desc = S ( " @1 (disabled) " , desc )
invimg = invimg .. " ^[multiply:#FF0000 "
end
2017-05-12 04:29:55 +02:00
minetest.register_craftitem (
2015-09-01 17:15:24 +02:00
mob ,
{
2017-05-16 04:48:28 +02:00
description = desc ,
inventory_image = invimg ,
2019-08-31 14:06:32 +02:00
groups = { spawn_egg = 1 } ,
2017-05-16 04:48:28 +02:00
on_place = function ( itemstack , placer , pointed_thing )
2020-03-10 08:49:22 +01:00
if peaceful_only and minetest.registered_entities [ mob ] . type == " monster " then
2019-09-03 21:39:29 +02:00
minetest.chat_send_player ( placer : get_player_name ( ) , minetest.colorize ( " #FFFF00 " , S ( " Hostile mobs are disabled! " ) ) )
return itemstack
end
2017-05-16 04:48:28 +02:00
local pos = pointed_thing.above
2019-09-25 21:54:22 +02:00
if pointed_thing.above then
if minetest.is_protected ( pos , placer : get_player_name ( ) ) and
not minetest.check_player_privs ( placer , " protection_bypass " ) then
minetest.record_protection_violation ( pos , placer : get_player_name ( ) )
return itemstack
end
2017-05-16 04:48:28 +02:00
pos.y = pos.y + 0.5
local mob = minetest.add_entity ( pos , mob )
local ent = mob : get_luaentity ( )
if ent.type ~= " monster " then
-- set owner
ent.owner = placer : get_player_name ( )
ent.tamed = true
end
2019-08-31 00:07:26 +02:00
if not minetest.settings : get_bool ( " creative_mode " ) then
itemstack : take_item ( )
end
2017-05-16 04:48:28 +02:00
end
return itemstack
end ,
} )
2015-09-01 17:15:24 +02:00
end
2017-05-16 04:48:28 +02:00
-- Capture critter (thanks to blert2112 for idea)
function mobs : capture_mob ( self , clicker , chance_hand , chance_net , chance_lasso ,
force_take , replacewith )
2015-09-01 17:15:24 +02:00
if clicker : is_player ( ) and clicker : get_inventory ( ) and not self.child then
local mobname = self.name
2017-05-16 04:48:28 +02:00
-- If not nil change what will be added to inventory
2015-09-01 17:15:24 +02:00
if replacewith then
2017-05-16 04:48:28 +02:00
mobname = replacewith
2015-09-01 17:15:24 +02:00
end
local name = clicker : get_player_name ( )
2017-05-16 04:48:28 +02:00
-- Is mob tamed?
2015-09-01 17:15:24 +02:00
if self.tamed == false and force_take == false then
2019-08-29 23:24:35 +02:00
minetest.chat_send_player ( name , minetest.colorize ( " #FFFF00 " , S ( " Not tamed! " ) ) )
2017-05-16 04:48:28 +02:00
return
2015-09-01 17:15:24 +02:00
end
2017-05-16 04:48:28 +02:00
-- Cannot pick up if not owner
2020-12-06 01:58:30 +01:00
if self.owner ~= " " and self.owner ~= nil and self.owner ~= name and force_take == false then
2019-08-29 23:24:35 +02:00
minetest.chat_send_player ( name , minetest.colorize ( " #FFFF00 " , S ( " @1 is owner! " , self.owner ) ) )
2017-05-16 04:48:28 +02:00
return
2015-09-01 17:15:24 +02:00
end
if clicker : get_inventory ( ) : room_for_item ( " main " , mobname ) then
2017-05-16 04:48:28 +02:00
-- Was mob clicked with hand, net, or lasso?
local tool = clicker : get_wielded_item ( )
local chance = 0
if tool : is_empty ( ) then
2015-09-01 17:15:24 +02:00
chance = chance_hand
2017-05-16 04:48:28 +02:00
elseif tool : get_name ( ) == " mobs:net " then
2015-09-01 17:15:24 +02:00
chance = chance_net
2019-08-31 00:07:26 +02:00
if not minetest.settings : get_bool ( " creative_mode " ) then
tool : add_wear ( 4000 ) -- 17 uses
end
2015-09-01 17:15:24 +02:00
clicker : set_wielded_item ( tool )
2017-05-16 04:48:28 +02:00
elseif tool : get_name ( ) == " mobs:lasso " then
2015-09-01 17:15:24 +02:00
chance = chance_lasso
2019-08-31 00:07:26 +02:00
if not minetest.settings : get_bool ( " creative_mode " ) then
tool : add_wear ( 1500 ) -- 43 uses
end
2015-09-01 17:15:24 +02:00
clicker : set_wielded_item ( tool )
2017-05-16 04:48:28 +02:00
end
-- Return if no chance
if chance == 0 then return end
-- Calculate chance.. was capture successful?
if math.random ( 100 ) <= chance then
-- Cuccessful capture.. add to inventory
2015-09-01 17:15:24 +02:00
clicker : get_inventory ( ) : add_item ( " main " , mobname )
2017-05-16 04:48:28 +02:00
2020-04-09 13:20:01 +02:00
minetest.log ( " action " , " [mobs] Mob " .. self.name .. " captured at " .. minetest.pos_to_string ( vector.round ( self.object : get_pos ( ) ) ) )
2021-03-09 02:06:36 +01:00
self.object : remove ( )
2017-05-16 04:48:28 +02:00
achievements.trigger_achievement ( clicker , " ranger " )
2020-04-07 14:57:19 +02:00
return
2017-05-16 04:48:28 +02:00
else
2019-08-29 23:24:35 +02:00
minetest.chat_send_player ( name , minetest.colorize ( " #FFFF00 " , S ( " Missed! " ) ) )
2017-05-16 04:48:28 +02:00
end
2015-09-01 17:15:24 +02:00
end
end
end
2017-05-16 04:48:28 +02:00
-- Feeding, taming and breeding (thanks blert2112)
2021-08-08 15:07:10 +02:00
function mobs : feed_tame ( self , clicker , feed_count , breed , effect )
2017-05-16 04:48:28 +02:00
-- Feed a mob(mostly a wrapper to handle self.check_fed)
2015-09-01 17:15:24 +02:00
local item = nil
local itemstring = clicker
local name = nil
if type ( clicker ) ~= " string " then
item = clicker : get_wielded_item ( )
2017-05-16 04:48:28 +02:00
2015-09-01 17:15:24 +02:00
itemstring = item : get_name ( )
2017-05-16 04:48:28 +02:00
2015-09-01 17:15:24 +02:00
name = clicker : get_player_name ( )
end
if itemstring == self.follow then
if name ~= nil then
2017-05-16 04:48:28 +02:00
-- Take item
2017-06-27 21:13:30 +02:00
if not minetest.settings : get_bool ( " creative_mode " ) then
2015-09-01 17:15:24 +02:00
item : take_item ( )
2017-05-16 04:48:28 +02:00
2015-09-01 17:15:24 +02:00
clicker : set_wielded_item ( item )
2017-05-16 04:48:28 +02:00
end
2015-09-01 17:15:24 +02:00
end
2021-08-08 14:38:33 +02:00
2021-08-08 15:07:10 +02:00
if effect ~= false then
mob_sound ( self , self.sounds . eat , true )
local mobpos = self.object : get_pos ( )
minetest.add_particlespawner (
{
amount = 10 ,
time = 0.1 ,
minpos = { x = mobpos.x - 0.1 , y = mobpos.y - 0.1 , z = mobpos.z - 0.1 } ,
maxpos = { x = mobpos.x + 0.1 , y = mobpos.y + 0.1 , z = mobpos.z + 0.1 } ,
minvel = { x = - 1 , y = - 1 , z = - 1 } ,
maxvel = { x = 1 , y = 0 , z = 1 } ,
minacc = { x = 0 , y = 6 , z = 0 } ,
maxacc = { x = 0 , y = 1 , z = 0 } ,
minexptime = 0.5 ,
maxexptime = 1 ,
minsize = 0.5 ,
maxsize = 2 ,
texture = " magicpuff.png "
} )
end
2015-09-01 17:15:24 +02:00
self : check_fed ( name , feed_count , breed )
return true
else
return false
end
2015-11-05 10:39:36 +01:00
end