Fix light_damage damaging mobs in rain/snow
[MineClone/MineClone2.git] / mods / ENTITIES / mobs / api.lua
blobdfbf42058b6d1b7cff331b42731f0339375a1ab5
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
620 -- bright light harms mob
621 if self.light_damage ~= 0
622 and (minetest.get_node_light(pos) or 0) > 12 then
624 if not (mod_weather and (mcl_weather.rain.raining or mcl_weather.state == "snow") and mcl_weather.is_outdoor(pos)) then
626 self.health = self.health - self.light_damage
628 effect(pos, 5, "tnt_smoke.png")
630 if check_for_death(self, "light", {type = "light"}) then return end
635 local y_level = self.collisionbox[2]
637 if self.child then
638 y_level = self.collisionbox[2] * 0.5
641 -- what is mob standing in?
642 pos.y = pos.y + y_level + 0.25 -- foot level
643 self.standing_in = node_ok(pos, "air").name
644 -- print ("standing in " .. self.standing_in)
646 -- don't fall when on ignore, just stand still
647 if self.standing_in == "ignore" then
648 self.object:setvelocity({x = 0, y = 0, z = 0})
651 local nodef = minetest.registered_nodes[self.standing_in]
653 -- rain
654 if self.rain_damage and mod_weather then
655 if mcl_weather.rain.raining and mcl_weather.is_outdoor(pos) then
657 self.health = self.health - self.rain_damage
659 if check_for_death(self, "rain", {type = "environment",
660 pos = pos, node = self.standing_in}) then return end
664 pos.y = pos.y + 1 -- for particle effect position
666 -- water
667 if self.water_damage
668 and nodef.groups.water then
670 if self.water_damage ~= 0 then
672 self.health = self.health - self.water_damage
674 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
676 if check_for_death(self, "water", {type = "environment",
677 pos = pos, node = self.standing_in}) then return end
680 -- lava or fire
681 elseif self.lava_damage
682 and (nodef.groups.lava
683 or self.standing_in == node_fire
684 or self.standing_in == node_permanent_flame) then
686 if self.lava_damage ~= 0 then
688 self.health = self.health - self.lava_damage
690 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
692 if check_for_death(self, "lava", {type = "environment",
693 pos = pos, node = self.standing_in}) then return end
696 -- damage_per_second node check
697 elseif nodef.damage_per_second ~= 0 then
699 self.health = self.health - nodef.damage_per_second
701 effect(pos, 5, "tnt_smoke.png")
703 if check_for_death(self, "dps", {type = "environment",
704 pos = pos, node = self.standing_in}) then return end
706 --[[
707 --- suffocation inside solid node
708 if self.suffocation ~= 0
709 and nodef.walkable == true
710 and nodef.groups.disable_suffocation ~= 1
711 and nodef.drawtype == "normal" then
713 self.health = self.health - self.suffocation
715 if check_for_death(self, "suffocation", {type = "environment",
716 pos = pos, node = self.standing_in}) then return end
719 check_for_death(self, "", {type = "unknown"})
723 -- jump if facing a solid node (not fences or gates)
724 local do_jump = function(self)
726 if not self.jump
727 or self.jump_height == 0
728 or self.fly
729 or self.child
730 or self.order == "stand" then
731 return false
734 self.facing_fence = false
736 -- something stopping us while moving?
737 if self.state ~= "stand"
738 and get_velocity(self) > 0.5
739 and self.object:getvelocity().y ~= 0 then
740 return false
743 local pos = self.object:get_pos()
744 local yaw = self.object:get_yaw()
746 -- what is mob standing on?
747 pos.y = pos.y + self.collisionbox[2] - 0.2
749 local nod = node_ok(pos)
751 --print ("standing on:", nod.name, pos.y)
753 if minetest.registered_nodes[nod.name].walkable == false then
754 return false
757 -- where is front
758 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
759 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
761 -- what is in front of mob?
762 local nod = node_ok({
763 x = pos.x + dir_x,
764 y = pos.y + 0.5,
765 z = pos.z + dir_z
768 -- thin blocks that do not need to be jumped
769 if nod.name == node_snow then
770 return false
773 --print ("in front:", nod.name, pos.y + 0.5)
775 if self.walk_chance == 0
776 or minetest.registered_items[nod.name].walkable then
778 if not nod.name:find("fence")
779 and not nod.name:find("gate") then
781 local v = self.object:getvelocity()
783 v.y = self.jump_height
785 set_animation(self, "jump") -- only when defined
787 self.object:setvelocity(v)
789 -- when in air move forward
790 minetest.after(0.3, function(self, v)
791 -- self.object:setvelocity({
792 self.object:set_acceleration({
793 x = v.x * 2,--1.5,
794 y = 0,
795 z = v.z * 2,--1.5
797 end, self, v)
799 if get_velocity(self) > 0 then
800 mob_sound(self, self.sounds.jump)
802 else
803 self.facing_fence = true
806 return true
809 return false
813 -- blast damage to entities nearby (modified from TNT mod)
814 local entity_physics = function(pos, radius)
816 radius = radius * 2
818 local objs = minetest.get_objects_inside_radius(pos, radius)
819 local obj_pos, dist
821 for n = 1, #objs do
823 obj_pos = objs[n]:get_pos()
825 dist = get_distance(pos, obj_pos)
826 if dist < 1 then dist = 1 end
828 local damage = floor((4 / dist) * radius)
829 local ent = objs[n]:get_luaentity()
831 -- punches work on entities AND players
832 objs[n]:punch(objs[n], 1.0, {
833 full_punch_interval = 1.0,
834 damage_groups = {fleshy = damage},
835 }, pos)
840 -- should mob follow what I'm holding ?
841 local follow_holding = function(self, clicker)
843 if mobs.invis[clicker:get_player_name()] then
844 return false
847 local item = clicker:get_wielded_item()
848 local t = type(self.follow)
850 -- single item
851 if t == "string"
852 and item:get_name() == self.follow then
853 return true
855 -- multiple items
856 elseif t == "table" then
858 for no = 1, #self.follow do
860 if self.follow[no] == item:get_name() then
861 return true
866 return false
870 -- find two animals of same type and breed if nearby and horny
871 local breed = function(self)
873 -- child takes 240 seconds before growing into adult
874 if self.child == true then
876 self.hornytimer = self.hornytimer + 1
878 if self.hornytimer > 240 then
880 self.child = false
881 self.hornytimer = 0
883 self.object:set_properties({
884 textures = self.base_texture,
885 mesh = self.base_mesh,
886 visual_size = self.base_size,
887 collisionbox = self.base_colbox,
888 selectionbox = self.base_selbox,
891 -- custom function when child grows up
892 if self.on_grown then
893 self.on_grown(self)
894 else
895 -- jump when fully grown so as not to fall into ground
896 self.object:setvelocity({
897 x = 0,
898 y = self.jump_height,
899 z = 0
904 return
907 -- horny animal can mate for 40 seconds,
908 -- afterwards horny animal cannot mate again for 200 seconds
909 if self.horny == true
910 and self.hornytimer < 240 then
912 self.hornytimer = self.hornytimer + 1
914 if self.hornytimer >= 240 then
915 self.hornytimer = 0
916 self.horny = false
920 -- find another same animal who is also horny and mate if nearby
921 if self.horny == true
922 and self.hornytimer <= 40 then
924 local pos = self.object:get_pos()
926 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
928 local objs = minetest.get_objects_inside_radius(pos, 3)
929 local num = 0
930 local ent = nil
932 for n = 1, #objs do
934 ent = objs[n]:get_luaentity()
936 -- check for same animal with different colour
937 local canmate = false
939 if ent then
941 if ent.name == self.name then
942 canmate = true
943 else
944 local entname = string.split(ent.name,":")
945 local selfname = string.split(self.name,":")
947 if entname[1] == selfname[1] then
948 entname = string.split(entname[2],"_")
949 selfname = string.split(selfname[2],"_")
951 if entname[1] == selfname[1] then
952 canmate = true
958 if ent
959 and canmate == true
960 and ent.horny == true
961 and ent.hornytimer <= 40 then
962 num = num + 1
965 -- found your mate? then have a baby
966 if num > 1 then
968 self.hornytimer = 41
969 ent.hornytimer = 41
971 -- spawn baby
972 minetest.after(5, function()
974 -- custom breed function
975 if self.on_breed then
976 -- when false skip going any further
977 if self.on_breed(self, ent) == false then
978 return
982 local child = mobs:spawn_child(pos, self.name)
984 local ent_c = child:get_luaentity()
986 -- Use parent's texture
987 local textures = self.base_texture
988 child:set_properties({
989 textures = textures,
992 -- tamed and owned by parents' owner
993 ent_c.tamed = true
994 ent_c.owner = self.owner
995 end)
997 num = 0
999 break
1006 -- find and replace what mob is looking for (grass, wheat etc.)
1007 local replace = function(self, pos)
1009 if not mobs_griefing
1010 or not self.replace_rate
1011 or not self.replace_what
1012 or self.child == true
1013 or self.object:getvelocity().y ~= 0
1014 or random(1, self.replace_rate) > 1 then
1015 return
1018 local what, with, y_offset
1020 if type(self.replace_what[1]) == "table" then
1022 local num = random(#self.replace_what)
1024 what = self.replace_what[num][1] or ""
1025 with = self.replace_what[num][2] or ""
1026 y_offset = self.replace_what[num][3] or 0
1027 else
1028 what = self.replace_what
1029 with = self.replace_with or ""
1030 y_offset = self.replace_offset or 0
1033 pos.y = pos.y + y_offset
1035 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
1037 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1039 local oldnode = {name = what}
1040 local newnode = {name = with}
1041 local on_replace_return
1043 if self.on_replace then
1044 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1047 if on_replace_return ~= false then
1049 minetest.set_node(pos, {name = with})
1051 -- when cow/sheep eats grass, replace wool and milk
1052 if self.gotten == true then
1053 self.gotten = false
1054 self.object:set_properties(self)
1061 -- check if daytime and also if mob is docile during daylight hours
1062 local day_docile = function(self)
1064 if self.docile_by_day == false then
1066 return false
1068 elseif self.docile_by_day == true
1069 and self.time_of_day > 0.2
1070 and self.time_of_day < 0.8 then
1072 return true
1077 local los_switcher = false
1078 local height_switcher = false
1080 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1081 local smart_mobs = function(self, s, p, dist, dtime)
1083 local s1 = self.path.lastpos
1085 local target_pos = self.attack:get_pos()
1087 -- is it becoming stuck?
1088 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
1089 self.path.stuck_timer = self.path.stuck_timer + dtime
1090 else
1091 self.path.stuck_timer = 0
1094 self.path.lastpos = {x = s.x, y = s.y, z = s.z}
1096 local use_pathfind = false
1097 local has_lineofsight = minetest.line_of_sight(
1098 {x = s.x, y = (s.y) + .5, z = s.z},
1099 {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
1101 -- im stuck, search for path
1102 if not has_lineofsight then
1104 if los_switcher == true then
1105 use_pathfind = true
1106 los_switcher = false
1107 end -- cannot see target!
1108 else
1109 if los_switcher == false then
1111 los_switcher = true
1112 use_pathfind = false
1114 minetest.after(1, function(self)
1115 if has_lineofsight then self.path.following = false end
1116 end, self)
1117 end -- can see target!
1120 if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
1122 use_pathfind = true
1123 self.path.stuck_timer = 0
1125 minetest.after(1, function(self)
1126 if has_lineofsight then self.path.following = false end
1127 end, self)
1130 if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
1132 use_pathfind = true
1133 self.path.stuck_timer = 0
1135 minetest.after(1, function(self)
1136 if has_lineofsight then self.path.following = false end
1137 end, self)
1140 if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
1142 if height_switcher then
1143 use_pathfind = true
1144 height_switcher = false
1146 else
1147 if not height_switcher then
1148 use_pathfind = false
1149 height_switcher = true
1153 if use_pathfind then
1154 -- lets try find a path, first take care of positions
1155 -- since pathfinder is very sensitive
1156 local sheight = self.collisionbox[5] - self.collisionbox[2]
1158 -- round position to center of node to avoid stuck in walls
1159 -- also adjust height for player models!
1160 s.x = floor(s.x + 0.5)
1161 -- s.y = floor(s.y + 0.5) - sheight
1162 s.z = floor(s.z + 0.5)
1164 local ssight, sground = minetest.line_of_sight(s, {
1165 x = s.x, y = s.y - 4, z = s.z}, 1)
1167 -- determine node above ground
1168 if not ssight then
1169 s.y = sground.y + 1
1172 local p1 = self.attack:get_pos()
1174 p1.x = floor(p1.x + 0.5)
1175 p1.y = floor(p1.y + 0.5)
1176 p1.z = floor(p1.z + 0.5)
1178 local dropheight = 6
1179 if self.fear_height ~= 0 then dropheight = self.fear_height end
1181 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "A*_noprefetch")
1182 --[[
1183 -- show path using particles
1184 if self.path.way and #self.path.way > 0 then
1185 print ("-- path length:" .. tonumber(#self.path.way))
1186 for _,pos in pairs(self.path.way) do
1187 minetest.add_particle({
1188 pos = pos,
1189 velocity = {x=0, y=0, z=0},
1190 acceleration = {x=0, y=0, z=0},
1191 expirationtime = 1,
1192 size = 4,
1193 collisiondetection = false,
1194 vertical = false,
1195 texture = "heart.png",
1201 self.state = ""
1202 do_attack(self, self.attack)
1204 -- no path found, try something else
1205 if not self.path.way then
1207 self.path.following = false
1209 -- lets make way by digging/building if not accessible
1210 if self.pathfinding == 2 and mobs_griefing then
1212 -- is player higher than mob?
1213 if s.y < p1.y then
1215 -- build upwards
1216 if not minetest.is_protected(s, "") then
1218 local ndef1 = minetest.registered_nodes[self.standing_in]
1220 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1222 minetest.set_node(s, {name = mobs.fallback_node})
1226 local sheight = math.ceil(self.collisionbox[5]) + 1
1228 -- assume mob is 2 blocks high so it digs above its head
1229 s.y = s.y + sheight
1231 -- remove one block above to make room to jump
1232 if not minetest.is_protected(s, "") then
1234 local node1 = node_ok(s, "air").name
1235 local ndef1 = minetest.registered_nodes[node1]
1237 if node1 ~= "air"
1238 and node1 ~= "ignore"
1239 and ndef1
1240 and not ndef1.groups.level
1241 and not ndef1.groups.unbreakable
1242 and not ndef1.groups.liquid then
1244 minetest.set_node(s, {name = "air"})
1245 minetest.add_item(s, ItemStack(node1))
1250 s.y = s.y - sheight
1251 self.object:setpos({x = s.x, y = s.y + 2, z = s.z})
1253 else -- dig 2 blocks to make door toward player direction
1255 local yaw1 = self.object:get_yaw() + pi / 2
1256 local p1 = {
1257 x = s.x + cos(yaw1),
1258 y = s.y,
1259 z = s.z + sin(yaw1)
1262 if not minetest.is_protected(p1, "") then
1264 local node1 = node_ok(p1, "air").name
1265 local ndef1 = minetest.registered_nodes[node1]
1267 if node1 ~= "air"
1268 and node1 ~= "ignore"
1269 and ndef1
1270 and not ndef1.groups.level
1271 and not ndef1.groups.unbreakable
1272 and not ndef1.groups.liquid then
1274 minetest.add_item(p1, ItemStack(node1))
1275 minetest.set_node(p1, {name = "air"})
1278 p1.y = p1.y + 1
1279 node1 = node_ok(p1, "air").name
1280 ndef1 = minetest.registered_nodes[node1]
1282 if node1 ~= "air"
1283 and node1 ~= "ignore"
1284 and ndef1
1285 and not ndef1.groups.level
1286 and not ndef1.groups.unbreakable
1287 and not ndef1.groups.liquid then
1289 minetest.add_item(p1, ItemStack(node1))
1290 minetest.set_node(p1, {name = "air"})
1297 -- will try again in 2 second
1298 self.path.stuck_timer = stuck_timeout - 2
1300 -- frustration! cant find the damn path :(
1301 mob_sound(self, self.sounds.random)
1302 else
1303 -- yay i found path
1304 mob_sound(self, self.sounds.war_cry)
1305 set_velocity(self, self.walk_velocity)
1307 -- follow path now that it has it
1308 self.path.following = true
1314 -- specific attacks
1315 local specific_attack = function(list, what)
1317 -- no list so attack default (player, animals etc.)
1318 if list == nil then
1319 return true
1322 -- found entity on list to attack?
1323 for no = 1, #list do
1325 if list[no] == what then
1326 return true
1330 return false
1334 -- monster find someone to attack
1335 local monster_attack = function(self)
1337 if self.type ~= "monster"
1338 or not damage_enabled
1339 or creative
1340 or self.state == "attack"
1341 or day_docile(self) then
1342 return
1345 local s = self.object:get_pos()
1346 local p, sp, dist
1347 local player, obj, min_player
1348 local type, name = "", ""
1349 local min_dist = self.view_range + 1
1350 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1352 for n = 1, #objs do
1354 if objs[n]:is_player() then
1356 if mobs.invis[ objs[n]:get_player_name() ] then
1358 type = ""
1359 else
1360 player = objs[n]
1361 type = "player"
1362 name = "player"
1364 else
1365 obj = objs[n]:get_luaentity()
1367 if obj then
1368 player = obj.object
1369 type = obj.type
1370 name = obj.name or ""
1374 -- find specific mob to attack, failing that attack player/npc/animal
1375 if specific_attack(self.specific_attack, name)
1376 and (type == "player" or type == "npc"
1377 or (type == "animal" and self.attack_animals == true)) then
1379 p = player:get_pos()
1380 sp = s
1382 dist = get_distance(p, s)
1384 -- aim higher to make looking up hills more realistic
1385 p.y = p.y + 1
1386 sp.y = sp.y + 1
1389 -- choose closest player to attack
1390 if dist < min_dist
1391 and line_of_sight(self, sp, p, 2) == true then
1392 min_dist = dist
1393 min_player = player
1398 -- attack player
1399 if min_player then
1400 do_attack(self, min_player)
1405 -- npc, find closest monster to attack
1406 local npc_attack = function(self)
1408 if self.type ~= "npc"
1409 or not self.attacks_monsters
1410 or self.state == "attack" then
1411 return
1414 local p, sp, obj, min_player
1415 local s = self.object:get_pos()
1416 local min_dist = self.view_range + 1
1417 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1419 for n = 1, #objs do
1421 obj = objs[n]:get_luaentity()
1423 if obj and obj.type == "monster" then
1425 p = obj.object:get_pos()
1426 sp = s
1428 dist = get_distance(p, s)
1430 -- aim higher to make looking up hills more realistic
1431 p.y = p.y + 1
1432 sp.y = sp.y + 1
1434 if dist < min_dist
1435 and line_of_sight(self, sp, p, 2) == true then
1436 min_dist = dist
1437 min_player = obj.object
1442 if min_player then
1443 do_attack(self, min_player)
1448 -- specific runaway
1449 local specific_runaway = function(list, what)
1451 -- no list so do not run
1452 if list == nil then
1453 return false
1456 -- found entity on list to attack?
1457 for no = 1, #list do
1459 if list[no] == what then
1460 return true
1464 return false
1468 -- find someone to runaway from
1469 local runaway_from = function(self)
1471 if not self.runaway_from then
1472 return
1475 local s = self.object:get_pos()
1476 local p, sp, dist
1477 local player, obj, min_player
1478 local type, name = "", ""
1479 local min_dist = self.view_range + 1
1480 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1482 for n = 1, #objs do
1484 if objs[n]:is_player() then
1486 if mobs.invis[ objs[n]:get_player_name() ]
1487 or self.owner == objs[n]:get_player_name() then
1489 type = ""
1490 else
1491 player = objs[n]
1492 type = "player"
1493 name = "player"
1495 else
1496 obj = objs[n]:get_luaentity()
1498 if obj then
1499 player = obj.object
1500 type = obj.type
1501 name = obj.name or ""
1505 -- find specific mob to runaway from
1506 if name ~= "" and name ~= self.name
1507 and specific_runaway(self.runaway_from, name) then
1509 p = player:get_pos()
1510 sp = s
1512 -- aim higher to make looking up hills more realistic
1513 p.y = p.y + 1
1514 sp.y = sp.y + 1
1516 dist = get_distance(p, s)
1519 -- choose closest player/mpb to runaway from
1520 if dist < min_dist
1521 and line_of_sight(self, sp, p, 2) == true then
1522 min_dist = dist
1523 min_player = player
1528 if min_player then
1530 local lp = player:get_pos()
1531 local vec = {
1532 x = lp.x - s.x,
1533 y = lp.y - s.y,
1534 z = lp.z - s.z
1537 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1539 if lp.x > s.x then
1540 yaw = yaw + pi
1543 yaw = set_yaw(self, yaw, 4)
1544 self.state = "runaway"
1545 self.runaway_timer = 3
1546 self.following = nil
1551 -- follow player if owner or holding item, if fish outta water then flop
1552 local follow_flop = function(self)
1554 -- find player to follow
1555 if (self.follow ~= ""
1556 or self.order == "follow")
1557 and not self.following
1558 and self.state ~= "attack"
1559 and self.state ~= "runaway" then
1561 local s = self.object:get_pos()
1562 local players = minetest.get_connected_players()
1564 for n = 1, #players do
1566 if get_distance(players[n]:get_pos(), s) < self.view_range
1567 and not mobs.invis[ players[n]:get_player_name() ] then
1569 self.following = players[n]
1571 break
1576 if self.type == "npc"
1577 and self.order == "follow"
1578 and self.state ~= "attack"
1579 and self.owner ~= "" then
1581 -- npc stop following player if not owner
1582 if self.following
1583 and self.owner
1584 and self.owner ~= self.following:get_player_name() then
1585 self.following = nil
1587 else
1588 -- stop following player if not holding specific item
1589 if self.following
1590 and self.following:is_player()
1591 and follow_holding(self, self.following) == false then
1592 self.following = nil
1597 -- follow that thing
1598 if self.following then
1600 local s = self.object:get_pos()
1601 local p
1603 if self.following:is_player() then
1605 p = self.following:get_pos()
1607 elseif self.following.object then
1609 p = self.following.object:get_pos()
1612 if p then
1614 local dist = get_distance(p, s)
1616 -- dont follow if out of range
1617 if dist > self.view_range then
1618 self.following = nil
1619 else
1620 local vec = {
1621 x = p.x - s.x,
1622 z = p.z - s.z
1625 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1627 if p.x > s.x then yaw = yaw + pi end
1629 yaw = set_yaw(self, yaw, 6)
1631 -- anyone but standing npc's can move along
1632 if dist > self.reach
1633 and self.order ~= "stand" then
1635 set_velocity(self, self.walk_velocity)
1637 if self.walk_chance ~= 0 then
1638 set_animation(self, "walk")
1640 else
1641 set_velocity(self, 0)
1642 set_animation(self, "stand")
1645 return
1650 -- swimmers flop when out of their element, and swim again when back in
1651 if self.fly then
1652 local s = self.object:get_pos()
1653 if not flight_check(self, s) then
1655 self.state = "flop"
1656 self.object:setvelocity({x = 0, y = -5, z = 0})
1658 set_animation(self, "stand")
1660 return
1661 elseif self.state == "flop" then
1662 self.state = "stand"
1668 -- dogshoot attack switch and counter function
1669 local dogswitch = function(self, dtime)
1671 -- switch mode not activated
1672 if not self.dogshoot_switch
1673 or not dtime then
1674 return 0
1677 self.dogshoot_count = self.dogshoot_count + dtime
1679 if (self.dogshoot_switch == 1
1680 and self.dogshoot_count > self.dogshoot_count_max)
1681 or (self.dogshoot_switch == 2
1682 and self.dogshoot_count > self.dogshoot_count2_max) then
1684 self.dogshoot_count = 0
1686 if self.dogshoot_switch == 1 then
1687 self.dogshoot_switch = 2
1688 else
1689 self.dogshoot_switch = 1
1693 return self.dogshoot_switch
1697 -- execute current state (stand, walk, run, attacks)
1698 local do_states = function(self, dtime)
1700 local yaw = self.object:get_yaw() or 0
1702 if self.state == "stand" then
1704 if random(1, 4) == 1 then
1706 local lp = nil
1707 local s = self.object:get_pos()
1708 local objs = minetest.get_objects_inside_radius(s, 3)
1710 for n = 1, #objs do
1712 if objs[n]:is_player() then
1713 lp = objs[n]:get_pos()
1714 break
1718 -- look at any players nearby, otherwise turn randomly
1719 if lp then
1721 local vec = {
1722 x = lp.x - s.x,
1723 z = lp.z - s.z
1726 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1728 if lp.x > s.x then yaw = yaw + pi end
1729 else
1730 yaw = yaw + random(-0.5, 0.5)
1733 yaw = set_yaw(self, yaw, 8)
1736 set_velocity(self, 0)
1737 set_animation(self, "stand")
1739 -- npc's ordered to stand stay standing
1740 if self.type ~= "npc"
1741 or self.order ~= "stand" then
1743 if self.walk_chance ~= 0
1744 and self.facing_fence ~= true
1745 and random(1, 100) <= self.walk_chance
1746 and is_at_cliff(self) == false then
1748 set_velocity(self, self.walk_velocity)
1749 self.state = "walk"
1750 set_animation(self, "walk")
1752 --[[ fly up/down randomly for flying mobs
1753 if self.fly and random(1, 100) <= self.walk_chance then
1755 local v = self.object:getvelocity()
1756 local ud = random(-1, 2) / 9
1758 self.object:setvelocity({x = v.x, y = ud, z = v.z})
1759 end--]]
1763 elseif self.state == "walk" then
1765 local s = self.object:get_pos()
1766 local lp = nil
1768 -- is there something I need to avoid?
1769 if self.water_damage > 0
1770 and self.lava_damage > 0 then
1772 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
1774 elseif self.water_damage > 0 then
1776 lp = minetest.find_node_near(s, 1, {"group:water"})
1778 elseif self.lava_damage > 0 then
1780 lp = minetest.find_node_near(s, 1, {"group:lava"})
1783 if lp then
1785 -- if mob in water or lava then look for land
1786 if (self.lava_damage
1787 and minetest.registered_nodes[self.standing_in].groups.lava)
1788 or (self.water_damage
1789 and minetest.registered_nodes[self.standing_in].groups.water) then
1791 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1792 "group:sand", node_ice, node_snowblock})
1794 -- did we find land?
1795 if lp then
1797 local vec = {
1798 x = lp.x - s.x,
1799 z = lp.z - s.z
1802 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1804 if lp.x > s.x then yaw = yaw + pi end
1806 -- look towards land and jump/move in that direction
1807 yaw = set_yaw(self, yaw, 6)
1808 do_jump(self)
1809 set_velocity(self, self.walk_velocity)
1810 else
1811 yaw = yaw + random(-0.5, 0.5)
1814 else
1816 local vec = {
1817 x = lp.x - s.x,
1818 z = lp.z - s.z
1821 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1823 if lp.x > s.x then yaw = yaw + pi end
1826 yaw = set_yaw(self, yaw, 8)
1828 -- otherwise randomly turn
1829 elseif random(1, 100) <= 30 then
1831 yaw = yaw + random(-0.5, 0.5)
1833 yaw = set_yaw(self, yaw, 8)
1836 -- stand for great fall in front
1837 local temp_is_cliff = is_at_cliff(self)
1839 if self.facing_fence == true
1840 or temp_is_cliff
1841 or random(1, 100) <= 30 then
1843 set_velocity(self, 0)
1844 self.state = "stand"
1845 set_animation(self, "stand")
1846 else
1847 set_velocity(self, self.walk_velocity)
1849 if flight_check(self)
1850 and self.animation
1851 and self.animation.fly_start
1852 and self.animation.fly_end then
1853 set_animation(self, "fly")
1854 else
1855 set_animation(self, "walk")
1859 -- runaway when punched
1860 elseif self.state == "runaway" then
1862 self.runaway_timer = self.runaway_timer + 1
1864 -- stop after 5 seconds or when at cliff
1865 if self.runaway_timer > 5
1866 or is_at_cliff(self) then
1867 self.runaway_timer = 0
1868 set_velocity(self, 0)
1869 self.state = "stand"
1870 set_animation(self, "stand")
1871 else
1872 set_velocity(self, self.run_velocity)
1873 set_animation(self, "walk")
1876 -- attack routines (explode, dogfight, shoot, dogshoot)
1877 elseif self.state == "attack" then
1879 -- calculate distance from mob and enemy
1880 local s = self.object:get_pos()
1881 local p = self.attack:get_pos() or s
1882 local dist = get_distance(p, s)
1884 -- stop attacking if player invisible or out of range
1885 if dist > self.view_range
1886 or not self.attack
1887 or not self.attack:get_pos()
1888 or self.attack:get_hp() <= 0
1889 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1891 -- print(" ** stop attacking **", dist, self.view_range)
1892 self.state = "stand"
1893 set_velocity(self, 0)
1894 set_animation(self, "stand")
1895 self.attack = nil
1896 self.v_start = false
1897 self.timer = 0
1898 self.blinktimer = 0
1899 self.path.way = nil
1901 return
1904 if self.attack_type == "explode" then
1906 local vec = {
1907 x = p.x - s.x,
1908 z = p.z - s.z
1911 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1913 if p.x > s.x then yaw = yaw + pi end
1915 yaw = set_yaw(self, yaw)
1917 local node_break_radius = self.explosion_radius or 1
1918 local entity_damage_radius = self.explosion_damage_radius
1919 or (node_break_radius * 2)
1921 -- start timer when in reach and line of sight
1922 if not self.v_start
1923 and dist <= self.reach
1924 and line_of_sight(self, s, p, 2) then
1926 self.v_start = true
1927 self.timer = 0
1928 self.blinktimer = 0
1929 mob_sound(self, self.sounds.fuse)
1930 -- print ("=== explosion timer started", self.explosion_timer)
1932 -- stop timer if out of reach or direct line of sight
1933 elseif self.allow_fuse_reset
1934 and self.v_start
1935 and (dist > self.reach
1936 or not line_of_sight(self, s, p, 2)) then
1937 self.v_start = false
1938 self.timer = 0
1939 self.blinktimer = 0
1940 self.blinkstatus = false
1941 self.object:settexturemod("")
1944 -- walk right up to player unless the timer is active
1945 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1946 set_velocity(self, 0)
1947 else
1948 set_velocity(self, self.run_velocity)
1951 if self.animation and self.animation.run_start then
1952 set_animation(self, "run")
1953 else
1954 set_animation(self, "walk")
1957 if self.v_start then
1959 self.timer = self.timer + dtime
1960 self.blinktimer = (self.blinktimer or 0) + dtime
1962 if self.blinktimer > 0.2 then
1964 self.blinktimer = 0
1966 if self.blinkstatus then
1967 self.object:settexturemod("")
1968 else
1969 self.object:settexturemod("^[brighten")
1972 self.blinkstatus = not self.blinkstatus
1975 -- print ("=== explosion timer", self.timer)
1977 if self.timer > self.explosion_timer then
1979 local pos = self.object:get_pos()
1981 -- dont damage anything if area protected or next to water
1982 if minetest.find_node_near(pos, 1, {"group:water"})
1983 or minetest.is_protected(pos, "") then
1985 node_break_radius = 1
1988 self.object:remove()
1990 if mod_tnt and tnt and tnt.boom
1991 and not minetest.is_protected(pos, "") then
1993 tnt.boom(pos, {
1994 radius = node_break_radius,
1995 damage_radius = entity_damage_radius,
1996 sound = self.sounds.explode,
1998 else
2000 minetest.sound_play(self.sounds.explode, {
2001 pos = pos,
2002 gain = 1.0,
2003 max_hear_distance = self.sounds.distance or 32
2006 entity_physics(pos, entity_damage_radius)
2007 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2010 return
2014 elseif self.attack_type == "dogfight"
2015 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2016 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2018 if self.fly
2019 and dist > self.reach then
2021 local p1 = s
2022 local me_y = floor(p1.y)
2023 local p2 = p
2024 local p_y = floor(p2.y + 1)
2025 local v = self.object:getvelocity()
2027 if flight_check(self, s) then
2029 if me_y < p_y then
2031 self.object:setvelocity({
2032 x = v.x,
2033 y = 1 * self.walk_velocity,
2034 z = v.z
2037 elseif me_y > p_y then
2039 self.object:setvelocity({
2040 x = v.x,
2041 y = -1 * self.walk_velocity,
2042 z = v.z
2045 else
2046 if me_y < p_y then
2048 self.object:setvelocity({
2049 x = v.x,
2050 y = 0.01,
2051 z = v.z
2054 elseif me_y > p_y then
2056 self.object:setvelocity({
2057 x = v.x,
2058 y = -0.01,
2059 z = v.z
2066 -- rnd: new movement direction
2067 if self.path.following
2068 and self.path.way
2069 and self.attack_type ~= "dogshoot" then
2071 -- no paths longer than 50
2072 if #self.path.way > 50
2073 or dist < self.reach then
2074 self.path.following = false
2075 return
2078 local p1 = self.path.way[1]
2080 if not p1 then
2081 self.path.following = false
2082 return
2085 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2086 -- reached waypoint, remove it from queue
2087 table.remove(self.path.way, 1)
2090 -- set new temporary target
2091 p = {x = p1.x, y = p1.y, z = p1.z}
2094 local vec = {
2095 x = p.x - s.x,
2096 z = p.z - s.z
2099 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2101 if p.x > s.x then yaw = yaw + pi end
2103 yaw = set_yaw(self, yaw)
2105 -- move towards enemy if beyond mob reach
2106 if dist > self.reach then
2108 -- path finding by rnd
2109 if self.pathfinding -- only if mob has pathfinding enabled
2110 and enable_pathfinding then
2112 smart_mobs(self, s, p, dist, dtime)
2115 if is_at_cliff(self) then
2117 set_velocity(self, 0)
2118 set_animation(self, "stand")
2119 else
2121 if self.path.stuck then
2122 set_velocity(self, self.walk_velocity)
2123 else
2124 set_velocity(self, self.run_velocity)
2127 if self.animation and self.animation.run_start then
2128 set_animation(self, "run")
2129 else
2130 set_animation(self, "walk")
2134 else -- rnd: if inside reach range
2136 self.path.stuck = false
2137 self.path.stuck_timer = 0
2138 self.path.following = false -- not stuck anymore
2140 set_velocity(self, 0)
2142 if not self.custom_attack then
2144 if self.timer > 1 then
2146 self.timer = 0
2148 if self.double_melee_attack
2149 and random(1, 2) == 1 then
2150 set_animation(self, "punch2")
2151 else
2152 set_animation(self, "punch")
2155 local p2 = p
2156 local s2 = s
2158 p2.y = p2.y + .5
2159 s2.y = s2.y + .5
2161 if line_of_sight(self, p2, s2) == true then
2163 -- play attack sound
2164 mob_sound(self, self.sounds.attack)
2166 -- punch player (or what player is attached to)
2167 local attached = self.attack:get_attach()
2168 if attached then
2169 self.attack = attached
2171 self.attack:punch(self.object, 1.0, {
2172 full_punch_interval = 1.0,
2173 damage_groups = {fleshy = self.damage}
2174 }, nil)
2177 else -- call custom attack every second
2178 if self.custom_attack
2179 and self.timer > 1 then
2181 self.timer = 0
2183 self.custom_attack(self, p)
2188 elseif self.attack_type == "shoot"
2189 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2190 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2192 p.y = p.y - .5
2193 s.y = s.y + .5
2195 local dist = get_distance(p, s)
2196 local vec = {
2197 x = p.x - s.x,
2198 y = p.y - s.y,
2199 z = p.z - s.z
2202 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2204 if p.x > s.x then yaw = yaw + pi end
2206 yaw = set_yaw(self, yaw)
2208 set_velocity(self, 0)
2210 if self.shoot_interval
2211 and self.timer > self.shoot_interval
2212 and random(1, 100) <= 60 then
2214 self.timer = 0
2215 set_animation(self, "shoot")
2217 -- play shoot attack sound
2218 mob_sound(self, self.sounds.shoot_attack)
2220 local p = self.object:get_pos()
2222 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
2224 if minetest.registered_entities[self.arrow] then
2226 local obj = minetest.add_entity(p, self.arrow)
2227 local ent = obj:get_luaentity()
2228 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2229 local v = ent.velocity or 1 -- or set to default
2231 ent.switch = 1
2232 ent.owner_id = tostring(self.object) -- add unique owner id to arrow
2234 -- offset makes shoot aim accurate
2235 vec.y = vec.y + self.shoot_offset
2236 vec.x = vec.x * (v / amount)
2237 vec.y = vec.y * (v / amount)
2238 vec.z = vec.z * (v / amount)
2240 obj:setvelocity(vec)
2248 -- falling and fall damage
2249 local falling = function(self, pos)
2251 if self.fly then
2252 return
2255 -- floating in water (or falling)
2256 local v = self.object:getvelocity()
2258 if v.y > 0 then
2260 -- apply gravity when moving up
2261 self.object:setacceleration({
2262 x = 0,
2263 y = -10,
2264 z = 0
2267 elseif v.y <= 0 and v.y > self.fall_speed then
2269 -- fall downwards at set speed
2270 self.object:setacceleration({
2271 x = 0,
2272 y = self.fall_speed,
2273 z = 0
2275 else
2276 -- stop accelerating once max fall speed hit
2277 self.object:setacceleration({x = 0, y = 0, z = 0})
2280 -- in water then float up
2281 if minetest.registered_nodes[node_ok(pos).name].groups.water then
2283 if self.floats == 1 then
2285 self.object:setacceleration({
2286 x = 0,
2287 y = -self.fall_speed / (max(1, v.y) ^ 2),
2288 z = 0
2291 else
2293 -- fall damage onto solid ground
2294 if self.fall_damage == 1
2295 and self.object:getvelocity().y == 0 then
2297 local d = (self.old_y or 0) - self.object:get_pos().y
2299 if d > 5 then
2301 self.health = self.health - floor(d - 5)
2303 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
2305 if check_for_death(self, "fall", {type = "fall"}) then
2306 return
2310 self.old_y = self.object:get_pos().y
2316 -- deal damage and effects when mob punched
2317 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2319 -- custom punch function
2320 if self.do_punch then
2322 -- when false skip going any further
2323 if self.do_punch(self, hitter, tflp, tool_caps, dir) == false then
2324 return
2328 -- mob health check
2329 -- if self.health <= 0 then
2330 -- return
2331 -- end
2333 -- error checking when mod profiling is enabled
2334 if not tool_capabilities then
2335 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2336 return
2339 -- is mob protected?
2340 if self.protected and hitter:is_player()
2341 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2342 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2343 return
2347 -- weapon wear
2348 local weapon = hitter:get_wielded_item()
2349 local punch_interval = 1.4
2351 -- calculate mob damage
2352 local damage = 0
2353 local armor = self.object:get_armor_groups() or {}
2354 local tmp
2356 -- quick error check incase it ends up 0 (serialize.h check test)
2357 if tflp == 0 then
2358 tflp = 0.2
2361 if use_cmi then
2362 damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
2363 else
2365 for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
2367 tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
2369 if tmp < 0 then
2370 tmp = 0.0
2371 elseif tmp > 1 then
2372 tmp = 1.0
2375 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2376 * tmp * ((armor[group] or 0) / 100.0)
2380 -- check for tool immunity or special damage
2381 for n = 1, #self.immune_to do
2383 if self.immune_to[n][1] == weapon:get_name() then
2385 damage = self.immune_to[n][2] or 0
2386 break
2390 -- healing
2391 if damage <= -1 then
2392 self.health = self.health - floor(damage)
2393 return
2396 -- print ("Mob Damage is", damage)
2398 if use_cmi then
2400 local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage)
2402 if cancel then return end
2405 -- add weapon wear
2406 if tool_capabilities then
2407 punch_interval = tool_capabilities.full_punch_interval or 1.4
2410 if weapon:get_definition()
2411 and weapon:get_definition().tool_capabilities then
2413 weapon:add_wear(floor((punch_interval / 75) * 9000))
2414 hitter:set_wielded_item(weapon)
2417 -- only play hit sound and show blood effects if damage is 1 or over
2418 if damage >= 1 then
2420 -- weapon sounds
2421 if weapon:get_definition().sounds ~= nil then
2423 local s = random(0, #weapon:get_definition().sounds)
2425 minetest.sound_play(weapon:get_definition().sounds[s], {
2426 object = self.object, --hitter,
2427 max_hear_distance = 8
2429 else
2430 minetest.sound_play("default_punch", {
2431 object = self.object, --hitter,
2432 max_hear_distance = 5
2436 -- blood_particles
2437 if self.blood_amount > 0
2438 and not disable_blood then
2440 local pos = self.object:get_pos()
2442 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2444 -- do we have a single blood texture or multiple?
2445 if type(self.blood_texture) == "table" then
2447 local blood = self.blood_texture[random(1, #self.blood_texture)]
2449 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2450 else
2451 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2455 -- do damage
2456 self.health = self.health - floor(damage)
2458 -- exit here if dead, special item check
2459 if weapon:get_name() == "mobs:pick_lava" then
2460 if check_for_death(self, "lava", {type = "punch",
2461 puncher = hitter}) then
2462 return
2464 else
2465 if check_for_death(self, "hit", {type = "punch",
2466 puncher = hitter}) then
2467 return
2471 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2472 core.after(0.1, function()
2473 self.object:settexturemod("^[colorize:#c9900070")
2475 core.after(0.3, function()
2476 self.object:settexturemod("")
2477 end)
2478 end) ]]
2480 -- knock back effect (only on full punch)
2481 if self.knock_back
2482 and tflp >= punch_interval then
2484 local v = self.object:getvelocity()
2485 local r = 1.4 - min(punch_interval, 1.4)
2486 local kb = r * 5
2487 local up = 2
2489 -- if already in air then dont go up anymore when hit
2490 if v.y > 0
2491 or self.fly then
2492 up = 0
2495 -- direction error check
2496 dir = dir or {x = 0, y = 0, z = 0}
2498 -- check if tool already has specific knockback value
2499 if tool_capabilities.damage_groups["knockback"] then
2500 kb = tool_capabilities.damage_groups["knockback"]
2501 else
2502 kb = kb * 1.5
2505 self.object:setvelocity({
2506 x = dir.x * kb,
2507 y = up,
2508 z = dir.z * kb
2511 self.pause_timer = 0.25
2513 end -- END if damage
2515 -- if skittish then run away
2516 if self.runaway == true then
2518 local lp = hitter:get_pos()
2519 local s = self.object:get_pos()
2520 local vec = {
2521 x = lp.x - s.x,
2522 y = lp.y - s.y,
2523 z = lp.z - s.z
2526 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2528 if lp.x > s.x then
2529 yaw = yaw + pi
2532 yaw = set_yaw(self, yaw, 6)
2533 self.state = "runaway"
2534 self.runaway_timer = 0
2535 self.following = nil
2538 local name = hitter:get_player_name() or ""
2540 -- attack puncher and call other mobs for help
2541 if self.passive == false
2542 and self.state ~= "flop"
2543 and self.child == false
2544 and hitter:get_player_name() ~= self.owner
2545 and not mobs.invis[ name ] then
2547 -- attack whoever punched mob
2548 self.state = ""
2549 do_attack(self, hitter)
2551 -- alert others to the attack
2552 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2553 local obj = nil
2555 for n = 1, #objs do
2557 obj = objs[n]:get_luaentity()
2559 if obj then
2561 -- only alert members of same mob
2562 if obj.group_attack == true
2563 and obj.state ~= "attack"
2564 and obj.owner ~= name
2565 and obj.name == self.name then
2566 do_attack(obj, hitter)
2569 -- have owned mobs attack player threat
2570 if obj.owner == name and obj.owner_loyal then
2571 do_attack(obj, self.object)
2579 -- get entity staticdata
2580 local mob_staticdata = function(self)
2582 -- remove mob when out of range unless tamed
2583 if remove_far
2584 and self.remove_ok
2585 and self.type ~= "npc"
2586 and self.state ~= "attack"
2587 and not self.tamed
2588 and self.lifetimer < 20000 then
2590 --print ("REMOVED " .. self.name)
2592 self.object:remove()
2594 return ""-- nil
2597 self.remove_ok = true
2598 self.attack = nil
2599 self.following = nil
2600 self.state = "stand"
2602 -- used to rotate older mobs
2603 if self.drawtype
2604 and self.drawtype == "side" then
2605 self.rotate = math.rad(90)
2608 if use_cmi then
2609 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2612 local tmp = {}
2614 for _,stat in pairs(self) do
2616 local t = type(stat)
2618 if t ~= "function"
2619 and t ~= "nil"
2620 and t ~= "userdata"
2621 and _ ~= "_cmi_components" then
2622 tmp[_] = self[_]
2626 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
2627 return minetest.serialize(tmp)
2631 -- activate mob and reload settings
2632 local mob_activate = function(self, staticdata, def, dtime)
2634 -- remove monsters in peaceful mode
2635 if self.type == "monster"
2636 and peaceful_only then
2638 self.object:remove()
2640 return
2643 -- load entity variables
2644 local tmp = minetest.deserialize(staticdata)
2646 if tmp then
2647 for _,stat in pairs(tmp) do
2648 self[_] = stat
2652 -- select random texture, set model and size
2653 if not self.base_texture then
2655 -- compatiblity with old simple mobs textures
2656 if type(def.textures[1]) == "string" then
2657 def.textures = {def.textures}
2660 self.base_texture = def.textures[random(1, #def.textures)]
2661 self.base_mesh = def.mesh
2662 self.base_size = self.visual_size
2663 self.base_colbox = self.collisionbox
2664 self.base_selbox = self.selectionbox
2667 -- for current mobs that dont have this set
2668 if not self.base_selbox then
2669 self.base_selbox = self.selectionbox or self.base_colbox
2672 -- set texture, model and size
2673 local textures = self.base_texture
2674 local mesh = self.base_mesh
2675 local vis_size = self.base_size
2676 local colbox = self.base_colbox
2677 local selbox = self.base_selbox
2679 -- specific texture if gotten
2680 if self.gotten == true
2681 and def.gotten_texture then
2682 textures = def.gotten_texture
2685 -- specific mesh if gotten
2686 if self.gotten == true
2687 and def.gotten_mesh then
2688 mesh = def.gotten_mesh
2691 -- set child objects to half size
2692 if self.child == true then
2694 vis_size = {
2695 x = self.base_size.x * .5,
2696 y = self.base_size.y * .5,
2699 if def.child_texture then
2700 textures = def.child_texture[1]
2703 colbox = {
2704 self.base_colbox[1] * .5,
2705 self.base_colbox[2] * .5,
2706 self.base_colbox[3] * .5,
2707 self.base_colbox[4] * .5,
2708 self.base_colbox[5] * .5,
2709 self.base_colbox[6] * .5
2711 selbox = {
2712 self.base_selbox[1] * .5,
2713 self.base_selbox[2] * .5,
2714 self.base_selbox[3] * .5,
2715 self.base_selbox[4] * .5,
2716 self.base_selbox[5] * .5,
2717 self.base_selbox[6] * .5
2721 if self.health == 0 then
2722 self.health = random (self.hp_min, self.hp_max)
2725 -- pathfinding init
2726 self.path = {}
2727 self.path.way = {} -- path to follow, table of positions
2728 self.path.lastpos = {x = 0, y = 0, z = 0}
2729 self.path.stuck = false
2730 self.path.following = false -- currently following path?
2731 self.path.stuck_timer = 0 -- if stuck for too long search for path
2733 -- mob defaults
2734 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
2735 self.old_y = self.object:get_pos().y
2736 self.old_health = self.health
2737 self.sounds.distance = self.sounds.distance or 10
2738 self.textures = textures
2739 self.mesh = mesh
2740 self.collisionbox = colbox
2741 self.selectionbox = selbox
2742 self.visual_size = vis_size
2743 self.standing_in = ""
2745 -- check existing nametag
2746 if not self.nametag then
2747 self.nametag = def.nametag
2750 -- set anything changed above
2751 self.object:set_properties(self)
2752 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2753 update_tag(self)
2754 set_animation(self, "stand")
2756 -- run on_spawn function if found
2757 if self.on_spawn and not self.on_spawn_run then
2758 if self.on_spawn(self) then
2759 self.on_spawn_run = true -- if true, set flag to run once only
2763 -- run after_activate
2764 if def.after_activate then
2765 def.after_activate(self, staticdata, def, dtime)
2768 if use_cmi then
2769 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
2770 cmi.notify_activate(self.object, dtime)
2775 -- main mob function
2776 local mob_step = function(self, dtime)
2778 if use_cmi then
2779 cmi.notify_step(self.object, dtime)
2782 local pos = self.object:get_pos()
2783 local yaw = 0
2785 -- when lifetimer expires remove mob (except npc and tamed)
2786 if self.type ~= "npc"
2787 and not self.tamed
2788 and self.state ~= "attack"
2789 and remove_far ~= true
2790 and self.lifetimer < 20000 then
2792 self.lifetimer = self.lifetimer - dtime
2794 if self.lifetimer <= 0 then
2796 -- only despawn away from player
2797 local objs = minetest.get_objects_inside_radius(pos, 15)
2799 for n = 1, #objs do
2801 if objs[n]:is_player() then
2803 self.lifetimer = 20
2805 return
2809 -- minetest.log("action",
2810 -- S("lifetimer expired, removed @1", self.name))
2812 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
2814 self.object:remove()
2816 return
2820 falling(self, pos)
2822 -- smooth rotation by ThomasMonroe314
2824 if self.delay and self.delay > 0 then
2826 local yaw = self.object:get_yaw()
2828 if self.delay == 1 then
2829 yaw = self.target_yaw
2830 else
2831 local dif = abs(yaw - self.target_yaw)
2833 if yaw > self.target_yaw then
2835 if dif > pi then
2836 dif = 2 * pi - dif -- need to add
2837 yaw = yaw + dif / self.delay
2838 else
2839 yaw = yaw - dif / self.delay -- need to subtract
2842 elseif yaw < self.target_yaw then
2844 if dif > pi then
2845 dif = 2 * pi - dif
2846 yaw = yaw - dif / self.delay -- need to subtract
2847 else
2848 yaw = yaw + dif / self.delay -- need to add
2852 if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
2853 if yaw < 0 then yaw = yaw + (pi * 2) end
2856 self.delay = self.delay - 1
2857 self.object:set_yaw(yaw)
2860 -- end rotation
2862 -- knockback timer
2863 if self.pause_timer > 0 then
2865 self.pause_timer = self.pause_timer - dtime
2867 return
2870 -- run custom function (defined in mob lua file)
2871 if self.do_custom then
2873 -- when false skip going any further
2874 if self.do_custom(self, dtime) == false then
2875 return
2879 -- attack timer
2880 self.timer = self.timer + dtime
2882 if self.state ~= "attack" then
2884 if self.timer < 1 then
2885 return
2888 self.timer = 0
2891 -- never go over 100
2892 if self.timer > 100 then
2893 self.timer = 1
2896 -- mob plays random sound at times
2897 if random(1, 100) == 1 then
2898 mob_sound(self, self.sounds.random)
2901 -- environmental damage timer (every 1 second)
2902 self.env_damage_timer = self.env_damage_timer + dtime
2904 if (self.state == "attack" and self.env_damage_timer > 1)
2905 or self.state ~= "attack" then
2907 self.env_damage_timer = 0
2909 -- check for environmental damage (water, fire, lava etc.)
2910 do_env_damage(self)
2912 -- node replace check (cow eats grass etc.)
2913 replace(self, pos)
2916 monster_attack(self)
2918 npc_attack(self)
2920 breed(self)
2922 follow_flop(self)
2924 do_states(self, dtime)
2926 do_jump(self)
2928 runaway_from(self)
2933 -- default function when mobs are blown up with TNT
2934 local do_tnt = function(obj, damage)
2936 --print ("----- Damage", damage)
2938 obj.object:punch(obj.object, 1.0, {
2939 full_punch_interval = 1.0,
2940 damage_groups = {fleshy = damage},
2941 }, nil)
2943 return false, true, {}
2947 mobs.spawning_mobs = {}
2949 -- Code to execute before custom on_rightclick handling
2950 local on_rightclick_prefix = function(self, clicker)
2951 local item = clicker:get_wielded_item()
2953 -- Name mob with nametag
2954 if not self.ignores_nametag and item:get_name() == "mobs:nametag" then
2956 local tag = item:get_meta():get_string("name")
2957 if tag ~= "" then
2958 if string.len(tag) > MAX_MOB_NAME_LENGTH then
2959 tag = string.sub(tag, 1, MAX_MOB_NAME_LENGTH)
2961 self.nametag = tag
2963 update_tag(self)
2965 if not mobs.is_creative(name) then
2966 item:take_item()
2967 player:set_wielded_item(item)
2969 return true
2973 return false
2976 local create_mob_on_rightclick = function(on_rightclick)
2977 return function(self, clicker)
2978 local stop = on_rightclick_prefix(self, clicker)
2979 if (not stop) and (on_rightclick) then
2980 on_rightclick(self, clicker)
2985 -- register mob entity
2986 function mobs:register_mob(name, def)
2988 mobs.spawning_mobs[name] = true
2990 minetest.register_entity(name, {
2992 stepheight = def.stepheight or 1.1, -- was 0.6
2993 name = name,
2994 type = def.type,
2995 attack_type = def.attack_type,
2996 fly = def.fly,
2997 fly_in = def.fly_in or "air",
2998 owner = def.owner or "",
2999 order = def.order or "",
3000 on_die = def.on_die,
3001 do_custom = def.do_custom,
3002 jump_height = def.jump_height or 4, -- was 6
3003 drawtype = def.drawtype, -- DEPRECATED, use rotate instead
3004 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
3005 lifetimer = def.lifetimer or 180, -- 3 minutes
3006 hp_min = max(1, (def.hp_min or 5) * difficulty),
3007 hp_max = max(1, (def.hp_max or 10) * difficulty),
3008 physical = true,
3009 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3010 selectionbox = def.selectionbox or def.collisionbox,
3011 visual = def.visual,
3012 visual_size = def.visual_size or {x = 1, y = 1},
3013 mesh = def.mesh,
3014 makes_footstep_sound = def.makes_footstep_sound or false,
3015 view_range = def.view_range or 5,
3016 walk_velocity = def.walk_velocity or 1,
3017 run_velocity = def.run_velocity or 2,
3018 damage = max(0, (def.damage or 0) * difficulty),
3019 light_damage = def.light_damage or 0,
3020 water_damage = def.water_damage or 0,
3021 lava_damage = def.lava_damage or 0,
3022 suffocation = def.suffocation or 2,
3023 fall_damage = def.fall_damage or 1,
3024 fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
3025 drops = def.drops or {},
3026 armor = def.armor or 100,
3027 on_rightclick = create_mob_on_rightclick(def.on_rightclick),
3028 arrow = def.arrow,
3029 shoot_interval = def.shoot_interval,
3030 sounds = def.sounds or {},
3031 animation = def.animation,
3032 follow = def.follow,
3033 jump = def.jump ~= false,
3034 walk_chance = def.walk_chance or 50,
3035 attacks_monsters = def.attacks_monsters or false,
3036 group_attack = def.group_attack or false,
3037 passive = def.passive or false,
3038 knock_back = def.knock_back ~= false,
3039 blood_amount = def.blood_amount or 5,
3040 blood_texture = def.blood_texture or "mobs_blood.png",
3041 shoot_offset = def.shoot_offset or 0,
3042 floats = def.floats or 1, -- floats in water by default
3043 replace_rate = def.replace_rate,
3044 replace_what = def.replace_what,
3045 replace_with = def.replace_with,
3046 replace_offset = def.replace_offset or 0,
3047 on_replace = def.on_replace,
3048 timer = 0,
3049 env_damage_timer = 0, -- only used when state = "attack"
3050 tamed = false,
3051 pause_timer = 0,
3052 horny = false,
3053 hornytimer = 0,
3054 child = false,
3055 gotten = false,
3056 health = 0,
3057 reach = def.reach or 3,
3058 htimer = 0,
3059 texture_list = def.textures,
3060 child_texture = def.child_texture,
3061 docile_by_day = def.docile_by_day or false,
3062 time_of_day = 0.5,
3063 fear_height = def.fear_height or 0,
3064 runaway = def.runaway,
3065 runaway_timer = 0,
3066 pathfinding = def.pathfinding,
3067 immune_to = def.immune_to or {},
3068 explosion_radius = def.explosion_radius,
3069 explosion_damage_radius = def.explosion_damage_radius,
3070 explosion_timer = def.explosion_timer or 3,
3071 allow_fuse_reset = def.allow_fuse_reset ~= false,
3072 stop_to_explode = def.stop_to_explode ~= false,
3073 custom_attack = def.custom_attack,
3074 double_melee_attack = def.double_melee_attack,
3075 dogshoot_switch = def.dogshoot_switch,
3076 dogshoot_count = 0,
3077 dogshoot_count_max = def.dogshoot_count_max or 5,
3078 dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
3079 attack_animals = def.attack_animals or false,
3080 specific_attack = def.specific_attack,
3081 runaway_from = def.runaway_from,
3082 owner_loyal = def.owner_loyal,
3083 facing_fence = false,
3084 _cmi_is_mob = true,
3086 -- MCL2 extensions
3087 ignores_nametag = def.ignores_nametag or false,
3088 rain_damage = def.rain_damage or 0,
3090 on_spawn = def.on_spawn,
3092 on_blast = def.on_blast or do_tnt,
3094 on_step = mob_step,
3096 do_punch = def.do_punch,
3098 on_punch = mob_punch,
3100 on_breed = def.on_breed,
3102 on_grown = def.on_grown,
3104 on_activate = function(self, staticdata, dtime)
3105 return mob_activate(self, staticdata, def, dtime)
3106 end,
3108 get_staticdata = function(self)
3109 return mob_staticdata(self)
3110 end,
3114 end -- END mobs:register_mob function
3117 -- count how many mobs of one type are inside an area
3118 local count_mobs = function(pos, type)
3120 local num_type = 0
3121 local num_total = 0
3122 local objs = minetest.get_objects_inside_radius(pos, aoc_range)
3124 for n = 1, #objs do
3126 if not objs[n]:is_player() then
3128 local obj = objs[n]:get_luaentity()
3130 -- count mob type and add to total also
3131 if obj and obj.name and obj.name == type then
3133 num_type = num_type + 1
3134 num_total = num_total + 1
3136 -- add to total mobs
3137 elseif obj and obj.name and obj.health ~= nil then
3139 num_total = num_total + 1
3144 return num_type, num_total
3148 -- global functions
3150 function mobs:spawn_abm_check(pos, node, name)
3151 -- global function to add additional spawn checks
3152 -- return true to stop spawning mob
3156 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3157 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3159 -- Do mobs spawn at all?
3160 if not mobs_spawn then
3161 return
3164 -- chance/spawn number override in minetest.conf for registered mob
3165 local numbers = minetest.settings:get(name)
3167 if numbers then
3168 numbers = numbers:split(",")
3169 chance = tonumber(numbers[1]) or chance
3170 aoc = tonumber(numbers[2]) or aoc
3172 if chance == 0 then
3173 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3174 return
3177 minetest.log("action",
3178 string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
3182 minetest.register_abm({
3184 label = name .. " spawning",
3185 nodenames = nodes,
3186 neighbors = neighbors,
3187 interval = interval,
3188 chance = max(1, (chance * mob_chance_multiplier)),
3189 catch_up = false,
3191 action = function(pos, node, active_object_count, active_object_count_wider)
3193 -- is mob actually registered?
3194 if not mobs.spawning_mobs[name]
3195 or not minetest.registered_entities[name] then
3196 --print ("--- mob doesn't exist", name)
3197 return
3200 -- additional custom checks for spawning mob
3201 if mobs:spawn_abm_check(pos, node, name) == true then
3202 return
3205 -- do not spawn if too many of same mob in area
3206 if active_object_count_wider >= max_per_block
3207 or count_mobs(pos, name) >= aoc then
3208 --print ("--- too many entities", name, aoc, active_object_count_wider)
3209 return
3212 -- if toggle set to nil then ignore day/night check
3213 if day_toggle ~= nil then
3215 local tod = (minetest.get_timeofday() or 0) * 24000
3217 if tod > 4500 and tod < 19500 then
3218 -- daylight, but mob wants night
3219 if day_toggle == false then
3220 --print ("--- mob needs night", name)
3221 return
3223 else
3224 -- night time but mob wants day
3225 if day_toggle == true then
3226 --print ("--- mob needs day", name)
3227 return
3232 -- spawn above node
3233 pos.y = pos.y + 1
3235 -- only spawn away from player
3236 local objs = minetest.get_objects_inside_radius(pos, 10)
3238 for n = 1, #objs do
3240 if objs[n]:is_player() then
3241 --print ("--- player too close", name)
3242 return
3246 -- mobs cannot spawn in protected areas when enabled
3247 if not spawn_protected
3248 and minetest.is_protected(pos, "") then
3249 --print ("--- inside protected area", name)
3250 return
3253 -- are we spawning within height limits?
3254 if pos.y > max_height
3255 or pos.y < min_height then
3256 --print ("--- height limits not met", name, pos.y)
3257 return
3260 -- are light levels ok?
3261 local light = minetest.get_node_light(pos)
3262 if not light
3263 or light > max_light
3264 or light < min_light then
3265 --print ("--- light limits not met", name, light)
3266 return
3269 -- do we have enough height clearance to spawn mob?
3270 local ent = minetest.registered_entities[name]
3271 local height = max(0, math.ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
3273 for n = 0, height do
3275 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
3277 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3278 --print ("--- inside block", name, node_ok(pos2).name)
3279 return
3283 -- spawn mob half block higher than ground
3284 pos.y = pos.y + 0.5
3286 local mob = minetest.add_entity(pos, name)
3287 --[[
3288 print ("[mobs] Spawned " .. name .. " at "
3289 .. minetest.pos_to_string(pos) .. " on "
3290 .. node.name .. " near " .. neighbors[1])
3292 if on_spawn then
3294 local ent = mob:get_luaentity()
3296 on_spawn(ent, pos)
3303 -- compatibility with older mob registration
3304 function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
3306 mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
3307 chance, active_object_count, -31000, max_height, day_toggle)
3311 -- MarkBu's spawn function
3312 function mobs:spawn(def)
3314 local name = def.name
3315 local nodes = def.nodes or {"group:soil", "group:stone"}
3316 local neighbors = def.neighbors or {"air"}
3317 local min_light = def.min_light or 0
3318 local max_light = def.max_light or 15
3319 local interval = def.interval or 30
3320 local chance = def.chance or 5000
3321 local active_object_count = def.active_object_count or 1
3322 local min_height = def.min_height or -31000
3323 local max_height = def.max_height or 31000
3324 local day_toggle = def.day_toggle
3325 local on_spawn = def.on_spawn
3327 mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
3328 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
3332 -- register arrow for shoot attack
3333 function mobs:register_arrow(name, def)
3335 if not name or not def then return end -- errorcheck
3337 minetest.register_entity(name, {
3339 physical = false,
3340 visual = def.visual,
3341 visual_size = def.visual_size,
3342 textures = def.textures,
3343 velocity = def.velocity,
3344 hit_player = def.hit_player,
3345 hit_node = def.hit_node,
3346 hit_mob = def.hit_mob,
3347 drop = def.drop or false, -- drops arrow as registered item when true
3348 collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
3349 timer = 0,
3350 switch = 0,
3351 owner_id = def.owner_id,
3352 rotate = def.rotate,
3353 automatic_face_movement_dir = def.rotate
3354 and (def.rotate - (pi / 180)) or false,
3356 on_activate = def.on_activate,
3358 on_step = def.on_step or function(self, dtime)
3360 self.timer = self.timer + 1
3362 local pos = self.object:get_pos()
3364 if self.switch == 0
3365 or self.timer > 150
3366 or not within_limits(pos, 0) then
3368 self.object:remove() ; -- print ("removed arrow")
3370 return
3373 -- does arrow have a tail (fireball)
3374 if def.tail
3375 and def.tail == 1
3376 and def.tail_texture then
3378 minetest.add_particle({
3379 pos = pos,
3380 velocity = {x = 0, y = 0, z = 0},
3381 acceleration = {x = 0, y = 0, z = 0},
3382 expirationtime = def.expire or 0.25,
3383 collisiondetection = false,
3384 texture = def.tail_texture,
3385 size = def.tail_size or 5,
3386 glow = def.glow or 0,
3390 if self.hit_node then
3392 local node = node_ok(pos).name
3394 if minetest.registered_nodes[node].walkable then
3396 self.hit_node(self, pos, node)
3398 if self.drop == true then
3400 pos.y = pos.y + 1
3402 self.lastpos = (self.lastpos or pos)
3404 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
3407 self.object:remove() ; -- print ("hit node")
3409 return
3413 if self.hit_player or self.hit_mob then
3415 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
3417 if self.hit_player
3418 and player:is_player() then
3420 self.hit_player(self, player)
3421 self.object:remove() ; -- print ("hit player")
3422 return
3425 local entity = player:get_luaentity()
3427 if entity
3428 and self.hit_mob
3429 and entity._cmi_is_mob == true
3430 and tostring(player) ~= self.owner_id
3431 and entity.name ~= self.object:get_luaentity().name then
3433 self.hit_mob(self, player)
3435 self.object:remove() ; --print ("hit mob")
3437 return
3442 self.lastpos = pos
3448 -- compatibility function
3449 function mobs:explosion(pos, radius)
3450 local self = {sounds = {}}
3451 self.sounds.explode = "tnt_explode"
3452 mobs:boom(self, pos, radius)
3456 -- no damage to nodes explosion
3457 function mobs:safe_boom(self, pos, radius)
3459 minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
3460 pos = pos,
3461 gain = 1.0,
3462 max_hear_distance = self.sounds and self.sounds.distance or 32
3465 entity_physics(pos, radius)
3466 effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
3470 -- make explosion with protection and tnt mod check
3471 function mobs:boom(self, pos, radius)
3473 if mobs_griefing
3474 and mod_tnt and tnt and tnt.boom
3475 and not minetest.is_protected(pos, "") then
3477 tnt.boom(pos, {
3478 radius = radius,
3479 damage_radius = radius,
3480 sound = self.sounds and self.sounds.explode,
3481 explode_center = true,
3483 else
3484 mobs:safe_boom(self, pos, radius)
3489 -- Register spawn eggs
3491 -- Note: This also introduces the “spawn_egg” group:
3492 -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
3493 -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
3494 function mobs:register_egg(mob, desc, background, addegg, no_creative)
3496 local grp = {spawn_egg = 1}
3498 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3499 if creative and no_creative == true then
3500 grp.not_in_creative_inventory = 1
3503 local invimg = background
3505 if addegg == 1 then
3506 invimg = "mobs_chicken_egg.png^(" .. invimg ..
3507 "^[mask:mobs_chicken_egg_overlay.png)"
3510 -- register new spawn egg containing mob information
3511 --[=[ DISABLED IN MCL2
3512 minetest.register_craftitem(mob .. "_set", {
3514 description = S("@1 (Tamed)", desc),
3515 inventory_image = invimg,
3516 groups = {spawn_egg = 2, not_in_creative_inventory = 1},
3517 stack_max = 1,
3519 on_place = function(itemstack, placer, pointed_thing)
3521 local pos = pointed_thing.above
3523 -- am I clicking on something with existing on_rightclick function?
3524 local under = minetest.get_node(pointed_thing.under)
3525 local def = minetest.registered_nodes[under.name]
3526 if def and def.on_rightclick then
3527 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3530 if pos
3531 and within_limits(pos, 0)
3532 and not minetest.is_protected(pos, placer:get_player_name()) then
3534 if not minetest.registered_entities[mob] then
3535 return
3538 pos.y = pos.y + 1
3540 local data = itemstack:get_metadata()
3541 local mob = minetest.add_entity(pos, mob, data)
3542 local ent = mob:get_luaentity()
3544 -- set owner if not a monster
3545 if ent.type ~= "monster" then
3546 ent.owner = placer:get_player_name()
3547 ent.tamed = true
3550 -- since mob is unique we remove egg once spawned
3551 itemstack:take_item()
3554 return itemstack
3555 end,
3559 -- register old stackable mob egg
3560 minetest.register_craftitem(mob, {
3562 description = desc,
3563 inventory_image = invimg,
3564 groups = grp,
3566 _doc_items_longdesc = "This allows you to place a single mob.",
3567 _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.",
3569 on_place = function(itemstack, placer, pointed_thing)
3571 local pos = pointed_thing.above
3573 -- am I clicking on something with existing on_rightclick function?
3574 local under = minetest.get_node(pointed_thing.under)
3575 local def = minetest.registered_nodes[under.name]
3576 if def and def.on_rightclick then
3577 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3580 if pos
3581 and within_limits(pos, 0)
3582 and not minetest.is_protected(pos, placer:get_player_name()) then
3584 local name = placer:get_player_name()
3585 local privs = minetest.get_player_privs(name)
3586 if not privs.maphack then
3587 minetest.chat_send_player(name, "You need the “maphack” privilege to change the mob spawner.")
3588 return itemstack
3590 if mod_mobspawners and under.name == "mcl_mobspawners:spawner" then
3591 mcl_mobspawners.setup_spawner(pointed_thing.under, itemstack:get_name())
3592 if not minetest.settings:get_bool("creative_mode") then
3593 itemstack:take_item()
3595 return itemstack
3598 if not minetest.registered_entities[mob] then
3599 return itemstack
3602 pos.y = pos.y + 1
3604 local mob = minetest.add_entity(pos, mob)
3605 local ent = mob:get_luaentity()
3607 -- don't set owner if monster or sneak pressed
3608 if ent.type ~= "monster"
3609 and not placer:get_player_control().sneak then
3610 ent.owner = placer:get_player_name()
3611 ent.tamed = true
3614 -- set nametag
3615 local nametag = itemstack:get_meta():get_string("name")
3616 if nametag ~= "" then
3617 if string.len(nametag) > MAX_MOB_NAME_LENGTH then
3618 nametag = string.sub(nametag, 1, MAX_MOB_NAME_LENGTH)
3620 ent.nametag = nametag
3621 update_tag(ent)
3624 -- if not in creative then take item
3625 if not mobs.is_creative(placer:get_player_name()) then
3626 itemstack:take_item()
3630 return itemstack
3631 end,
3637 -- capture critter (thanks to blert2112 for idea)
3638 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
3639 return false
3640 --[=[ DISABLED IN MCL2
3641 if self.child
3642 or not clicker:is_player()
3643 or not clicker:get_inventory() then
3644 return false
3647 -- get name of clicked mob
3648 local mobname = self.name
3650 -- if not nil change what will be added to inventory
3651 if replacewith then
3652 mobname = replacewith
3655 local name = clicker:get_player_name()
3656 local tool = clicker:get_wielded_item()
3658 -- are we using hand, net or lasso to pick up mob?
3659 if tool:get_name() ~= ""
3660 and tool:get_name() ~= "mobs:net"
3661 and tool:get_name() ~= "mobs:lasso" then
3662 return false
3665 -- is mob tamed?
3666 if self.tamed == false
3667 and force_take == false then
3669 minetest.chat_send_player(name, S("Not tamed!"))
3671 return true -- false
3674 -- cannot pick up if not owner
3675 if self.owner ~= name
3676 and force_take == false then
3678 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
3680 return true -- false
3683 if clicker:get_inventory():room_for_item("main", mobname) then
3685 -- was mob clicked with hand, net, or lasso?
3686 local chance = 0
3688 if tool:get_name() == "" then
3689 chance = chance_hand
3691 elseif tool:get_name() == "mobs:net" then
3693 chance = chance_net
3695 tool:add_wear(4000) -- 17 uses
3697 clicker:set_wielded_item(tool)
3699 elseif tool:get_name() == "mobs:lasso" then
3701 chance = chance_lasso
3703 tool:add_wear(650) -- 100 uses
3705 clicker:set_wielded_item(tool)
3709 -- calculate chance.. add to inventory if successful?
3710 if chance > 0 and random(1, 100) <= chance then
3712 -- default mob egg
3713 local new_stack = ItemStack(mobname)
3715 -- add special mob egg with all mob information
3716 -- unless 'replacewith' contains new item to use
3717 if not replacewith then
3719 new_stack = ItemStack(mobname .. "_set")
3721 local tmp = {}
3723 for _,stat in pairs(self) do
3724 local t = type(stat)
3725 if t ~= "function"
3726 and t ~= "nil"
3727 and t ~= "userdata" then
3728 tmp[_] = self[_]
3732 local data_str = minetest.serialize(tmp)
3734 new_stack:set_metadata(data_str)
3737 local inv = clicker:get_inventory()
3739 if inv:room_for_item("main", new_stack) then
3740 inv:add_item("main", new_stack)
3741 else
3742 minetest.add_item(clicker:get_pos(), new_stack)
3745 self.object:remove()
3747 mob_sound(self, "default_place_node_hard")
3749 elseif chance ~= 0 then
3750 minetest.chat_send_player(name, S("Missed!"))
3752 mob_sound(self, "mobs_swing")
3756 return true
3761 -- protect tamed mob with rune item
3762 function mobs:protect(self, clicker)
3763 local name = clicker:get_player_name()
3764 local tool = clicker:get_wielded_item()
3766 if tool:get_name() ~= "mobs:protector" then
3767 return false
3770 if self.tamed == false then
3771 minetest.chat_send_player(name, S("Not tamed!"))
3772 return true -- false
3775 if self.protected == true then
3776 minetest.chat_send_player(name, S("Already protected!"))
3777 return true -- false
3780 if not mobs.is_creative(clicker:get_player_name()) then
3781 tool:take_item() -- take 1 protection rune
3782 clicker:set_wielded_item(tool)
3785 self.protected = true
3787 local pos = self.object:get_pos()
3788 pos.y = pos.y + self.collisionbox[2] + 0.5
3790 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3792 mob_sound(self, "mobs_spell")
3794 return true
3798 local mob_obj = {}
3799 local mob_sta = {}
3801 -- feeding, taming and breeding (thanks blert2112)
3802 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
3803 if not self.follow then
3804 return false
3807 -- can eat/tame with item in hand
3808 if follow_holding(self, clicker) then
3810 -- if not in creative then take item
3811 if not mobs.is_creative(clicker:get_player_name()) then
3813 local item = clicker:get_wielded_item()
3815 item:take_item()
3817 clicker:set_wielded_item(item)
3820 -- increase health
3821 self.health = self.health + 4
3823 if self.health >= self.hp_max then
3825 self.health = self.hp_max
3827 if self.htimer < 1 then
3828 -- DISABLED IN MCL2
3829 --[=[
3830 minetest.chat_send_player(clicker:get_player_name(),
3831 S("@1 at full health (@2)",
3832 self.name:split(":")[2], tostring(self.health)))
3834 self.htimer = 5
3838 self.object:set_hp(self.health)
3840 update_tag(self)
3842 -- make children grow quicker
3843 if self.child == true then
3845 self.hornytimer = self.hornytimer + 20
3847 return true
3850 -- feed and tame
3851 self.food = (self.food or 0) + 1
3852 if self.food >= feed_count then
3854 self.food = 0
3856 if breed and self.hornytimer == 0 then
3857 self.horny = true
3860 self.gotten = false
3862 if tame then
3864 if self.tamed == false then
3865 --[[ DISABLED IN MCL2
3866 minetest.chat_send_player(clicker:get_player_name(),
3867 S("@1 has been tamed!",
3868 self.name:split(":")[2]))
3872 self.tamed = true
3874 if not self.owner or self.owner == "" then
3875 self.owner = clicker:get_player_name()
3879 -- make sound when fed so many times
3880 mob_sound(self, self.sounds.random)
3883 return true
3886 return false
3889 -- Spawn a child
3890 function mobs:spawn_child(pos, mob_type)
3891 local child = minetest.add_entity(pos, mob_type)
3892 if not child then
3893 return
3896 local ent = child:get_luaentity()
3897 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
3899 ent.child = true
3901 local textures
3902 -- using specific child texture (if found)
3903 if ent.child_texture then
3904 textures = ent.child_texture[1]
3907 -- and resize to half height
3908 child:set_properties({
3909 textures = textures,
3910 visual_size = {
3911 x = ent.base_size.x * .5,
3912 y = ent.base_size.y * .5,
3914 collisionbox = {
3915 ent.base_colbox[1] * .5,
3916 ent.base_colbox[2] * .5,
3917 ent.base_colbox[3] * .5,
3918 ent.base_colbox[4] * .5,
3919 ent.base_colbox[5] * .5,
3920 ent.base_colbox[6] * .5,
3922 selectionbox = {
3923 ent.base_selbox[1] * .5,
3924 ent.base_selbox[2] * .5,
3925 ent.base_selbox[3] * .5,
3926 ent.base_selbox[4] * .5,
3927 ent.base_selbox[5] * .5,
3928 ent.base_selbox[6] * .5,
3932 return child
3936 -- DISABLED IN MCL2
3937 --[=[
3938 -- inspired by blockmen's nametag mod
3939 minetest.register_on_player_receive_fields(function(player, formname, fields)
3941 -- right-clicked with nametag and name entered?
3942 if formname == "mobs_nametag"
3943 and fields.name
3944 and fields.name ~= "" then
3946 local name = player:get_player_name()
3948 if not mob_obj[name]
3949 or not mob_obj[name].object then
3950 return
3953 -- make sure nametag is being used to name mob
3954 local item = player:get_wielded_item()
3956 if item:get_name() ~= "mobs:nametag" then
3957 return
3960 -- limit name entered to 64 characters long
3961 if string.len(fields.name) > 64 then
3962 fields.name = string.sub(fields.name, 1, 64)
3965 -- update nametag
3966 mob_obj[name].nametag = fields.name
3968 update_tag(mob_obj[name])
3970 -- if not in creative then take item
3971 if not mobs.is_creative(name) then
3973 mob_sta[name]:take_item()
3975 player:set_wielded_item(mob_sta[name])
3978 -- reset external variables
3979 mob_obj[name] = nil
3980 mob_sta[name] = nil
3982 end)
3986 -- compatibility function for old entities to new modpack entities
3987 function mobs:alias_mob(old_name, new_name)
3989 -- spawn egg
3990 minetest.register_alias(old_name, new_name)
3992 -- entity
3993 minetest.register_entity(":" .. old_name, {
3995 physical = false,
3997 on_step = function(self)
3999 if minetest.registered_entities[new_name] then
4000 minetest.add_entity(self.object:get_pos(), new_name)
4003 self.object:remove()