Fix breeding giving weird childs
[MineClone/MineClone2.git] / mods / ENTITIES / mobs / api.lua
blob95ef5d471ed18f9d86da3d452d93f420d31d68f1
2 -- Mobs Api
4 mobs = {}
5 mobs.mod = "redo"
6 mobs.version = "20180523"
8 local MAX_MOB_NAME_LENGTH = 30
10 -- Intllib
11 local MP = minetest.get_modpath(minetest.get_current_modname())
12 local S, NS = dofile(MP .. "/intllib.lua")
13 mobs.intllib = S
16 -- CMI support check
17 local use_cmi = minetest.global_exists("cmi")
20 -- Invisibility mod check
21 mobs.invis = {}
22 if minetest.global_exists("invisibility") then
23 mobs.invis = invisibility
24 end
27 -- creative check
28 local creative_mode_cache = minetest.settings:get_bool("creative_mode")
29 function mobs.is_creative(name)
30 return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
31 end
34 -- localize math functions
35 local pi = math.pi
36 local square = math.sqrt
37 local sin = math.sin
38 local cos = math.cos
39 local abs = math.abs
40 local min = math.min
41 local max = math.max
42 local atann = math.atan
43 local random = math.random
44 local floor = math.floor
45 local atan = function(x)
46 if not x or x ~= x then
47 --error("atan bassed NaN")
48 return 0
49 else
50 return atann(x)
51 end
52 end
55 -- Load settings
56 local damage_enabled = minetest.settings:get_bool("enable_damage")
57 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
58 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
59 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
60 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
61 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
62 local creative = minetest.settings:get_bool("creative_mode")
63 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
64 local remove_far = false
65 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
66 local show_health = false
67 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
68 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
70 -- Peaceful mode message so players will know there are no monsters
71 if peaceful_only then
72 minetest.register_on_joinplayer(function(player)
73 minetest.chat_send_player(player:get_player_name(),
74 S("** Peaceful Mode Active - No Monsters Will Spawn"))
75 end)
76 end
78 -- calculate aoc range for mob count
79 local aosrb = tonumber(minetest.settings:get("active_object_send_range_blocks"))
80 local abr = tonumber(minetest.settings:get("active_block_range"))
81 local aoc_range = max(aosrb, abr) * 16
83 -- pathfinding settings
84 local enable_pathfinding = true
85 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
86 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
88 -- default nodes
89 local node_fire = "mcl_fire:fire"
90 local node_permanent_flame = "mcl_fire:eternal_fire"
91 local node_ice = "mcl_core:ice"
92 local node_snowblock = "mcl_core:snowblock"
93 local node_snow = "mcl_core:snow"
94 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "mcl_core:dirt"
96 local mod_weather = minetest.get_modpath("mcl_weather") ~= nil
97 local mod_tnt = minetest.get_modpath("mcl_tnt") ~= nil
98 local mod_mobspawners = minetest.get_modpath("mcl_mobspawners") ~= nil
100 -- play sound
101 local mob_sound = function(self, sound)
103 if sound then
104 minetest.sound_play(sound, {
105 object = self.object,
106 gain = 1.0,
107 max_hear_distance = self.sounds.distance
113 -- attack player/mob
114 local do_attack = function(self, player)
116 if self.state == "attack" then
117 return
120 self.attack = player
121 self.state = "attack"
123 if random(0, 100) < 90 then
124 mob_sound(self, self.sounds.war_cry)
129 -- move mob in facing direction
130 local set_velocity = function(self, v)
132 -- do not move if mob has been ordered to stay
133 if self.order == "stand" then
134 self.object:setvelocity({x = 0, y = 0, z = 0})
135 return
138 local yaw = (self.object:get_yaw() or 0) + self.rotate
140 self.object:setvelocity({
141 x = sin(yaw) * -v,
142 y = self.object:getvelocity().y,
143 z = cos(yaw) * v
148 -- calculate mob velocity
149 local get_velocity = function(self)
151 local v = self.object:getvelocity()
153 return (v.x * v.x + v.z * v.z) ^ 0.5
157 -- set and return valid yaw
158 local set_yaw = function(self, yaw, delay)
160 if not yaw or yaw ~= yaw then
161 yaw = 0
164 delay = delay or 0
166 if delay == 0 then
167 self.object:set_yaw(yaw)
168 return yaw
171 self.target_yaw = yaw
172 self.delay = delay
174 return self.target_yaw
177 -- global function to set mob yaw
178 function mobs:yaw(self, yaw, delay)
179 set_yaw(self, yaw, delay)
183 -- set defined animation
184 local set_animation = function(self, anim)
186 if not self.animation
187 or not anim then return end
189 self.animation.current = self.animation.current or ""
191 if anim == self.animation.current
192 or not self.animation[anim .. "_start"]
193 or not self.animation[anim .. "_end"] then
194 return
197 self.animation.current = anim
199 self.object:set_animation({
200 x = self.animation[anim .. "_start"],
201 y = self.animation[anim .. "_end"]},
202 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
203 0, self.animation[anim .. "_loop"] ~= false)
207 -- above function exported for mount.lua
208 function mobs:set_animation(self, anim)
209 set_animation(self, anim)
213 -- calculate distance
214 local get_distance = function(a, b)
216 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
218 return square(x * x + y * y + z * z)
222 -- check line of sight (BrunoMine)
223 local line_of_sight = function(self, pos1, pos2, stepsize)
225 stepsize = stepsize or 1
227 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
229 -- normal walking and flying mobs can see you through air
230 if s == true then
231 return true
234 -- New pos1 to be analyzed
235 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
237 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
239 -- Checks the return
240 if r == true then return true end
242 -- Nodename found
243 local nn = minetest.get_node(pos).name
245 -- Target Distance (td) to travel
246 local td = get_distance(pos1, pos2)
248 -- Actual Distance (ad) traveled
249 local ad = 0
251 -- It continues to advance in the line of sight in search of a real
252 -- obstruction which counts as 'normal' nodebox.
253 while minetest.registered_nodes[nn]
254 and (minetest.registered_nodes[nn].walkable == false
255 or minetest.registered_nodes[nn].drawtype == "nodebox") do
257 -- Check if you can still move forward
258 if td < ad + stepsize then
259 return true -- Reached the target
262 -- Moves the analyzed pos
263 local d = get_distance(pos1, pos2)
265 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
266 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
267 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
269 -- NaN checks
270 if d == 0
271 or npos1.x ~= npos1.x
272 or npos1.y ~= npos1.y
273 or npos1.z ~= npos1.z then
274 return false
277 ad = ad + stepsize
279 -- scan again
280 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
282 if r == true then return true end
284 -- New Nodename found
285 nn = minetest.get_node(pos).name
289 return false
293 -- are we flying in what we are suppose to? (taikedz)
294 local flight_check = function(self, pos_w)
296 local nod = self.standing_in
297 local def = minetest.registered_nodes[nod]
299 if not def then return false end -- nil check
301 if type(self.fly_in) == "string"
302 and nod == self.fly_in then
304 return true
306 elseif type(self.fly_in) == "table" then
308 for _,fly_in in pairs(self.fly_in) do
310 if nod == fly_in then
312 return true
317 -- stops mobs getting stuck inside stairs and plantlike nodes
318 if def.drawtype ~= "airlike"
319 and def.drawtype ~= "liquid"
320 and def.drawtype ~= "flowingliquid" then
321 return true
324 return false
328 -- custom particle effects
329 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
331 radius = radius or 2
332 min_size = min_size or 0.5
333 max_size = max_size or 1
334 gravity = gravity or -10
335 glow = glow or 0
337 minetest.add_particlespawner({
338 amount = amount,
339 time = 0.25,
340 minpos = pos,
341 maxpos = pos,
342 minvel = {x = -radius, y = -radius, z = -radius},
343 maxvel = {x = radius, y = radius, z = radius},
344 minacc = {x = 0, y = gravity, z = 0},
345 maxacc = {x = 0, y = gravity, z = 0},
346 minexptime = 0.1,
347 maxexptime = 1,
348 minsize = min_size,
349 maxsize = max_size,
350 texture = texture,
351 glow = glow,
356 local update_tag = function(self)
357 --DISABLED IN MCL2
358 --[=[
359 local col = "#00FF00"
360 local qua = self.hp_max / 4
362 if self.health <= floor(qua * 3) then
363 col = "#FFFF00"
366 if self.health <= floor(qua * 2) then
367 col = "#FF6600"
370 if self.health <= floor(qua) then
371 col = "#FF0000"
375 self.object:set_properties({
376 nametag = self.nametag,
382 -- drop items
383 local item_drop = function(self, cooked)
385 -- no drops if disabled by setting
386 if not mobs_drop_items then return end
388 -- no drops for child mobs
389 if self.child then return end
391 local obj, item, num
392 local pos = self.object:get_pos()
394 self.drops = self.drops or {} -- nil check
396 for n = 1, #self.drops do
398 if random(1, self.drops[n].chance) == 1 then
400 num = random(self.drops[n].min or 1, self.drops[n].max or 1)
401 item = self.drops[n].name
403 -- cook items when true
404 if cooked then
406 local output = minetest.get_craft_result({
407 method = "cooking", width = 1, items = {item}})
409 if output and output.item and not output.item:is_empty() then
410 item = output.item:get_name()
414 -- add item if it exists
415 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
417 if obj and obj:get_luaentity() then
419 obj:setvelocity({
420 x = random(-10, 10) / 9,
421 y = 6,
422 z = random(-10, 10) / 9,
424 elseif obj then
425 obj:remove() -- item does not exist
430 self.drops = {}
434 -- check if mob is dead or only hurt
435 local check_for_death = function(self, cause, cmi_cause)
437 -- has health actually changed?
438 if self.health == self.old_health and self.health > 0 then
439 return
442 self.old_health = self.health
444 -- still got some health? play hurt sound
445 if self.health > 0 then
447 mob_sound(self, self.sounds.damage)
449 -- make sure health isn't higher than max
450 if self.health > self.hp_max then
451 self.health = self.hp_max
454 -- backup nametag so we can show health stats
455 if not self.nametag2 then
456 self.nametag2 = self.nametag or ""
459 if show_health
460 and (cmi_cause and cmi_cause.type == "punch") then
462 self.htimer = 2
463 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
465 update_tag(self)
468 return false
471 -- dropped cooked item if mob died in lava
472 if cause == "lava" then
473 item_drop(self, true)
474 else
475 item_drop(self, nil)
478 mob_sound(self, self.sounds.death)
480 local pos = self.object:get_pos()
482 -- execute custom death function
483 if self.on_die then
485 self.on_die(self, pos)
487 if use_cmi then
488 cmi.notify_die(self.object, cmi_cause)
491 self.object:remove()
493 return true
496 -- default death function and die animation (if defined)
497 if self.animation
498 and self.animation.die_start
499 and self.animation.die_end then
501 local frames = self.animation.die_end - self.animation.die_start
502 local speed = self.animation.die_speed or 15
503 local length = max(frames / speed, 0)
505 self.attack = nil
506 self.v_start = false
507 self.timer = 0
508 self.blinktimer = 0
509 self.passive = true
510 self.state = "die"
511 set_velocity(self, 0)
512 set_animation(self, "die")
514 minetest.after(length, function(self)
516 if use_cmi then
517 cmi.notify_die(self.object, cmi_cause)
520 self.object:remove()
521 end, self)
522 else
524 if use_cmi then
525 cmi.notify_die(self.object, cmi_cause)
528 self.object:remove()
531 effect(pos, 20, "tnt_smoke.png")
533 return true
537 -- check if within physical map limits (-30911 to 30927)
538 local within_limits = function(pos, radius)
540 if (pos.x - radius) > -30913
541 and (pos.x + radius) < 30928
542 and (pos.y - radius) > -30913
543 and (pos.y + radius) < 30928
544 and (pos.z - radius) > -30913
545 and (pos.z + radius) < 30928 then
546 return true -- within limits
549 return false -- beyond limits
553 -- is mob facing a cliff
554 local is_at_cliff = function(self)
556 if self.fear_height == 0 then -- 0 for no falling protection!
557 return false
560 local yaw = self.object:get_yaw()
561 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
562 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
563 local pos = self.object:get_pos()
564 local ypos = pos.y + self.collisionbox[2] -- just above floor
566 if minetest.line_of_sight(
567 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
568 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
569 , 1) then
571 return true
574 return false
578 -- get node but use fallback for nil or unknown
579 local node_ok = function(pos, fallback)
581 fallback = fallback or mobs.fallback_node
583 local node = minetest.get_node_or_nil(pos)
585 if node and minetest.registered_nodes[node.name] then
586 return node
589 return minetest.registered_nodes[fallback]
593 -- environmental damage (water, lava, fire, light etc.)
594 local do_env_damage = function(self)
596 -- feed/tame text timer (so mob 'full' messages dont spam chat)
597 if self.htimer > 0 then
598 self.htimer = self.htimer - 1
601 -- reset nametag after showing health stats
602 if self.htimer < 1 and self.nametag2 then
604 self.nametag = self.nametag2
605 self.nametag2 = nil
607 update_tag(self)
610 local pos = self.object:get_pos()
612 self.time_of_day = minetest.get_timeofday()
614 -- remove mob if beyond map limits
615 if not within_limits(pos, 0) then
616 self.object:remove()
617 return
621 local deal_light_damage = function(self, pos, damage)
622 if not (mod_weather and (mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
623 self.health = self.health - damage
625 effect(pos, 5, "tnt_smoke.png")
627 if check_for_death(self, "light", {type = "light"}) then return end
631 -- bright light harms mob
632 if self.light_damage ~= 0 and (minetest.get_node_light(pos) or 0) > 12 then
633 deal_light_damage(self, pos, self.light_damage)
635 local _, dim = mcl_worlds.y_to_layer(pos.y)
636 if self.sunlight_damage ~= 0 and (minetest.get_node_light(pos) or 0) >= minetest.LIGHT_MAX and dim == "overworld" then
637 deal_light_damage(self, pos, self.sunlight_damage)
640 local y_level = self.collisionbox[2]
642 if self.child then
643 y_level = self.collisionbox[2] * 0.5
646 -- what is mob standing in?
647 pos.y = pos.y + y_level + 0.25 -- foot level
648 self.standing_in = node_ok(pos, "air").name
649 -- print ("standing in " .. self.standing_in)
651 -- don't fall when on ignore, just stand still
652 if self.standing_in == "ignore" then
653 self.object:setvelocity({x = 0, y = 0, z = 0})
656 local nodef = minetest.registered_nodes[self.standing_in]
658 -- rain
659 if self.rain_damage and mod_weather then
660 if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
662 self.health = self.health - self.rain_damage
664 if check_for_death(self, "rain", {type = "environment",
665 pos = pos, node = self.standing_in}) then return end
669 pos.y = pos.y + 1 -- for particle effect position
671 -- water
672 if self.water_damage
673 and nodef.groups.water then
675 if self.water_damage ~= 0 then
677 self.health = self.health - self.water_damage
679 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
681 if check_for_death(self, "water", {type = "environment",
682 pos = pos, node = self.standing_in}) then return end
685 -- lava or fire
686 elseif self.lava_damage
687 and (nodef.groups.lava
688 or self.standing_in == node_fire
689 or self.standing_in == node_permanent_flame) then
691 if self.lava_damage ~= 0 then
693 self.health = self.health - self.lava_damage
695 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
697 if check_for_death(self, "lava", {type = "environment",
698 pos = pos, node = self.standing_in}) then return end
701 -- damage_per_second node check
702 elseif nodef.damage_per_second ~= 0 then
704 self.health = self.health - nodef.damage_per_second
706 effect(pos, 5, "tnt_smoke.png")
708 if check_for_death(self, "dps", {type = "environment",
709 pos = pos, node = self.standing_in}) then return end
711 --[[
712 --- suffocation inside solid node
713 if self.suffocation ~= 0
714 and nodef.walkable == true
715 and nodef.groups.disable_suffocation ~= 1
716 and nodef.drawtype == "normal" then
718 self.health = self.health - self.suffocation
720 if check_for_death(self, "suffocation", {type = "environment",
721 pos = pos, node = self.standing_in}) then return end
724 check_for_death(self, "", {type = "unknown"})
728 -- jump if facing a solid node (not fences or gates)
729 local do_jump = function(self)
731 if not self.jump
732 or self.jump_height == 0
733 or self.fly
734 or self.child
735 or self.order == "stand" then
736 return false
739 self.facing_fence = false
741 -- something stopping us while moving?
742 if self.state ~= "stand"
743 and get_velocity(self) > 0.5
744 and self.object:getvelocity().y ~= 0 then
745 return false
748 local pos = self.object:get_pos()
749 local yaw = self.object:get_yaw()
751 -- what is mob standing on?
752 pos.y = pos.y + self.collisionbox[2] - 0.2
754 local nod = node_ok(pos)
756 --print ("standing on:", nod.name, pos.y)
758 if minetest.registered_nodes[nod.name].walkable == false then
759 return false
762 -- where is front
763 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
764 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
766 -- what is in front of mob?
767 local nod = node_ok({
768 x = pos.x + dir_x,
769 y = pos.y + 0.5,
770 z = pos.z + dir_z
773 -- thin blocks that do not need to be jumped
774 if nod.name == node_snow then
775 return false
778 --print ("in front:", nod.name, pos.y + 0.5)
780 if self.walk_chance == 0
781 or minetest.registered_items[nod.name].walkable then
783 if not nod.name:find("fence")
784 and not nod.name:find("gate") then
786 local v = self.object:getvelocity()
788 v.y = self.jump_height
790 set_animation(self, "jump") -- only when defined
792 self.object:setvelocity(v)
794 -- when in air move forward
795 minetest.after(0.3, function(self, v)
796 -- self.object:setvelocity({
797 self.object:set_acceleration({
798 x = v.x * 2,--1.5,
799 y = 0,
800 z = v.z * 2,--1.5
802 end, self, v)
804 if get_velocity(self) > 0 then
805 mob_sound(self, self.sounds.jump)
807 else
808 self.facing_fence = true
811 return true
814 return false
818 -- blast damage to entities nearby (modified from TNT mod)
819 local entity_physics = function(pos, radius)
821 radius = radius * 2
823 local objs = minetest.get_objects_inside_radius(pos, radius)
824 local obj_pos, dist
826 for n = 1, #objs do
828 obj_pos = objs[n]:get_pos()
830 dist = get_distance(pos, obj_pos)
831 if dist < 1 then dist = 1 end
833 local damage = floor((4 / dist) * radius)
834 local ent = objs[n]:get_luaentity()
836 -- punches work on entities AND players
837 objs[n]:punch(objs[n], 1.0, {
838 full_punch_interval = 1.0,
839 damage_groups = {fleshy = damage},
840 }, pos)
845 -- should mob follow what I'm holding ?
846 local follow_holding = function(self, clicker)
848 if mobs.invis[clicker:get_player_name()] then
849 return false
852 local item = clicker:get_wielded_item()
853 local t = type(self.follow)
855 -- single item
856 if t == "string"
857 and item:get_name() == self.follow then
858 return true
860 -- multiple items
861 elseif t == "table" then
863 for no = 1, #self.follow do
865 if self.follow[no] == item:get_name() then
866 return true
871 return false
875 -- find two animals of same type and breed if nearby and horny
876 local breed = function(self)
878 -- child takes 240 seconds before growing into adult
879 if self.child == true then
881 self.hornytimer = self.hornytimer + 1
883 if self.hornytimer > 240 then
885 self.child = false
886 self.hornytimer = 0
888 self.object:set_properties({
889 textures = self.base_texture,
890 mesh = self.base_mesh,
891 visual_size = self.base_size,
892 collisionbox = self.base_colbox,
893 selectionbox = self.base_selbox,
896 -- custom function when child grows up
897 if self.on_grown then
898 self.on_grown(self)
899 else
900 -- jump when fully grown so as not to fall into ground
901 self.object:setvelocity({
902 x = 0,
903 y = self.jump_height,
904 z = 0
909 return
912 -- horny animal can mate for 40 seconds,
913 -- afterwards horny animal cannot mate again for 200 seconds
914 if self.horny == true
915 and self.hornytimer < 240 then
917 self.hornytimer = self.hornytimer + 1
919 if self.hornytimer >= 240 then
920 self.hornytimer = 0
921 self.horny = false
925 -- find another same animal who is also horny and mate if nearby
926 if self.horny == true
927 and self.hornytimer <= 40 then
929 local pos = self.object:get_pos()
931 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
933 local objs = minetest.get_objects_inside_radius(pos, 3)
934 local num = 0
935 local ent = nil
937 for n = 1, #objs do
939 ent = objs[n]:get_luaentity()
941 -- check for same animal with different colour
942 local canmate = false
944 if ent then
946 if ent.name == self.name then
947 canmate = true
948 else
949 local entname = string.split(ent.name,":")
950 local selfname = string.split(self.name,":")
952 if entname[1] == selfname[1] then
953 entname = string.split(entname[2],"_")
954 selfname = string.split(selfname[2],"_")
956 if entname[1] == selfname[1] then
957 canmate = true
963 if ent
964 and canmate == true
965 and ent.horny == true
966 and ent.hornytimer <= 40 then
967 num = num + 1
970 -- found your mate? then have a baby
971 if num > 1 then
973 self.hornytimer = 41
974 ent.hornytimer = 41
976 -- spawn baby
977 minetest.after(5, function()
979 -- custom breed function
980 if self.on_breed then
981 -- when false skip going any further
982 if self.on_breed(self, ent) == false then
983 return
987 local child = mobs:spawn_child(pos, self.name)
989 local ent_c = child:get_luaentity()
992 -- Use texture of one of the parents
993 local p = math.random(1, 2)
994 if p == 1 then
995 ent_c.base_texture = self.base_texture
996 else
997 ent_c.base_texture = ent.base_texture
999 child:set_properties({
1000 textures = ent_c.base_texture
1003 -- tamed and owned by parents' owner
1004 ent_c.tamed = true
1005 ent_c.owner = self.owner
1006 end)
1008 num = 0
1010 break
1017 -- find and replace what mob is looking for (grass, wheat etc.)
1018 local replace = function(self, pos)
1020 if not mobs_griefing
1021 or not self.replace_rate
1022 or not self.replace_what
1023 or self.child == true
1024 or self.object:getvelocity().y ~= 0
1025 or random(1, self.replace_rate) > 1 then
1026 return
1029 local what, with, y_offset
1031 if type(self.replace_what[1]) == "table" then
1033 local num = random(#self.replace_what)
1035 what = self.replace_what[num][1] or ""
1036 with = self.replace_what[num][2] or ""
1037 y_offset = self.replace_what[num][3] or 0
1038 else
1039 what = self.replace_what
1040 with = self.replace_with or ""
1041 y_offset = self.replace_offset or 0
1044 pos.y = pos.y + y_offset
1046 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
1048 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1050 local oldnode = {name = what}
1051 local newnode = {name = with}
1052 local on_replace_return
1054 if self.on_replace then
1055 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1058 if on_replace_return ~= false then
1060 minetest.set_node(pos, {name = with})
1062 -- when cow/sheep eats grass, replace wool and milk
1063 if self.gotten == true then
1064 self.gotten = false
1065 self.object:set_properties(self)
1072 -- check if daytime and also if mob is docile during daylight hours
1073 local day_docile = function(self)
1075 if self.docile_by_day == false then
1077 return false
1079 elseif self.docile_by_day == true
1080 and self.time_of_day > 0.2
1081 and self.time_of_day < 0.8 then
1083 return true
1088 local los_switcher = false
1089 local height_switcher = false
1091 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1092 local smart_mobs = function(self, s, p, dist, dtime)
1094 local s1 = self.path.lastpos
1096 local target_pos = self.attack:get_pos()
1098 -- is it becoming stuck?
1099 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
1100 self.path.stuck_timer = self.path.stuck_timer + dtime
1101 else
1102 self.path.stuck_timer = 0
1105 self.path.lastpos = {x = s.x, y = s.y, z = s.z}
1107 local use_pathfind = false
1108 local has_lineofsight = minetest.line_of_sight(
1109 {x = s.x, y = (s.y) + .5, z = s.z},
1110 {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
1112 -- im stuck, search for path
1113 if not has_lineofsight then
1115 if los_switcher == true then
1116 use_pathfind = true
1117 los_switcher = false
1118 end -- cannot see target!
1119 else
1120 if los_switcher == false then
1122 los_switcher = true
1123 use_pathfind = false
1125 minetest.after(1, function(self)
1126 if has_lineofsight then self.path.following = false end
1127 end, self)
1128 end -- can see target!
1131 if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
1133 use_pathfind = true
1134 self.path.stuck_timer = 0
1136 minetest.after(1, function(self)
1137 if has_lineofsight then self.path.following = false end
1138 end, self)
1141 if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
1143 use_pathfind = true
1144 self.path.stuck_timer = 0
1146 minetest.after(1, function(self)
1147 if has_lineofsight then self.path.following = false end
1148 end, self)
1151 if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
1153 if height_switcher then
1154 use_pathfind = true
1155 height_switcher = false
1157 else
1158 if not height_switcher then
1159 use_pathfind = false
1160 height_switcher = true
1164 if use_pathfind then
1165 -- lets try find a path, first take care of positions
1166 -- since pathfinder is very sensitive
1167 local sheight = self.collisionbox[5] - self.collisionbox[2]
1169 -- round position to center of node to avoid stuck in walls
1170 -- also adjust height for player models!
1171 s.x = floor(s.x + 0.5)
1172 -- s.y = floor(s.y + 0.5) - sheight
1173 s.z = floor(s.z + 0.5)
1175 local ssight, sground = minetest.line_of_sight(s, {
1176 x = s.x, y = s.y - 4, z = s.z}, 1)
1178 -- determine node above ground
1179 if not ssight then
1180 s.y = sground.y + 1
1183 local p1 = self.attack:get_pos()
1185 p1.x = floor(p1.x + 0.5)
1186 p1.y = floor(p1.y + 0.5)
1187 p1.z = floor(p1.z + 0.5)
1189 local dropheight = 6
1190 if self.fear_height ~= 0 then dropheight = self.fear_height end
1192 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "A*_noprefetch")
1193 --[[
1194 -- show path using particles
1195 if self.path.way and #self.path.way > 0 then
1196 print ("-- path length:" .. tonumber(#self.path.way))
1197 for _,pos in pairs(self.path.way) do
1198 minetest.add_particle({
1199 pos = pos,
1200 velocity = {x=0, y=0, z=0},
1201 acceleration = {x=0, y=0, z=0},
1202 expirationtime = 1,
1203 size = 4,
1204 collisiondetection = false,
1205 vertical = false,
1206 texture = "heart.png",
1212 self.state = ""
1213 do_attack(self, self.attack)
1215 -- no path found, try something else
1216 if not self.path.way then
1218 self.path.following = false
1220 -- lets make way by digging/building if not accessible
1221 if self.pathfinding == 2 and mobs_griefing then
1223 -- is player higher than mob?
1224 if s.y < p1.y then
1226 -- build upwards
1227 if not minetest.is_protected(s, "") then
1229 local ndef1 = minetest.registered_nodes[self.standing_in]
1231 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1233 minetest.set_node(s, {name = mobs.fallback_node})
1237 local sheight = math.ceil(self.collisionbox[5]) + 1
1239 -- assume mob is 2 blocks high so it digs above its head
1240 s.y = s.y + sheight
1242 -- remove one block above to make room to jump
1243 if not minetest.is_protected(s, "") then
1245 local node1 = node_ok(s, "air").name
1246 local ndef1 = minetest.registered_nodes[node1]
1248 if node1 ~= "air"
1249 and node1 ~= "ignore"
1250 and ndef1
1251 and not ndef1.groups.level
1252 and not ndef1.groups.unbreakable
1253 and not ndef1.groups.liquid then
1255 minetest.set_node(s, {name = "air"})
1256 minetest.add_item(s, ItemStack(node1))
1261 s.y = s.y - sheight
1262 self.object:setpos({x = s.x, y = s.y + 2, z = s.z})
1264 else -- dig 2 blocks to make door toward player direction
1266 local yaw1 = self.object:get_yaw() + pi / 2
1267 local p1 = {
1268 x = s.x + cos(yaw1),
1269 y = s.y,
1270 z = s.z + sin(yaw1)
1273 if not minetest.is_protected(p1, "") then
1275 local node1 = node_ok(p1, "air").name
1276 local ndef1 = minetest.registered_nodes[node1]
1278 if node1 ~= "air"
1279 and node1 ~= "ignore"
1280 and ndef1
1281 and not ndef1.groups.level
1282 and not ndef1.groups.unbreakable
1283 and not ndef1.groups.liquid then
1285 minetest.add_item(p1, ItemStack(node1))
1286 minetest.set_node(p1, {name = "air"})
1289 p1.y = p1.y + 1
1290 node1 = node_ok(p1, "air").name
1291 ndef1 = minetest.registered_nodes[node1]
1293 if node1 ~= "air"
1294 and node1 ~= "ignore"
1295 and ndef1
1296 and not ndef1.groups.level
1297 and not ndef1.groups.unbreakable
1298 and not ndef1.groups.liquid then
1300 minetest.add_item(p1, ItemStack(node1))
1301 minetest.set_node(p1, {name = "air"})
1308 -- will try again in 2 second
1309 self.path.stuck_timer = stuck_timeout - 2
1311 -- frustration! cant find the damn path :(
1312 mob_sound(self, self.sounds.random)
1313 else
1314 -- yay i found path
1315 mob_sound(self, self.sounds.war_cry)
1316 set_velocity(self, self.walk_velocity)
1318 -- follow path now that it has it
1319 self.path.following = true
1325 -- specific attacks
1326 local specific_attack = function(list, what)
1328 -- no list so attack default (player, animals etc.)
1329 if list == nil then
1330 return true
1333 -- found entity on list to attack?
1334 for no = 1, #list do
1336 if list[no] == what then
1337 return true
1341 return false
1345 -- monster find someone to attack
1346 local monster_attack = function(self)
1348 if self.type ~= "monster"
1349 or not damage_enabled
1350 or creative
1351 or self.state == "attack"
1352 or day_docile(self) then
1353 return
1356 local s = self.object:get_pos()
1357 local p, sp, dist
1358 local player, obj, min_player
1359 local type, name = "", ""
1360 local min_dist = self.view_range + 1
1361 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1363 for n = 1, #objs do
1365 if objs[n]:is_player() then
1367 if mobs.invis[ objs[n]:get_player_name() ] then
1369 type = ""
1370 else
1371 player = objs[n]
1372 type = "player"
1373 name = "player"
1375 else
1376 obj = objs[n]:get_luaentity()
1378 if obj then
1379 player = obj.object
1380 type = obj.type
1381 name = obj.name or ""
1385 -- find specific mob to attack, failing that attack player/npc/animal
1386 if specific_attack(self.specific_attack, name)
1387 and (type == "player" or type == "npc"
1388 or (type == "animal" and self.attack_animals == true)) then
1390 p = player:get_pos()
1391 sp = s
1393 dist = get_distance(p, s)
1395 -- aim higher to make looking up hills more realistic
1396 p.y = p.y + 1
1397 sp.y = sp.y + 1
1400 -- choose closest player to attack
1401 if dist < min_dist
1402 and line_of_sight(self, sp, p, 2) == true then
1403 min_dist = dist
1404 min_player = player
1409 -- attack player
1410 if min_player then
1411 do_attack(self, min_player)
1416 -- npc, find closest monster to attack
1417 local npc_attack = function(self)
1419 if self.type ~= "npc"
1420 or not self.attacks_monsters
1421 or self.state == "attack" then
1422 return
1425 local p, sp, obj, min_player
1426 local s = self.object:get_pos()
1427 local min_dist = self.view_range + 1
1428 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1430 for n = 1, #objs do
1432 obj = objs[n]:get_luaentity()
1434 if obj and obj.type == "monster" then
1436 p = obj.object:get_pos()
1437 sp = s
1439 dist = get_distance(p, s)
1441 -- aim higher to make looking up hills more realistic
1442 p.y = p.y + 1
1443 sp.y = sp.y + 1
1445 if dist < min_dist
1446 and line_of_sight(self, sp, p, 2) == true then
1447 min_dist = dist
1448 min_player = obj.object
1453 if min_player then
1454 do_attack(self, min_player)
1459 -- specific runaway
1460 local specific_runaway = function(list, what)
1462 -- no list so do not run
1463 if list == nil then
1464 return false
1467 -- found entity on list to attack?
1468 for no = 1, #list do
1470 if list[no] == what then
1471 return true
1475 return false
1479 -- find someone to runaway from
1480 local runaway_from = function(self)
1482 if not self.runaway_from then
1483 return
1486 local s = self.object:get_pos()
1487 local p, sp, dist
1488 local player, obj, min_player
1489 local type, name = "", ""
1490 local min_dist = self.view_range + 1
1491 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1493 for n = 1, #objs do
1495 if objs[n]:is_player() then
1497 if mobs.invis[ objs[n]:get_player_name() ]
1498 or self.owner == objs[n]:get_player_name() then
1500 type = ""
1501 else
1502 player = objs[n]
1503 type = "player"
1504 name = "player"
1506 else
1507 obj = objs[n]:get_luaentity()
1509 if obj then
1510 player = obj.object
1511 type = obj.type
1512 name = obj.name or ""
1516 -- find specific mob to runaway from
1517 if name ~= "" and name ~= self.name
1518 and specific_runaway(self.runaway_from, name) then
1520 p = player:get_pos()
1521 sp = s
1523 -- aim higher to make looking up hills more realistic
1524 p.y = p.y + 1
1525 sp.y = sp.y + 1
1527 dist = get_distance(p, s)
1530 -- choose closest player/mpb to runaway from
1531 if dist < min_dist
1532 and line_of_sight(self, sp, p, 2) == true then
1533 min_dist = dist
1534 min_player = player
1539 if min_player then
1541 local lp = player:get_pos()
1542 local vec = {
1543 x = lp.x - s.x,
1544 y = lp.y - s.y,
1545 z = lp.z - s.z
1548 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1550 if lp.x > s.x then
1551 yaw = yaw + pi
1554 yaw = set_yaw(self, yaw, 4)
1555 self.state = "runaway"
1556 self.runaway_timer = 3
1557 self.following = nil
1562 -- follow player if owner or holding item, if fish outta water then flop
1563 local follow_flop = function(self)
1565 -- find player to follow
1566 if (self.follow ~= ""
1567 or self.order == "follow")
1568 and not self.following
1569 and self.state ~= "attack"
1570 and self.state ~= "runaway" then
1572 local s = self.object:get_pos()
1573 local players = minetest.get_connected_players()
1575 for n = 1, #players do
1577 if get_distance(players[n]:get_pos(), s) < self.view_range
1578 and not mobs.invis[ players[n]:get_player_name() ] then
1580 self.following = players[n]
1582 break
1587 if self.type == "npc"
1588 and self.order == "follow"
1589 and self.state ~= "attack"
1590 and self.owner ~= "" then
1592 -- npc stop following player if not owner
1593 if self.following
1594 and self.owner
1595 and self.owner ~= self.following:get_player_name() then
1596 self.following = nil
1598 else
1599 -- stop following player if not holding specific item
1600 if self.following
1601 and self.following:is_player()
1602 and follow_holding(self, self.following) == false then
1603 self.following = nil
1608 -- follow that thing
1609 if self.following then
1611 local s = self.object:get_pos()
1612 local p
1614 if self.following:is_player() then
1616 p = self.following:get_pos()
1618 elseif self.following.object then
1620 p = self.following.object:get_pos()
1623 if p then
1625 local dist = get_distance(p, s)
1627 -- dont follow if out of range
1628 if dist > self.view_range then
1629 self.following = nil
1630 else
1631 local vec = {
1632 x = p.x - s.x,
1633 z = p.z - s.z
1636 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1638 if p.x > s.x then yaw = yaw + pi end
1640 yaw = set_yaw(self, yaw, 6)
1642 -- anyone but standing npc's can move along
1643 if dist > self.reach
1644 and self.order ~= "stand" then
1646 set_velocity(self, self.walk_velocity)
1648 if self.walk_chance ~= 0 then
1649 set_animation(self, "walk")
1651 else
1652 set_velocity(self, 0)
1653 set_animation(self, "stand")
1656 return
1661 -- swimmers flop when out of their element, and swim again when back in
1662 if self.fly then
1663 local s = self.object:get_pos()
1664 if not flight_check(self, s) then
1666 self.state = "flop"
1667 self.object:setvelocity({x = 0, y = -5, z = 0})
1669 set_animation(self, "stand")
1671 return
1672 elseif self.state == "flop" then
1673 self.state = "stand"
1679 -- dogshoot attack switch and counter function
1680 local dogswitch = function(self, dtime)
1682 -- switch mode not activated
1683 if not self.dogshoot_switch
1684 or not dtime then
1685 return 0
1688 self.dogshoot_count = self.dogshoot_count + dtime
1690 if (self.dogshoot_switch == 1
1691 and self.dogshoot_count > self.dogshoot_count_max)
1692 or (self.dogshoot_switch == 2
1693 and self.dogshoot_count > self.dogshoot_count2_max) then
1695 self.dogshoot_count = 0
1697 if self.dogshoot_switch == 1 then
1698 self.dogshoot_switch = 2
1699 else
1700 self.dogshoot_switch = 1
1704 return self.dogshoot_switch
1708 -- execute current state (stand, walk, run, attacks)
1709 local do_states = function(self, dtime)
1711 local yaw = self.object:get_yaw() or 0
1713 if self.state == "stand" then
1715 if random(1, 4) == 1 then
1717 local lp = nil
1718 local s = self.object:get_pos()
1719 local objs = minetest.get_objects_inside_radius(s, 3)
1721 for n = 1, #objs do
1723 if objs[n]:is_player() then
1724 lp = objs[n]:get_pos()
1725 break
1729 -- look at any players nearby, otherwise turn randomly
1730 if lp then
1732 local vec = {
1733 x = lp.x - s.x,
1734 z = lp.z - s.z
1737 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1739 if lp.x > s.x then yaw = yaw + pi end
1740 else
1741 yaw = yaw + random(-0.5, 0.5)
1744 yaw = set_yaw(self, yaw, 8)
1747 set_velocity(self, 0)
1748 set_animation(self, "stand")
1750 -- npc's ordered to stand stay standing
1751 if self.type ~= "npc"
1752 or self.order ~= "stand" then
1754 if self.walk_chance ~= 0
1755 and self.facing_fence ~= true
1756 and random(1, 100) <= self.walk_chance
1757 and is_at_cliff(self) == false then
1759 set_velocity(self, self.walk_velocity)
1760 self.state = "walk"
1761 set_animation(self, "walk")
1763 --[[ fly up/down randomly for flying mobs
1764 if self.fly and random(1, 100) <= self.walk_chance then
1766 local v = self.object:getvelocity()
1767 local ud = random(-1, 2) / 9
1769 self.object:setvelocity({x = v.x, y = ud, z = v.z})
1770 end--]]
1774 elseif self.state == "walk" then
1776 local s = self.object:get_pos()
1777 local lp = nil
1779 -- is there something I need to avoid?
1780 if self.water_damage > 0
1781 and self.lava_damage > 0 then
1783 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
1785 elseif self.water_damage > 0 then
1787 lp = minetest.find_node_near(s, 1, {"group:water"})
1789 elseif self.lava_damage > 0 then
1791 lp = minetest.find_node_near(s, 1, {"group:lava"})
1794 if lp then
1796 -- if mob in water or lava then look for land
1797 if (self.lava_damage
1798 and minetest.registered_nodes[self.standing_in].groups.lava)
1799 or (self.water_damage
1800 and minetest.registered_nodes[self.standing_in].groups.water) then
1802 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1803 "group:sand", node_ice, node_snowblock})
1805 -- did we find land?
1806 if lp then
1808 local vec = {
1809 x = lp.x - s.x,
1810 z = lp.z - s.z
1813 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1815 if lp.x > s.x then yaw = yaw + pi end
1817 -- look towards land and jump/move in that direction
1818 yaw = set_yaw(self, yaw, 6)
1819 do_jump(self)
1820 set_velocity(self, self.walk_velocity)
1821 else
1822 yaw = yaw + random(-0.5, 0.5)
1825 else
1827 local vec = {
1828 x = lp.x - s.x,
1829 z = lp.z - s.z
1832 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1834 if lp.x > s.x then yaw = yaw + pi end
1837 yaw = set_yaw(self, yaw, 8)
1839 -- otherwise randomly turn
1840 elseif random(1, 100) <= 30 then
1842 yaw = yaw + random(-0.5, 0.5)
1844 yaw = set_yaw(self, yaw, 8)
1847 -- stand for great fall in front
1848 local temp_is_cliff = is_at_cliff(self)
1850 if self.facing_fence == true
1851 or temp_is_cliff
1852 or random(1, 100) <= 30 then
1854 set_velocity(self, 0)
1855 self.state = "stand"
1856 set_animation(self, "stand")
1857 else
1858 set_velocity(self, self.walk_velocity)
1860 if flight_check(self)
1861 and self.animation
1862 and self.animation.fly_start
1863 and self.animation.fly_end then
1864 set_animation(self, "fly")
1865 else
1866 set_animation(self, "walk")
1870 -- runaway when punched
1871 elseif self.state == "runaway" then
1873 self.runaway_timer = self.runaway_timer + 1
1875 -- stop after 5 seconds or when at cliff
1876 if self.runaway_timer > 5
1877 or is_at_cliff(self) then
1878 self.runaway_timer = 0
1879 set_velocity(self, 0)
1880 self.state = "stand"
1881 set_animation(self, "stand")
1882 else
1883 set_velocity(self, self.run_velocity)
1884 set_animation(self, "walk")
1887 -- attack routines (explode, dogfight, shoot, dogshoot)
1888 elseif self.state == "attack" then
1890 -- calculate distance from mob and enemy
1891 local s = self.object:get_pos()
1892 local p = self.attack:get_pos() or s
1893 local dist = get_distance(p, s)
1895 -- stop attacking if player invisible or out of range
1896 if dist > self.view_range
1897 or not self.attack
1898 or not self.attack:get_pos()
1899 or self.attack:get_hp() <= 0
1900 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1902 -- print(" ** stop attacking **", dist, self.view_range)
1903 self.state = "stand"
1904 set_velocity(self, 0)
1905 set_animation(self, "stand")
1906 self.attack = nil
1907 self.v_start = false
1908 self.timer = 0
1909 self.blinktimer = 0
1910 self.path.way = nil
1912 return
1915 if self.attack_type == "explode" then
1917 local vec = {
1918 x = p.x - s.x,
1919 z = p.z - s.z
1922 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1924 if p.x > s.x then yaw = yaw + pi end
1926 yaw = set_yaw(self, yaw)
1928 local node_break_radius = self.explosion_radius or 1
1929 local entity_damage_radius = self.explosion_damage_radius
1930 or (node_break_radius * 2)
1932 -- start timer when in reach and line of sight
1933 if not self.v_start
1934 and dist <= self.reach
1935 and line_of_sight(self, s, p, 2) then
1937 self.v_start = true
1938 self.timer = 0
1939 self.blinktimer = 0
1940 mob_sound(self, self.sounds.fuse)
1941 -- print ("=== explosion timer started", self.explosion_timer)
1943 -- stop timer if out of reach or direct line of sight
1944 elseif self.allow_fuse_reset
1945 and self.v_start
1946 and (dist > self.reach
1947 or not line_of_sight(self, s, p, 2)) then
1948 self.v_start = false
1949 self.timer = 0
1950 self.blinktimer = 0
1951 self.blinkstatus = false
1952 self.object:settexturemod("")
1955 -- walk right up to player unless the timer is active
1956 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1957 set_velocity(self, 0)
1958 else
1959 set_velocity(self, self.run_velocity)
1962 if self.animation and self.animation.run_start then
1963 set_animation(self, "run")
1964 else
1965 set_animation(self, "walk")
1968 if self.v_start then
1970 self.timer = self.timer + dtime
1971 self.blinktimer = (self.blinktimer or 0) + dtime
1973 if self.blinktimer > 0.2 then
1975 self.blinktimer = 0
1977 if self.blinkstatus then
1978 self.object:settexturemod("")
1979 else
1980 self.object:settexturemod("^[brighten")
1983 self.blinkstatus = not self.blinkstatus
1986 -- print ("=== explosion timer", self.timer)
1988 if self.timer > self.explosion_timer then
1990 local pos = self.object:get_pos()
1992 -- dont damage anything if area protected or next to water
1993 if minetest.find_node_near(pos, 1, {"group:water"})
1994 or minetest.is_protected(pos, "") then
1996 node_break_radius = 1
1999 self.object:remove()
2001 if mod_tnt and tnt and tnt.boom
2002 and not minetest.is_protected(pos, "") then
2004 tnt.boom(pos, {
2005 radius = node_break_radius,
2006 damage_radius = entity_damage_radius,
2007 sound = self.sounds.explode,
2009 else
2011 minetest.sound_play(self.sounds.explode, {
2012 pos = pos,
2013 gain = 1.0,
2014 max_hear_distance = self.sounds.distance or 32
2017 entity_physics(pos, entity_damage_radius)
2018 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2021 return
2025 elseif self.attack_type == "dogfight"
2026 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2027 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2029 if self.fly
2030 and dist > self.reach then
2032 local p1 = s
2033 local me_y = floor(p1.y)
2034 local p2 = p
2035 local p_y = floor(p2.y + 1)
2036 local v = self.object:getvelocity()
2038 if flight_check(self, s) then
2040 if me_y < p_y then
2042 self.object:setvelocity({
2043 x = v.x,
2044 y = 1 * self.walk_velocity,
2045 z = v.z
2048 elseif me_y > p_y then
2050 self.object:setvelocity({
2051 x = v.x,
2052 y = -1 * self.walk_velocity,
2053 z = v.z
2056 else
2057 if me_y < p_y then
2059 self.object:setvelocity({
2060 x = v.x,
2061 y = 0.01,
2062 z = v.z
2065 elseif me_y > p_y then
2067 self.object:setvelocity({
2068 x = v.x,
2069 y = -0.01,
2070 z = v.z
2077 -- rnd: new movement direction
2078 if self.path.following
2079 and self.path.way
2080 and self.attack_type ~= "dogshoot" then
2082 -- no paths longer than 50
2083 if #self.path.way > 50
2084 or dist < self.reach then
2085 self.path.following = false
2086 return
2089 local p1 = self.path.way[1]
2091 if not p1 then
2092 self.path.following = false
2093 return
2096 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2097 -- reached waypoint, remove it from queue
2098 table.remove(self.path.way, 1)
2101 -- set new temporary target
2102 p = {x = p1.x, y = p1.y, z = p1.z}
2105 local vec = {
2106 x = p.x - s.x,
2107 z = p.z - s.z
2110 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2112 if p.x > s.x then yaw = yaw + pi end
2114 yaw = set_yaw(self, yaw)
2116 -- move towards enemy if beyond mob reach
2117 if dist > self.reach then
2119 -- path finding by rnd
2120 if self.pathfinding -- only if mob has pathfinding enabled
2121 and enable_pathfinding then
2123 smart_mobs(self, s, p, dist, dtime)
2126 if is_at_cliff(self) then
2128 set_velocity(self, 0)
2129 set_animation(self, "stand")
2130 else
2132 if self.path.stuck then
2133 set_velocity(self, self.walk_velocity)
2134 else
2135 set_velocity(self, self.run_velocity)
2138 if self.animation and self.animation.run_start then
2139 set_animation(self, "run")
2140 else
2141 set_animation(self, "walk")
2145 else -- rnd: if inside reach range
2147 self.path.stuck = false
2148 self.path.stuck_timer = 0
2149 self.path.following = false -- not stuck anymore
2151 set_velocity(self, 0)
2153 if not self.custom_attack then
2155 if self.timer > 1 then
2157 self.timer = 0
2159 if self.double_melee_attack
2160 and random(1, 2) == 1 then
2161 set_animation(self, "punch2")
2162 else
2163 set_animation(self, "punch")
2166 local p2 = p
2167 local s2 = s
2169 p2.y = p2.y + .5
2170 s2.y = s2.y + .5
2172 if line_of_sight(self, p2, s2) == true then
2174 -- play attack sound
2175 mob_sound(self, self.sounds.attack)
2177 -- punch player (or what player is attached to)
2178 local attached = self.attack:get_attach()
2179 if attached then
2180 self.attack = attached
2182 self.attack:punch(self.object, 1.0, {
2183 full_punch_interval = 1.0,
2184 damage_groups = {fleshy = self.damage}
2185 }, nil)
2188 else -- call custom attack every second
2189 if self.custom_attack
2190 and self.timer > 1 then
2192 self.timer = 0
2194 self.custom_attack(self, p)
2199 elseif self.attack_type == "shoot"
2200 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2201 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2203 p.y = p.y - .5
2204 s.y = s.y + .5
2206 local dist = get_distance(p, s)
2207 local vec = {
2208 x = p.x - s.x,
2209 y = p.y - s.y,
2210 z = p.z - s.z
2213 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2215 if p.x > s.x then yaw = yaw + pi end
2217 yaw = set_yaw(self, yaw)
2219 set_velocity(self, 0)
2221 if self.shoot_interval
2222 and self.timer > self.shoot_interval
2223 and random(1, 100) <= 60 then
2225 self.timer = 0
2226 set_animation(self, "shoot")
2228 -- play shoot attack sound
2229 mob_sound(self, self.sounds.shoot_attack)
2231 local p = self.object:get_pos()
2233 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
2235 if minetest.registered_entities[self.arrow] then
2237 local obj = minetest.add_entity(p, self.arrow)
2238 local ent = obj:get_luaentity()
2239 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2240 local v = ent.velocity or 1 -- or set to default
2242 ent.switch = 1
2243 ent.owner_id = tostring(self.object) -- add unique owner id to arrow
2245 -- offset makes shoot aim accurate
2246 vec.y = vec.y + self.shoot_offset
2247 vec.x = vec.x * (v / amount)
2248 vec.y = vec.y * (v / amount)
2249 vec.z = vec.z * (v / amount)
2251 obj:setvelocity(vec)
2259 -- falling and fall damage
2260 local falling = function(self, pos)
2262 if self.fly then
2263 return
2266 -- floating in water (or falling)
2267 local v = self.object:getvelocity()
2269 if v.y > 0 then
2271 -- apply gravity when moving up
2272 self.object:setacceleration({
2273 x = 0,
2274 y = -10,
2275 z = 0
2278 elseif v.y <= 0 and v.y > self.fall_speed then
2280 -- fall downwards at set speed
2281 self.object:setacceleration({
2282 x = 0,
2283 y = self.fall_speed,
2284 z = 0
2286 else
2287 -- stop accelerating once max fall speed hit
2288 self.object:setacceleration({x = 0, y = 0, z = 0})
2291 -- in water then float up
2292 if minetest.registered_nodes[node_ok(pos).name].groups.water then
2294 if self.floats == 1 then
2296 self.object:setacceleration({
2297 x = 0,
2298 y = -self.fall_speed / (max(1, v.y) ^ 2),
2299 z = 0
2302 else
2304 -- fall damage onto solid ground
2305 if self.fall_damage == 1
2306 and self.object:getvelocity().y == 0 then
2308 local d = (self.old_y or 0) - self.object:get_pos().y
2310 if d > 5 then
2312 self.health = self.health - floor(d - 5)
2314 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
2316 if check_for_death(self, "fall", {type = "fall"}) then
2317 return
2321 self.old_y = self.object:get_pos().y
2327 -- deal damage and effects when mob punched
2328 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2330 -- custom punch function
2331 if self.do_punch then
2333 -- when false skip going any further
2334 if self.do_punch(self, hitter, tflp, tool_caps, dir) == false then
2335 return
2339 -- mob health check
2340 -- if self.health <= 0 then
2341 -- return
2342 -- end
2344 -- error checking when mod profiling is enabled
2345 if not tool_capabilities then
2346 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2347 return
2350 -- is mob protected?
2351 if self.protected and hitter:is_player()
2352 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2353 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2354 return
2358 -- weapon wear
2359 local weapon = hitter:get_wielded_item()
2360 local punch_interval = 1.4
2362 -- calculate mob damage
2363 local damage = 0
2364 local armor = self.object:get_armor_groups() or {}
2365 local tmp
2367 -- quick error check incase it ends up 0 (serialize.h check test)
2368 if tflp == 0 then
2369 tflp = 0.2
2372 if use_cmi then
2373 damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
2374 else
2376 for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
2378 tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
2380 if tmp < 0 then
2381 tmp = 0.0
2382 elseif tmp > 1 then
2383 tmp = 1.0
2386 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2387 * tmp * ((armor[group] or 0) / 100.0)
2391 -- check for tool immunity or special damage
2392 for n = 1, #self.immune_to do
2394 if self.immune_to[n][1] == weapon:get_name() then
2396 damage = self.immune_to[n][2] or 0
2397 break
2401 -- healing
2402 if damage <= -1 then
2403 self.health = self.health - floor(damage)
2404 return
2407 -- print ("Mob Damage is", damage)
2409 if use_cmi then
2411 local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage)
2413 if cancel then return end
2416 -- add weapon wear
2417 if tool_capabilities then
2418 punch_interval = tool_capabilities.full_punch_interval or 1.4
2421 if weapon:get_definition()
2422 and weapon:get_definition().tool_capabilities then
2424 weapon:add_wear(floor((punch_interval / 75) * 9000))
2425 hitter:set_wielded_item(weapon)
2428 -- only play hit sound and show blood effects if damage is 1 or over
2429 if damage >= 1 then
2431 -- weapon sounds
2432 if weapon:get_definition().sounds ~= nil then
2434 local s = random(0, #weapon:get_definition().sounds)
2436 minetest.sound_play(weapon:get_definition().sounds[s], {
2437 object = self.object, --hitter,
2438 max_hear_distance = 8
2440 else
2441 minetest.sound_play("default_punch", {
2442 object = self.object, --hitter,
2443 max_hear_distance = 5
2447 -- blood_particles
2448 if self.blood_amount > 0
2449 and not disable_blood then
2451 local pos = self.object:get_pos()
2453 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2455 -- do we have a single blood texture or multiple?
2456 if type(self.blood_texture) == "table" then
2458 local blood = self.blood_texture[random(1, #self.blood_texture)]
2460 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2461 else
2462 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2466 -- do damage
2467 self.health = self.health - floor(damage)
2469 -- exit here if dead, special item check
2470 if weapon:get_name() == "mobs:pick_lava" then
2471 if check_for_death(self, "lava", {type = "punch",
2472 puncher = hitter}) then
2473 return
2475 else
2476 if check_for_death(self, "hit", {type = "punch",
2477 puncher = hitter}) then
2478 return
2482 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2483 core.after(0.1, function()
2484 self.object:settexturemod("^[colorize:#c9900070")
2486 core.after(0.3, function()
2487 self.object:settexturemod("")
2488 end)
2489 end) ]]
2491 -- knock back effect (only on full punch)
2492 if self.knock_back
2493 and tflp >= punch_interval then
2495 local v = self.object:getvelocity()
2496 local r = 1.4 - min(punch_interval, 1.4)
2497 local kb = r * 5
2498 local up = 2
2500 -- if already in air then dont go up anymore when hit
2501 if v.y > 0
2502 or self.fly then
2503 up = 0
2506 -- direction error check
2507 dir = dir or {x = 0, y = 0, z = 0}
2509 -- check if tool already has specific knockback value
2510 if tool_capabilities.damage_groups["knockback"] then
2511 kb = tool_capabilities.damage_groups["knockback"]
2512 else
2513 kb = kb * 1.5
2516 self.object:setvelocity({
2517 x = dir.x * kb,
2518 y = up,
2519 z = dir.z * kb
2522 self.pause_timer = 0.25
2524 end -- END if damage
2526 -- if skittish then run away
2527 if self.runaway == true then
2529 local lp = hitter:get_pos()
2530 local s = self.object:get_pos()
2531 local vec = {
2532 x = lp.x - s.x,
2533 y = lp.y - s.y,
2534 z = lp.z - s.z
2537 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2539 if lp.x > s.x then
2540 yaw = yaw + pi
2543 yaw = set_yaw(self, yaw, 6)
2544 self.state = "runaway"
2545 self.runaway_timer = 0
2546 self.following = nil
2549 local name = hitter:get_player_name() or ""
2551 -- attack puncher and call other mobs for help
2552 if self.passive == false
2553 and self.state ~= "flop"
2554 and self.child == false
2555 and hitter:get_player_name() ~= self.owner
2556 and not mobs.invis[ name ] then
2558 -- attack whoever punched mob
2559 self.state = ""
2560 do_attack(self, hitter)
2562 -- alert others to the attack
2563 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2564 local obj = nil
2566 for n = 1, #objs do
2568 obj = objs[n]:get_luaentity()
2570 if obj then
2572 -- only alert members of same mob
2573 if obj.group_attack == true
2574 and obj.state ~= "attack"
2575 and obj.owner ~= name
2576 and obj.name == self.name then
2577 do_attack(obj, hitter)
2580 -- have owned mobs attack player threat
2581 if obj.owner == name and obj.owner_loyal then
2582 do_attack(obj, self.object)
2590 -- get entity staticdata
2591 local mob_staticdata = function(self)
2593 -- remove mob when out of range unless tamed
2594 if remove_far
2595 and self.remove_ok
2596 and self.type ~= "npc"
2597 and self.state ~= "attack"
2598 and not self.tamed
2599 and self.lifetimer < 20000 then
2601 --print ("REMOVED " .. self.name)
2603 self.object:remove()
2605 return ""-- nil
2608 self.remove_ok = true
2609 self.attack = nil
2610 self.following = nil
2611 self.state = "stand"
2613 -- used to rotate older mobs
2614 if self.drawtype
2615 and self.drawtype == "side" then
2616 self.rotate = math.rad(90)
2619 if use_cmi then
2620 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2623 local tmp = {}
2625 for _,stat in pairs(self) do
2627 local t = type(stat)
2629 if t ~= "function"
2630 and t ~= "nil"
2631 and t ~= "userdata"
2632 and _ ~= "_cmi_components" then
2633 tmp[_] = self[_]
2637 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
2638 return minetest.serialize(tmp)
2642 -- activate mob and reload settings
2643 local mob_activate = function(self, staticdata, def, dtime)
2645 -- remove monsters in peaceful mode
2646 if self.type == "monster"
2647 and peaceful_only then
2649 self.object:remove()
2651 return
2654 -- load entity variables
2655 local tmp = minetest.deserialize(staticdata)
2657 if tmp then
2658 for _,stat in pairs(tmp) do
2659 self[_] = stat
2663 -- select random texture, set model and size
2664 if not self.base_texture then
2666 -- compatiblity with old simple mobs textures
2667 if type(def.textures[1]) == "string" then
2668 def.textures = {def.textures}
2671 self.base_texture = def.textures[random(1, #def.textures)]
2672 self.base_mesh = def.mesh
2673 self.base_size = self.visual_size
2674 self.base_colbox = self.collisionbox
2675 self.base_selbox = self.selectionbox
2678 -- for current mobs that dont have this set
2679 if not self.base_selbox then
2680 self.base_selbox = self.selectionbox or self.base_colbox
2683 -- set texture, model and size
2684 local textures = self.base_texture
2685 local mesh = self.base_mesh
2686 local vis_size = self.base_size
2687 local colbox = self.base_colbox
2688 local selbox = self.base_selbox
2690 -- specific texture if gotten
2691 if self.gotten == true
2692 and def.gotten_texture then
2693 textures = def.gotten_texture
2696 -- specific mesh if gotten
2697 if self.gotten == true
2698 and def.gotten_mesh then
2699 mesh = def.gotten_mesh
2702 -- set child objects to half size
2703 if self.child == true then
2705 vis_size = {
2706 x = self.base_size.x * .5,
2707 y = self.base_size.y * .5,
2710 if def.child_texture then
2711 textures = def.child_texture[1]
2714 colbox = {
2715 self.base_colbox[1] * .5,
2716 self.base_colbox[2] * .5,
2717 self.base_colbox[3] * .5,
2718 self.base_colbox[4] * .5,
2719 self.base_colbox[5] * .5,
2720 self.base_colbox[6] * .5
2722 selbox = {
2723 self.base_selbox[1] * .5,
2724 self.base_selbox[2] * .5,
2725 self.base_selbox[3] * .5,
2726 self.base_selbox[4] * .5,
2727 self.base_selbox[5] * .5,
2728 self.base_selbox[6] * .5
2732 if self.health == 0 then
2733 self.health = random (self.hp_min, self.hp_max)
2736 -- pathfinding init
2737 self.path = {}
2738 self.path.way = {} -- path to follow, table of positions
2739 self.path.lastpos = {x = 0, y = 0, z = 0}
2740 self.path.stuck = false
2741 self.path.following = false -- currently following path?
2742 self.path.stuck_timer = 0 -- if stuck for too long search for path
2744 -- mob defaults
2745 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
2746 self.old_y = self.object:get_pos().y
2747 self.old_health = self.health
2748 self.sounds.distance = self.sounds.distance or 10
2749 self.textures = textures
2750 self.mesh = mesh
2751 self.collisionbox = colbox
2752 self.selectionbox = selbox
2753 self.visual_size = vis_size
2754 self.standing_in = ""
2756 -- check existing nametag
2757 if not self.nametag then
2758 self.nametag = def.nametag
2761 -- set anything changed above
2762 self.object:set_properties(self)
2763 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2764 update_tag(self)
2765 set_animation(self, "stand")
2767 -- run on_spawn function if found
2768 if self.on_spawn and not self.on_spawn_run then
2769 if self.on_spawn(self) then
2770 self.on_spawn_run = true -- if true, set flag to run once only
2774 -- run after_activate
2775 if def.after_activate then
2776 def.after_activate(self, staticdata, def, dtime)
2779 if use_cmi then
2780 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
2781 cmi.notify_activate(self.object, dtime)
2786 -- main mob function
2787 local mob_step = function(self, dtime)
2789 if use_cmi then
2790 cmi.notify_step(self.object, dtime)
2793 local pos = self.object:get_pos()
2794 local yaw = 0
2796 -- when lifetimer expires remove mob (except npc and tamed)
2797 if self.type ~= "npc"
2798 and not self.tamed
2799 and self.state ~= "attack"
2800 and remove_far ~= true
2801 and self.lifetimer < 20000 then
2803 self.lifetimer = self.lifetimer - dtime
2805 if self.lifetimer <= 0 then
2807 -- only despawn away from player
2808 local objs = minetest.get_objects_inside_radius(pos, 15)
2810 for n = 1, #objs do
2812 if objs[n]:is_player() then
2814 self.lifetimer = 20
2816 return
2820 -- minetest.log("action",
2821 -- S("lifetimer expired, removed @1", self.name))
2823 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
2825 self.object:remove()
2827 return
2831 falling(self, pos)
2833 -- smooth rotation by ThomasMonroe314
2835 if self.delay and self.delay > 0 then
2837 local yaw = self.object:get_yaw()
2839 if self.delay == 1 then
2840 yaw = self.target_yaw
2841 else
2842 local dif = abs(yaw - self.target_yaw)
2844 if yaw > self.target_yaw then
2846 if dif > pi then
2847 dif = 2 * pi - dif -- need to add
2848 yaw = yaw + dif / self.delay
2849 else
2850 yaw = yaw - dif / self.delay -- need to subtract
2853 elseif yaw < self.target_yaw then
2855 if dif > pi then
2856 dif = 2 * pi - dif
2857 yaw = yaw - dif / self.delay -- need to subtract
2858 else
2859 yaw = yaw + dif / self.delay -- need to add
2863 if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
2864 if yaw < 0 then yaw = yaw + (pi * 2) end
2867 self.delay = self.delay - 1
2868 self.object:set_yaw(yaw)
2871 -- end rotation
2873 -- knockback timer
2874 if self.pause_timer > 0 then
2876 self.pause_timer = self.pause_timer - dtime
2878 return
2881 -- run custom function (defined in mob lua file)
2882 if self.do_custom then
2884 -- when false skip going any further
2885 if self.do_custom(self, dtime) == false then
2886 return
2890 -- attack timer
2891 self.timer = self.timer + dtime
2893 if self.state ~= "attack" then
2895 if self.timer < 1 then
2896 return
2899 self.timer = 0
2902 -- never go over 100
2903 if self.timer > 100 then
2904 self.timer = 1
2907 -- mob plays random sound at times
2908 if random(1, 100) == 1 then
2909 mob_sound(self, self.sounds.random)
2912 -- environmental damage timer (every 1 second)
2913 self.env_damage_timer = self.env_damage_timer + dtime
2915 if (self.state == "attack" and self.env_damage_timer > 1)
2916 or self.state ~= "attack" then
2918 self.env_damage_timer = 0
2920 -- check for environmental damage (water, fire, lava etc.)
2921 do_env_damage(self)
2923 -- node replace check (cow eats grass etc.)
2924 replace(self, pos)
2927 monster_attack(self)
2929 npc_attack(self)
2931 breed(self)
2933 follow_flop(self)
2935 do_states(self, dtime)
2937 do_jump(self)
2939 runaway_from(self)
2944 -- default function when mobs are blown up with TNT
2945 local do_tnt = function(obj, damage)
2947 --print ("----- Damage", damage)
2949 obj.object:punch(obj.object, 1.0, {
2950 full_punch_interval = 1.0,
2951 damage_groups = {fleshy = damage},
2952 }, nil)
2954 return false, true, {}
2958 mobs.spawning_mobs = {}
2960 -- Code to execute before custom on_rightclick handling
2961 local on_rightclick_prefix = function(self, clicker)
2962 local item = clicker:get_wielded_item()
2964 -- Name mob with nametag
2965 if not self.ignores_nametag and item:get_name() == "mobs:nametag" then
2967 local tag = item:get_meta():get_string("name")
2968 if tag ~= "" then
2969 if string.len(tag) > MAX_MOB_NAME_LENGTH then
2970 tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH)
2972 self.nametag = tag
2974 update_tag(self)
2976 if not mobs.is_creative(name) then
2977 item:take_item()
2978 player:set_wielded_item(item)
2980 return true
2984 return false
2987 local create_mob_on_rightclick = function(on_rightclick)
2988 return function(self, clicker)
2989 local stop = on_rightclick_prefix(self, clicker)
2990 if (not stop) and (on_rightclick) then
2991 on_rightclick(self, clicker)
2996 -- register mob entity
2997 function mobs:register_mob(name, def)
2999 mobs.spawning_mobs[name] = true
3001 minetest.register_entity(name, {
3003 stepheight = def.stepheight or 1.1, -- was 0.6
3004 name = name,
3005 type = def.type,
3006 attack_type = def.attack_type,
3007 fly = def.fly,
3008 fly_in = def.fly_in or "air",
3009 owner = def.owner or "",
3010 order = def.order or "",
3011 on_die = def.on_die,
3012 do_custom = def.do_custom,
3013 jump_height = def.jump_height or 4, -- was 6
3014 drawtype = def.drawtype, -- DEPRECATED, use rotate instead
3015 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
3016 lifetimer = def.lifetimer or 180, -- 3 minutes
3017 hp_min = max(1, (def.hp_min or 5) * difficulty),
3018 hp_max = max(1, (def.hp_max or 10) * difficulty),
3019 physical = true,
3020 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3021 selectionbox = def.selectionbox or def.collisionbox,
3022 visual = def.visual,
3023 visual_size = def.visual_size or {x = 1, y = 1},
3024 mesh = def.mesh,
3025 makes_footstep_sound = def.makes_footstep_sound or false,
3026 view_range = def.view_range or 5,
3027 walk_velocity = def.walk_velocity or 1,
3028 run_velocity = def.run_velocity or 2,
3029 damage = max(0, (def.damage or 0) * difficulty),
3030 light_damage = def.light_damage or 0,
3031 sunlight_damage = def.sunlight_damage or 0,
3032 water_damage = def.water_damage or 0,
3033 lava_damage = def.lava_damage or 0,
3034 suffocation = def.suffocation or 2,
3035 fall_damage = def.fall_damage or 1,
3036 fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
3037 drops = def.drops or {},
3038 armor = def.armor or 100,
3039 on_rightclick = create_mob_on_rightclick(def.on_rightclick),
3040 arrow = def.arrow,
3041 shoot_interval = def.shoot_interval,
3042 sounds = def.sounds or {},
3043 animation = def.animation,
3044 follow = def.follow,
3045 jump = def.jump ~= false,
3046 walk_chance = def.walk_chance or 50,
3047 attacks_monsters = def.attacks_monsters or false,
3048 group_attack = def.group_attack or false,
3049 passive = def.passive or false,
3050 knock_back = def.knock_back ~= false,
3051 blood_amount = def.blood_amount or 5,
3052 blood_texture = def.blood_texture or "mobs_blood.png",
3053 shoot_offset = def.shoot_offset or 0,
3054 floats = def.floats or 1, -- floats in water by default
3055 replace_rate = def.replace_rate,
3056 replace_what = def.replace_what,
3057 replace_with = def.replace_with,
3058 replace_offset = def.replace_offset or 0,
3059 on_replace = def.on_replace,
3060 timer = 0,
3061 env_damage_timer = 0, -- only used when state = "attack"
3062 tamed = false,
3063 pause_timer = 0,
3064 horny = false,
3065 hornytimer = 0,
3066 child = false,
3067 gotten = false,
3068 health = 0,
3069 reach = def.reach or 3,
3070 htimer = 0,
3071 texture_list = def.textures,
3072 child_texture = def.child_texture,
3073 docile_by_day = def.docile_by_day or false,
3074 time_of_day = 0.5,
3075 fear_height = def.fear_height or 0,
3076 runaway = def.runaway,
3077 runaway_timer = 0,
3078 pathfinding = def.pathfinding,
3079 immune_to = def.immune_to or {},
3080 explosion_radius = def.explosion_radius,
3081 explosion_damage_radius = def.explosion_damage_radius,
3082 explosion_timer = def.explosion_timer or 3,
3083 allow_fuse_reset = def.allow_fuse_reset ~= false,
3084 stop_to_explode = def.stop_to_explode ~= false,
3085 custom_attack = def.custom_attack,
3086 double_melee_attack = def.double_melee_attack,
3087 dogshoot_switch = def.dogshoot_switch,
3088 dogshoot_count = 0,
3089 dogshoot_count_max = def.dogshoot_count_max or 5,
3090 dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
3091 attack_animals = def.attack_animals or false,
3092 specific_attack = def.specific_attack,
3093 runaway_from = def.runaway_from,
3094 owner_loyal = def.owner_loyal,
3095 facing_fence = false,
3096 _cmi_is_mob = true,
3098 -- MCL2 extensions
3099 ignores_nametag = def.ignores_nametag or false,
3100 rain_damage = def.rain_damage or 0,
3102 on_spawn = def.on_spawn,
3104 on_blast = def.on_blast or do_tnt,
3106 on_step = mob_step,
3108 do_punch = def.do_punch,
3110 on_punch = mob_punch,
3112 on_breed = def.on_breed,
3114 on_grown = def.on_grown,
3116 on_activate = function(self, staticdata, dtime)
3117 return mob_activate(self, staticdata, def, dtime)
3118 end,
3120 get_staticdata = function(self)
3121 return mob_staticdata(self)
3122 end,
3126 end -- END mobs:register_mob function
3129 -- count how many mobs of one type are inside an area
3130 local count_mobs = function(pos, type)
3132 local num_type = 0
3133 local num_total = 0
3134 local objs = minetest.get_objects_inside_radius(pos, aoc_range)
3136 for n = 1, #objs do
3138 if not objs[n]:is_player() then
3140 local obj = objs[n]:get_luaentity()
3142 -- count mob type and add to total also
3143 if obj and obj.name and obj.name == type then
3145 num_type = num_type + 1
3146 num_total = num_total + 1
3148 -- add to total mobs
3149 elseif obj and obj.name and obj.health ~= nil then
3151 num_total = num_total + 1
3156 return num_type, num_total
3160 -- global functions
3162 function mobs:spawn_abm_check(pos, node, name)
3163 -- global function to add additional spawn checks
3164 -- return true to stop spawning mob
3168 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3169 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3171 -- Do mobs spawn at all?
3172 if not mobs_spawn then
3173 return
3176 -- chance/spawn number override in minetest.conf for registered mob
3177 local numbers = minetest.settings:get(name)
3179 if numbers then
3180 numbers = numbers:split(",")
3181 chance = tonumber(numbers[1]) or chance
3182 aoc = tonumber(numbers[2]) or aoc
3184 if chance == 0 then
3185 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3186 return
3189 minetest.log("action",
3190 string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
3194 minetest.register_abm({
3196 label = name .. " spawning",
3197 nodenames = nodes,
3198 neighbors = neighbors,
3199 interval = interval,
3200 chance = max(1, (chance * mob_chance_multiplier)),
3201 catch_up = false,
3203 action = function(pos, node, active_object_count, active_object_count_wider)
3205 -- is mob actually registered?
3206 if not mobs.spawning_mobs[name]
3207 or not minetest.registered_entities[name] then
3208 --print ("--- mob doesn't exist", name)
3209 return
3212 -- additional custom checks for spawning mob
3213 if mobs:spawn_abm_check(pos, node, name) == true then
3214 return
3217 -- do not spawn if too many of same mob in area
3218 if active_object_count_wider >= max_per_block
3219 or count_mobs(pos, name) >= aoc then
3220 --print ("--- too many entities", name, aoc, active_object_count_wider)
3221 return
3224 -- if toggle set to nil then ignore day/night check
3225 if day_toggle ~= nil then
3227 local tod = (minetest.get_timeofday() or 0) * 24000
3229 if tod > 4500 and tod < 19500 then
3230 -- daylight, but mob wants night
3231 if day_toggle == false then
3232 --print ("--- mob needs night", name)
3233 return
3235 else
3236 -- night time but mob wants day
3237 if day_toggle == true then
3238 --print ("--- mob needs day", name)
3239 return
3244 -- spawn above node
3245 pos.y = pos.y + 1
3247 -- only spawn away from player
3248 local objs = minetest.get_objects_inside_radius(pos, 10)
3250 for n = 1, #objs do
3252 if objs[n]:is_player() then
3253 --print ("--- player too close", name)
3254 return
3258 -- mobs cannot spawn in protected areas when enabled
3259 if not spawn_protected
3260 and minetest.is_protected(pos, "") then
3261 --print ("--- inside protected area", name)
3262 return
3265 -- are we spawning within height limits?
3266 if pos.y > max_height
3267 or pos.y < min_height then
3268 --print ("--- height limits not met", name, pos.y)
3269 return
3272 -- are light levels ok?
3273 local light = minetest.get_node_light(pos)
3274 if not light
3275 or light > max_light
3276 or light < min_light then
3277 --print ("--- light limits not met", name, light)
3278 return
3281 -- do we have enough height clearance to spawn mob?
3282 local ent = minetest.registered_entities[name]
3283 local height = max(0, math.ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
3285 for n = 0, height do
3287 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
3289 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3290 --print ("--- inside block", name, node_ok(pos2).name)
3291 return
3295 -- spawn mob half block higher than ground
3296 pos.y = pos.y + 0.5
3298 local mob = minetest.add_entity(pos, name)
3299 --[[
3300 print ("[mobs] Spawned " .. name .. " at "
3301 .. minetest.pos_to_string(pos) .. " on "
3302 .. node.name .. " near " .. neighbors[1])
3304 if on_spawn then
3306 local ent = mob:get_luaentity()
3308 on_spawn(ent, pos)
3315 -- compatibility with older mob registration
3316 function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
3318 mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
3319 chance, active_object_count, -31000, max_height, day_toggle)
3323 -- MarkBu's spawn function
3324 function mobs:spawn(def)
3326 local name = def.name
3327 local nodes = def.nodes or {"group:soil", "group:stone"}
3328 local neighbors = def.neighbors or {"air"}
3329 local min_light = def.min_light or 0
3330 local max_light = def.max_light or 15
3331 local interval = def.interval or 30
3332 local chance = def.chance or 5000
3333 local active_object_count = def.active_object_count or 1
3334 local min_height = def.min_height or -31000
3335 local max_height = def.max_height or 31000
3336 local day_toggle = def.day_toggle
3337 local on_spawn = def.on_spawn
3339 mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
3340 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
3344 -- register arrow for shoot attack
3345 function mobs:register_arrow(name, def)
3347 if not name or not def then return end -- errorcheck
3349 minetest.register_entity(name, {
3351 physical = false,
3352 visual = def.visual,
3353 visual_size = def.visual_size,
3354 textures = def.textures,
3355 velocity = def.velocity,
3356 hit_player = def.hit_player,
3357 hit_node = def.hit_node,
3358 hit_mob = def.hit_mob,
3359 drop = def.drop or false, -- drops arrow as registered item when true
3360 collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
3361 timer = 0,
3362 switch = 0,
3363 owner_id = def.owner_id,
3364 rotate = def.rotate,
3365 automatic_face_movement_dir = def.rotate
3366 and (def.rotate - (pi / 180)) or false,
3368 on_activate = def.on_activate,
3370 on_step = def.on_step or function(self, dtime)
3372 self.timer = self.timer + 1
3374 local pos = self.object:get_pos()
3376 if self.switch == 0
3377 or self.timer > 150
3378 or not within_limits(pos, 0) then
3380 self.object:remove() ; -- print ("removed arrow")
3382 return
3385 -- does arrow have a tail (fireball)
3386 if def.tail
3387 and def.tail == 1
3388 and def.tail_texture then
3390 minetest.add_particle({
3391 pos = pos,
3392 velocity = {x = 0, y = 0, z = 0},
3393 acceleration = {x = 0, y = 0, z = 0},
3394 expirationtime = def.expire or 0.25,
3395 collisiondetection = false,
3396 texture = def.tail_texture,
3397 size = def.tail_size or 5,
3398 glow = def.glow or 0,
3402 if self.hit_node then
3404 local node = node_ok(pos).name
3406 if minetest.registered_nodes[node].walkable then
3408 self.hit_node(self, pos, node)
3410 if self.drop == true then
3412 pos.y = pos.y + 1
3414 self.lastpos = (self.lastpos or pos)
3416 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
3419 self.object:remove() ; -- print ("hit node")
3421 return
3425 if self.hit_player or self.hit_mob then
3427 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
3429 if self.hit_player
3430 and player:is_player() then
3432 self.hit_player(self, player)
3433 self.object:remove() ; -- print ("hit player")
3434 return
3437 local entity = player:get_luaentity()
3439 if entity
3440 and self.hit_mob
3441 and entity._cmi_is_mob == true
3442 and tostring(player) ~= self.owner_id
3443 and entity.name ~= self.object:get_luaentity().name then
3445 self.hit_mob(self, player)
3447 self.object:remove() ; --print ("hit mob")
3449 return
3454 self.lastpos = pos
3460 -- compatibility function
3461 function mobs:explosion(pos, radius)
3462 local self = {sounds = {}}
3463 self.sounds.explode = "tnt_explode"
3464 mobs:boom(self, pos, radius)
3468 -- no damage to nodes explosion
3469 function mobs:safe_boom(self, pos, radius)
3471 minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
3472 pos = pos,
3473 gain = 1.0,
3474 max_hear_distance = self.sounds and self.sounds.distance or 32
3477 entity_physics(pos, radius)
3478 effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
3482 -- make explosion with protection and tnt mod check
3483 function mobs:boom(self, pos, radius)
3485 if mobs_griefing
3486 and mod_tnt and tnt and tnt.boom
3487 and not minetest.is_protected(pos, "") then
3489 tnt.boom(pos, {
3490 radius = radius,
3491 damage_radius = radius,
3492 sound = self.sounds and self.sounds.explode,
3493 explode_center = true,
3495 else
3496 mobs:safe_boom(self, pos, radius)
3501 -- Register spawn eggs
3503 -- Note: This also introduces the “spawn_egg” group:
3504 -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
3505 -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
3506 function mobs:register_egg(mob, desc, background, addegg, no_creative)
3508 local grp = {spawn_egg = 1}
3510 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3511 if creative and no_creative == true then
3512 grp.not_in_creative_inventory = 1
3515 local invimg = background
3517 if addegg == 1 then
3518 invimg = "mobs_chicken_egg.png^(" .. invimg ..
3519 "^[mask:mobs_chicken_egg_overlay.png)"
3522 -- register new spawn egg containing mob information
3523 --[=[ DISABLED IN MCL2
3524 minetest.register_craftitem(mob .. "_set", {
3526 description = S("@1 (Tamed)", desc),
3527 inventory_image = invimg,
3528 groups = {spawn_egg = 2, not_in_creative_inventory = 1},
3529 stack_max = 1,
3531 on_place = function(itemstack, placer, pointed_thing)
3533 local pos = pointed_thing.above
3535 -- am I clicking on something with existing on_rightclick function?
3536 local under = minetest.get_node(pointed_thing.under)
3537 local def = minetest.registered_nodes[under.name]
3538 if def and def.on_rightclick then
3539 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3542 if pos
3543 and within_limits(pos, 0)
3544 and not minetest.is_protected(pos, placer:get_player_name()) then
3546 if not minetest.registered_entities[mob] then
3547 return
3550 pos.y = pos.y + 1
3552 local data = itemstack:get_metadata()
3553 local mob = minetest.add_entity(pos, mob, data)
3554 local ent = mob:get_luaentity()
3556 -- set owner if not a monster
3557 if ent.type ~= "monster" then
3558 ent.owner = placer:get_player_name()
3559 ent.tamed = true
3562 -- since mob is unique we remove egg once spawned
3563 itemstack:take_item()
3566 return itemstack
3567 end,
3571 -- register old stackable mob egg
3572 minetest.register_craftitem(mob, {
3574 description = desc,
3575 inventory_image = invimg,
3576 groups = grp,
3578 _doc_items_longdesc = "This allows you to place a single mob.",
3579 _doc_items_usagehelp = "Just place it where you want the mob to appear. Animals will spawn tamed, unless you hold down the sneak key while placing. If you place this on a mob spawner, you change the mob it spawns.",
3581 on_place = function(itemstack, placer, pointed_thing)
3583 local pos = pointed_thing.above
3585 -- am I clicking on something with existing on_rightclick function?
3586 local under = minetest.get_node(pointed_thing.under)
3587 local def = minetest.registered_nodes[under.name]
3588 if def and def.on_rightclick then
3589 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3592 if pos
3593 and within_limits(pos, 0)
3594 and not minetest.is_protected(pos, placer:get_player_name()) then
3596 local name = placer:get_player_name()
3597 local privs = minetest.get_player_privs(name)
3598 if not privs.maphack then
3599 minetest.chat_send_player(name, "You need the “maphack” privilege to change the mob spawner.")
3600 return itemstack
3602 if mod_mobspawners and under.name == "mcl_mobspawners:spawner" then
3603 mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name())
3604 if not minetest.settings:get_bool("creative_mode") then
3605 itemstack:take_item()
3607 return itemstack
3610 if not minetest.registered_entities[mob] then
3611 return itemstack
3614 pos.y = pos.y + 1
3616 local mob = minetest.add_entity(pos, mob)
3617 local ent = mob:get_luaentity()
3619 -- don't set owner if monster or sneak pressed
3620 if ent.type ~= "monster"
3621 and not placer:get_player_control().sneak then
3622 ent.owner = placer:get_player_name()
3623 ent.tamed = true
3626 -- set nametag
3627 local nametag = itemstack:get_meta():get_string("name")
3628 if nametag ~= "" then
3629 if string.len(nametag) > MAX_MOB_NAME_LENGTH then
3630 nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
3632 ent.nametag = nametag
3633 update_tag(ent)
3636 -- if not in creative then take item
3637 if not mobs.is_creative(placer:get_player_name()) then
3638 itemstack:take_item()
3642 return itemstack
3643 end,
3649 -- capture critter (thanks to blert2112 for idea)
3650 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
3651 return false
3652 --[=[ DISABLED IN MCL2
3653 if self.child
3654 or not clicker:is_player()
3655 or not clicker:get_inventory() then
3656 return false
3659 -- get name of clicked mob
3660 local mobname = self.name
3662 -- if not nil change what will be added to inventory
3663 if replacewith then
3664 mobname = replacewith
3667 local name = clicker:get_player_name()
3668 local tool = clicker:get_wielded_item()
3670 -- are we using hand, net or lasso to pick up mob?
3671 if tool:get_name() ~= ""
3672 and tool:get_name() ~= "mobs:net"
3673 and tool:get_name() ~= "mobs:lasso" then
3674 return false
3677 -- is mob tamed?
3678 if self.tamed == false
3679 and force_take == false then
3681 minetest.chat_send_player(name, S("Not tamed!"))
3683 return true -- false
3686 -- cannot pick up if not owner
3687 if self.owner ~= name
3688 and force_take == false then
3690 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
3692 return true -- false
3695 if clicker:get_inventory():room_for_item("main", mobname) then
3697 -- was mob clicked with hand, net, or lasso?
3698 local chance = 0
3700 if tool:get_name() == "" then
3701 chance = chance_hand
3703 elseif tool:get_name() == "mobs:net" then
3705 chance = chance_net
3707 tool:add_wear(4000) -- 17 uses
3709 clicker:set_wielded_item(tool)
3711 elseif tool:get_name() == "mobs:lasso" then
3713 chance = chance_lasso
3715 tool:add_wear(650) -- 100 uses
3717 clicker:set_wielded_item(tool)
3721 -- calculate chance.. add to inventory if successful?
3722 if chance > 0 and random(1, 100) <= chance then
3724 -- default mob egg
3725 local new_stack = ItemStack(mobname)
3727 -- add special mob egg with all mob information
3728 -- unless 'replacewith' contains new item to use
3729 if not replacewith then
3731 new_stack = ItemStack(mobname .. "_set")
3733 local tmp = {}
3735 for _,stat in pairs(self) do
3736 local t = type(stat)
3737 if t ~= "function"
3738 and t ~= "nil"
3739 and t ~= "userdata" then
3740 tmp[_] = self[_]
3744 local data_str = minetest.serialize(tmp)
3746 new_stack:set_metadata(data_str)
3749 local inv = clicker:get_inventory()
3751 if inv:room_for_item("main", new_stack) then
3752 inv:add_item("main", new_stack)
3753 else
3754 minetest.add_item(clicker:get_pos(), new_stack)
3757 self.object:remove()
3759 mob_sound(self, "default_place_node_hard")
3761 elseif chance ~= 0 then
3762 minetest.chat_send_player(name, S("Missed!"))
3764 mob_sound(self, "mobs_swing")
3768 return true
3773 -- protect tamed mob with rune item
3774 function mobs:protect(self, clicker)
3775 local name = clicker:get_player_name()
3776 local tool = clicker:get_wielded_item()
3778 if tool:get_name() ~= "mobs:protector" then
3779 return false
3782 if self.tamed == false then
3783 minetest.chat_send_player(name, S("Not tamed!"))
3784 return true -- false
3787 if self.protected == true then
3788 minetest.chat_send_player(name, S("Already protected!"))
3789 return true -- false
3792 if not mobs.is_creative(clicker:get_player_name()) then
3793 tool:take_item() -- take 1 protection rune
3794 clicker:set_wielded_item(tool)
3797 self.protected = true
3799 local pos = self.object:get_pos()
3800 pos.y = pos.y + self.collisionbox[2] + 0.5
3802 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3804 mob_sound(self, "mobs_spell")
3806 return true
3810 local mob_obj = {}
3811 local mob_sta = {}
3813 -- feeding, taming and breeding (thanks blert2112)
3814 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
3815 if not self.follow then
3816 return false
3819 -- can eat/tame with item in hand
3820 if follow_holding(self, clicker) then
3822 -- if not in creative then take item
3823 if not mobs.is_creative(clicker:get_player_name()) then
3825 local item = clicker:get_wielded_item()
3827 item:take_item()
3829 clicker:set_wielded_item(item)
3832 -- increase health
3833 self.health = self.health + 4
3835 if self.health >= self.hp_max then
3837 self.health = self.hp_max
3839 if self.htimer < 1 then
3840 -- DISABLED IN MCL2
3841 --[=[
3842 minetest.chat_send_player(clicker:get_player_name(),
3843 S("@1 at full health (@2)",
3844 self.name:split(":")[2], tostring(self.health)))
3846 self.htimer = 5
3850 self.object:set_hp(self.health)
3852 update_tag(self)
3854 -- make children grow quicker
3855 if self.child == true then
3857 self.hornytimer = self.hornytimer + 20
3859 return true
3862 -- feed and tame
3863 self.food = (self.food or 0) + 1
3864 if self.food >= feed_count then
3866 self.food = 0
3868 if breed and self.hornytimer == 0 then
3869 self.horny = true
3872 self.gotten = false
3874 if tame then
3876 if self.tamed == false then
3877 --[[ DISABLED IN MCL2
3878 minetest.chat_send_player(clicker:get_player_name(),
3879 S("@1 has been tamed!",
3880 self.name:split(":")[2]))
3884 self.tamed = true
3886 if not self.owner or self.owner == "" then
3887 self.owner = clicker:get_player_name()
3891 -- make sound when fed so many times
3892 mob_sound(self, self.sounds.random)
3895 return true
3898 return false
3901 -- Spawn a child
3902 function mobs:spawn_child(pos, mob_type)
3903 local child = minetest.add_entity(pos, mob_type)
3904 if not child then
3905 return
3908 local ent = child:get_luaentity()
3909 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
3911 ent.child = true
3913 local textures
3914 -- using specific child texture (if found)
3915 if ent.child_texture then
3916 textures = ent.child_texture[1]
3919 -- and resize to half height
3920 child:set_properties({
3921 textures = textures,
3922 visual_size = {
3923 x = ent.base_size.x * .5,
3924 y = ent.base_size.y * .5,
3926 collisionbox = {
3927 ent.base_colbox[1] * .5,
3928 ent.base_colbox[2] * .5,
3929 ent.base_colbox[3] * .5,
3930 ent.base_colbox[4] * .5,
3931 ent.base_colbox[5] * .5,
3932 ent.base_colbox[6] * .5,
3934 selectionbox = {
3935 ent.base_selbox[1] * .5,
3936 ent.base_selbox[2] * .5,
3937 ent.base_selbox[3] * .5,
3938 ent.base_selbox[4] * .5,
3939 ent.base_selbox[5] * .5,
3940 ent.base_selbox[6] * .5,
3944 return child
3948 -- DISABLED IN MCL2
3949 --[=[
3950 -- inspired by blockmen's nametag mod
3951 minetest.register_on_player_receive_fields(function(player, formname, fields)
3953 -- right-clicked with nametag and name entered?
3954 if formname == "mobs_nametag"
3955 and fields.name
3956 and fields.name ~= "" then
3958 local name = player:get_player_name()
3960 if not mob_obj[name]
3961 or not mob_obj[name].object then
3962 return
3965 -- make sure nametag is being used to name mob
3966 local item = player:get_wielded_item()
3968 if item:get_name() ~= "mobs:nametag" then
3969 return
3972 -- limit name entered to 64 characters long
3973 if string.len(fields.name) > 64 then
3974 fields.name = string.sub(fields.name, 1, 64)
3977 -- update nametag
3978 mob_obj[name].nametag = fields.name
3980 update_tag(mob_obj[name])
3982 -- if not in creative then take item
3983 if not mobs.is_creative(name) then
3985 mob_sta[name]:take_item()
3987 player:set_wielded_item(mob_sta[name])
3990 -- reset external variables
3991 mob_obj[name] = nil
3992 mob_sta[name] = nil
3994 end)
3998 -- compatibility function for old entities to new modpack entities
3999 function mobs:alias_mob(old_name, new_name)
4001 -- spawn egg
4002 minetest.register_alias(old_name, new_name)
4004 -- entity
4005 minetest.register_entity(":" .. old_name, {
4007 physical = false,
4009 on_step = function(self)
4011 if minetest.registered_entities[new_name] then
4012 minetest.add_entity(self.object:get_pos(), new_name)
4015 self.object:remove()