0 | |
local MP = minetest.get_modpath(minetest.get_current_modname())
|
1 | |
|
2 | 0 |
-- Check for translation method
|
3 | 1 |
local S
|
4 | 2 |
if minetest.get_translator ~= nil then
|
|
7 | 5 |
if minetest.get_modpath("intllib") then
|
8 | 6 |
dofile(minetest.get_modpath("intllib") .. "/init.lua")
|
9 | 7 |
if intllib.make_gettext_pair then
|
10 | |
gettext, ngettext = intllib.make_gettext_pair() -- new gettext method
|
|
8 |
S = intllib.make_gettext_pair() -- new gettext method
|
11 | 9 |
else
|
12 | |
gettext = intllib.Getter() -- old text file method
|
13 | |
end
|
14 | |
S = gettext
|
|
10 |
S = intllib.Getter() -- old text file method
|
|
11 |
end
|
15 | 12 |
else -- boilerplate function
|
16 | 13 |
S = function(str, ...)
|
17 | 14 |
local args = {...}
|
|
27 | 24 |
|
28 | 25 |
mobs = {
|
29 | 26 |
mod = "redo",
|
30 | |
version = "20220314",
|
|
27 |
version = "20221213",
|
31 | 28 |
intllib = S,
|
32 | 29 |
invis = minetest.global_exists("invisibility") and invisibility or {}
|
33 | 30 |
}
|
|
44 | 41 |
local floor = math.floor
|
45 | 42 |
local ceil = math.ceil
|
46 | 43 |
local rad = math.rad
|
|
44 |
local deg = math.deg
|
47 | 45 |
local atann = math.atan
|
48 | 46 |
local atan = function(x)
|
49 | 47 |
if not x or x ~= x then
|
|
54 | 52 |
end
|
55 | 53 |
local table_copy = table.copy
|
56 | 54 |
local table_remove = table.remove
|
57 | |
local vadd = vector.add
|
58 | 55 |
local vdirection = vector.direction
|
59 | 56 |
local vmultiply = vector.multiply
|
60 | 57 |
local vsubtract = vector.subtract
|
|
79 | 76 |
local remove_far = settings:get_bool("remove_far_mobs") ~= false
|
80 | 77 |
local mob_area_spawn = settings:get_bool("mob_area_spawn")
|
81 | 78 |
local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0
|
82 | |
local show_health = settings:get_bool("mob_show_health") ~= false
|
83 | 79 |
local max_per_block = tonumber(settings:get("max_objects_per_block") or 99)
|
84 | 80 |
local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12)
|
85 | 81 |
local active_limit = tonumber(settings:get("mob_active_limit") or 0)
|
|
105 | 101 |
local stuck_path_timeout = 5 -- how long will mob follow path before giving up
|
106 | 102 |
|
107 | 103 |
-- default nodes
|
108 | |
local node_fire = "fire:basic_flame"
|
109 | |
local node_permanent_flame = "fire:permanent_flame"
|
|
104 |
--local node_fire = "fire:basic_flame"
|
|
105 |
--local node_permanent_flame = "fire:permanent_flame"
|
110 | 106 |
local node_ice = "default:ice"
|
111 | 107 |
local node_snowblock = "default:snowblock"
|
112 | 108 |
local node_snow = "default:snow"
|
|
109 |
|
113 | 110 |
mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
|
114 | 111 |
|
115 | |
local mob_class = {
|
|
112 |
mobs.mob_class = {
|
116 | 113 |
stepheight = 1.1,
|
117 | 114 |
fly_in = "air",
|
118 | 115 |
owner = "",
|
|
183 | 180 |
_cmi_is_mob = true
|
184 | 181 |
}
|
185 | 182 |
|
|
183 |
local mob_class = mobs.mob_class -- Compatibility
|
186 | 184 |
local mob_class_meta = {__index = mob_class}
|
187 | 185 |
|
188 | 186 |
|
189 | 187 |
-- play sound
|
190 | 188 |
function mob_class:mob_sound(sound)
|
191 | 189 |
|
192 | |
local pitch = 1.0
|
193 | |
|
194 | |
-- higher pitch for a child
|
195 | |
if self.child then pitch = pitch * 1.5 end
|
196 | |
|
197 | |
-- a little random pitch to be different
|
198 | |
pitch = pitch + random(-10, 10) * 0.005
|
199 | |
|
200 | 190 |
if sound then
|
|
191 |
|
|
192 |
-- higher pitch for a child
|
|
193 |
local pitch = self.child and 1.5 or 1.0
|
|
194 |
|
|
195 |
-- a little random pitch to be different
|
|
196 |
pitch = pitch + random(-10, 10) * 0.005
|
|
197 |
|
201 | 198 |
minetest.sound_play(sound, {
|
202 | 199 |
object = self.object,
|
203 | 200 |
gain = 1.0,
|
|
218 | 215 |
self.attack = player
|
219 | 216 |
self.state = "attack"
|
220 | 217 |
|
221 | |
if random(0, 100) < 90 then
|
|
218 |
if random(100) < 90 then
|
222 | 219 |
self:mob_sound(self.sounds.war_cry)
|
223 | 220 |
end
|
224 | 221 |
end
|
|
420 | 417 |
self.object:set_animation({
|
421 | 418 |
x = self.animation[anim .. "_start"],
|
422 | 419 |
y = self.animation[anim .. "_end"]},
|
423 | |
self.animation[anim .. "_speed"] or
|
424 | |
self.animation.speed_normal or 15,
|
|
420 |
self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
|
425 | 421 |
0, self.animation[anim .. "_loop"] ~= false)
|
426 | 422 |
end
|
427 | 423 |
|
|
435 | 431 |
|
436 | 432 |
stepsize = stepsize or 1
|
437 | 433 |
|
438 | |
local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
|
|
434 |
local s = minetest.line_of_sight(pos1, pos2, stepsize)
|
439 | 435 |
|
440 | 436 |
-- normal walking and flying mobs can see you through air
|
441 | 437 |
if s == true then
|
|
462 | 458 |
-- It continues to advance in the line of sight in search of a real
|
463 | 459 |
-- obstruction which counts as 'walkable' nodebox.
|
464 | 460 |
while minetest.registered_nodes[nn]
|
465 | |
and (minetest.registered_nodes[nn].walkable == false) do
|
|
461 |
and minetest.registered_nodes[nn].walkable == false do
|
466 | 462 |
|
467 | 463 |
-- Check if you can still move forward
|
468 | 464 |
if td < ad + stepsize then
|
|
499 | 495 |
end
|
500 | 496 |
|
501 | 497 |
|
502 | |
-- check line of sight (by BrunoMine, tweaked by Astrobe)
|
503 | |
local new_line_of_sight = function(self, pos1, pos2, stepsize)
|
504 | |
|
505 | |
if not pos1 or not pos2 then return end
|
506 | |
|
507 | |
stepsize = stepsize or 1
|
508 | |
|
509 | |
local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
|
510 | |
|
511 | |
local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
|
512 | |
|
513 | |
-- normal walking and flying mobs can see you through air
|
514 | |
if s == true then return true end
|
515 | |
|
516 | |
-- New pos1 to be analyzed
|
517 | |
local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
|
518 | |
|
519 | |
local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
|
520 | |
|
521 | |
-- Checks the return
|
522 | |
if r == true then return true end
|
523 | |
|
524 | |
-- Nodename found
|
525 | |
local nn = minetest.get_node(pos).name
|
526 | |
|
527 | |
-- It continues to advance in the line of sight in search of a real
|
528 | |
-- obstruction which counts as 'walkable' nodebox.
|
529 | |
while minetest.registered_nodes[nn]
|
530 | |
and (minetest.registered_nodes[nn].walkable == false) do
|
531 | |
|
532 | |
npos1 = vadd(npos1, stepv)
|
533 | |
|
534 | |
if get_distance(npos1, pos2) < stepsize then return true end
|
535 | |
|
536 | |
-- scan again
|
537 | |
r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
|
538 | |
|
539 | |
if r == true then return true end
|
540 | |
|
541 | |
-- New Nodename found
|
542 | |
nn = minetest.get_node(pos).name
|
543 | |
end
|
544 | |
|
545 | |
return false
|
546 | |
end
|
547 | |
|
548 | 498 |
-- check line of sight using raycasting (thanks Astrobe)
|
549 | 499 |
local ray_line_of_sight = function(self, pos1, pos2)
|
550 | 500 |
|
|
610 | 560 |
local escape_target = flyable_nodes[random(#flyable_nodes)]
|
611 | 561 |
local escape_direction = vdirection(pos, escape_target)
|
612 | 562 |
|
613 | |
self.object:set_velocity(
|
614 | |
vmultiply(escape_direction, 1))
|
|
563 |
self.object:set_velocity(vmultiply(escape_direction, 1))
|
615 | 564 |
|
616 | 565 |
return true
|
617 | 566 |
end
|
|
749 | 698 |
local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
|
750 | 699 |
|
751 | 700 |
|
752 | |
-- update nametag colour
|
|
701 |
-- update nametag and infotext
|
753 | 702 |
function mob_class:update_tag()
|
754 | 703 |
|
755 | 704 |
local col = "#00FF00"
|
|
783 | 732 |
|
784 | 733 |
end
|
785 | 734 |
|
|
735 |
if self.protected then
|
|
736 |
if self.protected == 2 then
|
|
737 |
text = text .. "\nProtection: Level 2"
|
|
738 |
else
|
|
739 |
text = text .. "\nProtection: Level 1"
|
|
740 |
end
|
|
741 |
end
|
|
742 |
|
786 | 743 |
self.infotext = "Health: " .. self.health .. " / " .. self.hp_max
|
787 | |
.. (self.owner == "" and "" or "\n" .. "Owner: " .. self.owner)
|
|
744 |
.. (self.owner == "" and "" or "\nOwner: " .. self.owner)
|
788 | 745 |
.. text
|
789 | 746 |
|
790 | 747 |
-- set changes
|
|
805 | 762 |
local pos = self.object:get_pos()
|
806 | 763 |
|
807 | 764 |
-- check for drops function
|
808 | |
self.drops = type(self.drops) == "function"
|
809 | |
and self.drops(pos) or self.drops
|
|
765 |
self.drops = type(self.drops) == "function" and self.drops(pos) or self.drops
|
810 | 766 |
|
811 | 767 |
-- check for nil or no drops
|
812 | 768 |
if not self.drops or #self.drops == 0 then
|
|
818 | 774 |
and self.cause_of_death.puncher
|
819 | 775 |
and self.cause_of_death.puncher:is_player()
|
820 | 776 |
|
|
777 |
-- check for tool 'looting_level' under tool_capabilities as default, or use
|
|
778 |
-- meta string 'looting_level' if found (max looting level is 3).
|
|
779 |
local looting = 0
|
|
780 |
|
|
781 |
if death_by_player then
|
|
782 |
|
|
783 |
local wield_stack = self.cause_of_death.puncher:get_wielded_item()
|
|
784 |
local wield_name = wield_stack:get_name()
|
|
785 |
local wield_stack_meta = wield_stack:get_meta()
|
|
786 |
local item_def = minetest.registered_items[wield_name]
|
|
787 |
local item_looting = item_def and item_def.tool_capabilities and
|
|
788 |
item_def.tool_capabilities.looting_level or 0
|
|
789 |
|
|
790 |
looting = tonumber(wield_stack_meta:get_string("looting_level")) or item_looting
|
|
791 |
looting = min(looting, 3)
|
|
792 |
end
|
|
793 |
|
|
794 |
--print("--- looting level", looting)
|
|
795 |
|
821 | 796 |
local obj, item, num
|
822 | 797 |
|
823 | 798 |
for n = 1, #self.drops do
|
|
840 | 815 |
|
841 | 816 |
-- only drop rare items (drops.min = 0) if killed by player
|
842 | 817 |
if death_by_player or self.drops[n].min ~= 0 then
|
843 | |
obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
|
|
818 |
obj = minetest.add_item(pos, ItemStack(item .. " " .. (num + looting)))
|
844 | 819 |
end
|
845 | 820 |
|
846 | 821 |
if obj and obj:get_luaentity() then
|
|
913 | 888 |
self.health = self.hp_max
|
914 | 889 |
end
|
915 | 890 |
|
916 | |
-- backup nametag so we can show health stats
|
917 | |
-- if not self.nametag2 then
|
918 | |
-- self.nametag2 = self.nametag or ""
|
919 | |
-- end
|
920 | |
|
921 | |
-- if show_health
|
922 | |
-- and (cmi_cause and cmi_cause.type == "punch") then
|
923 | |
|
924 | |
-- self.htimer = 2
|
925 | |
-- self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
|
926 | |
self:update_tag()
|
927 | |
-- end
|
|
891 |
self:update_tag()
|
928 | 892 |
|
929 | 893 |
return false
|
930 | 894 |
end
|
|
1008 | 972 |
-- get node but use fallback for nil or unknown
|
1009 | 973 |
local node_ok = function(pos, fallback)
|
1010 | 974 |
|
1011 | |
fallback = fallback or mobs.fallback_node
|
1012 | |
|
1013 | 975 |
local node = minetest.get_node_or_nil(pos)
|
1014 | 976 |
|
1015 | 977 |
if node and minetest.registered_nodes[node.name] then
|
1016 | 978 |
return node
|
1017 | 979 |
end
|
1018 | 980 |
|
1019 | |
return minetest.registered_nodes[fallback]
|
|
981 |
return minetest.registered_nodes[(fallback or mobs.fallback_node)]
|
1020 | 982 |
end
|
1021 | 983 |
|
1022 | 984 |
|
|
1053 | 1015 |
-- is mob facing a cliff
|
1054 | 1016 |
function mob_class:is_at_cliff()
|
1055 | 1017 |
|
1056 | |
if self.fear_height == 0 then -- 0 for no falling protection!
|
|
1018 |
if self.driver or self.fear_height == 0 then -- 0 for no falling protection!
|
1057 | 1019 |
return false
|
1058 | 1020 |
end
|
1059 | 1021 |
|
|
1076 | 1038 |
return true
|
1077 | 1039 |
end
|
1078 | 1040 |
|
1079 | |
local bnode = node_ok(blocker)
|
|
1041 |
local bnode = node_ok(blocker, "air")
|
1080 | 1042 |
|
1081 | 1043 |
-- will we drop onto dangerous node?
|
1082 | 1044 |
if is_node_dangerous(self, bnode.name) then
|
|
1092 | 1054 |
-- environmental damage (water, lava, fire, light etc.)
|
1093 | 1055 |
function mob_class:do_env_damage()
|
1094 | 1056 |
|
1095 | |
-- feed/tame text timer (so mob 'full' messages dont spam chat)
|
1096 | |
if self.htimer > 0 then
|
1097 | |
self.htimer = self.htimer - 1
|
1098 | |
end
|
1099 | |
|
1100 | |
-- reset nametag after showing health stats
|
1101 | |
-- if self.htimer < 1 and self.nametag2 then
|
1102 | |
|
1103 | |
-- self.nametag = self.nametag2
|
1104 | |
-- self.nametag2 = nil
|
1105 | |
|
1106 | |
self:update_tag()
|
1107 | |
-- end
|
|
1057 |
self:update_tag()
|
1108 | 1058 |
|
1109 | 1059 |
local pos = self.object:get_pos() ; if not pos then return end
|
1110 | 1060 |
|
|
1273 | 1223 |
-- set y_pos to base of mob
|
1274 | 1224 |
pos.y = pos.y + self.collisionbox[2]
|
1275 | 1225 |
|
1276 | |
-- what is in front of mob?
|
1277 | |
local nod = node_ok({
|
1278 | |
x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
|
1279 | |
})
|
1280 | |
|
1281 | |
-- what is above and in front?
|
1282 | |
local nodt = node_ok({
|
1283 | |
x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
|
1284 | |
})
|
1285 | |
|
|
1226 |
-- what is in front of mob and above?
|
|
1227 |
local nod = node_ok({x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z})
|
|
1228 |
local nodt = node_ok({x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z})
|
1286 | 1229 |
local blocked = minetest.registered_nodes[nodt.name].walkable
|
1287 | 1230 |
|
1288 | 1231 |
-- are we facing a fence or wall
|
1289 | 1232 |
if nod.name:find("fence") or nod.name:find("gate") or nod.name:find("wall") then
|
1290 | 1233 |
self.facing_fence = true
|
1291 | 1234 |
end
|
|
1235 |
|
1292 | 1236 |
--[[
|
1293 | 1237 |
print("on: " .. self.standing_on
|
1294 | 1238 |
.. ", front: " .. nod.name
|
|
1297 | 1241 |
.. ", fence: " .. (self.facing_fence and "yes" or "no")
|
1298 | 1242 |
)
|
1299 | 1243 |
]]
|
|
1244 |
|
|
1245 |
-- if mob can leap then remove blockages and let them try
|
|
1246 |
if self.can_leap == true then
|
|
1247 |
blocked = false
|
|
1248 |
self.facing_fence = false
|
|
1249 |
end
|
|
1250 |
|
1300 | 1251 |
-- jump if standing on solid node (not snow) and not blocked
|
1301 | 1252 |
if (self.walk_chance == 0 or minetest.registered_items[nod.name].walkable)
|
1302 | 1253 |
and not blocked and not self.facing_fence and nod.name ~= node_snow then
|
|
1341 | 1292 |
local yaw = self.object:get_yaw() or 0
|
1342 | 1293 |
local turn = random(0, 2) + 1.35
|
1343 | 1294 |
|
1344 | |
yaw = self:set_yaw(yaw + turn, 12)
|
|
1295 |
self:set_yaw(yaw + turn, 12)
|
1345 | 1296 |
|
1346 | 1297 |
self.jump_count = 0
|
1347 | 1298 |
end
|
|
1368 | 1319 |
if dist < 1 then dist = 1 end
|
1369 | 1320 |
|
1370 | 1321 |
local damage = floor((4 / dist) * radius)
|
1371 | |
local ent = objs[n]:get_luaentity()
|
1372 | 1322 |
|
1373 | 1323 |
-- punches work on entities AND players
|
1374 | 1324 |
objs[n]:punch(objs[n], 1.0, {
|
1375 | 1325 |
full_punch_interval = 1.0,
|
1376 | |
damage_groups = {fleshy = damage},
|
|
1326 |
damage_groups = {fleshy = damage}
|
1377 | 1327 |
}, pos)
|
1378 | 1328 |
end
|
1379 | 1329 |
end
|
|
1439 | 1389 |
self.object:set_pos(pos)
|
1440 | 1390 |
|
1441 | 1391 |
-- jump slightly when fully grown so as not to fall into ground
|
1442 | |
self.object:set_velocity({x = 0, y = 0.5, z = 0 })
|
|
1392 |
self.object:set_velocity({x = 0, y = 2, z = 0 })
|
1443 | 1393 |
end
|
1444 | 1394 |
end
|
1445 | 1395 |
|
|
1467 | 1417 |
|
1468 | 1418 |
local pos = self.object:get_pos()
|
1469 | 1419 |
|
1470 | |
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
|
1471 | |
"heart.png", 3, 4, 1, 0.1)
|
|
1420 |
effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
|
1472 | 1421 |
|
1473 | 1422 |
local objs = minetest.get_objects_inside_radius(pos, 3)
|
1474 | 1423 |
local ent
|
|
1489 | 1438 |
local selfname = self.name:split(":")
|
1490 | 1439 |
|
1491 | 1440 |
if entname[1] == selfname[1] then
|
|
1441 |
|
1492 | 1442 |
entname = entname[2]:split("_")
|
1493 | 1443 |
selfname = selfname[2]:split("_")
|
1494 | 1444 |
|
|
1576 | 1526 |
self.base_selbox[4] * .5,
|
1577 | 1527 |
self.base_selbox[5] * .5,
|
1578 | 1528 |
self.base_selbox[6] * .5
|
1579 | |
},
|
|
1529 |
}
|
1580 | 1530 |
})
|
1581 | 1531 |
-- tamed and owned by parents' owner
|
1582 | 1532 |
ent2.child = true
|
|
1652 | 1602 |
function mob_class:day_docile()
|
1653 | 1603 |
|
1654 | 1604 |
if self.docile_by_day == false then
|
1655 | |
|
1656 | 1605 |
return false
|
1657 | |
|
1658 | 1606 |
elseif self.docile_by_day == true
|
1659 | 1607 |
and self.time_of_day > 0.2
|
1660 | 1608 |
and self.time_of_day < 0.8 then
|
1661 | |
|
1662 | 1609 |
return true
|
1663 | 1610 |
end
|
1664 | 1611 |
end
|
|
1703 | 1650 |
|
1704 | 1651 |
|
1705 | 1652 |
local pathfinder_mod = minetest.get_modpath("pathfinder")
|
|
1653 |
|
1706 | 1654 |
-- path finding and smart mob routine by rnd,
|
1707 | 1655 |
-- line_of_sight and other edits by Elkien3
|
1708 | 1656 |
function mob_class:smart_mobs(s, p, dist, dtime)
|
|
1750 | 1698 |
end -- can see target!
|
1751 | 1699 |
end
|
1752 | 1700 |
|
1753 | |
if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
|
|
1701 |
if self.path.stuck_timer > stuck_timeout and not self.path.following then
|
1754 | 1702 |
|
1755 | 1703 |
use_pathfind = true
|
1756 | 1704 |
self.path.stuck_timer = 0
|
|
1766 | 1714 |
end, self)
|
1767 | 1715 |
end
|
1768 | 1716 |
|
1769 | |
if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
|
|
1717 |
if self.path.stuck_timer > stuck_path_timeout and self.path.following then
|
1770 | 1718 |
|
1771 | 1719 |
use_pathfind = true
|
1772 | 1720 |
self.path.stuck_timer = 0
|
|
1782 | 1730 |
end, self)
|
1783 | 1731 |
end
|
1784 | 1732 |
|
1785 | |
if abs(vsubtract(s,target_pos).y) > self.stepheight then
|
|
1733 |
if abs(vsubtract(s, target_pos).y) > self.stepheight then
|
1786 | 1734 |
|
1787 | 1735 |
if height_switcher then
|
1788 | 1736 |
use_pathfind = true
|
|
1844 | 1792 |
print("-- path length:" .. tonumber(#self.path.way))
|
1845 | 1793 |
|
1846 | 1794 |
for _,pos in pairs(self.path.way) do
|
|
1795 |
|
1847 | 1796 |
minetest.add_particle({
|
1848 | |
pos = pos,
|
1849 | |
velocity = {x=0, y=0, z=0},
|
1850 | |
acceleration = {x=0, y=0, z=0},
|
1851 | |
expirationtime = 1,
|
1852 | |
size = 4,
|
1853 | |
collisiondetection = false,
|
1854 | |
vertical = false,
|
1855 | |
texture = "heart.png",
|
|
1797 |
pos = pos,
|
|
1798 |
velocity = {x=0, y=0, z=0},
|
|
1799 |
acceleration = {x=0, y=0, z=0},
|
|
1800 |
expirationtime = 1,
|
|
1801 |
size = 4,
|
|
1802 |
collisiondetection = false,
|
|
1803 |
vertical = false,
|
|
1804 |
texture = "heart.png",
|
1856 | 1805 |
})
|
1857 | 1806 |
end
|
1858 | 1807 |
end
|
|
1948 | 1897 |
-- peaceful player privilege support
|
1949 | 1898 |
local function is_peaceful_player(player)
|
1950 | 1899 |
|
|
1900 |
-- main setting enabled
|
1951 | 1901 |
if peaceful_player_enabled then
|
1952 | |
|
1953 | |
local player_name = player:get_player_name()
|
1954 | |
|
1955 | |
if player_name
|
1956 | |
and minetest.check_player_privs(player_name, "peaceful_player") then
|
1957 | |
return true
|
1958 | |
end
|
|
1902 |
return true
|
|
1903 |
end
|
|
1904 |
|
|
1905 |
local player_name = player:get_player_name()
|
|
1906 |
|
|
1907 |
-- player priv enabled
|
|
1908 |
if player_name
|
|
1909 |
and minetest.check_player_privs(player_name, "peaceful_player") then
|
|
1910 |
return true
|
1959 | 1911 |
end
|
1960 | 1912 |
|
1961 | 1913 |
return false
|
|
2131 | 2083 |
|
2132 | 2084 |
for n = 1, #players do
|
2133 | 2085 |
|
2134 | |
if players[n] and get_distance(players[n]:get_pos(), s) < self.view_range
|
2135 | |
and not is_invisible(self, players[n]:get_player_name()) then
|
|
2086 |
if players[n]
|
|
2087 |
and not is_invisible(self, players[n]:get_player_name())
|
|
2088 |
and get_distance(players[n]:get_pos(), s) < self.view_range then
|
2136 | 2089 |
|
2137 | 2090 |
self.following = players[n]
|
2138 | 2091 |
|
|
2189 | 2142 |
yaw_to_pos(self, p)
|
2190 | 2143 |
|
2191 | 2144 |
-- anyone but standing npc's can move along
|
2192 | |
if dist > self.reach
|
|
2145 |
if dist >= self.reach
|
2193 | 2146 |
and self.order ~= "stand" then
|
2194 | 2147 |
|
2195 | 2148 |
self:set_velocity(self.walk_velocity)
|
|
2149 |
self.follow_stop = nil
|
2196 | 2150 |
|
2197 | 2151 |
if self.walk_chance ~= 0 then
|
2198 | 2152 |
self:set_animation("walk")
|
|
2200 | 2154 |
else
|
2201 | 2155 |
self:set_velocity(0)
|
2202 | 2156 |
self:set_animation("stand")
|
|
2157 |
self.follow_stop = true
|
2203 | 2158 |
end
|
2204 | 2159 |
|
2205 | 2160 |
return
|
|
2269 | 2224 |
|
2270 | 2225 |
local yaw = self.object:get_yaw() ; if not yaw then return end
|
2271 | 2226 |
|
2272 | |
if self.state == "stand" then
|
|
2227 |
if self.state == "stand" and not self.follow_stop then
|
2273 | 2228 |
|
2274 | 2229 |
if self.randomly_turn and random(4) == 1 then
|
2275 | 2230 |
|
|
2292 | 2247 |
yaw = yaw + random(-0.5, 0.5)
|
2293 | 2248 |
end
|
2294 | 2249 |
|
2295 | |
yaw = self:set_yaw(yaw, 8)
|
|
2250 |
self:set_yaw(yaw, 8)
|
2296 | 2251 |
end
|
2297 | 2252 |
|
2298 | 2253 |
self:set_velocity(0)
|
|
2356 | 2311 |
end
|
2357 | 2312 |
end
|
2358 | 2313 |
|
2359 | |
yaw = self:set_yaw(yaw, 8)
|
|
2314 |
self:set_yaw(yaw, 8)
|
2360 | 2315 |
|
2361 | 2316 |
-- otherwise randomly turn
|
2362 | 2317 |
elseif self.randomly_turn and random(100) <= 30 then
|
2363 | 2318 |
|
2364 | 2319 |
yaw = yaw + random(-0.5, 0.5)
|
2365 | 2320 |
|
2366 | |
yaw = self:set_yaw(yaw, 8)
|
|
2321 |
self:set_yaw(yaw, 8)
|
2367 | 2322 |
|
2368 | 2323 |
-- for flying/swimming mobs randomly move up and down also
|
2369 | 2324 |
if self.fly_in
|
|
2388 | 2343 |
else
|
2389 | 2344 |
self:set_velocity(self.walk_velocity)
|
2390 | 2345 |
|
|
2346 |
-- figure out which animation to use while in motion
|
2391 | 2347 |
if self:flight_check()
|
2392 | 2348 |
and self.animation
|
2393 | 2349 |
and self.animation.fly_start
|
2394 | 2350 |
and self.animation.fly_end then
|
2395 | |
self:set_animation("fly")
|
|
2351 |
|
|
2352 |
local on_ground = minetest.registered_nodes[self.standing_on].walkable
|
|
2353 |
local in_water = minetest.registered_nodes[self.standing_in].groups.water
|
|
2354 |
|
|
2355 |
if on_ground and in_water then
|
|
2356 |
self:set_animation("fly")
|
|
2357 |
elseif on_ground then
|
|
2358 |
self:set_animation("walk")
|
|
2359 |
else
|
|
2360 |
self:set_animation("fly")
|
|
2361 |
end
|
2396 | 2362 |
else
|
2397 | 2363 |
self:set_animation("walk")
|
2398 | 2364 |
end
|
|
2448 | 2414 |
|
2449 | 2415 |
if self.attack_type == "explode" then
|
2450 | 2416 |
|
2451 | |
yaw = yaw_to_pos(self, p)
|
|
2417 |
yaw_to_pos(self, p)
|
2452 | 2418 |
|
2453 | 2419 |
local node_break_radius = self.explosion_radius or 1
|
2454 | 2420 |
local entity_damage_radius = self.explosion_damage_radius
|
|
2510 | 2476 |
self.object:set_texture_mod(self.texture_mods)
|
2511 | 2477 |
else
|
2512 | 2478 |
|
2513 | |
self.object:set_texture_mod(self.texture_mods
|
2514 | |
.. "^[brighten")
|
|
2479 |
self.object:set_texture_mod(self.texture_mods .. "^[brighten")
|
2515 | 2480 |
end
|
2516 | 2481 |
|
2517 | 2482 |
self.blinkstatus = not self.blinkstatus
|
|
2532 | 2497 |
|
2533 | 2498 |
remove_mob(self, true)
|
2534 | 2499 |
|
2535 | |
if minetest.get_modpath("tnt") and tnt and tnt.boom
|
2536 | |
and not minetest.is_protected(pos, "") then
|
2537 | |
|
2538 | |
tnt.boom(pos, {
|
2539 | |
radius = node_break_radius,
|
2540 | |
damage_radius = entity_damage_radius,
|
2541 | |
sound = self.sounds.explode
|
2542 | |
})
|
2543 | |
else
|
2544 | |
|
2545 | |
minetest.sound_play(self.sounds.explode, {
|
2546 | |
pos = pos,
|
2547 | |
gain = 1.0,
|
2548 | |
max_hear_distance = self.sounds.distance or 32
|
2549 | |
})
|
2550 | |
|
2551 | |
entity_physics(pos, entity_damage_radius)
|
2552 | |
|
2553 | |
effect(pos, 32, "tnt_smoke.png", nil, nil,
|
2554 | |
node_break_radius, 1, 0)
|
2555 | |
end
|
|
2500 |
mobs:boom(self, pos, entity_damage_radius, node_break_radius)
|
2556 | 2501 |
|
2557 | 2502 |
return true
|
2558 | 2503 |
end
|
|
2638 | 2583 |
p = {x = p1.x, y = p1.y, z = p1.z}
|
2639 | 2584 |
end
|
2640 | 2585 |
|
2641 | |
yaw = yaw_to_pos(self, p)
|
|
2586 |
yaw_to_pos(self, p)
|
2642 | 2587 |
|
2643 | 2588 |
-- move towards enemy if beyond mob reach
|
2644 | |
if dist > self.reach then
|
|
2589 |
if dist > (self.reach + (self.reach_ext or 0)) then
|
2645 | 2590 |
|
2646 | 2591 |
-- path finding by rnd
|
2647 | 2592 |
if self.pathfinding -- only if mob has pathfinding enabled
|
|
2653 | 2598 |
-- distance padding to stop spinning mob
|
2654 | 2599 |
local pad = abs(p.x - s.x) + abs(p.z - s.z)
|
2655 | 2600 |
|
|
2601 |
self.reach_ext = 0 -- extended ready off by default
|
|
2602 |
|
2656 | 2603 |
if self.at_cliff or pad < 0.2 then
|
2657 | 2604 |
|
|
2605 |
-- when on top of player extend reach slightly so player can
|
|
2606 |
-- still be attacked.
|
|
2607 |
self.reach_ext = 0.8
|
2658 | 2608 |
self:set_velocity(0)
|
2659 | 2609 |
self:set_animation("stand")
|
2660 | 2610 |
else
|
|
2727 | 2677 |
|
2728 | 2678 |
local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
|
2729 | 2679 |
|
2730 | |
yaw = yaw_to_pos(self, p)
|
|
2680 |
yaw_to_pos(self, p)
|
2731 | 2681 |
|
2732 | 2682 |
self:set_velocity(0)
|
2733 | 2683 |
|
|
2887 | 2837 |
|
2888 | 2838 |
local weapon = hitter:get_wielded_item()
|
2889 | 2839 |
local weapon_def = weapon:get_definition() or {}
|
2890 | |
local punch_interval = 1.4
|
2891 | 2840 |
|
2892 | 2841 |
-- calculate mob damage
|
2893 | 2842 |
local damage = 0
|
|
2949 | 2898 |
end
|
2950 | 2899 |
|
2951 | 2900 |
-- add weapon wear
|
2952 | |
punch_interval = tool_capabilities.full_punch_interval or 1.4
|
|
2901 |
local punch_interval = tool_capabilities.full_punch_interval or 1.4
|
2953 | 2902 |
|
2954 | 2903 |
-- toolrank support
|
2955 | 2904 |
local wear = floor((punch_interval / 75) * 9000)
|
|
2976 | 2925 |
|
2977 | 2926 |
-- select tool use sound if found, or fallback to default
|
2978 | 2927 |
local snd = weapon_def.sound and weapon_def.sound.use
|
2979 | |
or "default_punch"
|
|
2928 |
or "mobs_punch"
|
2980 | 2929 |
|
2981 | 2930 |
minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
|
2982 | 2931 |
|
|
3012 | 2961 |
if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
|
3013 | 2962 |
return true
|
3014 | 2963 |
end
|
3015 | |
end -- END if damage
|
|
2964 |
end
|
3016 | 2965 |
|
3017 | 2966 |
-- knock back effect (only on full punch)
|
3018 | 2967 |
if self.knock_back and tflp >= punch_interval then
|
|
3047 | 2996 |
and self.order ~= "stand" then
|
3048 | 2997 |
|
3049 | 2998 |
local lp = hitter:get_pos()
|
3050 | |
local yaw = yaw_to_pos(self, lp, 3)
|
|
2999 |
yaw = yaw_to_pos(self, lp, 3)
|
3051 | 3000 |
|
3052 | 3001 |
self.state = "runaway"
|
3053 | 3002 |
self.runaway_timer = 0
|
|
3155 | 3104 |
end
|
3156 | 3105 |
end
|
3157 | 3106 |
|
3158 | |
--print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
|
3159 | |
|
3160 | 3107 |
return minetest.serialize(tmp)
|
3161 | 3108 |
end
|
3162 | 3109 |
|
|
3285 | 3232 |
if type(self.armor) == "table" then
|
3286 | 3233 |
armor = table_copy(self.armor)
|
3287 | 3234 |
else
|
3288 | |
armor = {fleshy = self.armor} -- immortal = 1
|
|
3235 |
armor = {fleshy = self.armor, immortal = 1}
|
3289 | 3236 |
end
|
3290 | 3237 |
self.object:set_armor_groups(armor)
|
3291 | 3238 |
|
|
3333 | 3280 |
end
|
3334 | 3281 |
|
3335 | 3282 |
if use_cmi then
|
3336 | |
self._cmi_components = cmi.activate_components(
|
3337 | |
self.serialized_cmi_components)
|
|
3283 |
self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
|
3338 | 3284 |
cmi.notify_activate(self.object, dtime)
|
3339 | 3285 |
end
|
3340 | 3286 |
end
|
|
3367 | 3313 |
end
|
3368 | 3314 |
end
|
3369 | 3315 |
|
3370 | |
-- minetest.log("action",
|
3371 | |
-- S("lifetimer expired, removed @1", self.name))
|
|
3316 |
-- minetest.log("action", S("lifetimer expired, removed @1", self.name))
|
3372 | 3317 |
|
3373 | 3318 |
effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
|
3374 | 3319 |
|
|
3395 | 3340 |
-- early warning check, if no yaw then no entity, skip rest of function
|
3396 | 3341 |
if not yaw then return end
|
3397 | 3342 |
|
3398 | |
-- get node at foot level every quarter second
|
3399 | 3343 |
self.node_timer = (self.node_timer or 0) + dtime
|
3400 | 3344 |
|
|
3345 |
-- get nodes above and below foot level every 1/4 second
|
3401 | 3346 |
if self.node_timer > 0.25 then
|
3402 | 3347 |
|
3403 | 3348 |
self.node_timer = 0
|
|
3419 | 3364 |
|
3420 | 3365 |
-- if standing inside solid block then jump to escape
|
3421 | 3366 |
if minetest.registered_nodes[self.standing_in].walkable
|
3422 | |
and minetest.registered_nodes[self.standing_in].drawtype
|
3423 | |
== "normal" then
|
3424 | |
|
3425 | |
self.object:set_velocity({
|
3426 | |
x = 0,
|
3427 | |
y = self.jump_height,
|
3428 | |
z = 0
|
3429 | |
})
|
|
3367 |
and minetest.registered_nodes[self.standing_in].drawtype == "normal" then
|
|
3368 |
|
|
3369 |
self.object:set_velocity({
|
|
3370 |
x = 0,
|
|
3371 |
y = self.jump_height,
|
|
3372 |
z = 0
|
|
3373 |
})
|
3430 | 3374 |
end
|
3431 | 3375 |
|
3432 | 3376 |
-- check and stop if standing at cliff and fear of heights
|
|
3456 | 3400 |
if yaw > self.target_yaw then
|
3457 | 3401 |
|
3458 | 3402 |
if dif > pi then
|
3459 | |
dif = 2 * pi - dif -- need to add
|
3460 | |
yaw = yaw + dif / self.delay
|
|
3403 |
dif = 2 * pi - dif
|
|
3404 |
yaw = yaw + dif / self.delay -- need to add
|
3461 | 3405 |
else
|
3462 | 3406 |
yaw = yaw - dif / self.delay -- need to subtract
|
3463 | 3407 |
end
|
|
3594 | 3538 |
on_flop = def.on_flop,
|
3595 | 3539 |
do_custom = def.do_custom,
|
3596 | 3540 |
jump_height = def.jump_height,
|
|
3541 |
can_leap = def.can_leap,
|
3597 | 3542 |
drawtype = def.drawtype, -- DEPRECATED, use rotate instead
|
3598 | 3543 |
rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2
|
3599 | 3544 |
glow = def.glow,
|
|
3678 | 3623 |
stay_near = def.stay_near,
|
3679 | 3624 |
randomly_turn = def.randomly_turn ~= false,
|
3680 | 3625 |
ignore_invisibility = def.ignore_invisibility,
|
|
3626 |
messages = def.messages,
|
3681 | 3627 |
|
3682 | 3628 |
on_spawn = def.on_spawn,
|
3683 | 3629 |
|
|
3695 | 3641 |
|
3696 | 3642 |
get_staticdata = function(self)
|
3697 | 3643 |
return self:mob_staticdata(self)
|
3698 | |
end,
|
|
3644 |
end
|
3699 | 3645 |
|
3700 | 3646 |
}, mob_class_meta))
|
3701 | 3647 |
|
|
3792 | 3738 |
-- global functions
|
3793 | 3739 |
|
3794 | 3740 |
function mobs:add_mob(pos, def)
|
|
3741 |
|
|
3742 |
-- nil check
|
|
3743 |
if not pos or not def then
|
|
3744 |
--print("--- no position or definition given")
|
|
3745 |
return
|
|
3746 |
end
|
3795 | 3747 |
|
3796 | 3748 |
-- is mob actually registered?
|
3797 | 3749 |
if not mobs.spawning_mobs[def.name]
|
|
3864 | 3816 |
ent.base_selbox[4] * .5,
|
3865 | 3817 |
ent.base_selbox[5] * .5,
|
3866 | 3818 |
ent.base_selbox[6] * .5
|
3867 | |
},
|
|
3819 |
}
|
3868 | 3820 |
})
|
3869 | 3821 |
|
3870 | 3822 |
ent.child = true
|
|
4280 | 4232 |
end
|
4281 | 4233 |
|
4282 | 4234 |
|
4283 | |
-- compatibility function
|
|
4235 |
-- compatibility function (deprecated)
|
4284 | 4236 |
function mobs:explosion(pos, radius)
|
4285 | |
mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius)
|
|
4237 |
mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius, radius, "tnt_smoke.png")
|
4286 | 4238 |
end
|
4287 | 4239 |
|
4288 | 4240 |
|
4289 | 4241 |
-- no damage to nodes explosion
|
4290 | |
function mobs:safe_boom(self, pos, radius)
|
|
4242 |
function mobs:safe_boom(self, pos, radius, texture)
|
4291 | 4243 |
|
4292 | 4244 |
minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
|
4293 | 4245 |
pos = pos,
|
|
4297 | 4249 |
|
4298 | 4250 |
entity_physics(pos, radius)
|
4299 | 4251 |
|
4300 | |
effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
|
|
4252 |
effect(pos, 32, texture, radius * 3, radius * 5, radius, 1, 0)
|
4301 | 4253 |
end
|
4302 | 4254 |
|
4303 | 4255 |
|
4304 | 4256 |
-- make explosion with protection and tnt mod check
|
4305 | |
function mobs:boom(self, pos, radius)
|
|
4257 |
function mobs:boom(self, pos, radius, damage_radius, texture)
|
4306 | 4258 |
|
4307 | 4259 |
if mobs_griefing
|
4308 | 4260 |
and minetest.get_modpath("tnt") and tnt and tnt.boom
|
|
4310 | 4262 |
|
4311 | 4263 |
tnt.boom(pos, {
|
4312 | 4264 |
radius = radius,
|
4313 | |
damage_radius = radius,
|
|
4265 |
damage_radius = damage_radius,
|
4314 | 4266 |
sound = self.sounds and self.sounds.explode,
|
4315 | |
explode_center = true
|
|
4267 |
explode_center = true,
|
|
4268 |
tiles = {(texture or "tnt_smoke.png")}
|
4316 | 4269 |
})
|
4317 | 4270 |
else
|
4318 | |
mobs:safe_boom(self, pos, radius)
|
|
4271 |
mobs:safe_boom(self, pos, radius, texture)
|
4319 | 4272 |
end
|
4320 | 4273 |
end
|
4321 | 4274 |
|
|
4389 | 4342 |
end
|
4390 | 4343 |
|
4391 | 4344 |
return itemstack
|
4392 | |
end,
|
|
4345 |
end
|
4393 | 4346 |
})
|
4394 | 4347 |
|
4395 | 4348 |
|
|
4451 | 4404 |
end
|
4452 | 4405 |
|
4453 | 4406 |
return itemstack
|
4454 | |
end,
|
|
4407 |
end
|
4455 | 4408 |
})
|
4456 | 4409 |
end
|
4457 | 4410 |
|
|
4470 | 4423 |
|
4471 | 4424 |
if t ~= "function"
|
4472 | 4425 |
and t ~= "nil"
|
4473 | |
and t ~= "userdata" then
|
|
4426 |
and t ~= "userdata"
|
|
4427 |
and _ ~= "object"
|
|
4428 |
and _ ~= "_cmi_components" then
|
4474 | 4429 |
tmp[_] = self[_]
|
4475 | 4430 |
end
|
4476 | 4431 |
end
|
|
4497 | 4452 |
function mobs:capture_mob(self, clicker, chance_hand, chance_net,
|
4498 | 4453 |
chance_lasso, force_take, replacewith)
|
4499 | 4454 |
|
4500 | |
if not self --self.child
|
|
4455 |
if not self
|
4501 | 4456 |
or not clicker:is_player()
|
4502 | 4457 |
or not clicker:get_inventory() then
|
4503 | 4458 |
return false
|
|
4584 | 4539 |
|
4585 | 4540 |
if t ~= "function"
|
4586 | 4541 |
and t ~= "nil"
|
4587 | |
and t ~= "userdata" then
|
|
4542 |
and t ~= "userdata"
|
|
4543 |
and _ ~= "object"
|
|
4544 |
and _ ~= "_cmi_components" then
|
4588 | 4545 |
tmp[_] = self[_]
|
4589 | 4546 |
end
|
4590 | 4547 |
end
|
|
4666 | 4623 |
|
4667 | 4624 |
pos.y = pos.y + self.collisionbox[2] + 0.5
|
4668 | 4625 |
|
4669 | |
effect(self.object:get_pos(), 25, "mobs_protect_particle.png",
|
4670 | |
0.5, 4, 2, 15)
|
|
4626 |
effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
|
4671 | 4627 |
|
4672 | 4628 |
self:mob_sound("mobs_spell")
|
4673 | 4629 |
|
|
4701 | 4657 |
if self.health >= self.hp_max then
|
4702 | 4658 |
|
4703 | 4659 |
self.health = self.hp_max
|
4704 | |
|
4705 | |
-- if self.htimer < 1 then
|
4706 | |
|
4707 | |
-- minetest.chat_send_player(clicker:get_player_name(),
|
4708 | |
-- S("@1 at full health (@2)",
|
4709 | |
-- self.name:split(":")[2], tostring(self.health)))
|
4710 | |
|
4711 | |
-- self.htimer = 5
|
4712 | |
-- end
|
4713 | 4660 |
end
|
4714 | 4661 |
|
4715 | 4662 |
self.object:set_hp(self.health)
|
|
4717 | 4664 |
-- make children grow quicker
|
4718 | 4665 |
if self.child == true then
|
4719 | 4666 |
|
4720 | |
-- self.hornytimer = self.hornytimer + 20
|
4721 | 4667 |
-- deduct 10% of the time to adulthood
|
4722 | 4668 |
self.hornytimer = math.floor(self.hornytimer + (
|
4723 | 4669 |
(CHILD_GROW_TIME - self.hornytimer) * 0.1))
|
|
4856 | 4802 |
end)
|
4857 | 4803 |
|
4858 | 4804 |
|
4859 | |
-- compatibility function for old entities to new modpack entities
|
|
4805 |
-- compatibility function for old mobs entities to new mobs_redo modpack
|
4860 | 4806 |
function mobs:alias_mob(old_name, new_name)
|
4861 | 4807 |
|
4862 | 4808 |
-- check old_name entity doesnt already exist
|
|
4887 | 4833 |
end
|
4888 | 4834 |
})
|
4889 | 4835 |
end
|
|
4836 |
|
|
4837 |
|
|
4838 |
-- admin command to remove untamed mobs around players
|
|
4839 |
minetest.register_chatcommand("clear_mobs", {
|
|
4840 |
params = "<text>",
|
|
4841 |
description = "Remove untamed mobs from around players.",
|
|
4842 |
privs = {server = true},
|
|
4843 |
|
|
4844 |
func = function (name, param)
|
|
4845 |
|
|
4846 |
local count = 0
|
|
4847 |
|
|
4848 |
for _, player in pairs(minetest.get_connected_players()) do
|
|
4849 |
|
|
4850 |
if player then
|
|
4851 |
|
|
4852 |
local pos = player:get_pos()
|
|
4853 |
|
|
4854 |
local objs = minetest.get_objects_inside_radius(pos, 28)
|
|
4855 |
|
|
4856 |
for _, obj in pairs(objs) do
|
|
4857 |
|
|
4858 |
if obj then
|
|
4859 |
|
|
4860 |
local ent = obj:get_luaentity()
|
|
4861 |
|
|
4862 |
-- only remove mobs redo mobs that are not tamed
|
|
4863 |
if ent and ent._cmi_is_mob and ent.tamed ~= true then
|
|
4864 |
|
|
4865 |
remove_mob(ent, true)
|
|
4866 |
|
|
4867 |
count = count + 1
|
|
4868 |
end
|
|
4869 |
end
|
|
4870 |
end
|
|
4871 |
end
|
|
4872 |
end
|
|
4873 |
|
|
4874 |
minetest.chat_send_player(name, S("@1 mobs removed.", count))
|
|
4875 |
end
|
|
4876 |
})
|