Codebase list minetest-mod-mobs-redo / d97bbbf
Import upstream version 20181016+git20210801.1.deee28f Debian Janitor 2 years ago
33 changed file(s) with 3491 addition(s) and 5623 deletion(s). Raw diff Collapse all Expand all
+2158
-1286
api.lua less more
0
1 -- Intllib and CMI support check
0 -- Load support for intllib.
21 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
2 local S = minetest.get_translator and minetest.get_translator("mobs_redo") or
3 dofile(MP .. "/intllib.lua")
4
5 -- CMI support check
46 local use_cmi = minetest.global_exists("cmi")
57
68 mobs = {
79 mod = "redo",
8 version = "20181005",
10 version = "20210801",
911 intllib = S,
10 invis = minetest.global_exists("invisibility") and invisibility or {},
12 invis = minetest.global_exists("invisibility") and invisibility or {}
1113 }
1214
13 -- creative check
14 local creative_cache = minetest.settings:get_bool("creative_mode")
15 function mobs.is_creative(name)
16 return creative_cache or minetest.check_player_privs(name, {creative = true})
17 end
18
19
20 -- localize math functions
15 -- localize common functions
2116 local pi = math.pi
2217 local square = math.sqrt
2318 local sin = math.sin
2520 local abs = math.abs
2621 local min = math.min
2722 local max = math.max
28 local atann = math.atan
2923 local random = math.random
3024 local floor = math.floor
25 local ceil = math.ceil
26 local rad = math.rad
27 local atann = math.atan
3128 local atan = function(x)
3229 if not x or x ~= x then
33 --error("atan bassed NaN")
34 return 0
30 return 0 -- NaN
3531 else
3632 return atann(x)
3733 end
3834 end
39
35 local table_copy = table.copy
36 local table_remove = table.remove
37 local vadd = vector.add
38 local vdirection = vector.direction
39 local vmultiply = vector.multiply
40 local vsubtract = vector.subtract
41 local settings = minetest.settings
42
43 -- creative check
44 local creative_cache = minetest.settings:get_bool("creative_mode")
45 function mobs.is_creative(name)
46 return creative_cache or minetest.check_player_privs(name,
47 {creative = true})
48 end
4049
4150 -- Load settings
42 local damage_enabled = minetest.settings:get_bool("enable_damage")
43 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
44 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
45 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
46 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
47 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
48 local creative = minetest.settings:get_bool("creative_mode")
49 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
50 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
51 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
52 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
53 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
54 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
51 local damage_enabled = settings:get_bool("enable_damage")
52 local mobs_spawn = settings:get_bool("mobs_spawn") ~= false
53 local peaceful_only = settings:get_bool("only_peaceful_mobs")
54 local disable_blood = settings:get_bool("mobs_disable_blood")
55 local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false
56 local mobs_griefing = settings:get_bool("mobs_griefing") ~= false
57 local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false
58 local spawn_monster_protected = settings:get_bool("mobs_spawn_monster_protected") ~= false
59 local remove_far = settings:get_bool("remove_far_mobs") ~= false
60 local mob_area_spawn = settings:get_bool("mob_area_spawn")
61 local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0
62 local show_health = settings:get_bool("mob_show_health") ~= false
63 local max_per_block = tonumber(settings:get("max_objects_per_block") or 99)
64 local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12)
65 local active_limit = tonumber(settings:get("mob_active_limit") or 0)
66 local mob_chance_multiplier = tonumber(settings:get("mob_chance_multiplier") or 1)
67 local peaceful_player_enabled = settings:get_bool("enable_peaceful_player")
68 local mob_smooth_rotate = settings:get_bool("mob_smooth_rotate") ~= false
69 local active_mobs = 0
5570
5671 -- Peaceful mode message so players will know there are no monsters
5772 if peaceful_only then
6277 end
6378
6479 -- calculate aoc range for mob count
65 local aoc_range = tonumber(minetest.settings:get("active_block_range")) * 16
80 local aoc_range = tonumber(settings:get("active_block_range")) * 16
6681
6782 -- pathfinding settings
6883 local enable_pathfinding = true
69 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
70 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
84 local stuck_timeout = 3 -- how long before stuck mod starts searching
85 local stuck_path_timeout = 5 -- how long will mob follow path before giving up
7186
7287 -- default nodes
7388 local node_fire = "fire:basic_flame"
7792 local node_snow = "default:snow"
7893 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
7994
95 local mob_class = {
96 stepheight = 1.1,
97 fly_in = "air",
98 owner = "",
99 order = "",
100 jump_height = 4,
101 lifetimer = 180, -- 3 minutes
102 physical = true,
103 collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
104 visual_size = {x = 1, y = 1},
105 texture_mods = "",
106 makes_footstep_sound = false,
107 view_range = 5,
108 walk_velocity = 1,
109 run_velocity = 2,
110 light_damage = 0,
111 light_damage_min = 14,
112 light_damage_max = 15,
113 water_damage = 0,
114 lava_damage = 4,
115 fire_damage = 4,
116 air_damage = 0,
117 suffocation = 2,
118 fall_damage = 1,
119 fall_speed = -10, -- must be lower than -2 (default: -10)
120 drops = {},
121 armor = 100,
122 sounds = {},
123 jump = true,
124 knock_back = true,
125 walk_chance = 50,
126 stand_chance = 30,
127 attack_chance = 5,
128 passive = false,
129 blood_amount = 5,
130 blood_texture = "mobs_blood.png",
131 shoot_offset = 0,
132 floats = 1, -- floats in water by default
133 replace_offset = 0,
134 timer = 0,
135 env_damage_timer = 0, -- only used when state = "attack"
136 tamed = false,
137 pause_timer = 0,
138 horny = false,
139 hornytimer = 0,
140 child = false,
141 gotten = false,
142 health = 0,
143 reach = 3,
144 htimer = 0,
145 docile_by_day = false,
146 time_of_day = 0.5,
147 fear_height = 0,
148 runaway_timer = 0,
149 immune_to = {},
150 explosion_timer = 3,
151 allow_fuse_reset = true,
152 stop_to_explode = true,
153 dogshoot_count = 0,
154 dogshoot_count_max = 5,
155 dogshoot_count2_max = 5,
156 group_attack = false,
157 attack_monsters = false,
158 attack_animals = false,
159 attack_players = true,
160 attack_npcs = true,
161 facing_fence = false,
162 _cmi_is_mob = true
163 }
164
165 local mob_class_meta = {__index = mob_class}
166
80167
81168 -- play sound
82 local mob_sound = function(self, sound)
169 function mob_class:mob_sound(sound)
170
171 local pitch = 1.0
172
173 -- higher pitch for a child
174 if self.child then pitch = pitch * 1.5 end
175
176 -- a little random pitch to be different
177 pitch = pitch + random(-10, 10) * 0.005
83178
84179 if sound then
85180 minetest.sound_play(sound, {
86181 object = self.object,
87182 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
183 max_hear_distance = self.sounds.distance,
184 pitch = pitch
185 }, true)
90186 end
91187 end
92188
93189
94190 -- attack player/mob
95 local do_attack = function(self, player)
191 function mob_class:do_attack(player)
96192
97193 if self.state == "attack" then
98194 return
102198 self.state = "attack"
103199
104200 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
201 self:mob_sound(self.sounds.war_cry)
106202 end
107203 end
108204
110206 -- calculate distance
111207 local get_distance = function(a, b)
112208
209 if not a or not b then return 50 end -- nil check
210
113211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114212
115213 return square(x * x + y * y + z * z)
117215
118216
119217 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
218 function mob_class:collision()
121219
122220 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124221 local x, z = 0, 0
125222 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126223
127224 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128225
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
226 if object:is_player() then
131227
132228 local pos2 = object:get_pos()
133229 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
141237 end
142238
143239
240 -- check if string exists in another string or table
241 local check_for = function(look_for, look_inside)
242
243 if type(look_inside) == "string" and look_inside == look_for then
244
245 return true
246
247 elseif type(look_inside) == "table" then
248
249 for _, str in pairs(look_inside) do
250
251 if str == look_for then
252 return true
253 end
254
255 if str:find("group:") then
256
257 local group = str:split(":")[2]
258
259 if minetest.get_item_group(look_for, group) ~= 0 then
260 return true
261 end
262 end
263 end
264 end
265
266 return false
267 end
268
269
144270 -- move mob in facing direction
145 local set_velocity = function(self, v)
271 function mob_class:set_velocity(v)
272
273 -- halt mob if it has been ordered to stay
274 if self.order == "stand" then
275
276 local vel = self.object:get_velocity() or {y = 0}
277
278 self.object:set_velocity({x = 0, y = vel.y, z = 0})
279
280 return
281 end
146282
147283 local c_x, c_y = 0, 0
148284
149285 -- can mob be pushed, if so calculate direction
150286 if self.pushable then
151 c_x, c_y = unpack(collision(self))
152 end
153
154 -- halt mob if it has been ordered to stay
155 if self.order == "stand" then
156 self.object:set_velocity({x = 0, y = 0, z = 0})
157 return
158 end
159
160 local yaw = (self.object:get_yaw() or 0) + self.rotate
161
162 self.object:set_velocity({
287 c_x, c_y = unpack(self:collision())
288 end
289
290 local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
291
292 -- nil check for velocity
293 v = v or 0.01
294
295 -- check if standing in liquid with max viscosity of 7
296 local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
297
298 -- only slow mob trying to move while inside a viscous fluid that
299 -- they aren't meant to be in (fish in water, spiders in cobweb etc)
300 if v > 0 and visc and visc > 0
301 and not check_for(self.standing_in, self.fly_in) then
302 v = v / (visc + 1)
303 end
304
305 -- set velocity
306 local vel = self.object:get_velocity() or 0
307
308 local new_vel = {
163309 x = (sin(yaw) * -v) + c_x,
164 y = self.object:get_velocity().y,
165 z = (cos(yaw) * v) + c_y,
166 })
310 y = vel.y,
311 z = (cos(yaw) * v) + c_y}
312
313 self.object:set_velocity(new_vel)
314 end
315
316 -- global version of above function
317 function mobs:set_velocity(entity, v)
318 mob_class.set_velocity(entity, v)
167319 end
168320
169321
170322 -- calculate mob velocity
171 local get_velocity = function(self)
323 function mob_class:get_velocity()
172324
173325 local v = self.object:get_velocity()
174326
327 if not v then return 0 end
328
175329 return (v.x * v.x + v.z * v.z) ^ 0.5
176330 end
177331
178332
179333 -- set and return valid yaw
180 local set_yaw = function(self, yaw, delay)
334 function mob_class:set_yaw(yaw, delay)
181335
182336 if not yaw or yaw ~= yaw then
183337 yaw = 0
184338 end
185339
186 delay = delay or 0
340 delay = mob_smooth_rotate and (delay or 0) or 0
187341
188342 if delay == 0 then
343
189344 self.object:set_yaw(yaw)
345
190346 return yaw
191347 end
192348
197353 end
198354
199355 -- global function to set mob yaw
200 function mobs:yaw(self, yaw, delay)
201 set_yaw(self, yaw, delay)
356 function mobs:yaw(entity, yaw, delay)
357 mob_class.set_yaw(entity, yaw, delay)
202358 end
203359
204360
205361 -- set defined animation
206 local set_animation = function(self, anim)
207
208 if not self.animation
209 or not anim then return end
362 function mob_class:set_animation(anim, force)
363
364 if not self.animation or not anim then return end
210365
211366 self.animation.current = self.animation.current or ""
212367
213 -- only set different animation for attacks when setting to same set
214 if anim ~= "punch" and anim ~= "shoot"
368 -- only use different animation for attacks when using same set
369 if force ~= true and anim ~= "punch" and anim ~= "shoot"
215370 and string.find(self.animation.current, anim) then
216371 return
217372 end
218373
219 -- check for more than one animation
220374 local num = 0
221375
376 -- check for more than one animation (max 4)
222377 for n = 1, 4 do
223378
224379 if self.animation[anim .. n .. "_start"]
244399 self.object:set_animation({
245400 x = self.animation[anim .. "_start"],
246401 y = self.animation[anim .. "_end"]},
247 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
402 self.animation[anim .. "_speed"] or
403 self.animation.speed_normal or 15,
248404 0, self.animation[anim .. "_loop"] ~= false)
249405 end
250406
251 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
407 function mobs:set_animation(entity, anim)
408 entity.set_animation(entity, anim)
409 end
410
411
412 -- check line of sight (BrunoMine)
413 local line_of_sight = function(self, pos1, pos2, stepsize)
414
415 stepsize = stepsize or 1
416
417 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
418
419 -- normal walking and flying mobs can see you through air
420 if s == true then
421 return true
422 end
423
424 -- New pos1 to be analyzed
425 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
426
427 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
428
429 -- Checks the return
430 if r == true then return true end
431
432 -- Nodename found
433 local nn = minetest.get_node(pos).name
434
435 -- Target Distance (td) to travel
436 local td = get_distance(pos1, pos2)
437
438 -- Actual Distance (ad) traveled
439 local ad = 0
440
441 -- It continues to advance in the line of sight in search of a real
442 -- obstruction which counts as 'walkable' nodebox.
443 while minetest.registered_nodes[nn]
444 and (minetest.registered_nodes[nn].walkable == false) do
445
446 -- Check if you can still move forward
447 if td < ad + stepsize then
448 return true -- Reached the target
449 end
450
451 -- Moves the analyzed pos
452 local d = get_distance(pos1, pos2)
453
454 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
455 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
456 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
457
458 -- NaN checks
459 if d == 0
460 or npos1.x ~= npos1.x
461 or npos1.y ~= npos1.y
462 or npos1.z ~= npos1.z then
463 return false
464 end
465
466 ad = ad + stepsize
467
468 -- scan again
469 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
470
471 if r == true then return true end
472
473 -- New Nodename found
474 nn = minetest.get_node(pos).name
475 end
476
477 return false
254478 end
255479
256480
257481 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
482 local new_line_of_sight = function(self, pos1, pos2, stepsize)
259483
260484 if not pos1 or not pos2 then return end
261485
262486 stepsize = stepsize or 1
263487
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
488 local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
265489
266490 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267491
280504 local nn = minetest.get_node(pos).name
281505
282506 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
507 -- obstruction which counts as 'walkable' nodebox.
284508 while minetest.registered_nodes[nn]
285509 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
510
511 npos1 = vadd(npos1, stepv)
289512
290513 if get_distance(npos1, pos2) < stepsize then return true end
291514
301524 return false
302525 end
303526
527 -- check line of sight using raycasting (thanks Astrobe)
528 local ray_line_of_sight = function(self, pos1, pos2)
529
530 local ray = minetest.raycast(pos1, pos2, true, false)
531 local thing = ray:next()
532
533 while thing do -- thing.type, thing.ref
534
535 if thing.type == "node" then
536
537 local name = minetest.get_node(thing.under).name
538
539 if minetest.registered_items[name]
540 and minetest.registered_items[name].walkable then
541 return false
542 end
543 end
544
545 thing = ray:next()
546 end
547
548 return true
549 end
550
551
552 function mob_class:line_of_sight(pos1, pos2, stepsize)
553
554 if minetest.raycast then -- only use if minetest 5.0 is detected
555 return ray_line_of_sight(self, pos1, pos2)
556 end
557
558 return line_of_sight(self, pos1, pos2, stepsize)
559 end
560
304561 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
562 function mobs:line_of_sight(entity, pos1, pos2, stepsize)
563 return entity:line_of_sight(pos1, pos2, stepsize)
564 end
565
566
567 function mob_class:attempt_flight_correction(override)
568
569 if self:flight_check() and override ~= true then return true end
570
571 -- We are not flying in what we are supposed to.
572 -- See if we can find intended flight medium and return to it
573 local pos = self.object:get_pos() ; if not pos then return true end
574 local searchnodes = self.fly_in
575
576 if type(searchnodes) == "string" then
577 searchnodes = {self.fly_in}
578 end
579
580 local flyable_nodes = minetest.find_nodes_in_area(
581 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
582 {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes)
583 -- pos.y + 0 hopefully fixes floating swimmers
584
585 if #flyable_nodes < 1 then
586 return false
587 end
588
589 local escape_target = flyable_nodes[random(#flyable_nodes)]
590 local escape_direction = vdirection(pos, escape_target)
591
592 self.object:set_velocity(
593 vmultiply(escape_direction, 1))
594
595 return true
308596 end
309597
310598
311599 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
600 function mob_class:flight_check()
313601
314602 local def = minetest.registered_nodes[self.standing_in]
315603
316 if not def then return false end -- nil check
317
318 if type(self.fly_in) == "string"
319 and self.standing_in == self.fly_in then
320
604 if not def then return false end
605
606 -- are we standing inside what we should be to fly/swim ?
607 if check_for(self.standing_in, self.fly_in) then
321608 return true
322
323 elseif type(self.fly_in) == "table" then
324
325 for _,fly_in in pairs(self.fly_in) do
326
327 if self.standing_in == fly_in then
328
329 return true
330 end
331 end
332609 end
333610
334611 -- stops mobs getting stuck inside stairs and plantlike nodes
342619 end
343620
344621
622 -- turn mob to face position
623 local yaw_to_pos = function(self, target, rot)
624
625 rot = rot or 0
626
627 local pos = self.object:get_pos()
628 local vec = {x = target.x - pos.x, z = target.z - pos.z}
629 local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
630
631 if target.x > pos.x then
632 yaw = yaw + pi
633 end
634
635 yaw = self:set_yaw(yaw, rot)
636
637 return yaw
638 end
639
640 function mobs:yaw_to_pos(self, target, rot)
641 return yaw_to_pos(self, target, rot)
642 end
643
644
645 -- if stay near set then periodically check for nodes and turn towards them
646 function mob_class:do_stay_near()
647
648 if not self.stay_near then return false end
649
650 local pos = self.object:get_pos()
651 local searchnodes = self.stay_near[1]
652 local chance = self.stay_near[2] or 10
653
654 if not pos or random(chance) > 1 then
655 return false
656 end
657
658 if type(searchnodes) == "string" then
659 searchnodes = {self.stay_near[1]}
660 end
661
662 local r = self.view_range
663 local nearby_nodes = minetest.find_nodes_in_area(
664 {x = pos.x - r, y = pos.y - 1, z = pos.z - r},
665 {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
666
667 if #nearby_nodes < 1 then
668 return false
669 end
670
671 yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)])
672
673 self:set_animation("walk")
674
675 self:set_velocity(self.walk_velocity)
676
677 return true
678 end
679
680
345681 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
682 local effect = function(pos, amount, texture, min_size, max_size,
683 radius, gravity, glow, fall)
347684
348685 radius = radius or 2
349686 min_size = min_size or 0.5
351688 gravity = gravity or -10
352689 glow = glow or 0
353690
691 if fall == true then
692 fall = 0
693 elseif fall == false then
694 fall = radius
695 else
696 fall = -radius
697 end
698
354699 minetest.add_particlespawner({
355700 amount = amount,
356701 time = 0.25,
357702 minpos = pos,
358703 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
704 minvel = {x = -radius, y = fall, z = -radius},
360705 maxvel = {x = radius, y = radius, z = radius},
361706 minacc = {x = 0, y = gravity, z = 0},
362707 maxacc = {x = 0, y = gravity, z = 0},
365710 minsize = min_size,
366711 maxsize = max_size,
367712 texture = texture,
368 glow = glow,
713 glow = glow
369714 })
370715 end
371716
717 function mobs:effect(pos, amount, texture, min_size, max_size,
718 radius, gravity, glow, fall)
719
720 effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
721 end
722
372723
373724 -- update nametag colour
374 local update_tag = function(self)
725 function mob_class:update_tag()
375726
376727 local col = "#00FF00"
377728 local qua = self.hp_max / 4
388739 col = "#FF0000"
389740 end
390741
742 -- build infotext
743 self.infotext = "Health: " .. self.health .. " / " .. self.hp_max
744 .. "\n" .. "Owner: " .. self.owner
745
746 -- set changes
391747 self.object:set_properties({
392748 nametag = self.nametag,
393 nametag_color = col
749 nametag_color = col,
750 infotext = self.infotext
394751 })
395
396752 end
397753
398754
399755 -- drop items
400 local item_drop = function(self)
756 function mob_class:item_drop()
757
758 -- no drops if disabled by setting or mob is child
759 if not mobs_drop_items or self.child then return end
760
761 local pos = self.object:get_pos()
762
763 -- check for drops function
764 self.drops = type(self.drops) == "function"
765 and self.drops(pos) or self.drops
401766
402767 -- check for nil or no drops
403768 if not self.drops or #self.drops == 0 then
404769 return
405770 end
406771
407 -- no drops if disabled by setting
408 if not mobs_drop_items then return end
409
410 -- no drops for child mobs
411 if self.child then return end
412
413772 -- was mob killed by player?
414 local death_by_player = self.cause_of_death and self.cause_of_death.puncher
415 and self.cause_of_death.puncher:is_player() or nil
773 local death_by_player = self.cause_of_death
774 and self.cause_of_death.puncher
775 and self.cause_of_death.puncher:is_player()
416776
417777 local obj, item, num
418 local pos = self.object:get_pos()
419778
420779 for n = 1, #self.drops do
421780
422 if random(1, self.drops[n].chance) == 1 then
781 if random(self.drops[n].chance) == 1 then
423782
424783 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425784 item = self.drops[n].name
435794 end
436795 end
437796
438 -- only drop rare items (drops.min=0) if killed by player
439 if death_by_player then
440 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
441
442 elseif self.drops[n].min ~= 0 then
797 -- only drop rare items (drops.min = 0) if killed by player
798 if death_by_player or self.drops[n].min ~= 0 then
443799 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
444800 end
445801
448804 obj:set_velocity({
449805 x = random(-10, 10) / 9,
450806 y = 6,
451 z = random(-10, 10) / 9,
807 z = random(-10, 10) / 9
452808 })
453809
454810 elseif obj then
461817 end
462818
463819
820 -- remove mob and descrease counter
821 local remove_mob = function(self, decrease)
822
823 self.object:remove()
824
825 if decrease and active_limit > 0 then
826
827 active_mobs = active_mobs - 1
828
829 if active_mobs < 0 then
830 active_mobs = 0
831 end
832 end
833 --print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
834 end
835
836 -- global function for removing mobs
837 function mobs:remove(self, decrease)
838 remove_mob(self, decrease)
839 end
840
841
464842 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
843 function mob_class:check_for_death(cmi_cause)
844
845 -- We dead already
846 if self.state == "die" then
847 return true
848 end
466849
467850 -- has health actually changed?
468851 if self.health == self.old_health and self.health > 0 then
469 return
470 end
852 return false
853 end
854
855 local damaged = self.health < self.old_health
471856
472857 self.old_health = self.health
473858
474859 -- still got some health? play hurt sound
475860 if self.health > 0 then
476861
477 mob_sound(self, self.sounds.damage)
862 -- only play hurt sound if damaged
863 if damaged then
864 self:mob_sound(self.sounds.damage)
865 end
478866
479867 -- make sure health isn't higher than max
480868 if self.health > self.hp_max then
482870 end
483871
484872 -- backup nametag so we can show health stats
485 if not self.nametag2 then
486 self.nametag2 = self.nametag or ""
487 end
488
489 if show_health
490 and (cmi_cause and cmi_cause.type == "punch") then
491
492 self.htimer = 2
493 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
494
495 update_tag(self)
496 end
873 -- if not self.nametag2 then
874 -- self.nametag2 = self.nametag or ""
875 -- end
876
877 -- if show_health
878 -- and (cmi_cause and cmi_cause.type == "punch") then
879
880 -- self.htimer = 2
881 -- self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
882 self:update_tag()
883 -- end
497884
498885 return false
499886 end
501888 self.cause_of_death = cmi_cause
502889
503890 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
891 self:item_drop()
892
893 self:mob_sound(self.sounds.death)
507894
508895 local pos = self.object:get_pos()
509896
510897 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
898 if pos and self.on_die then
899
900 self:on_die(pos)
514901
515902 if use_cmi then
516903 cmi.notify_die(self.object, cmi_cause)
517904 end
518905
519 self.object:remove()
906 remove_mob(self, true)
520907
521908 return true
522909 end
523910
524 -- default death function and die animation (if defined)
911 -- check for custom death function and die animation
525912 if self.animation
526913 and self.animation.die_start
527914 and self.animation.die_end then
528915
529916 local frames = self.animation.die_end - self.animation.die_start
530917 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
918 local length = max((frames / speed), 0)
919 local rot = self.animation.die_rotate and 5
532920
533921 self.attack = nil
922 self.following = nil
534923 self.v_start = false
535924 self.timer = 0
536925 self.blinktimer = 0
537926 self.passive = true
538927 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
928 self.object:set_properties({
929 pointable = false, collide_with_objects = false,
930 automatic_rotate = rot, static_save = false
931 })
932 self:set_velocity(0)
933 self:set_animation("die")
541934
542935 minetest.after(length, function(self)
543936
544 if use_cmi and self.object:get_luaentity() then
545 cmi.notify_die(self.object, cmi_cause)
546 end
547
548 self.object:remove()
937 if self.object:get_luaentity() then
938
939 if use_cmi then
940 cmi.notify_die(self.object, cmi_cause)
941 end
942
943 remove_mob(self, true)
944 end
549945 end, self)
550 else
946
947 return true
948
949 elseif pos then -- otherwise remove mod and show particle effect
551950
552951 if use_cmi then
553952 cmi.notify_die(self.object, cmi_cause)
554953 end
555954
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
955 remove_mob(self, true)
956
957 effect(pos, 20, "tnt_smoke.png")
958 end
560959
561960 return true
562961 end
563962
564963
964 -- get node but use fallback for nil or unknown
965 local node_ok = function(pos, fallback)
966
967 fallback = fallback or mobs.fallback_node
968
969 local node = minetest.get_node_or_nil(pos)
970
971 if node and minetest.registered_nodes[node.name] then
972 return node
973 end
974
975 return minetest.registered_nodes[fallback]
976 end
977
978
979 -- Returns true is node can deal damage to self
980 local is_node_dangerous = function(self, nodename)
981
982 if self.water_damage > 0
983 and minetest.get_item_group(nodename, "water") ~= 0 then
984 return true
985 end
986
987 if self.lava_damage > 0
988 and minetest.get_item_group(nodename, "lava") ~= 0 then
989 return true
990 end
991
992 if self.fire_damage > 0
993 and minetest.get_item_group(nodename, "fire") ~= 0 then
994 return true
995 end
996
997 if minetest.registered_nodes[nodename].damage_per_second > 0 then
998 return true
999 end
1000
1001 return false
1002 end
1003
1004
5651005 -- is mob facing a cliff
566 local is_at_cliff = function(self)
1006 function mob_class:is_at_cliff()
5671007
5681008 if self.fear_height == 0 then -- 0 for no falling protection!
5691009 return false
5701010 end
5711011
1012 -- get yaw but if nil returned object no longer exists
5721013 local yaw = self.object:get_yaw()
1014
1015 if not yaw then return false end
1016
5731017 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
5741018 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
5751019 local pos = self.object:get_pos()
5761020 local ypos = pos.y + self.collisionbox[2] -- just above floor
5771021
578 if minetest.line_of_sight(
1022 local free_fall, blocker = minetest.line_of_sight(
5791023 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
580 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
581 , 1) then
582
1024 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
1025
1026 -- check for straight drop
1027 if free_fall then
5831028 return true
5841029 end
5851030
586 return false
587 end
588
589
590 -- get node but use fallback for nil or unknown
591 local node_ok = function(pos, fallback)
592
593 fallback = fallback or mobs.fallback_node
594
595 local node = minetest.get_node_or_nil(pos)
596
597 if node and minetest.registered_nodes[node.name] then
598 return node
599 end
600
601 return minetest.registered_nodes[fallback]
1031 local bnode = node_ok(blocker)
1032
1033 -- will we drop onto dangerous node?
1034 if is_node_dangerous(self, bnode.name) then
1035 return true
1036 end
1037
1038 local def = minetest.registered_nodes[bnode.name]
1039
1040 return (not def and def.walkable)
6021041 end
6031042
6041043
6051044 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
1045 function mob_class:do_env_damage()
6071046
6081047 -- feed/tame text timer (so mob 'full' messages dont spam chat)
6091048 if self.htimer > 0 then
6111050 end
6121051
6131052 -- reset nametag after showing health stats
614 if self.htimer < 1 and self.nametag2 then
615
616 self.nametag = self.nametag2
617 self.nametag2 = nil
618
619 update_tag(self)
620 end
621
622 local pos = self.object:get_pos()
1053 -- if self.htimer < 1 and self.nametag2 then
1054
1055 -- self.nametag = self.nametag2
1056 -- self.nametag2 = nil
1057
1058 self:update_tag()
1059 -- end
1060
1061 local pos = self.object:get_pos() ; if not pos then return end
6231062
6241063 self.time_of_day = minetest.get_timeofday()
6251064
626 -- remove mob if standing inside ignore node
1065 -- halt mob if standing inside ignore node
6271066 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
1067
1068 self.object:set_velocity({x = 0, y = 0, z = 0})
1069
1070 return true
1071 end
1072
1073 -- particle appears at random mob height
1074 local py = {
1075 x = pos.x,
1076 y = pos.y + random(self.collisionbox[2], self.collisionbox[5]),
1077 z = pos.z
1078 }
1079
1080 local nodef = minetest.registered_nodes[self.standing_in]
1081
1082 -- water
1083 if self.water_damage ~= 0 and nodef.groups.water then
1084
1085 self.health = self.health - self.water_damage
1086
1087 effect(py, 5, "bubble.png", nil, nil, 1, nil)
1088
1089 if self:check_for_death({type = "environment",
1090 pos = pos, node = self.standing_in}) then
1091 return true
1092 end
1093
1094 -- lava damage
1095 elseif self.lava_damage ~= 0 and nodef.groups.lava then
1096
1097 self.health = self.health - self.lava_damage
1098
1099 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1100
1101 if self:check_for_death({type = "environment", pos = pos,
1102 node = self.standing_in, hot = true}) then
1103 return true
1104 end
1105
1106 -- fire damage
1107 elseif self.fire_damage ~= 0 and nodef.groups.fire then
1108
1109 self.health = self.health - self.fire_damage
1110
1111 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1112
1113 if self:check_for_death({type = "environment", pos = pos,
1114 node = self.standing_in, hot = true}) then
1115 return true
1116 end
1117
1118 -- damage_per_second node check (not fire and lava)
1119 elseif nodef.damage_per_second ~= 0
1120 and nodef.groups.lava == nil and nodef.groups.fire == nil then
1121
1122 self.health = self.health - nodef.damage_per_second
1123
1124 effect(py, 5, "tnt_smoke.png")
1125
1126 if self:check_for_death({type = "environment",
1127 pos = pos, node = self.standing_in}) then
1128 return true
1129 end
1130 end
1131
1132 -- air damage
1133 if self.air_damage ~= 0 and self.standing_in == "air" then
1134
1135 self.health = self.health - self.air_damage
1136
1137 effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
1138
1139 if self:check_for_death({type = "environment",
1140 pos = pos, node = self.standing_in}) then
1141 return true
1142 end
6301143 end
6311144
6321145 -- is mob light sensative, or scared of the dark :P
6391152
6401153 self.health = self.health - self.light_damage
6411154
642 effect(pos, 5, "tnt_smoke.png")
643
644 if check_for_death(self, {type = "light"}) then return end
645 end
646 end
647
648 local nodef = minetest.registered_nodes[self.standing_in]
649
650 pos.y = pos.y + 1 -- for particle effect position
651
652 -- water
653 if self.water_damage
654 and nodef.groups.water then
655
656 if self.water_damage ~= 0 then
657
658 self.health = self.health - self.water_damage
659
660 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
661
662 if check_for_death(self, {type = "environment",
663 pos = pos, node = self.standing_in}) then return end
664 end
665
666 -- lava or fire or ignition source
667 elseif self.lava_damage
668 and nodef.groups.igniter then
669 -- and (nodef.groups.lava
670 -- or self.standing_in == node_fire
671 -- or self.standing_in == node_permanent_flame) then
672
673 if self.lava_damage ~= 0 then
674
675 self.health = self.health - self.lava_damage
676
677 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
678
679 if check_for_death(self, {type = "environment",
680 pos = pos, node = self.standing_in, hot = true}) then return end
681 end
682
683 -- damage_per_second node check
684 elseif nodef.damage_per_second ~= 0 then
685
686 self.health = self.health - nodef.damage_per_second
687
688 effect(pos, 5, "tnt_smoke.png")
689
690 if check_for_death(self, {type = "environment",
691 pos = pos, node = self.standing_in}) then return end
692 end
693 --[[
1155 effect(py, 5, "tnt_smoke.png")
1156
1157 if self:check_for_death({type = "light"}) then
1158 return true
1159 end
1160 end
1161 end
1162
6941163 --- suffocation inside solid node
695 if self.suffocation ~= 0
696 and nodef.walkable == true
697 and nodef.groups.disable_suffocation ~= 1
698 and nodef.drawtype == "normal" then
699
700 self.health = self.health - self.suffocation
701
702 if check_for_death(self, {type = "environment",
703 pos = pos, node = self.standing_in}) then return end
704 end
705 ]]
706 check_for_death(self, {type = "unknown"})
1164 if (self.suffocation and self.suffocation ~= 0)
1165 and (nodef.walkable == nil or nodef.walkable == true)
1166 and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
1167 and (nodef.node_box == nil or nodef.node_box.type == "regular")
1168 and (nodef.groups.disable_suffocation ~= 1) then
1169
1170 local damage
1171
1172 if self.suffocation == true then
1173 damage = 2
1174 else
1175 damage = (self.suffocation or 2)
1176 end
1177
1178 self.health = self.health - damage
1179
1180 if self:check_for_death({type = "suffocation",
1181 pos = pos, node = self.standing_in}) then
1182 return true
1183 end
1184 end
1185
1186 return self:check_for_death({type = "unknown"})
7071187 end
7081188
7091189
7101190 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
1191 function mob_class:do_jump()
7121192
7131193 if not self.jump
7141194 or self.jump_height == 0
7221202
7231203 -- something stopping us while moving?
7241204 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
1205 and self:get_velocity() > 0.5
7261206 and self.object:get_velocity().y ~= 0 then
7271207 return false
7281208 end
7301210 local pos = self.object:get_pos()
7311211 local yaw = self.object:get_yaw()
7321212
733 -- what is mob standing on?
734 pos.y = pos.y + self.collisionbox[2] - 0.2
735
736 local nod = node_ok(pos)
737
738 --print ("standing on:", nod.name, pos.y)
739
740 if minetest.registered_nodes[nod.name].walkable == false then
1213 -- sanity check
1214 if not yaw then return false end
1215
1216 -- we can only jump if standing on solid node
1217 if minetest.registered_nodes[self.standing_on].walkable == false then
7411218 return false
7421219 end
7431220
7451222 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
7461223 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
7471224
1225 -- set y_pos to base of mob
1226 pos.y = pos.y + self.collisionbox[2]
1227
7481228 -- what is in front of mob?
7491229 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
1230 x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
7531231 })
7541232
755 -- thin blocks that do not need to be jumped
756 if nod.name == node_snow then
757 return false
758 end
759
760 --print ("in front:", nod.name, pos.y + 0.5)
761
762 if self.walk_chance == 0
763 or minetest.registered_items[nod.name].walkable then
764
765 if not nod.name:find("fence")
766 and not nod.name:find("gate") then
767
768 local v = self.object:get_velocity()
769
770 v.y = self.jump_height
771
772 set_animation(self, "jump") -- only when defined
773
774 self.object:set_velocity(v)
775
776 -- when in air move forward
777 minetest.after(0.3, function(self, v)
778
779 if self.object:get_luaentity() then
780
781 self.object:set_acceleration({
782 x = v.x * 2,--1.5,
783 y = 0,
784 z = v.z * 2,--1.5
785 })
786 end
787 end, self, v)
788
789 if get_velocity(self) > 0 then
790 mob_sound(self, self.sounds.jump)
791 end
792 else
793 self.facing_fence = true
794 end
1233 -- what is above and in front?
1234 local nodt = node_ok({
1235 x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
1236 })
1237
1238 local blocked = minetest.registered_nodes[nodt.name].walkable
1239
1240 -- are we facing a fence or wall
1241 if nod.name:find("fence") or nod.name:find("gate") or nod.name:find("wall") then
1242 self.facing_fence = true
1243 end
1244 --[[
1245 print("on: " .. self.standing_on
1246 .. ", front: " .. nod.name
1247 .. ", front above: " .. nodt.name
1248 .. ", blocked: " .. (blocked and "yes" or "no")
1249 .. ", fence: " .. (self.facing_fence and "yes" or "no")
1250 )
1251 ]]
1252 -- jump if standing on solid node (not snow) and not blocked
1253 if (self.walk_chance == 0 or minetest.registered_items[nod.name].walkable)
1254 and not blocked and not self.facing_fence and nod.name ~= node_snow then
1255
1256 local v = self.object:get_velocity()
1257
1258 v.y = self.jump_height
1259
1260 self:set_animation("jump") -- only when defined
1261
1262 self.object:set_velocity(v)
1263
1264 -- when in air move forward
1265 minetest.after(0.3, function(self, v)
1266
1267 if self.object:get_luaentity() then
1268
1269 self.object:set_acceleration({
1270 x = v.x * 2,
1271 y = 0,
1272 z = v.z * 2
1273 })
1274 end
1275 end, self, v)
1276
1277 if self:get_velocity() > 0 then
1278 self:mob_sound(self.sounds.jump)
1279 end
1280
1281 self.jump_count = 0
7951282
7961283 return true
1284 end
1285
1286 -- if blocked for 3 counts then turn
1287 if not self.following and (self.facing_fence or blocked) then
1288
1289 self.jump_count = (self.jump_count or 0) + 1
1290
1291 if self.jump_count > 2 then
1292
1293 local yaw = self.object:get_yaw() or 0
1294 local turn = random(0, 2) + 1.35
1295
1296 yaw = self:set_yaw(yaw + turn, 12)
1297
1298 self.jump_count = 0
1299 end
7971300 end
7981301
7991302 return false
8131316 obj_pos = objs[n]:get_pos()
8141317
8151318 dist = get_distance(pos, obj_pos)
1319
8161320 if dist < 1 then dist = 1 end
8171321
8181322 local damage = floor((4 / dist) * radius)
8271331 end
8281332
8291333
1334 -- can mob see player
1335 local is_invisible = function(self, player_name)
1336
1337 if mobs.invis[player_name] and not self.ignore_invisibility then
1338 return true
1339 end
1340 end
1341
1342
8301343 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
1344 function mob_class:follow_holding(clicker)
1345
1346 if is_invisible(self, clicker:get_player_name()) then
8341347 return false
8351348 end
8361349
8371350 local item = clicker:get_wielded_item()
838 local t = type(self.follow)
839
840 -- single item
841 if t == "string"
842 and item:get_name() == self.follow then
1351
1352 -- are we holding an item mob can follow ?
1353 if check_for(item:get_name(), self.follow) then
8431354 return true
844
845 -- multiple items
846 elseif t == "table" then
847
848 for no = 1, #self.follow do
849
850 if self.follow[no] == item:get_name() then
851 return true
852 end
853 end
8541355 end
8551356
8561357 return false
8571358 end
8581359
1360 -- Thanks Wuzzy for the following editable settings
1361 local HORNY_TIME = 30
1362 local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes
1363 local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
8591364
8601365 -- find two animals of same type and breed if nearby and horny
861 local breed = function(self)
862
863 -- child takes 240 seconds before growing into adult
1366 function mob_class:breed()
1367
1368 -- child takes a long time before growing into adult
8641369 if self.child == true then
8651370
8661371 self.hornytimer = self.hornytimer + 1
8671372
868 if self.hornytimer > 240 then
1373 if self.hornytimer > CHILD_GROW_TIME then
8691374
8701375 self.child = false
8711376 self.hornytimer = 0
8751380 mesh = self.base_mesh,
8761381 visual_size = self.base_size,
8771382 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
1383 selectionbox = self.base_selbox
8791384 })
8801385
8811386 -- custom function when child grows up
8821387 if self.on_grown then
8831388 self.on_grown(self)
8841389 else
885 -- jump when fully grown so as not to fall into ground
886 self.object:set_velocity({
887 x = 0,
888 y = self.jump_height,
889 z = 0
890 })
1390 local pos = self.object:get_pos() ; if not pos then return end
1391 local ent = self.object:get_luaentity()
1392
1393 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
1394
1395 self.object:set_pos(pos)
1396
1397 -- jump slightly when fully grown so as not to fall into ground
1398 self.object:set_velocity({x = 0, y = 0.5, z = 0 })
8911399 end
8921400 end
8931401
8941402 return
8951403 end
8961404
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
1405 -- horny animal can mate for HORNY_TIME seconds,
1406 -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
8991407 if self.horny == true
900 and self.hornytimer < 240 then
1408 and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
9011409
9021410 self.hornytimer = self.hornytimer + 1
9031411
904 if self.hornytimer >= 240 then
1412 if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
9051413 self.hornytimer = 0
9061414 self.horny = false
9071415 end
9091417
9101418 -- find another same animal who is also horny and mate if nearby
9111419 if self.horny == true
912 and self.hornytimer <= 40 then
1420 and self.hornytimer <= HORNY_TIME then
9131421
9141422 local pos = self.object:get_pos()
9151423
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
1424 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
1425 "heart.png", 3, 4, 1, 0.1)
9171426
9181427 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
1428 local ent
9211429
9221430 for n = 1, #objs do
9231431
9311439 if ent.name == self.name then
9321440 canmate = true
9331441 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
1442 local entname = ent.name:split(":")
1443 local selfname = self.name:split(":")
9361444
9371445 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
1446 entname = entname[2]:split("_")
1447 selfname = selfname[2]:split("_")
9401448
9411449 if entname[1] == selfname[1] then
9421450 canmate = true
9451453 end
9461454 end
9471455
948 if ent
1456 -- found another similar horny animal that isn't self?
1457 if ent and ent.object ~= self.object
9491458 and canmate == true
9501459 and ent.horny == true
951 and ent.hornytimer <= 40 then
952 num = num + 1
953 end
954
955 -- found your mate? then have a baby
956 if num > 1 then
957
958 self.hornytimer = 41
959 ent.hornytimer = 41
1460 and ent.hornytimer <= HORNY_TIME then
1461
1462 local pos2 = ent.object:get_pos()
1463
1464 -- Have mobs face one another
1465 yaw_to_pos(self, pos2)
1466 yaw_to_pos(ent, self.object:get_pos())
1467
1468 self.hornytimer = HORNY_TIME + 1
1469 ent.hornytimer = HORNY_TIME + 1
1470
1471 -- have we reached active mob limit
1472 if active_limit > 0 and active_mobs >= active_limit then
1473 minetest.chat_send_player(self.owner,
1474 S("Active Mob Limit Reached!")
1475 .. " (" .. active_mobs
1476 .. " / " .. active_limit .. ")")
1477 return
1478 end
9601479
9611480 -- spawn baby
9621481 minetest.after(5, function(self, ent)
9691488 if self.on_breed then
9701489
9711490 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
1491 if self:on_breed(ent) == false then
9731492 return
9741493 end
9751494 else
9761495 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
9771496 end
9781497
1498 pos.y = pos.y + 0.5 -- spawn child a little higher
1499
9791500 local mob = minetest.add_entity(pos, self.name)
9801501 local ent2 = mob:get_luaentity()
9811502 local textures = self.base_texture
9901511 textures = textures,
9911512 visual_size = {
9921513 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
1514 y = self.base_size.y * .5
9941515 },
9951516 collisionbox = {
9961517 self.base_colbox[1] * .5,
9981519 self.base_colbox[3] * .5,
9991520 self.base_colbox[4] * .5,
10001521 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1522 self.base_colbox[6] * .5
10021523 },
10031524 selectionbox = {
10041525 self.base_selbox[1] * .5,
10061527 self.base_selbox[3] * .5,
10071528 self.base_selbox[4] * .5,
10081529 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1530 self.base_selbox[6] * .5
10101531 },
10111532 })
10121533 -- tamed and owned by parents' owner
10151536 ent2.owner = self.owner
10161537 end, self, ent)
10171538
1018 num = 0
1019
10201539 break
10211540 end
10221541 end
10251544
10261545
10271546 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1547 function mob_class:replace(pos)
1548
1549 local vel = self.object:get_velocity()
1550 if not vel then return end
10291551
10301552 if not mobs_griefing
10311553 or not self.replace_rate
10321554 or not self.replace_what
10331555 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1556 or vel.y ~= 0
1557 or random(self.replace_rate) > 1 then
10361558 return
10371559 end
10381560
10551577
10561578 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
10571579
1058 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1059
1060 local oldnode = {name = what}
1061 local newnode = {name = with}
1062 local on_replace_return
1580 -- print("replace node = ".. minetest.get_node(pos).name, pos.y)
10631581
10641582 if self.on_replace then
1065 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1066 end
1067
1068 if on_replace_return ~= false then
1069
1070 minetest.set_node(pos, {name = with})
1071
1072 -- when cow/sheep eats grass, replace wool and milk
1073 if self.gotten == true then
1074 self.gotten = false
1075 self.object:set_properties(self)
1076 end
1077 end
1583
1584 local oldnode = what or ""
1585 local newnode = with
1586
1587 -- pass actual node name when using table or groups
1588 if type(oldnode) == "table"
1589 or oldnode:find("group:") then
1590 oldnode = minetest.get_node(pos).name
1591 end
1592
1593 if self:on_replace(pos, oldnode, newnode) == false then
1594 return
1595 end
1596 end
1597
1598 minetest.set_node(pos, {name = with})
10781599 end
10791600 end
10801601
10811602
10821603 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1604 function mob_class:day_docile()
10841605
10851606 if self.docile_by_day == false then
10861607
10971618
10981619 local los_switcher = false
10991620 local height_switcher = false
1100
1101 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1102 local smart_mobs = function(self, s, p, dist, dtime)
1621 local can_dig_drop = function(pos)
1622
1623 if minetest.is_protected(pos, "") then
1624 return false
1625 end
1626
1627 local node = node_ok(pos, "air").name
1628 local ndef = minetest.registered_nodes[node]
1629
1630 if node ~= "ignore"
1631 and ndef
1632 and ndef.drawtype ~= "airlike"
1633 and not ndef.groups.level
1634 and not ndef.groups.unbreakable
1635 and not ndef.groups.liquid then
1636
1637 local drops = minetest.get_node_drops(node)
1638
1639 for _, item in ipairs(drops) do
1640
1641 minetest.add_item({
1642 x = pos.x - 0.5 + random(),
1643 y = pos.y - 0.5 + random(),
1644 z = pos.z - 0.5 + random()
1645 }, item)
1646 end
1647
1648 minetest.remove_node(pos)
1649
1650 return true
1651 end
1652
1653 return false
1654 end
1655
1656
1657 local pathfinder_mod = minetest.get_modpath("pathfinder")
1658 -- path finding and smart mob routine by rnd,
1659 -- line_of_sight and other edits by Elkien3
1660 function mob_class:smart_mobs(s, p, dist, dtime)
11031661
11041662 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1663 local target_pos = p
1664
11071665
11081666 -- is it becoming stuck?
11091667 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
11761734 end, self)
11771735 end
11781736
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1737 if abs(vsubtract(s,target_pos).y) > self.stepheight then
11801738
11811739 if height_switcher then
11821740 use_pathfind = true
11891747 end
11901748 end
11911749
1750 -- lets try find a path, first take care of positions
1751 -- since pathfinder is very sensitive
11921752 if use_pathfind then
1193 -- lets try find a path, first take care of positions
1194 -- since pathfinder is very sensitive
1195 local sheight = self.collisionbox[5] - self.collisionbox[2]
11961753
11971754 -- round position to center of node to avoid stuck in walls
11981755 -- also adjust height for player models!
11991756 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
12011757 s.z = floor(s.z + 0.5)
12021758
12031759 local ssight, sground = minetest.line_of_sight(s, {
12151771 p1.z = floor(p1.z + 0.5)
12161772
12171773 local dropheight = 6
1774
12181775 if self.fear_height ~= 0 then dropheight = self.fear_height end
12191776
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1221
1777 local jumpheight = 0
1778
1779 if self.jump and self.jump_height >= 4 then
1780 jumpheight = min(ceil(self.jump_height / 4), 4)
1781
1782 elseif self.stepheight > 0.5 then
1783 jumpheight = 1
1784 end
1785
1786 if pathfinder_mod then
1787 self.path.way = pathfinder.find_path(s, p1, self, dtime)
1788 else
1789 self.path.way = minetest.find_path(s, p1, 16, jumpheight,
1790 dropheight, "Dijkstra")
1791 end
12221792 --[[
12231793 -- show path using particles
12241794 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1795
1796 print("-- path length:" .. tonumber(#self.path.way))
1797
12261798 for _,pos in pairs(self.path.way) do
12271799 minetest.add_particle({
12281800 pos = pos,
12391811 ]]
12401812
12411813 self.state = ""
1242 do_attack(self, self.attack)
1814
1815 if self.attack then
1816 self:do_attack(self.attack)
1817 end
12431818
12441819 -- no path found, try something else
12451820 if not self.path.way then
12491824 -- lets make way by digging/building if not accessible
12501825 if self.pathfinding == 2 and mobs_griefing then
12511826
1252 -- is player higher than mob?
1253 if s.y < p1.y then
1827 -- is player more than 1 block higher than mob?
1828 if p1.y > (s.y + 1) then
12541829
12551830 -- build upwards
12561831 if not minetest.is_protected(s, "") then
12581833 local ndef1 = minetest.registered_nodes[self.standing_in]
12591834
12601835 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1261
1262 minetest.set_node(s, {name = mobs.fallback_node})
1836 minetest.set_node(s, {name = mobs.fallback_node})
12631837 end
12641838 end
12651839
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1840 local sheight = ceil(self.collisionbox[5]) + 1
12671841
12681842 -- assume mob is 2 blocks high so it digs above its head
12691843 s.y = s.y + sheight
12701844
12711845 -- remove one block above to make room to jump
1272 if not minetest.is_protected(s, "") then
1273
1274 local node1 = node_ok(s, "air").name
1275 local ndef1 = minetest.registered_nodes[node1]
1276
1277 if node1 ~= "air"
1278 and node1 ~= "ignore"
1279 and ndef1
1280 and not ndef1.groups.level
1281 and not ndef1.groups.unbreakable
1282 and not ndef1.groups.liquid then
1283
1284 minetest.set_node(s, {name = "air"})
1285 minetest.add_item(s, ItemStack(node1))
1286
1287 end
1288 end
1846 can_dig_drop(s)
12891847
12901848 s.y = s.y - sheight
12911849 self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
1850
1851 -- is player more than 1 block lower than mob
1852 elseif p1.y < (s.y - 1) then
1853
1854 -- dig down
1855 s.y = s.y - self.collisionbox[4] - 0.2
1856
1857 can_dig_drop(s)
12921858
12931859 else -- dig 2 blocks to make door toward player direction
12941860
12991865 z = s.z + sin(yaw1)
13001866 }
13011867
1302 if not minetest.is_protected(p1, "") then
1303
1304 local node1 = node_ok(p1, "air").name
1305 local ndef1 = minetest.registered_nodes[node1]
1306
1307 if node1 ~= "air"
1308 and node1 ~= "ignore"
1309 and ndef1
1310 and not ndef1.groups.level
1311 and not ndef1.groups.unbreakable
1312 and not ndef1.groups.liquid then
1313
1314 minetest.add_item(p1, ItemStack(node1))
1315 minetest.set_node(p1, {name = "air"})
1316 end
1317
1318 p1.y = p1.y + 1
1319 node1 = node_ok(p1, "air").name
1320 ndef1 = minetest.registered_nodes[node1]
1321
1322 if node1 ~= "air"
1323 and node1 ~= "ignore"
1324 and ndef1
1325 and not ndef1.groups.level
1326 and not ndef1.groups.unbreakable
1327 and not ndef1.groups.liquid then
1328
1329 minetest.add_item(p1, ItemStack(node1))
1330 minetest.set_node(p1, {name = "air"})
1331 end
1332
1333 end
1868 -- dig bottom node first incase of door
1869 can_dig_drop(p1)
1870
1871 p1.y = p1.y + 1
1872
1873 can_dig_drop(p1)
13341874 end
13351875 end
13361876
13371877 -- will try again in 2 second
13381878 self.path.stuck_timer = stuck_timeout - 2
13391879
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1880 elseif s.y < p1.y and (not self.fly) then
1881 self:do_jump() --add jump to pathfinding
1882 self.path.following = true
13421883 else
13431884 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1885 if self.attack then
1886 self:mob_sound(self.sounds.war_cry)
1887 else
1888 self:mob_sound(self.sounds.random)
1889 end
1890
1891 self:set_velocity(self.walk_velocity)
13461892
13471893 -- follow path now that it has it
13481894 self.path.following = true
13511897 end
13521898
13531899
1354 -- specific attacks
1355 local specific_attack = function(list, what)
1356
1357 -- no list so attack default (player, animals etc.)
1358 if list == nil then
1359 return true
1360 end
1361
1362 -- found entity on list to attack?
1363 for no = 1, #list do
1364
1365 if list[no] == what then
1900 -- peaceful player privilege support
1901 local function is_peaceful_player(player)
1902
1903 if peaceful_player_enabled then
1904
1905 local player_name = player:get_player_name()
1906
1907 if player_name
1908 and minetest.check_player_privs(player_name, "peaceful_player") then
13661909 return true
13671910 end
13681911 end
13711914 end
13721915
13731916
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1917 -- general attack function for all mobs
1918 function mob_class:general_attack()
13761919
13771920 -- return if already attacking, passive or docile during day
13781921 if self.passive
1922 or self.state == "runaway"
13791923 or self.state == "attack"
1380 or day_docile(self) then
1924 or self:day_docile() then
13811925 return
13821926 end
13831927
1384 local s = self.object:get_pos()
1928 local s = self.object:get_pos() ; if not s then return end
13851929 local objs = minetest.get_objects_inside_radius(s, self.view_range)
13861930
13871931 -- remove entities we aren't interested in
13921936 -- are we a player?
13931937 if objs[n]:is_player() then
13941938
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1939 -- if player invisible or mob cannot attack then remove from list
1940 if not damage_enabled
1941 or self.attack_players == false
13971942 or (self.owner and self.type ~= "monster")
1398 or mobs.invis[objs[n]:get_player_name()]
1399 or not specific_attack(self.specific_attack, "player") then
1943 or is_invisible(self, objs[n]:get_player_name())
1944 or (self.specific_attack
1945 and not check_for("player", self.specific_attack)) then
14001946 objs[n] = nil
14011947 --print("- pla", n)
14021948 end
14091955 or (not self.attack_animals and ent.type == "animal")
14101956 or (not self.attack_monsters and ent.type == "monster")
14111957 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1958 or (self.specific_attack
1959 and not check_for(ent.name, self.specific_attack)) then
14131960 objs[n] = nil
14141961 --print("- mob", n, self.name, ent.name)
14151962 end
14391986 -- choose closest player to attack that isnt self
14401987 if dist ~= 0
14411988 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
1989 and self:line_of_sight(sp, p, 2) == true
1990 and not is_peaceful_player(player) then
14431991 min_dist = dist
14441992 min_player = player
14451993 end
14461994 end
14471995
14481996 -- attack closest player or mob
1449 if min_player and random(1, 100) > self.attack_chance then
1450 do_attack(self, min_player)
1451 end
1452 end
1453
1454
1455 -- specific runaway
1456 local specific_runaway = function(list, what)
1457
1458 -- no list so do not run
1459 if list == nil then
1460 return false
1461 end
1462
1463 -- found entity on list to attack?
1464 for no = 1, #list do
1465
1466 if list[no] == what then
1467 return true
1468 end
1469 end
1470
1471 return false
1997 if min_player and random(100) > self.attack_chance then
1998 self:do_attack(min_player)
1999 end
14722000 end
14732001
14742002
14752003 -- find someone to runaway from
1476 local runaway_from = function(self)
2004 function mob_class:do_runaway_from()
14772005
14782006 if not self.runaway_from then
14792007 return
14802008 end
14812009
1482 local s = self.object:get_pos()
2010 local s = self.object:get_pos() ; if not s then return end
14832011 local p, sp, dist, pname
14842012 local player, obj, min_player, name
14852013 local min_dist = self.view_range + 1
14912019
14922020 pname = objs[n]:get_player_name()
14932021
1494 if mobs.invis[pname]
2022 if is_invisible(self, pname)
14952023 or self.owner == pname then
14962024
14972025 name = ""
15102038
15112039 -- find specific mob to runaway from
15122040 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
2041 and (self.runaway_from and check_for(name, self.runaway_from)) then
2042
15162043 sp = s
2044 p = player and player:get_pos() or s
15172045
15182046 -- aim higher to make looking up hills more realistic
15192047 p.y = p.y + 1
15232051
15242052 -- choose closest player/mob to runaway from
15252053 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
2054 and self:line_of_sight(sp, p, 2) == true then
15272055 min_dist = dist
15282056 min_player = player
15292057 end
15322060
15332061 if min_player then
15342062
1535 local lp = player:get_pos()
1536 local vec = {
1537 x = lp.x - s.x,
1538 y = lp.y - s.y,
1539 z = lp.z - s.z
1540 }
1541
1542 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1543
1544 if lp.x > s.x then
1545 yaw = yaw + pi
1546 end
1547
1548 yaw = set_yaw(self, yaw, 4)
2063 yaw_to_pos(self, min_player:get_pos(), 3)
2064
15492065 self.state = "runaway"
15502066 self.runaway_timer = 3
15512067 self.following = nil
15542070
15552071
15562072 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
2073 function mob_class:follow_flop()
15582074
15592075 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
2076 if (self.follow ~= "" or self.order == "follow")
15622077 and not self.following
15632078 and self.state ~= "attack"
15642079 and self.state ~= "runaway" then
15652080
1566 local s = self.object:get_pos()
2081 local s = self.object:get_pos() ; if not s then return end
15672082 local players = minetest.get_connected_players()
15682083
15692084 for n = 1, #players do
15702085
15712086 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
2087 and not is_invisible(self, players[n]:get_player_name()) then
15732088
15742089 self.following = players[n]
15752090
15902105 self.following = nil
15912106 end
15922107 else
1593 -- stop following player if not holding specific item
2108 -- stop following player if not holding specific item or mob is horny
15942109 if self.following
15952110 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
2111 and (self:follow_holding(self.following) == false
2112 or self.horny) then
15972113 self.following = nil
15982114 end
15992115
16222138 if dist > self.view_range then
16232139 self.following = nil
16242140 else
1625 local vec = {
1626 x = p.x - s.x,
1627 z = p.z - s.z
1628 }
1629
1630 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1631
1632 if p.x > s.x then yaw = yaw + pi end
1633
1634 yaw = set_yaw(self, yaw, 6)
2141 yaw_to_pos(self, p)
16352142
16362143 -- anyone but standing npc's can move along
16372144 if dist > self.reach
16382145 and self.order ~= "stand" then
16392146
1640 set_velocity(self, self.walk_velocity)
2147 self:set_velocity(self.walk_velocity)
16412148
16422149 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
2150 self:set_animation("walk")
16442151 end
16452152 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
2153 self:set_velocity(0)
2154 self:set_animation("stand")
16482155 end
16492156
16502157 return
16542161
16552162 -- swimmers flop when out of their element, and swim again when back in
16562163 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
2164
2165 if not self:attempt_flight_correction() then
16592166
16602167 self.state = "flop"
2168
2169 -- do we have a custom on_flop function?
2170 if self.on_flop then
2171
2172 if self:on_flop(self) then
2173 return
2174 end
2175 end
2176
16612177 self.object:set_velocity({x = 0, y = -5, z = 0})
16622178
1663 set_animation(self, "stand")
2179 self:set_animation("stand")
16642180
16652181 return
2182
16662183 elseif self.state == "flop" then
16672184 self.state = "stand"
16682185 end
16712188
16722189
16732190 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
2191 function mob_class:dogswitch(dtime)
16752192
16762193 -- switch mode not activated
16772194 if not self.dogshoot_switch
17002217
17012218
17022219 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
2220 function mob_class:do_states(dtime)
2221
2222 local yaw = self.object:get_yaw() ; if not yaw then return end
17062223
17072224 if self.state == "stand" then
17082225
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
2226 if self.randomly_turn and random(4) == 1 then
2227
2228 local lp
17122229 local s = self.object:get_pos()
17132230 local objs = minetest.get_objects_inside_radius(s, 3)
17142231
17222239
17232240 -- look at any players nearby, otherwise turn randomly
17242241 if lp then
1725
1726 local vec = {
1727 x = lp.x - s.x,
1728 z = lp.z - s.z
1729 }
1730
1731 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1732
1733 if lp.x > s.x then yaw = yaw + pi end
2242 yaw = yaw_to_pos(self, lp)
17342243 else
17352244 yaw = yaw + random(-0.5, 0.5)
17362245 end
17372246
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
2247 yaw = self:set_yaw(yaw, 8)
2248 end
2249
2250 self:set_velocity(0)
2251 self:set_animation("stand")
17432252
17442253 -- mobs ordered to stand stay standing
17452254 if self.order ~= "stand"
17462255 and self.walk_chance ~= 0
17472256 and self.facing_fence ~= true
1748 and random(1, 100) <= self.walk_chance
1749 and is_at_cliff(self) == false then
1750
1751 set_velocity(self, self.walk_velocity)
2257 and random(100) <= self.walk_chance
2258 and self.at_cliff == false then
2259
2260 self:set_velocity(self.walk_velocity)
17522261 self.state = "walk"
1753 set_animation(self, "walk")
2262 self:set_animation("walk")
17542263 end
17552264
17562265 elseif self.state == "walk" then
17572266
17582267 local s = self.object:get_pos()
1759 local lp = nil
2268 local lp
17602269
17612270 -- is there something I need to avoid?
17622271 if self.water_damage > 0
17632272 and self.lava_damage > 0 then
17642273
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
2274 lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"})
17662275
17672276 elseif self.water_damage > 0 then
17682277
17702279
17712280 elseif self.lava_damage > 0 then
17722281
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
2282 lp = minetest.find_node_near(s, 1, {"group:igniter"})
17742283 end
17752284
17762285 if lp then
17772286
1778 -- if mob in water or lava then look for land
1779 if (self.lava_damage
1780 and minetest.registered_nodes[self.standing_in].groups.lava)
1781 or (self.water_damage
1782 and minetest.registered_nodes[self.standing_in].groups.water) then
1783
1784 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1785 "group:sand", node_ice, node_snowblock})
2287 -- if mob in dangerous node then look for land
2288 if not is_node_dangerous(self, self.standing_in) then
2289
2290 lp = minetest.find_nodes_in_area_under_air(
2291 {s.x - 5, s.y - 1, s.z - 5},
2292 {s.x + 5, s.y + 2, s.z + 5},
2293 {"group:soil", "group:stone", "group:sand",
2294 node_ice, node_snowblock})
2295
2296 -- select position of random block to climb onto
2297 lp = #lp > 0 and lp[random(#lp)]
17862298
17872299 -- did we find land?
17882300 if lp then
17892301
1790 local vec = {
1791 x = lp.x - s.x,
1792 z = lp.z - s.z
1793 }
1794
1795 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1796
1797 if lp.x > s.x then yaw = yaw + pi end
1798
1799 -- look towards land and jump/move in that direction
1800 yaw = set_yaw(self, yaw, 6)
1801 do_jump(self)
1802 set_velocity(self, self.walk_velocity)
2302 yaw = yaw_to_pos(self, lp)
2303
2304 self:do_jump()
2305 self:set_velocity(self.walk_velocity)
18032306 else
18042307 yaw = yaw + random(-0.5, 0.5)
18052308 end
1806
1807 else
1808
1809 local vec = {
1810 x = lp.x - s.x,
1811 z = lp.z - s.z
1812 }
1813
1814 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1815
1816 if lp.x > s.x then yaw = yaw + pi end
1817 end
1818
1819 yaw = set_yaw(self, yaw, 8)
2309 end
2310
2311 yaw = self:set_yaw(yaw, 8)
18202312
18212313 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
2314 elseif self.randomly_turn and random(100) <= 30 then
18232315
18242316 yaw = yaw + random(-0.5, 0.5)
18252317
1826 yaw = set_yaw(self, yaw, 8)
2318 yaw = self:set_yaw(yaw, 8)
2319
2320 -- for flying/swimming mobs randomly move up and down also
2321 if self.fly_in
2322 and not self.following then
2323 self:attempt_flight_correction(true)
2324 end
18272325 end
18282326
18292327 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
18322328 if self.facing_fence == true
1833 or temp_is_cliff
1834 or random(1, 100) <= 30 then
1835
1836 set_velocity(self, 0)
1837 self.state = "stand"
1838 set_animation(self, "stand")
2329 or self.at_cliff
2330 or random(100) <= self.stand_chance then
2331
2332 -- don't stand if mob flies and keep_flying set
2333 if (self.fly and not self.keep_flying)
2334 or not self.fly then
2335
2336 self:set_velocity(0)
2337 self.state = "stand"
2338 self:set_animation("stand", true)
2339 end
18392340 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
2341 self:set_velocity(self.walk_velocity)
2342
2343 if self:flight_check()
18432344 and self.animation
18442345 and self.animation.fly_start
18452346 and self.animation.fly_end then
1846 set_animation(self, "fly")
2347 self:set_animation("fly")
18472348 else
1848 set_animation(self, "walk")
2349 self:set_animation("walk")
18492350 end
18502351 end
18512352
18562357
18572358 -- stop after 5 seconds or when at cliff
18582359 if self.runaway_timer > 5
1859 or is_at_cliff(self)
2360 or self.at_cliff
18602361 or self.order == "stand" then
18612362 self.runaway_timer = 0
1862 set_velocity(self, 0)
2363 self:set_velocity(0)
18632364 self.state = "stand"
1864 set_animation(self, "stand")
2365 self:set_animation("stand")
18652366 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
2367 self:set_velocity(self.run_velocity)
2368 self:set_animation("walk")
18682369 end
18692370
18702371 -- attack routines (explode, dogfight, shoot, dogshoot)
18712372 elseif self.state == "attack" then
18722373
1873 -- calculate distance from mob and enemy
2374 -- get mob and enemy positions and distance between
18742375 local s = self.object:get_pos()
1875 local p = self.attack:get_pos() or s
1876 local dist = get_distance(p, s)
1877
1878 -- stop attacking if player invisible or out of range
2376 local p = self.attack and self.attack:get_pos()
2377 local dist = p and get_distance(p, s) or 500
2378
2379 -- stop attacking if player out of range or invisible
18792380 if dist > self.view_range
18802381 or not self.attack
18812382 or not self.attack:get_pos()
18822383 or self.attack:get_hp() <= 0
1883 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1884
1885 -- print(" ** stop attacking **", dist, self.view_range)
2384 or (self.attack:is_player()
2385 and is_invisible(self, self.attack:get_player_name())) then
2386
2387 --print(" ** stop attacking **", dist, self.view_range)
2388
18862389 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
2390 self:set_velocity(0)
2391 self:set_animation("stand")
18892392 self.attack = nil
18902393 self.v_start = false
18912394 self.timer = 0
18972400
18982401 if self.attack_type == "explode" then
18992402
1900 local vec = {
1901 x = p.x - s.x,
1902 z = p.z - s.z
1903 }
1904
1905 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1906
1907 if p.x > s.x then yaw = yaw + pi end
1908
1909 yaw = set_yaw(self, yaw)
2403 yaw = yaw_to_pos(self, p)
19102404
19112405 local node_break_radius = self.explosion_radius or 1
19122406 local entity_damage_radius = self.explosion_damage_radius
19132407 or (node_break_radius * 2)
19142408
2409 -- look a little higher to fix raycast
2410 s.y = s.y + 0.5 ; p.y = p.y + 0.5
2411
19152412 -- start timer when in reach and line of sight
19162413 if not self.v_start
19172414 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
2415 and self:line_of_sight(s, p, 2) then
19192416
19202417 self.v_start = true
19212418 self.timer = 0
19222419 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
2420 self:mob_sound(self.sounds.fuse)
2421
2422 --print("=== explosion timer started", self.explosion_timer)
19252423
19262424 -- stop timer if out of reach or direct line of sight
19272425 elseif self.allow_fuse_reset
19282426 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
2427 and (dist > self.reach or not self:line_of_sight(s, p, 2)) then
2428
2429 --print("=== explosion timer stopped")
2430
19312431 self.v_start = false
19322432 self.timer = 0
19332433 self.blinktimer = 0
19342434 self.blinkstatus = false
1935 self.object:settexturemod("")
2435 self.object:set_texture_mod("")
19362436 end
19372437
19382438 -- walk right up to player unless the timer is active
19392439 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
2440 self:set_velocity(0)
19412441 else
1942 set_velocity(self, self.run_velocity)
2442 self:set_velocity(self.run_velocity)
19432443 end
19442444
19452445 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
2446 self:set_animation("run")
19472447 else
1948 set_animation(self, "walk")
2448 self:set_animation("walk")
19492449 end
19502450
19512451 if self.v_start then
19582458 self.blinktimer = 0
19592459
19602460 if self.blinkstatus then
1961 self.object:settexturemod("")
2461
2462 self.object:set_texture_mod(self.texture_mods)
19622463 else
1963 self.object:settexturemod("^[brighten")
2464
2465 self.object:set_texture_mod(self.texture_mods
2466 .. "^[brighten")
19642467 end
19652468
19662469 self.blinkstatus = not self.blinkstatus
19672470 end
19682471
1969 -- print ("=== explosion timer", self.timer)
2472 --print("=== explosion timer", self.timer)
19702473
19712474 if self.timer > self.explosion_timer then
19722475
19792482 node_break_radius = 1
19802483 end
19812484
1982 self.object:remove()
2485 remove_mob(self, true)
19832486
19842487 if minetest.get_modpath("tnt") and tnt and tnt.boom
19852488 and not minetest.is_protected(pos, "") then
19872490 tnt.boom(pos, {
19882491 radius = node_break_radius,
19892492 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
2493 sound = self.sounds.explode
19912494 })
19922495 else
19932496
19982501 })
19992502
20002503 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2504
2505 effect(pos, 32, "tnt_smoke.png", nil, nil,
2506 node_break_radius, 1, 0)
20022507 end
20032508
2004 return
2509 return true
20052510 end
20062511 end
20072512
20082513 elseif self.attack_type == "dogfight"
2009 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2010 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2514 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
2515 or (self.attack_type == "dogshoot" and dist <= self.reach
2516 and self:dogswitch() == 0) then
20112517
20122518 if self.fly
20132519 and dist > self.reach then
20182524 local p_y = floor(p2.y + 1)
20192525 local v = self.object:get_velocity()
20202526
2021 if flight_check(self, s) then
2527 if self:flight_check() then
20222528
20232529 if me_y < p_y then
20242530
20542560 })
20552561 end
20562562 end
2057
20582563 end
20592564
20602565 -- rnd: new movement direction
20762581 return
20772582 end
20782583
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2584 if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
20802585 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2586 table_remove(self.path.way, 1)
20822587 end
20832588
20842589 -- set new temporary target
20852590 p = {x = p1.x, y = p1.y, z = p1.z}
20862591 end
20872592
2088 local vec = {
2089 x = p.x - s.x,
2090 z = p.z - s.z
2091 }
2092
2093 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2094
2095 if p.x > s.x then yaw = yaw + pi end
2096
2097 yaw = set_yaw(self, yaw)
2593 yaw = yaw_to_pos(self, p)
20982594
20992595 -- move towards enemy if beyond mob reach
21002596 if dist > self.reach then
21032599 if self.pathfinding -- only if mob has pathfinding enabled
21042600 and enable_pathfinding then
21052601
2106 smart_mobs(self, s, p, dist, dtime)
2602 self:smart_mobs(s, p, dist, dtime)
21072603 end
21082604
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2605 -- distance padding to stop spinning mob
2606 local pad = abs(p.x - s.x) + abs(p.z - s.z)
2607
2608 if self.at_cliff or pad < 0.2 then
2609
2610 self:set_velocity(0)
2611 self:set_animation("stand")
21132612 else
21142613
21152614 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2615 self:set_velocity(self.walk_velocity)
21172616 else
2118 set_velocity(self, self.run_velocity)
2617 self:set_velocity(self.run_velocity)
21192618 end
21202619
21212620 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2621 self:set_animation("run")
21232622 else
2124 set_animation(self, "walk")
2623 self:set_animation("walk")
21252624 end
21262625 end
2127
21282626 else -- rnd: if inside reach range
21292627
21302628 self.path.stuck = false
21312629 self.path.stuck_timer = 0
21322630 self.path.following = false -- not stuck anymore
21332631
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2632 self:set_velocity(0)
2633
2634 if self.timer > 1 then
2635
2636 -- no custom attack or custom attack returns true to continue
2637 if not self.custom_attack
2638 or self:custom_attack(self, p) == true then
21392639
21402640 self.timer = 0
2141
2142 -- if self.double_melee_attack
2143 -- and random(1, 2) == 1 then
2144 -- set_animation(self, "punch2")
2145 -- else
2146 set_animation(self, "punch")
2147 -- end
2641 self:set_animation("punch")
21482642
21492643 local p2 = p
21502644 local s2 = s
21522646 p2.y = p2.y + .5
21532647 s2.y = s2.y + .5
21542648
2155 if line_of_sight(self, p2, s2) == true then
2649 if self:line_of_sight(p2, s2) == true then
21562650
21572651 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2652 self:mob_sound(self.sounds.attack)
21592653
21602654 -- punch player (or what player is attached to)
21612655 local attached = self.attack:get_attach()
2656
21622657 if attached then
21632658 self.attack = attached
21642659 end
2660
2661 local dgroup = self.damage_group or "fleshy"
2662
21652663 self.attack:punch(self.object, 1.0, {
21662664 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2665 damage_groups = {[dgroup] = self.damage}
21682666 }, nil)
21692667 end
21702668 end
2171 else -- call custom attack every second
2172 if self.custom_attack
2173 and self.timer > 1 then
2174
2175 self.timer = 0
2176
2177 self.custom_attack(self, p)
2178 end
21792669 end
21802670 end
21812671
21822672 elseif self.attack_type == "shoot"
2183 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2184 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2673 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
2674 or (self.attack_type == "dogshoot" and dist > self.reach and
2675 self:dogswitch() == 0) then
21852676
21862677 p.y = p.y - .5
21872678 s.y = s.y + .5
21882679
2189 local dist = get_distance(p, s)
2190 local vec = {
2191 x = p.x - s.x,
2192 y = p.y - s.y,
2193 z = p.z - s.z
2194 }
2195
2196 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2197
2198 if p.x > s.x then yaw = yaw + pi end
2199
2200 yaw = set_yaw(self, yaw)
2201
2202 set_velocity(self, 0)
2680 local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
2681
2682 yaw = yaw_to_pos(self, p)
2683
2684 self:set_velocity(0)
22032685
22042686 if self.shoot_interval
22052687 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2688 and random(100) <= 60 then
22072689
22082690 self.timer = 0
2209 set_animation(self, "shoot")
2691 self:set_animation("shoot")
22102692
22112693 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2694 self:mob_sound(self.sounds.shoot_attack)
22132695
22142696 local p = self.object:get_pos()
22152697
22202702 local obj = minetest.add_entity(p, self.arrow)
22212703 local ent = obj:get_luaentity()
22222704 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2705
2706 -- check for custom override for arrow
2707 if self.arrow_override then
2708 self.arrow_override(ent)
2709 end
2710
22232711 local v = ent.velocity or 1 -- or set to default
22242712
22252713 ent.switch = 1
22402728
22412729
22422730 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2731 function mob_class:falling(pos)
2732
2733 if self.fly or self.disable_falling then
22462734 return
22472735 end
22482736
22492737 -- floating in water (or falling)
22502738 local v = self.object:get_velocity()
22512739
2252 if v.y > 0 then
2253
2254 -- apply gravity when moving up
2255 self.object:set_acceleration({
2256 x = 0,
2257 y = -10,
2258 z = 0
2259 })
2260
2261 elseif v.y <= 0 and v.y > self.fall_speed then
2262
2263 -- fall downwards at set speed
2264 self.object:set_acceleration({
2265 x = 0,
2266 y = self.fall_speed,
2267 z = 0
2268 })
2269 else
2270 -- stop accelerating once max fall speed hit
2271 self.object:set_acceleration({x = 0, y = 0, z = 0})
2272 end
2273
2274 -- in water then float up
2275 if self.standing_in
2276 and minetest.registered_nodes[self.standing_in].groups.water then
2277
2278 if self.floats == 1 then
2279
2280 self.object:set_acceleration({
2281 x = 0,
2282 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2283 z = 0
2284 })
2285 end
2740 -- sanity check
2741 if not v then return end
2742
2743 local fall_speed = self.fall_speed
2744
2745 -- in water then use liquid viscosity for float/sink speed
2746 if self.floats == 1 and self.standing_in
2747 and minetest.registered_nodes[self.standing_in].groups.liquid then
2748
2749 local visc = min(
2750 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2751
2752 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2753 fall_speed = -1.2 / visc
22862754 else
22872755
22882756 -- fall damage onto solid ground
22972765
22982766 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992767
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2768 if self:check_for_death({type = "fall"}) then
2769 return true
23022770 end
23032771 end
23042772
23052773 self.old_y = self.object:get_pos().y
23062774 end
23072775 end
2776
2777 -- fall at set speed
2778 self.object:set_acceleration({x = 0, y = fall_speed, z = 0})
23082779 end
23092780
23102781
23122783 local tr = minetest.get_modpath("toolranks")
23132784
23142785 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2786 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162787
23172788 -- mob health check
23182789 if self.health <= 0 then
2319 return
2790 return true
23202791 end
23212792
23222793 -- custom punch function
23232794 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2795 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2796 return true
23262797 end
23272798
23282799 -- error checking when mod profiling is enabled
23292800 if not tool_capabilities then
2330 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2331 return
2332 end
2333
2334 -- is mob protected?
2335 if self.protected and hitter:is_player()
2336 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2337 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2338 return
2801
2802 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2803
2804 return true
2805 end
2806
2807 -- is mob protected
2808 if self.protected then
2809
2810 -- did player hit mob and if so is it in protected area
2811 if hitter:is_player() then
2812
2813 local player_name = hitter:get_player_name()
2814
2815 if player_name ~= self.owner
2816 and minetest.is_protected(self.object:get_pos(), player_name) then
2817
2818 minetest.chat_send_player(hitter:get_player_name(),
2819 S("Mob has been protected!"))
2820
2821 return true
2822 end
2823
2824 -- if protection is on level 2 then dont let arrows harm mobs
2825 elseif self.protected == 2 then
2826
2827 local ent = hitter and hitter:get_luaentity()
2828
2829 if ent and ent._is_arrow then
2830
2831 return true -- arrow entity
2832
2833 elseif not ent then
2834
2835 return true -- non entity
2836 end
2837 end
23392838 end
23402839
23412840 local weapon = hitter:get_wielded_item()
23672866 end
23682867
23692868 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2869 * tmp * ((armor[group] or 0) / 100.0)
23712870 end
23722871 end
23732872
23772876 if self.immune_to[n][1] == weapon_def.name then
23782877
23792878 damage = self.immune_to[n][2] or 0
2879
23802880 break
23812881
2382 -- if "all" then no tool does damage unless it's specified in list
2882 -- if "all" then no tools deal damage unless it's specified in list
23832883 elseif self.immune_to[n][1] == "all" then
23842884 damage = self.immune_to[n][2] or 0
23852885 end
23862886 end
23872887
2888 --print("Mob Damage is", damage)
2889
23882890 -- healing
23892891 if damage <= -1 then
2892
23902893 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2894
2895 return true
2896 end
23952897
23962898 if use_cmi
23972899 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2900 return true
23992901 end
24002902
24012903 -- add weapon wear
24132915 end
24142916 end
24152917
2416 if tr then
2417 if weapon_def.original_description then
2418 weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
2419 end
2918 if tr and weapon_def.original_description then
2919 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24202920 else
24212921 weapon:add_wear(wear)
24222922 end
24262926 -- only play hit sound and show blood effects if damage is 1 or over
24272927 if damage >= 1 then
24282928
2429 -- weapon sounds
2430 if weapon_def.sounds then
2431
2432 local s = random(0, #weapon_def.sounds)
2433
2434 minetest.sound_play(weapon_def.sounds[s], {
2435 object = self.object,
2436 max_hear_distance = 8
2437 })
2438 else
2439 minetest.sound_play("default_punch", {
2440 object = self.object,
2441 max_hear_distance = 5
2442 })
2443 end
2929 -- select tool use sound if found, or fallback to default
2930 local snd = weapon_def.sound and weapon_def.sound.use
2931 or "default_punch"
2932
2933 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442934
24452935 -- blood_particles
24462936 if not disable_blood and self.blood_amount > 0 then
24472937
24482938 local pos = self.object:get_pos()
2939 local blood = self.blood_texture
2940 local amount = self.blood_amount
24492941
24502942 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2943
2944 -- lots of damage = more blood :)
2945 if damage > 10 then
2946 amount = self.blood_amount * 2
2947 end
24512948
24522949 -- do we have a single blood texture or multiple?
24532950 if type(self.blood_texture) == "table" then
2454
2455 local blood = self.blood_texture[random(1, #self.blood_texture)]
2456
2457 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2458 else
2459 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2460 end
2951 blood = self.blood_texture[random(#self.blood_texture)]
2952 end
2953
2954 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612955 end
24622956
24632957 -- do damage
24672961 local hot = tool_capabilities and tool_capabilities.damage_groups
24682962 and tool_capabilities.damage_groups.fire
24692963
2470 if check_for_death(self, {type = "punch",
2471 puncher = hitter, hot = hot}) then
2472 return
2473 end
2474
2475 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2476 minetest.after(0.1, function()
2477
2478 if not self.object:get_luaentity() then return end
2479
2480 self.object:settexturemod("^[colorize:#c9900070")
2481
2482 core.after(0.3, function()
2483 self.object:settexturemod("")
2484 end)
2485 end) ]]
2486
2964 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2965 return true
2966 end
24872967 end -- END if damage
24882968
24892969 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2970 if self.knock_back and tflp >= punch_interval then
24922971
24932972 local v = self.object:get_velocity()
2973
2974 -- sanity check
2975 if not v then return true end
2976
24942977 local kb = damage or 1
24952978 local up = 2
24962979
25042987 dir = dir or {x = 0, y = 0, z = 0}
25052988
25062989 -- use tool knockback value or default
2507 kb = tool_capabilities.damage_groups["knockback"] or (kb * 1.5)
2508
2509 self.object:set_velocity({
2510 x = dir.x * kb,
2511 y = up,
2512 z = dir.z * kb
2513 })
2990 kb = tool_capabilities.damage_groups["knockback"] or kb
2991
2992 self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb})
25142993
25152994 self.pause_timer = 0.25
25162995 end
25202999 and self.order ~= "stand" then
25213000
25223001 local lp = hitter:get_pos()
2523 local s = self.object:get_pos()
2524 local vec = {
2525 x = lp.x - s.x,
2526 y = lp.y - s.y,
2527 z = lp.z - s.z
2528 }
2529
2530 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2531
2532 if lp.x > s.x then
2533 yaw = yaw + pi
2534 end
2535
2536 yaw = set_yaw(self, yaw, 6)
3002 local yaw = yaw_to_pos(self, lp, 3)
3003
25373004 self.state = "runaway"
25383005 self.runaway_timer = 0
25393006 self.following = nil
25473014 and self.child == false
25483015 and self.attack_players == true
25493016 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3017 and not is_invisible(self, name)
3018 and self.object ~= hitter then
25513019
25523020 -- attack whoever punched mob
25533021 self.state = ""
2554 do_attack(self, hitter)
3022 self:do_attack(hitter)
25553023
25563024 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3025 local objs = minetest.get_objects_inside_radius(
3026 hitter:get_pos(), self.view_range)
3027 local obj
25593028
25603029 for n = 1, #objs do
25613030
25633032
25643033 if obj and obj._cmi_is_mob then
25653034
2566 -- only alert members of same mob
3035 -- only alert members of same mob and assigned helper
25673036 if obj.group_attack == true
25683037 and obj.state ~= "attack"
25693038 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3039 and (obj.name == self.name
3040 or obj.name == self.group_helper) then
3041
3042 obj:do_attack(hitter)
25723043 end
25733044
25743045 -- have owned mobs attack player threat
25753046 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3047 obj:do_attack(self.object)
25773048 end
25783049 end
25793050 end
25823053
25833054
25843055 -- get entity staticdata
2585 local mob_staticdata = function(self)
3056 function mob_class:mob_staticdata()
3057
3058 -- this handles mob count for mobs activated, unloaded, reloaded
3059 if active_limit > 0 and self.active_toggle then
3060 active_mobs = active_mobs + self.active_toggle
3061 self.active_toggle = -self.active_toggle
3062 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3063 end
25863064
25873065 -- remove mob when out of range unless tamed
25883066 if remove_far
25923070 and not self.tamed
25933071 and self.lifetimer < 20000 then
25943072
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3073 --print("REMOVED " .. self.name)
3074
3075 remove_mob(self, true)
3076
3077 return minetest.serialize({remove_ok = true, static_save = true})
26003078 end
26013079
26023080 self.remove_ok = true
26053083 self.state = "stand"
26063084
26073085 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3086 if self.drawtype and self.drawtype == "side" then
3087 self.rotate = rad(90)
26113088 end
26123089
26133090 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3091 self.serialized_cmi_components = cmi.serialize_components(
3092 self._cmi_components)
3093 end
3094
3095 local tmp, t = {}
26183096
26193097 for _,stat in pairs(self) do
26203098
2621 local t = type(stat)
3099 t = type(stat)
26223100
26233101 if t ~= "function"
26243102 and t ~= "nil"
26253103 and t ~= "userdata"
3104 and _ ~= "object"
26263105 and _ ~= "_cmi_components" then
26273106 tmp[_] = self[_]
26283107 end
26293108 end
26303109
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3110 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3111
26323112 return minetest.serialize(tmp)
26333113 end
26343114
26353115
26363116 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
3117 function mob_class:mob_activate(staticdata, def, dtime)
3118
3119 -- if dtime == 0 then entity has just been created
3120 -- anything higher means it is respawning (thanks SorceryKid)
3121 if dtime == 0 and active_limit > 0 then
3122 self.active_toggle = 1
3123 end
3124
3125 -- remove mob if not tamed and mob total reached
3126 if active_limit > 0 and active_mobs >= active_limit and not self.tamed then
3127
3128 remove_mob(self)
3129 --print("-- mob limit reached, removing " .. self.name)
3130 return
3131 end
26383132
26393133 -- remove monsters in peaceful mode
2640 if self.type == "monster"
2641 and peaceful_only then
2642
2643 self.object:remove()
3134 if self.type == "monster" and peaceful_only then
3135
3136 remove_mob(self, true)
26443137
26453138 return
26463139 end
26493142 local tmp = minetest.deserialize(staticdata)
26503143
26513144 if tmp then
3145
3146 local t
3147
26523148 for _,stat in pairs(tmp) do
2653 self[_] = stat
2654 end
2655 end
3149
3150 t = type(stat)
3151
3152 if t ~= "function"
3153 and t ~= "nil"
3154 and t ~= "userdata" then
3155 self[_] = stat
3156 end
3157 end
3158 end
3159
3160 -- force current model into mob
3161 self.mesh = def.mesh
3162 self.base_mesh = def.mesh
3163 self.collisionbox = def.collisionbox
3164 self.selectionbox = def.selectionbox
26563165
26573166 -- select random texture, set model and size
26583167 if not self.base_texture then
26623171 def.textures = {def.textures}
26633172 end
26643173
2665 self.base_texture = def.textures and def.textures[random(1, #def.textures)]
3174 self.base_texture = def.textures and def.textures[random(#def.textures)]
26663175 self.base_mesh = def.mesh
26673176 self.base_size = self.visual_size
26683177 self.base_colbox = self.collisionbox
26823191 local selbox = self.base_selbox
26833192
26843193 -- specific texture if gotten
2685 if self.gotten == true
2686 and def.gotten_texture then
3194 if self.gotten == true and def.gotten_texture then
26873195 textures = def.gotten_texture
26883196 end
26893197
26903198 -- specific mesh if gotten
2691 if self.gotten == true
2692 and def.gotten_mesh then
3199 if self.gotten == true and def.gotten_mesh then
26933200 mesh = def.gotten_mesh
26943201 end
26953202
26963203 -- set child objects to half size
26973204 if self.child == true then
26983205
2699 vis_size = {
2700 x = self.base_size.x * .5,
2701 y = self.base_size.y * .5,
2702 }
3206 vis_size = {x = self.base_size.x * .5, y = self.base_size.y * .5}
27033207
27043208 if def.child_texture then
27053209 textures = def.child_texture[1]
27063210 end
27073211
27083212 colbox = {
2709 self.base_colbox[1] * .5,
2710 self.base_colbox[2] * .5,
2711 self.base_colbox[3] * .5,
2712 self.base_colbox[4] * .5,
2713 self.base_colbox[5] * .5,
2714 self.base_colbox[6] * .5
2715 }
3213 self.base_colbox[1] * .5, self.base_colbox[2] * .5,
3214 self.base_colbox[3] * .5, self.base_colbox[4] * .5,
3215 self.base_colbox[5] * .5, self.base_colbox[6] * .5}
3216
27163217 selbox = {
2717 self.base_selbox[1] * .5,
2718 self.base_selbox[2] * .5,
2719 self.base_selbox[3] * .5,
2720 self.base_selbox[4] * .5,
2721 self.base_selbox[5] * .5,
2722 self.base_selbox[6] * .5
2723 }
3218 self.base_selbox[1] * .5, self.base_selbox[2] * .5,
3219 self.base_selbox[3] * .5, self.base_selbox[4] * .5,
3220 self.base_selbox[5] * .5, self.base_selbox[6] * .5}
27243221 end
27253222
27263223 if self.health == 0 then
2727 self.health = random (self.hp_min, self.hp_max)
3224 self.health = random(self.hp_min, self.hp_max)
27283225 end
27293226
27303227 -- pathfinding init
27353232 self.path.following = false -- currently following path?
27363233 self.path.stuck_timer = 0 -- if stuck for too long search for path
27373234
3235 -- Armor groups (immortal = 1 for custom damage handling)
3236 local armor
3237 if type(self.armor) == "table" then
3238 armor = table_copy(self.armor)
3239 else
3240 armor = {fleshy = self.armor} -- immortal = 1
3241 end
3242 self.object:set_armor_groups(armor)
3243
27383244 -- mob defaults
2739 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
27403245 self.old_y = self.object:get_pos().y
27413246 self.old_health = self.health
27423247 self.sounds.distance = self.sounds.distance or 10
27463251 self.selectionbox = selbox
27473252 self.visual_size = vis_size
27483253 self.standing_in = "air"
3254 self.standing_on = "air"
27493255
27503256 -- check existing nametag
27513257 if not self.nametag then
27543260
27553261 -- set anything changed above
27563262 self.object:set_properties(self)
2757 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2758 update_tag(self)
2759 set_animation(self, "stand")
3263 self:set_yaw((random(0, 360) - 180) / 180 * pi, 6)
3264 self:update_tag()
3265 self:set_animation("stand")
3266
3267 -- apply any texture mods
3268 self.object:set_texture_mod(self.texture_mods)
3269
3270 -- set 5.x flag to remove monsters when map area unloaded
3271 if remove_far and self.type == "monster" and not self.tamed then
3272 self.static_save = false
3273 end
27603274
27613275 -- run on_spawn function if found
27623276 if self.on_spawn and not self.on_spawn_run then
27633277 if self.on_spawn(self) then
2764 self.on_spawn_run = true -- if true, set flag to run once only
3278 self.on_spawn_run = true -- if true, set flag to run once only
27653279 end
27663280 end
27673281
27713285 end
27723286
27733287 if use_cmi then
2774 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
3288 self._cmi_components = cmi.activate_components(
3289 self.serialized_cmi_components)
27753290 cmi.notify_activate(self.object, dtime)
27763291 end
27773292 end
27783293
27793294
27803295 -- handle mob lifetimer and expiration
2781 local mob_expire = function(self, pos, dtime)
3296 function mob_class:mob_expire(pos, dtime)
27823297
27833298 -- when lifetimer expires remove mob (except npc and tamed)
27843299 if self.type ~= "npc"
28093324
28103325 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
28113326
2812 self.object:remove()
3327 remove_mob(self, true)
28133328
28143329 return
28153330 end
28183333
28193334
28203335 -- main mob function
2821 local mob_step = function(self, dtime)
3336 function mob_class:on_step(dtime, moveresult)
3337
3338 if self.state == "die" then return end
28223339
28233340 if use_cmi then
28243341 cmi.notify_step(self.object, dtime)
28253342 end
28263343
28273344 local pos = self.object:get_pos()
2828 local yaw = 0
3345 local yaw = self.object:get_yaw()
3346
3347 -- early warning check, if no yaw then no entity, skip rest of function
3348 if not yaw then return end
28293349
28303350 -- get node at foot level every quarter second
28313351 self.node_timer = (self.node_timer or 0) + dtime
28433363 -- what is mob standing in?
28443364 self.standing_in = node_ok({
28453365 x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
2846 -- print ("standing in " .. self.standing_in)
2847
2848 -- check for mob expiration (0.25 instead of dtime since were in a timer)
2849 mob_expire(self, pos, 0.25)
2850 end
2851
2852 -- check if falling, flying, floating
2853 falling(self, pos)
3366
3367 self.standing_on = node_ok({
3368 x = pos.x, y = pos.y + y_level - 0.25, z = pos.z}, "air").name
3369
3370 --print("standing in " .. self.standing_in)
3371
3372 -- if standing inside solid block then jump to escape
3373 if minetest.registered_nodes[self.standing_in].walkable
3374 and minetest.registered_nodes[self.standing_in].drawtype
3375 == "normal" then
3376
3377 self.object:set_velocity({
3378 x = 0,
3379 y = self.jump_height,
3380 z = 0
3381 })
3382 end
3383
3384 -- check and stop if standing at cliff and fear of heights
3385 self.at_cliff = self:is_at_cliff()
3386
3387 if self.at_cliff then
3388 self:set_velocity(0)
3389 end
3390
3391 -- has mob expired (0.25 instead of dtime since were in a timer)
3392 self:mob_expire(pos, 0.25)
3393 end
3394
3395 -- check if falling, flying, floating and return if player died
3396 if self:falling(pos) then
3397 return
3398 end
28543399
28553400 -- smooth rotation by ThomasMonroe314
2856
28573401 if self.delay and self.delay > 0 then
2858
2859 local yaw = self.object:get_yaw()
28603402
28613403 if self.delay == 1 then
28623404 yaw = self.target_yaw
28903432 self.object:set_yaw(yaw)
28913433 end
28923434
2893 -- end rotation
2894
28953435 -- knockback timer
28963436 if self.pause_timer > 0 then
28973437
29043444 if self.do_custom then
29053445
29063446 -- when false skip going any further
2907 if self.do_custom(self, dtime) == false then
3447 if self:do_custom(dtime) == false then
29083448 return
29093449 end
29103450 end
29273467 end
29283468
29293469 -- mob plays random sound at times
2930 if random(1, 100) == 1 then
2931 mob_sound(self, self.sounds.random)
3470 if random(100) == 1 then
3471 self:mob_sound(self.sounds.random)
29323472 end
29333473
29343474 -- environmental damage timer (every 1 second)
29403480 self.env_damage_timer = 0
29413481
29423482 -- check for environmental damage (water, fire, lava etc.)
2943 do_env_damage(self)
3483 if self:do_env_damage() then return end
29443484
29453485 -- node replace check (cow eats grass etc.)
2946 replace(self, pos)
2947 end
2948
2949 general_attack(self)
2950
2951 breed(self)
2952
2953 follow_flop(self)
2954
2955 do_states(self, dtime)
2956
2957 do_jump(self)
2958
2959 runaway_from(self)
2960
3486 self:replace(pos)
3487 end
3488
3489 self:general_attack()
3490
3491 self:breed()
3492
3493 self:follow_flop()
3494
3495 if self:do_states(dtime) then return end
3496
3497 self:do_jump()
3498
3499 self:do_runaway_from(self)
3500
3501 self:do_stay_near()
29613502 end
29623503
29633504
29643505 -- default function when mobs are blown up with TNT
2965 local do_tnt = function(obj, damage)
2966
2967 --print ("----- Damage", damage)
2968
2969 obj.object:punch(obj.object, 1.0, {
3506 function mob_class:on_blast(damage)
3507
3508 --print("-- blast damage", damage)
3509
3510 self.object:punch(self.object, 1.0, {
29703511 full_punch_interval = 1.0,
29713512 damage_groups = {fleshy = damage},
29723513 }, nil)
29733514
2974 return false, true, {}
3515 -- return no damage, no knockback, no item drops, mob api handles all
3516 return false, false, {}
29753517 end
29763518
29773519
29803522 -- register mob entity
29813523 function mobs:register_mob(name, def)
29823524
2983 mobs.spawning_mobs[name] = true
2984
2985 minetest.register_entity(name, {
2986
2987 stepheight = def.stepheight or 1.1, -- was 0.6
3525 mobs.spawning_mobs[name] = {}
3526
3527 local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
3528
3529 -- quick fix to stop mobs glitching through nodes if too small
3530 if -collisionbox[2] + collisionbox[5] < 1.01 then
3531 collisionbox[5] = collisionbox[2] + 0.99
3532 end
3533
3534 minetest.register_entity(name, setmetatable({
3535
3536 stepheight = def.stepheight,
29883537 name = name,
29893538 type = def.type,
29903539 attack_type = def.attack_type,
29913540 fly = def.fly,
2992 fly_in = def.fly_in or "air",
2993 owner = def.owner or "",
2994 order = def.order or "",
3541 fly_in = def.fly_in,
3542 keep_flying = def.keep_flying,
3543 owner = def.owner,
3544 order = def.order,
29953545 on_die = def.on_die,
3546 on_flop = def.on_flop,
29963547 do_custom = def.do_custom,
2997 jump_height = def.jump_height or 4, -- was 6
3548 jump_height = def.jump_height,
29983549 drawtype = def.drawtype, -- DEPRECATED, use rotate instead
2999 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
3000 lifetimer = def.lifetimer or 180, -- 3 minutes
3550 rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2
3551 glow = def.glow,
3552 lifetimer = def.lifetimer,
30013553 hp_min = max(1, (def.hp_min or 5) * difficulty),
30023554 hp_max = max(1, (def.hp_max or 10) * difficulty),
3003 physical = true,
3004 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3005 selectionbox = def.selectionbox or def.collisionbox,
3555 collisionbox = collisionbox, --def.collisionbox,
3556 selectionbox = def.selectionbox or collisionbox, --def.collisionbox,
30063557 visual = def.visual,
3007 visual_size = def.visual_size or {x = 1, y = 1},
3558 visual_size = def.visual_size,
30083559 mesh = def.mesh,
3009 makes_footstep_sound = def.makes_footstep_sound or false,
3010 view_range = def.view_range or 5,
3011 walk_velocity = def.walk_velocity or 1,
3012 run_velocity = def.run_velocity or 2,
3560 makes_footstep_sound = def.makes_footstep_sound,
3561 view_range = def.view_range,
3562 walk_velocity = def.walk_velocity,
3563 run_velocity = def.run_velocity,
30133564 damage = max(0, (def.damage or 0) * difficulty),
3014 light_damage = def.light_damage or 0,
3015 light_damage_min = def.light_damage_min or 14,
3016 light_damage_max = def.light_damage_max or 15,
3017 water_damage = def.water_damage or 0,
3018 lava_damage = def.lava_damage or 0,
3019 suffocation = def.suffocation or 2,
3020 fall_damage = def.fall_damage or 1,
3021 fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
3022 drops = def.drops or {},
3023 armor = def.armor or 100,
3565 damage_group = def.damage_group,
3566 damage_texture_modifier = def.damage_texture_modifier,
3567 light_damage = def.light_damage,
3568 light_damage_min = def.light_damage_min,
3569 light_damage_max = def.light_damage_max,
3570 water_damage = def.water_damage,
3571 lava_damage = def.lava_damage,
3572 fire_damage = def.fire_damage,
3573 air_damage = def.air_damage,
3574 suffocation = def.suffocation,
3575 fall_damage = def.fall_damage,
3576 fall_speed = def.fall_speed,
3577 drops = def.drops,
3578 armor = def.armor,
30243579 on_rightclick = def.on_rightclick,
30253580 arrow = def.arrow,
3581 arrow_override = def.arrow_override,
30263582 shoot_interval = def.shoot_interval,
3027 sounds = def.sounds or {},
3583 sounds = def.sounds,
30283584 animation = def.animation,
30293585 follow = def.follow,
3030 jump = def.jump ~= false,
3031 walk_chance = def.walk_chance or 50,
3032 attack_chance = def.attack_chance or 5,
3033 passive = def.passive or false,
3034 knock_back = def.knock_back ~= false,
3035 blood_amount = def.blood_amount or 5,
3036 blood_texture = def.blood_texture or "mobs_blood.png",
3037 shoot_offset = def.shoot_offset or 0,
3038 floats = def.floats or 1, -- floats in water by default
3586 jump = def.jump,
3587 walk_chance = def.walk_chance,
3588 stand_chance = def.stand_chance,
3589 attack_chance = def.attack_chance,
3590 passive = def.passive,
3591 knock_back = def.knock_back,
3592 blood_amount = def.blood_amount,
3593 blood_texture = def.blood_texture,
3594 shoot_offset = def.shoot_offset,
3595 floats = def.floats,
30393596 replace_rate = def.replace_rate,
30403597 replace_what = def.replace_what,
30413598 replace_with = def.replace_with,
3042 replace_offset = def.replace_offset or 0,
3599 replace_offset = def.replace_offset,
30433600 on_replace = def.on_replace,
3044 timer = 0,
3045 env_damage_timer = 0, -- only used when state = "attack"
3046 tamed = false,
3047 pause_timer = 0,
3048 horny = false,
3049 hornytimer = 0,
3050 child = false,
3051 gotten = false,
3052 health = 0,
3053 reach = def.reach or 3,
3054 htimer = 0,
3601 reach = def.reach,
30553602 texture_list = def.textures,
3603 texture_mods = def.texture_mods or "",
30563604 child_texture = def.child_texture,
3057 docile_by_day = def.docile_by_day or false,
3058 time_of_day = 0.5,
3059 fear_height = def.fear_height or 0,
3605 docile_by_day = def.docile_by_day,
3606 fear_height = def.fear_height,
30603607 runaway = def.runaway,
3061 runaway_timer = 0,
30623608 pathfinding = def.pathfinding,
3063 immune_to = def.immune_to or {},
3609 immune_to = def.immune_to,
30643610 explosion_radius = def.explosion_radius,
30653611 explosion_damage_radius = def.explosion_damage_radius,
3066 explosion_timer = def.explosion_timer or 3,
3067 allow_fuse_reset = def.allow_fuse_reset ~= false,
3068 stop_to_explode = def.stop_to_explode ~= false,
3612 explosion_timer = def.explosion_timer,
3613 allow_fuse_reset = def.allow_fuse_reset,
3614 stop_to_explode = def.stop_to_explode,
30693615 custom_attack = def.custom_attack,
30703616 double_melee_attack = def.double_melee_attack,
30713617 dogshoot_switch = def.dogshoot_switch,
3072 dogshoot_count = 0,
3073 dogshoot_count_max = def.dogshoot_count_max or 5,
3074 dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
3075 group_attack = def.group_attack or false,
3076 attack_monsters = def.attacks_monsters or def.attack_monsters or false,
3077 attack_animals = def.attack_animals or false,
3078 attack_players = def.attack_players ~= false,
3079 attack_npcs = def.attack_npcs ~= false,
3618 dogshoot_count_max = def.dogshoot_count_max,
3619 dogshoot_count2_max = def.dogshoot_count2_max or def.dogshoot_count_max,
3620 group_attack = def.group_attack,
3621 group_helper = def.group_helper,
3622 attack_monsters = def.attacks_monsters or def.attack_monsters,
3623 attack_animals = def.attack_animals,
3624 attack_players = def.attack_players,
3625 attack_npcs = def.attack_npcs,
30803626 specific_attack = def.specific_attack,
30813627 runaway_from = def.runaway_from,
30823628 owner_loyal = def.owner_loyal,
3083 facing_fence = false,
30843629 pushable = def.pushable,
3085 _cmi_is_mob = true,
3630 stay_near = def.stay_near,
3631 randomly_turn = def.randomly_turn ~= false,
3632 ignore_invisibility = def.ignore_invisibility,
30863633
30873634 on_spawn = def.on_spawn,
30883635
3089 on_blast = def.on_blast or do_tnt,
3090
3091 on_step = mob_step,
3636 on_blast = def.on_blast, -- class redifinition
30923637
30933638 do_punch = def.do_punch,
30943639
3095 on_punch = mob_punch,
3096
30973640 on_breed = def.on_breed,
30983641
30993642 on_grown = def.on_grown,
31003643
31013644 on_activate = function(self, staticdata, dtime)
3102 return mob_activate(self, staticdata, def, dtime)
3645 return self:mob_activate(staticdata, def, dtime)
31033646 end,
31043647
31053648 get_staticdata = function(self)
3106 return mob_staticdata(self)
3649 return self:mob_staticdata(self)
31073650 end,
31083651
3109 })
3652 }, mob_class_meta))
31103653
31113654 end -- END mobs:register_mob function
31123655
31133656
31143657 -- count how many mobs of one type are inside an area
3658 -- will also return true for second value if player is inside area
31153659 local count_mobs = function(pos, type)
31163660
31173661 local total = 0
31183662 local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2)
31193663 local ent
3664 local players
31203665
31213666 for n = 1, #objs do
31223667
31283673 if ent and ent.name and ent.name == type then
31293674 total = total + 1
31303675 end
3131 end
3132 end
3133
3134 return total
3676 else
3677 players = true
3678 end
3679 end
3680
3681 return total, players
3682 end
3683
3684
3685 -- do we have enough space to spawn mob? (thanks wuzzy)
3686 local can_spawn = function(pos, name)
3687
3688 local ent = minetest.registered_entities[name]
3689 local width_x = max(1, ceil(ent.collisionbox[4] - ent.collisionbox[1]))
3690 local min_x, max_x
3691
3692 if width_x % 2 == 0 then
3693 max_x = floor(width_x / 2)
3694 min_x = -(max_x - 1)
3695 else
3696 max_x = floor(width_x / 2)
3697 min_x = -max_x
3698 end
3699
3700 local width_z = max(1, ceil(ent.collisionbox[6] - ent.collisionbox[3]))
3701 local min_z, max_z
3702
3703 if width_z % 2 == 0 then
3704 max_z = floor(width_z / 2)
3705 min_z = -(max_z - 1)
3706 else
3707 max_z = floor(width_z / 2)
3708 min_z = -max_z
3709 end
3710
3711 local max_y = max(0, ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
3712 local pos2
3713
3714 for y = 0, max_y do
3715 for x = min_x, max_x do
3716 for z = min_z, max_z do
3717
3718 pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
3719
3720 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3721 return nil
3722 end
3723 end
3724 end
3725 end
3726
3727 -- tweak X/Z spawn pos
3728 if width_x % 2 == 0 then
3729 pos.x = pos.x + 0.5
3730 end
3731
3732 if width_z % 2 == 0 then
3733 pos.z = pos.z + 0.5
3734 end
3735
3736 return pos
3737 end
3738
3739 function mobs:can_spawn(pos, name)
3740 return can_spawn(pos, name)
31353741 end
31363742
31373743
31383744 -- global functions
3745
3746 function mobs:add_mob(pos, def)
3747
3748 -- is mob actually registered?
3749 if not mobs.spawning_mobs[def.name]
3750 or not minetest.registered_entities[def.name] then
3751 --print("--- mob doesn't exist", def.name)
3752 return
3753 end
3754
3755 -- are we over active mob limit
3756 if active_limit > 0 and active_mobs >= active_limit then
3757 --print("--- active mob limit reached", active_mobs, active_limit)
3758 return
3759 end
3760
3761 -- get total number of this mob in area
3762 local num_mob, is_pla = count_mobs(pos, def.name)
3763
3764 if not is_pla then
3765 --print("--- no players within active area, will not spawn " .. def.name)
3766 return
3767 end
3768
3769 local aoc = mobs.spawning_mobs[def.name]
3770 and mobs.spawning_mobs[def.name].aoc or 1
3771
3772 if def.ignore_count ~= true and num_mob >= aoc then
3773 --print("--- too many " .. def.name .. " in area", num_mob .. "/" .. aoc)
3774 return
3775 end
3776
3777 local mob = minetest.add_entity(pos, def.name)
3778
3779 --print("[mobs] Spawned " .. def.name .. " at " .. minetest.pos_to_string(pos))
3780
3781 local ent = mob:get_luaentity()
3782
3783 if not ent then
3784 --print("[mobs] entity not found " .. def.name)
3785 return false
3786 end
3787
3788 if def.child then
3789
3790 local textures = ent.base_texture
3791
3792 -- using specific child texture (if found)
3793 if ent.child_texture then
3794 textures = ent.child_texture[1]
3795 end
3796
3797 -- and resize to half height
3798 mob:set_properties({
3799 textures = textures,
3800 visual_size = {
3801 x = ent.base_size.x * .5,
3802 y = ent.base_size.y * .5
3803 },
3804 collisionbox = {
3805 ent.base_colbox[1] * .5,
3806 ent.base_colbox[2] * .5,
3807 ent.base_colbox[3] * .5,
3808 ent.base_colbox[4] * .5,
3809 ent.base_colbox[5] * .5,
3810 ent.base_colbox[6] * .5
3811 },
3812 selectionbox = {
3813 ent.base_selbox[1] * .5,
3814 ent.base_selbox[2] * .5,
3815 ent.base_selbox[3] * .5,
3816 ent.base_selbox[4] * .5,
3817 ent.base_selbox[5] * .5,
3818 ent.base_selbox[6] * .5
3819 },
3820 })
3821
3822 ent.child = true
3823 end
3824
3825 if def.owner then
3826 ent.tamed = true
3827 ent.owner = def.owner
3828 end
3829
3830 if def.nametag then
3831
3832 -- limit name entered to 64 characters long
3833 if def.nametag:len() > 64 then
3834 def.nametag = def.nametag:sub(1, 64)
3835 end
3836
3837 ent.nametag = def.nametag
3838
3839 ent:update_tag()
3840 end
3841
3842 return ent
3843 end
3844
31393845
31403846 function mobs:spawn_abm_check(pos, node, name)
31413847 -- global function to add additional spawn checks
31433849 end
31443850
31453851
3146 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3147 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3852 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
3853 chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load)
31483854
31493855 -- Do mobs spawn at all?
3150 if not mobs_spawn then
3856 if not mobs_spawn or not mobs.spawning_mobs[name] then
3857 --print ("--- spawning not registered for " .. name)
31513858 return
31523859 end
31533860
31543861 -- chance/spawn number override in minetest.conf for registered mob
3155 local numbers = minetest.settings:get(name)
3862 local numbers = settings:get(name)
31563863
31573864 if numbers then
3865
31583866 numbers = numbers:split(",")
31593867 chance = tonumber(numbers[1]) or chance
31603868 aoc = tonumber(numbers[2]) or aoc
31613869
31623870 if chance == 0 then
3163 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3871
3872 minetest.log("warning",
3873 string.format("[mobs] %s has spawning disabled", name))
31643874 return
31653875 end
31663876
3167 minetest.log("action",
3168 string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
3169
3170 end
3171
3172 minetest.register_abm({
3173
3174 label = name .. " spawning",
3175 nodenames = nodes,
3176 neighbors = neighbors,
3177 interval = interval,
3178 chance = max(1, (chance * mob_chance_multiplier)),
3179 catch_up = false,
3180
3181 action = function(pos, node, active_object_count, active_object_count_wider)
3182
3183 -- is mob actually registered?
3184 if not mobs.spawning_mobs[name]
3185 or not minetest.registered_entities[name] then
3186 --print ("--- mob doesn't exist", name)
3187 return
3188 end
3189
3190 -- additional custom checks for spawning mob
3191 if mobs:spawn_abm_check(pos, node, name) == true then
3192 return
3193 end
3194
3195 -- do not spawn if too many entities in area
3196 if active_object_count_wider >= max_per_block then
3877 minetest.log("action", string.format(
3878 "[mobs] Chance setting for %s changed to %s (total: %s)",
3879 name, chance, aoc))
3880 end
3881
3882 mobs.spawning_mobs[name].aoc = aoc
3883
3884 local spawn_action = function(pos, node, active_object_count,
3885 active_object_count_wider)
3886
3887 -- use instead of abm's chance setting when using lbm
3888 if map_load and random(max(1, (chance * mob_chance_multiplier))) > 1 then
3889 return
3890 end
3891
3892 -- use instead of abm's neighbor setting when using lbm
3893 if map_load and not minetest.find_node_near(pos, 1, neighbors) then
3894 --print("--- lbm neighbors not found")
3895 return
3896 end
3897
3898 -- is mob actually registered?
3899 if not mobs.spawning_mobs[name]
3900 or not minetest.registered_entities[name] then
3901 --print("--- mob doesn't exist", name)
3902 return
3903 end
3904
3905 -- are we over active mob limit
3906 if active_limit > 0 and active_mobs >= active_limit then
3907 --print("--- active mob limit reached", active_mobs, active_limit)
3908 return
3909 end
3910
3911 -- additional custom checks for spawning mob
3912 if mobs:spawn_abm_check(pos, node, name) == true then
3913 return
3914 end
3915
3916 -- do not spawn if too many entities in area
3917 if active_object_count_wider
3918 and active_object_count_wider >= max_per_block then
31973919 --print("--- too many entities in area", active_object_count_wider)
3198 return
3199 end
3200
3201 -- get total number of this mob in area
3202 local num_mob = count_mobs(pos, name)
3203
3204 if num_mob >= aoc then
3205 --print ("--- too many " .. name .. " in area", num_mob .. "/" .. aoc)
3206 return
3207 end
3920 return
3921 end
3922
3923 -- get total number of this mob in area
3924 local num_mob, is_pla = count_mobs(pos, name)
3925
3926 if not is_pla then
3927 --print("--- no players within active area, will not spawn " .. name)
3928 return
3929 end
3930
3931 if num_mob >= aoc then
3932 --print("--- too many " .. name .. " in area", num_mob .. "/" .. aoc)
3933 return
3934 end
32083935
32093936 -- if toggle set to nil then ignore day/night check
3210 if day_toggle ~= nil then
3211
3212 local tod = (minetest.get_timeofday() or 0) * 24000
3213
3214 if tod > 4500 and tod < 19500 then
3215 -- daylight, but mob wants night
3216 if day_toggle == false then
3217 --print ("--- mob needs night", name)
3218 return
3219 end
3220 else
3221 -- night time but mob wants day
3222 if day_toggle == true then
3223 --print ("--- mob needs day", name)
3224 return
3225 end
3226 end
3227 end
3228
3229 -- spawn above node
3230 pos.y = pos.y + 1
3231
3232 -- are we spawning within height limits?
3233 if pos.y > max_height
3234 or pos.y < min_height then
3235 --print ("--- height limits not met", name, pos.y)
3236 return
3237 end
3238
3239 -- are light levels ok?
3240 local light = minetest.get_node_light(pos)
3241 if not light
3242 or light > max_light
3243 or light < min_light then
3244 --print ("--- light limits not met", name, light)
3245 return
3246 end
3247
3248 -- only spawn away from player
3249 local objs = minetest.get_objects_inside_radius(pos, 10)
3250
3251 for n = 1, #objs do
3252
3253 if objs[n]:is_player() then
3254 --print ("--- player too close", name)
3937 if day_toggle ~= nil then
3938
3939 local tod = (minetest.get_timeofday() or 0) * 24000
3940
3941 if tod > 4500 and tod < 19500 then
3942 -- daylight, but mob wants night
3943 if day_toggle == false then
3944 --print("--- mob needs night", name)
32553945 return
32563946 end
3257 end
3947 else
3948 -- night time but mob wants day
3949 if day_toggle == true then
3950 --print("--- mob needs day", name)
3951 return
3952 end
3953 end
3954 end
3955
3956 -- spawn above node
3957 pos.y = pos.y + 1
3958
3959 -- are we spawning within height limits?
3960 if pos.y > max_height
3961 or pos.y < min_height then
3962 --print("--- height limits not met", name, pos.y)
3963 return
3964 end
3965
3966 -- are light levels ok?
3967 local light = minetest.get_node_light(pos)
3968 if not light
3969 or light > max_light
3970 or light < min_light then
3971 --print("--- light limits not met", name, light)
3972 return
3973 end
3974
3975 -- check if mob can spawn inside protected areas
3976 if (spawn_protected == false
3977 or (spawn_monster_protected == false
3978 and minetest.registered_entities[name].type == "monster"))
3979 and minetest.is_protected(pos, "") then
3980 --print("--- inside protected area", name)
3981 return
3982 end
3983
3984 -- only spawn a set distance away from player
3985 local objs = minetest.get_objects_inside_radius(pos, mob_nospawn_range)
3986
3987 for n = 1, #objs do
3988
3989 if objs[n]:is_player() then
3990 --print("--- player too close", name)
3991 return
3992 end
3993 end
3994
3995 local ent = minetest.registered_entities[name]
3996
3997 -- should we check mob area for obstructions ?
3998 if mob_area_spawn ~= true then
32583999
32594000 -- do we have enough height clearance to spawn mob?
3260 local ent = minetest.registered_entities[name]
3261 local height = max(1, math.ceil(
3262 (ent.collisionbox[5] or 0.25) -
3263 (ent.collisionbox[2] or -0.25) - 1))
3264
3265 for n = 0, height do
4001 local height = max(0, ent.collisionbox[5] - ent.collisionbox[2])
4002
4003 for n = 0, floor(height) do
32664004
32674005 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
32684006
32714009 return
32724010 end
32734011 end
3274
3275 -- mobs cannot spawn in protected areas when enabled
3276 if not spawn_protected
3277 and minetest.is_protected(pos, "") then
3278 --print ("--- inside protected area", name)
3279 return
3280 end
3281
3282 -- spawn mob half block higher than ground
3283 pos.y = pos.y + 0.5
4012 else
4013 -- returns position if we have enough space to spawn mob
4014 pos = can_spawn(pos, name)
4015 end
4016
4017 if pos then
4018
4019 -- adjust for mob collision box
4020 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
32844021
32854022 local mob = minetest.add_entity(pos, name)
3286 --[[
3287 print ("[mobs] Spawned " .. name .. " at "
3288 .. minetest.pos_to_string(pos) .. " on "
3289 .. node.name .. " near " .. neighbors[1])
3290 ]]
4023
4024 -- print("[mobs] Spawned " .. name .. " at "
4025 -- .. minetest.pos_to_string(pos) .. " on "
4026 -- .. node.name .. " near " .. neighbors[1])
4027
32914028 if on_spawn then
3292
3293 local ent = mob:get_luaentity()
3294
3295 on_spawn(ent, pos)
3296 end
3297 end
3298 })
4029 on_spawn(mob:get_luaentity(), pos)
4030 end
4031 else
4032 --print("--- not enough space to spawn", name)
4033 end
4034 end
4035
4036
4037 -- are we registering an abm or lbm?
4038 if map_load == true then
4039
4040 minetest.register_lbm({
4041 name = name .. "_spawning",
4042 label = name .. " spawning",
4043 nodenames = nodes,
4044 run_at_every_load = false,
4045
4046 action = function(pos, node)
4047 spawn_action(pos, node)
4048 end
4049 })
4050
4051 else
4052
4053 minetest.register_abm({
4054 label = name .. " spawning",
4055 nodenames = nodes,
4056 neighbors = neighbors,
4057 interval = interval,
4058 chance = max(1, (chance * mob_chance_multiplier)),
4059 catch_up = false,
4060
4061 action = function(pos, node, active_object_count, active_object_count_wider)
4062 spawn_action(pos, node, active_object_count, active_object_count_wider)
4063 end
4064 })
4065 end
32994066 end
33004067
33014068
33084075 end
33094076
33104077
3311 -- MarkBu's spawn function
4078 -- MarkBu's spawn function (USE this one please)
33124079 function mobs:spawn(def)
33134080
33144081 mobs:spawn_specific(
33234090 def.min_height or -31000,
33244091 def.max_height or 31000,
33254092 def.day_toggle,
3326 def.on_spawn
3327 )
4093 def.on_spawn,
4094 def.on_map_load)
33284095 end
33294096
33304097
33354102
33364103 minetest.register_entity(name, {
33374104
3338 physical = false,
4105 physical = def.physical or false,
4106 collide_with_objects = def.collide_with_objects or false,
4107 static_save = false,
4108
33394109 visual = def.visual,
33404110 visual_size = def.visual_size,
33414111 textures = def.textures,
33434113 hit_player = def.hit_player,
33444114 hit_node = def.hit_node,
33454115 hit_mob = def.hit_mob,
4116 hit_object = def.hit_object,
33464117 drop = def.drop or false, -- drops arrow as registered item when true
3347 collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
4118 collisionbox = def.collisionbox or {-.1, -.1, -.1, .1, .1, .1},
33484119 timer = 0,
4120 lifetime = def.lifetime or 4.5,
33494121 switch = 0,
33504122 owner_id = def.owner_id,
33514123 rotate = def.rotate,
33544126
33554127 on_activate = def.on_activate,
33564128
3357 on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir)
4129 on_punch = def.on_punch or function(
4130 self, hitter, tflp, tool_capabilities, dir)
33584131 end,
33594132
33604133 on_step = def.on_step or function(self, dtime)
33614134
3362 self.timer = self.timer + 1
4135 self.timer = self.timer + dtime
33634136
33644137 local pos = self.object:get_pos()
33654138
3366 if self.switch == 0
3367 or self.timer > 150 then
3368
3369 self.object:remove() ; -- print ("removed arrow")
4139 if self.switch == 0 or self.timer > self.lifetime then
4140
4141 self.object:remove() ; -- print("removed arrow")
33704142
33714143 return
33724144 end
33734145
33744146 -- does arrow have a tail (fireball)
3375 if def.tail
3376 and def.tail == 1
3377 and def.tail_texture then
4147 if def.tail and def.tail == 1 and def.tail_texture then
33784148
33794149 minetest.add_particle({
33804150 pos = pos,
33844154 collisiondetection = false,
33854155 texture = def.tail_texture,
33864156 size = def.tail_size or 5,
3387 glow = def.glow or 0,
4157 glow = def.glow or 0
33884158 })
33894159 end
33904160
33944164
33954165 if minetest.registered_nodes[node].walkable then
33964166
3397 self.hit_node(self, pos, node)
4167 self:hit_node(pos, node)
33984168
33994169 if self.drop == true then
34004170
34024172
34034173 self.lastpos = (self.lastpos or pos)
34044174
3405 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
4175 minetest.add_item(self.lastpos,
4176 self.object:get_luaentity().name)
34064177 end
34074178
3408 self.object:remove() ; -- print ("hit node")
4179 self.object:remove() ; -- print("hit node")
34094180
34104181 return
34114182 end
34124183 end
34134184
3414 if self.hit_player or self.hit_mob then
3415
3416 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
3417
3418 if self.hit_player
3419 and player:is_player() then
3420
3421 self.hit_player(self, player)
3422 self.object:remove() ; -- print ("hit player")
4185 if self.hit_player or self.hit_mob or self.hit_object then
4186
4187 for _,player in pairs(
4188 minetest.get_objects_inside_radius(pos, 1.0)) do
4189
4190 if self.hit_player and player:is_player() then
4191
4192 self:hit_player(player)
4193
4194 self.object:remove() ; -- print("hit player")
4195
34234196 return
34244197 end
34254198
34314204 and tostring(player) ~= self.owner_id
34324205 and entity.name ~= self.object:get_luaentity().name then
34334206
3434 self.hit_mob(self, player)
3435
3436 self.object:remove() ; --print ("hit mob")
4207 self:hit_mob(player)
4208
4209 self.object:remove() ; --print("hit mob")
4210
4211 return
4212 end
4213
4214 if entity
4215 and self.hit_object
4216 and (not entity._cmi_is_mob)
4217 and tostring(player) ~= self.owner_id
4218 and entity.name ~= self.object:get_luaentity().name then
4219
4220 self:hit_object(player)
4221
4222 self.object:remove() ; -- print("hit object")
34374223
34384224 return
34394225 end
34484234
34494235 -- compatibility function
34504236 function mobs:explosion(pos, radius)
3451
3452 local self = {sounds = {explode = "tnt_explode"}}
3453
3454 mobs:boom(self, pos, radius)
4237 mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius)
34554238 end
34564239
34574240
34624245 pos = pos,
34634246 gain = 1.0,
34644247 max_hear_distance = self.sounds and self.sounds.distance or 32
3465 })
4248 }, true)
34664249
34674250 entity_physics(pos, radius)
34684251
34814264 radius = radius,
34824265 damage_radius = radius,
34834266 sound = self.sounds and self.sounds.explode,
3484 explode_center = true,
4267 explode_center = true
34854268 })
34864269 else
34874270 mobs:safe_boom(self, pos, radius)
34994282 local grp = {spawn_egg = 1}
35004283
35014284 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3502 if creative and no_creative == true then
4285 if no_creative == true then
35034286 grp.not_in_creative_inventory = 1
35044287 end
35054288
35224305
35234306 local pos = pointed_thing.above
35244307
3525 -- am I clicking on something with existing on_rightclick function?
4308 -- does existing on_rightclick function exist?
35264309 local under = minetest.get_node(pointed_thing.under)
35274310 local def = minetest.registered_nodes[under.name]
4311
35284312 if def and def.on_rightclick then
3529 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4313
4314 return def.on_rightclick(
4315 pointed_thing.under, under, placer, itemstack)
35304316 end
35314317
35324318 if pos
35394325 pos.y = pos.y + 1
35404326
35414327 local data = itemstack:get_metadata()
3542 local mob = minetest.add_entity(pos, mob, data)
3543 local ent = mob:get_luaentity()
4328 local smob = minetest.add_entity(pos, mob, data)
4329 local ent = smob and smob:get_luaentity()
4330
4331 if not ent then return end -- sanity check
35444332
35454333 -- set owner if not a monster
35464334 if ent.type ~= "monster" then
35684356
35694357 local pos = pointed_thing.above
35704358
3571 -- am I clicking on something with existing on_rightclick function?
4359 -- does existing on_rightclick function exist?
35724360 local under = minetest.get_node(pointed_thing.under)
35734361 local def = minetest.registered_nodes[under.name]
4362
35744363 if def and def.on_rightclick then
3575 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4364
4365 return def.on_rightclick(
4366 pointed_thing.under, under, placer, itemstack)
35764367 end
35774368
35784369 if pos
35824373 return
35834374 end
35844375
4376 -- have we reached active mob limit
4377 if active_limit > 0 and active_mobs >= active_limit then
4378 minetest.chat_send_player(placer:get_player_name(),
4379 S("Active Mob Limit Reached!")
4380 .. " (" .. active_mobs
4381 .. " / " .. active_limit .. ")")
4382 return
4383 end
4384
35854385 pos.y = pos.y + 1
35864386
3587 local mob = minetest.add_entity(pos, mob)
3588 local ent = mob:get_luaentity()
4387 local smob = minetest.add_entity(pos, mob)
4388 local ent = smob and smob:get_luaentity()
4389
4390 if not ent then return end -- sanity check
35894391
35904392 -- don't set owner if monster or sneak pressed
35914393 if ent.type ~= "monster"
36034405 return itemstack
36044406 end,
36054407 })
3606
4408 end
4409
4410
4411 -- force capture a mob if space available in inventory, or drop as spawn egg
4412 function mobs:force_capture(self, clicker)
4413
4414 -- add special mob egg with all mob information
4415 local new_stack = ItemStack(self.name .. "_set")
4416
4417 local tmp, t = {}
4418
4419 for _,stat in pairs(self) do
4420
4421 t = type(stat)
4422
4423 if t ~= "function"
4424 and t ~= "nil"
4425 and t ~= "userdata" then
4426 tmp[_] = self[_]
4427 end
4428 end
4429
4430 local data_str = minetest.serialize(tmp)
4431
4432 new_stack:set_metadata(data_str)
4433
4434 local inv = clicker:get_inventory()
4435
4436 if inv:room_for_item("main", new_stack) then
4437 inv:add_item("main", new_stack)
4438 else
4439 minetest.add_item(clicker:get_pos(), new_stack)
4440 end
4441
4442 self:mob_sound("default_place_node_hard")
4443
4444 remove_mob(self, true)
36074445 end
36084446
36094447
36104448 -- capture critter (thanks to blert2112 for idea)
3611 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
3612 force_take, replacewith)
4449 function mobs:capture_mob(self, clicker, chance_hand, chance_net,
4450 chance_lasso, force_take, replacewith)
36134451
36144452 if self.child
36154453 or not clicker:is_player()
36364474 end
36374475
36384476 -- is mob tamed?
3639 if self.tamed == false
3640 and force_take == false then
4477 if self.tamed == false and force_take == false then
36414478
36424479 minetest.chat_send_player(name, S("Not tamed!"))
36434480
3644 return true -- false
3645 end
3646
3647 -- cannot pick up if not owner
3648 if self.owner ~= name
3649 and force_take == false then
4481 return false
4482 end
4483
4484 -- cannot pick up if not owner (unless player has protection_bypass priv)
4485 if not minetest.check_player_privs(name, "protection_bypass")
4486 and self.owner ~= name and force_take == false then
36504487
36514488 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
36524489
3653 return true -- false
4490 return false
36544491 end
36554492
36564493 if clicker:get_inventory():room_for_item("main", mobname) then
36804517 end
36814518
36824519 -- calculate chance.. add to inventory if successful?
3683 if chance > 0 and random(1, 100) <= chance then
4520 if chance and chance > 0 and random(100) <= chance then
36844521
36854522 -- default mob egg
36864523 local new_stack = ItemStack(mobname)
36914528
36924529 new_stack = ItemStack(mobname .. "_set")
36934530
3694 local tmp = {}
4531 local tmp, t = {}
36954532
36964533 for _,stat in pairs(self) do
3697 local t = type(stat)
4534
4535 t = type(stat)
4536
36984537 if t ~= "function"
36994538 and t ~= "nil"
37004539 and t ~= "userdata" then
37154554 minetest.add_item(clicker:get_pos(), new_stack)
37164555 end
37174556
3718 self.object:remove()
3719
3720 mob_sound(self, "default_place_node_hard")
3721
3722 elseif chance ~= 0 then
4557 self:mob_sound("default_place_node_hard")
4558
4559 remove_mob(self, true)
4560
4561 return new_stack
4562
4563 -- when chance above fails or set to 0, miss!
4564 elseif chance and chance ~= 0 then
4565
37234566 minetest.chat_send_player(name, S("Missed!"))
37244567
3725 mob_sound(self, "mobs_swing")
4568 self:mob_sound("mobs_swing")
4569
4570 return false
4571
4572 -- when chance is nil always return a miss (used for npc walk/follow)
4573 elseif not chance then
4574 return false
37264575 end
37274576 end
37284577
37354584
37364585 local name = clicker:get_player_name()
37374586 local tool = clicker:get_wielded_item()
3738
3739 if tool:get_name() ~= "mobs:protector" then
4587 local tool_name = tool:get_name()
4588
4589 if tool_name ~= "mobs:protector"
4590 and tool_name ~= "mobs:protector2" then
37404591 return false
37414592 end
37424593
3743 if self.tamed == false then
4594 if not self.tamed then
37444595 minetest.chat_send_player(name, S("Not tamed!"))
3745 return true -- false
3746 end
3747
3748 if self.protected == true then
4596 return true
4597 end
4598
4599 if (self.protected and tool_name == "mobs:protector")
4600 or (self.protected == 2 and tool_name == "mobs:protector2") then
37494601 minetest.chat_send_player(name, S("Already protected!"))
3750 return true -- false
4602 return true
37514603 end
37524604
37534605 if not mobs.is_creative(clicker:get_player_name()) then
37554607 clicker:set_wielded_item(tool)
37564608 end
37574609
3758 self.protected = true
4610 -- set protection level
4611 if tool_name == "mobs:protector" then
4612 self.protected = true
4613 else
4614 self.protected = 2 ; self.fire_damage = 0
4615 end
37594616
37604617 local pos = self.object:get_pos()
4618
37614619 pos.y = pos.y + self.collisionbox[2] + 0.5
37624620
3763 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3764
3765 mob_sound(self, "mobs_spell")
4621 effect(self.object:get_pos(), 25, "mobs_protect_particle.png",
4622 0.5, 4, 2, 15)
4623
4624 self:mob_sound("mobs_spell")
37664625
37674626 return true
37684627 end
37744633 -- feeding, taming and breeding (thanks blert2112)
37754634 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
37764635
3777 if not self.follow then
3778 return false
3779 end
3780
37814636 -- can eat/tame with item in hand
3782 if follow_holding(self, clicker) then
4637 if self.follow
4638 and self:follow_holding(clicker) then
37834639
37844640 -- if not in creative then take item
37854641 if not mobs.is_creative(clicker:get_player_name()) then
37984654
37994655 self.health = self.hp_max
38004656
3801 if self.htimer < 1 then
3802
3803 minetest.chat_send_player(clicker:get_player_name(),
3804 S("@1 at full health (@2)",
3805 self.name:split(":")[2], tostring(self.health)))
3806
3807 self.htimer = 5
3808 end
4657 -- if self.htimer < 1 then
4658
4659 -- minetest.chat_send_player(clicker:get_player_name(),
4660 -- S("@1 at full health (@2)",
4661 -- self.name:split(":")[2], tostring(self.health)))
4662
4663 -- self.htimer = 5
4664 -- end
38094665 end
38104666
38114667 self.object:set_hp(self.health)
38124668
3813 update_tag(self)
4669 self:update_tag()
38144670
38154671 -- make children grow quicker
38164672 if self.child == true then
38174673
3818 self.hornytimer = self.hornytimer + 20
3819
4674 -- self.hornytimer = self.hornytimer + 20
4675 -- deduct 10% of the time to adulthood
4676 self.hornytimer = self.hornytimer + (
4677 (CHILD_GROW_TIME - self.hornytimer) * 0.1)
4678 --print ("====", self.hornytimer)
38204679 return true
38214680 end
38224681
38234682 -- feed and tame
38244683 self.food = (self.food or 0) + 1
4684
38254685 if self.food >= feed_count then
38264686
38274687 self.food = 0
38294689 if breed and self.hornytimer == 0 then
38304690 self.horny = true
38314691 end
3832
3833 self.gotten = false
38344692
38354693 if tame then
38364694
38414699 end
38424700
38434701 self.tamed = true
4702 self.static_save = true
38444703
38454704 if not self.owner or self.owner == "" then
38464705 self.owner = clicker:get_player_name()
38484707 end
38494708
38504709 -- make sound when fed so many times
3851 mob_sound(self, self.sounds.random)
4710 self:mob_sound(self.sounds.random)
38524711 end
38534712
38544713 return true
38554714 end
38564715
38574716 local item = clicker:get_wielded_item()
4717 local name = clicker:get_player_name()
38584718
38594719 -- if mob has been tamed you can name it with a nametag
38604720 if item:get_name() == "mobs:nametag"
3861 and clicker:get_player_name() == self.owner then
3862
3863 local name = clicker:get_player_name()
4721 and (name == self.owner
4722 or minetest.check_player_privs(name, "protection_bypass")) then
38644723
38654724 -- store mob and nametag stack in external variables
38664725 mob_obj[name] = self
38674726 mob_sta[name] = item
38684727
38694728 local tag = self.nametag or ""
3870
3871 minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
3872 .. default.gui_bg
3873 .. default.gui_bg_img
3874 .. "field[0.5,1;7.5,0;name;"
3875 .. minetest.formspec_escape(S("Enter name:")) .. ";" .. tag .. "]"
3876 .. "button_exit[2.5,3.5;3,1;mob_rename;"
3877 .. minetest.formspec_escape(S("Rename")) .. "]")
4729 local esc = minetest.formspec_escape
4730
4731 minetest.show_formspec(name, "mobs_nametag",
4732 "size[8,4]" ..
4733 "field[0.5,1;7.5,0;name;" ..
4734 esc(S("Enter name:")) ..
4735 ";" .. tag .. "]" ..
4736 "button_exit[2.5,3.5;3,1;mob_rename;" ..
4737 esc(S("Rename")) .. "]")
4738
4739 return true
38784740 end
38794741
38804742 return false
39044766 end
39054767
39064768 -- limit name entered to 64 characters long
3907 if string.len(fields.name) > 64 then
3908 fields.name = string.sub(fields.name, 1, 64)
4769 if fields.name:len() > 64 then
4770 fields.name = fields.name:sub(1, 64)
39094771 end
39104772
39114773 -- update nametag
39124774 mob_obj[name].nametag = fields.name
39134775
3914 update_tag(mob_obj[name])
4776 mob_obj[name]:update_tag()
39154777
39164778 -- if not in creative then take item
39174779 if not mobs.is_creative(name) then
39314793 -- compatibility function for old entities to new modpack entities
39324794 function mobs:alias_mob(old_name, new_name)
39334795
4796 -- check old_name entity doesnt already exist
4797 if minetest.registered_entities[old_name] then
4798 return
4799 end
4800
39344801 -- spawn egg
39354802 minetest.register_alias(old_name, new_name)
39364803
39374804 -- entity
39384805 minetest.register_entity(":" .. old_name, {
39394806
3940 physical = false,
3941
3942 on_activate = function(self)
4807 physical = false, static_save = false,
4808
4809 on_activate = function(self, staticdata)
39434810
39444811 if minetest.registered_entities[new_name] then
3945 minetest.add_entity(self.object:get_pos(), new_name)
3946 end
3947
3948 self.object:remove()
4812
4813 minetest.add_entity(self.object:get_pos(), new_name, staticdata)
4814 end
4815
4816 remove_mob(self)
4817 end,
4818
4819 get_staticdata = function(self)
4820 return self
39494821 end
39504822 })
39514823 end
+0
-3985
api.lua_testspawn less more
0
1 -- Mobs Api
2
3 mobs = {}
4 mobs.mod = "redo"
5 mobs.version = "20180623"
6
7
8 -- Intllib
9 local MP = minetest.get_modpath(minetest.get_current_modname())
10 local S, NS = dofile(MP .. "/intllib.lua")
11 mobs.intllib = S
12
13
14 -- CMI support check
15 local use_cmi = minetest.global_exists("cmi")
16
17
18 -- Invisibility mod check
19 mobs.invis = {}
20 if minetest.global_exists("invisibility") then
21 mobs.invis = invisibility
22 end
23
24
25 -- creative check
26 local creative_mode_cache = minetest.settings:get_bool("creative_mode")
27 function mobs.is_creative(name)
28 return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
29 end
30
31
32 -- localize math functions
33 local pi = math.pi
34 local square = math.sqrt
35 local sin = math.sin
36 local cos = math.cos
37 local abs = math.abs
38 local min = math.min
39 local max = math.max
40 local atann = math.atan
41 local random = math.random
42 local floor = math.floor
43 local atan = function(x)
44 if not x or x ~= x then
45 --error("atan bassed NaN")
46 return 0
47 else
48 return atann(x)
49 end
50 end
51
52
53 -- Load settings
54 local damage_enabled = minetest.settings:get_bool("enable_damage")
55 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
56 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
57 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
58 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
59 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
60 local creative = minetest.settings:get_bool("creative_mode")
61 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
62 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
63 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
64 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
65 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
66 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
67
68 -- Peaceful mode message so players will know there are no monsters
69 if peaceful_only then
70 minetest.register_on_joinplayer(function(player)
71 minetest.chat_send_player(player:get_player_name(),
72 S("** Peaceful Mode Active - No Monsters Will Spawn"))
73 end)
74 end
75
76 -- calculate aoc range for mob count
77 local aosrb = tonumber(minetest.settings:get("active_object_send_range_blocks"))
78 local abr = tonumber(minetest.settings:get("active_block_range"))
79 local aoc_range = max(aosrb, abr) * 16
80
81 -- pathfinding settings
82 local enable_pathfinding = true
83 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
84 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
85
86 -- default nodes
87 local node_fire = "fire:basic_flame"
88 local node_permanent_flame = "fire:permanent_flame"
89 local node_ice = "default:ice"
90 local node_snowblock = "default:snowblock"
91 local node_snow = "default:snow"
92 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
93
94
95 -- play sound
96 local mob_sound = function(self, sound)
97
98 if sound then
99 minetest.sound_play(sound, {
100 object = self.object,
101 gain = 1.0,
102 max_hear_distance = self.sounds.distance
103 })
104 end
105 end
106
107
108 -- attack player/mob
109 local do_attack = function(self, player)
110
111 if self.state == "attack" then
112 return
113 end
114
115 self.attack = player
116 self.state = "attack"
117
118 if random(0, 100) < 90 then
119 mob_sound(self, self.sounds.war_cry)
120 end
121 end
122
123
124 -- move mob in facing direction
125 local set_velocity = function(self, v)
126
127 -- do not move if mob has been ordered to stay
128 if self.order == "stand" then
129 self.object:setvelocity({x = 0, y = 0, z = 0})
130 return
131 end
132
133 local yaw = (self.object:get_yaw() or 0) + self.rotate
134
135 self.object:setvelocity({
136 x = sin(yaw) * -v,
137 y = self.object:getvelocity().y,
138 z = cos(yaw) * v
139 })
140 end
141
142
143 -- calculate mob velocity
144 local get_velocity = function(self)
145
146 local v = self.object:getvelocity()
147
148 return (v.x * v.x + v.z * v.z) ^ 0.5
149 end
150
151
152 -- set and return valid yaw
153 local set_yaw = function(self, yaw, delay)
154
155 if not yaw or yaw ~= yaw then
156 yaw = 0
157 end
158
159 delay = delay or 0
160
161 if delay == 0 then
162 self.object:set_yaw(yaw)
163 return yaw
164 end
165
166 self.target_yaw = yaw
167 self.delay = delay
168
169 return self.target_yaw
170 end
171
172 -- global function to set mob yaw
173 function mobs:yaw(self, yaw, delay)
174 set_yaw(self, yaw, delay)
175 end
176
177
178 -- set defined animation
179 local set_animation = function(self, anim)
180
181 if not self.animation
182 or not anim then return end
183
184 self.animation.current = self.animation.current or ""
185
186 if anim == self.animation.current
187 or not self.animation[anim .. "_start"]
188 or not self.animation[anim .. "_end"] then
189 return
190 end
191
192 self.animation.current = anim
193
194 self.object:set_animation({
195 x = self.animation[anim .. "_start"],
196 y = self.animation[anim .. "_end"]},
197 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
198 0, self.animation[anim .. "_loop"] ~= false)
199 end
200
201
202 -- above function exported for mount.lua
203 function mobs:set_animation(self, anim)
204 set_animation(self, anim)
205 end
206
207
208 -- calculate distance
209 local get_distance = function(a, b)
210
211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
212
213 return square(x * x + y * y + z * z)
214 end
215
216
217 -- check line of sight (BrunoMine)
218 local line_of_sight = function(self, pos1, pos2, stepsize)
219
220 stepsize = stepsize or 1
221
222 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
223
224 -- normal walking and flying mobs can see you through air
225 if s == true then
226 return true
227 end
228
229 -- New pos1 to be analyzed
230 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
231
232 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
233
234 -- Checks the return
235 if r == true then return true end
236
237 -- Nodename found
238 local nn = minetest.get_node(pos).name
239
240 -- Target Distance (td) to travel
241 local td = get_distance(pos1, pos2)
242
243 -- Actual Distance (ad) traveled
244 local ad = 0
245
246 -- It continues to advance in the line of sight in search of a real
247 -- obstruction which counts as 'normal' nodebox.
248 while minetest.registered_nodes[nn]
249 and (minetest.registered_nodes[nn].walkable == false
250 or minetest.registered_nodes[nn].drawtype == "nodebox") do
251
252 -- Check if you can still move forward
253 if td < ad + stepsize then
254 return true -- Reached the target
255 end
256
257 -- Moves the analyzed pos
258 local d = get_distance(pos1, pos2)
259
260 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
261 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
262 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
263
264 -- NaN checks
265 if d == 0
266 or npos1.x ~= npos1.x
267 or npos1.y ~= npos1.y
268 or npos1.z ~= npos1.z then
269 return false
270 end
271
272 ad = ad + stepsize
273
274 -- scan again
275 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
276
277 if r == true then return true end
278
279 -- New Nodename found
280 nn = minetest.get_node(pos).name
281
282 end
283
284 return false
285 end
286
287
288 -- are we flying in what we are suppose to? (taikedz)
289 local flight_check = function(self, pos_w)
290
291 local def = minetest.registered_nodes[self.standing_in]
292
293 if not def then return false end -- nil check
294
295 if type(self.fly_in) == "string"
296 and self.standing_in == self.fly_in then
297
298 return true
299
300 elseif type(self.fly_in) == "table" then
301
302 for _,fly_in in pairs(self.fly_in) do
303
304 if self.standing_in == fly_in then
305
306 return true
307 end
308 end
309 end
310
311 -- stops mobs getting stuck inside stairs and plantlike nodes
312 if def.drawtype ~= "airlike"
313 and def.drawtype ~= "liquid"
314 and def.drawtype ~= "flowingliquid" then
315 return true
316 end
317
318 return false
319 end
320
321
322 -- custom particle effects
323 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
324
325 radius = radius or 2
326 min_size = min_size or 0.5
327 max_size = max_size or 1
328 gravity = gravity or -10
329 glow = glow or 0
330
331 minetest.add_particlespawner({
332 amount = amount,
333 time = 0.25,
334 minpos = pos,
335 maxpos = pos,
336 minvel = {x = -radius, y = -radius, z = -radius},
337 maxvel = {x = radius, y = radius, z = radius},
338 minacc = {x = 0, y = gravity, z = 0},
339 maxacc = {x = 0, y = gravity, z = 0},
340 minexptime = 0.1,
341 maxexptime = 1,
342 minsize = min_size,
343 maxsize = max_size,
344 texture = texture,
345 glow = glow,
346 })
347 end
348
349
350 -- update nametag colour
351 local update_tag = function(self)
352
353 local col = "#00FF00"
354 local qua = self.hp_max / 4
355
356 if self.health <= floor(qua * 3) then
357 col = "#FFFF00"
358 end
359
360 if self.health <= floor(qua * 2) then
361 col = "#FF6600"
362 end
363
364 if self.health <= floor(qua) then
365 col = "#FF0000"
366 end
367
368 self.object:set_properties({
369 nametag = self.nametag,
370 nametag_color = col
371 })
372
373 end
374
375
376 -- drop items
377 local item_drop = function(self, cooked)
378
379 -- no drops if disabled by setting
380 if not mobs_drop_items then return end
381
382 -- no drops for child mobs
383 if self.child then return end
384
385 local obj, item, num
386 local pos = self.object:get_pos()
387
388 self.drops = self.drops or {} -- nil check
389
390 for n = 1, #self.drops do
391
392 if random(1, self.drops[n].chance) == 1 then
393
394 num = random(self.drops[n].min or 1, self.drops[n].max or 1)
395 item = self.drops[n].name
396
397 -- cook items when true
398 if cooked then
399
400 local output = minetest.get_craft_result({
401 method = "cooking", width = 1, items = {item}})
402
403 if output and output.item and not output.item:is_empty() then
404 item = output.item:get_name()
405 end
406 end
407
408 -- add item if it exists
409 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
410
411 if obj and obj:get_luaentity() then
412
413 obj:setvelocity({
414 x = random(-10, 10) / 9,
415 y = 6,
416 z = random(-10, 10) / 9,
417 })
418 elseif obj then
419 obj:remove() -- item does not exist
420 end
421 end
422 end
423
424 self.drops = {}
425 end
426
427
428 -- check if mob is dead or only hurt
429 local check_for_death = function(self, cause, cmi_cause)
430
431 -- has health actually changed?
432 if self.health == self.old_health and self.health > 0 then
433 return
434 end
435
436 self.old_health = self.health
437
438 -- still got some health? play hurt sound
439 if self.health > 0 then
440
441 mob_sound(self, self.sounds.damage)
442
443 -- make sure health isn't higher than max
444 if self.health > self.hp_max then
445 self.health = self.hp_max
446 end
447
448 -- backup nametag so we can show health stats
449 if not self.nametag2 then
450 self.nametag2 = self.nametag or ""
451 end
452
453 if show_health
454 and (cmi_cause and cmi_cause.type == "punch") then
455
456 self.htimer = 2
457 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
458
459 update_tag(self)
460 end
461
462 return false
463 end
464
465 -- dropped cooked item if mob died in lava
466 if cause == "lava" then
467 item_drop(self, true)
468 else
469 item_drop(self, nil)
470 end
471
472 mob_sound(self, self.sounds.death)
473
474 local pos = self.object:get_pos()
475
476 -- execute custom death function
477 if self.on_die then
478
479 self.on_die(self, pos)
480
481 if use_cmi then
482 cmi.notify_die(self.object, cmi_cause)
483 end
484
485 self.object:remove()
486
487 return true
488 end
489
490 -- default death function and die animation (if defined)
491 if self.animation
492 and self.animation.die_start
493 and self.animation.die_end then
494
495 local frames = self.animation.die_end - self.animation.die_start
496 local speed = self.animation.die_speed or 15
497 local length = max(frames / speed, 0)
498
499 self.attack = nil
500 self.v_start = false
501 self.timer = 0
502 self.blinktimer = 0
503 self.passive = true
504 self.state = "die"
505 set_velocity(self, 0)
506 set_animation(self, "die")
507
508 minetest.after(length, function(self)
509
510 if use_cmi and self.object:get_luaentity() then
511 cmi.notify_die(self.object, cmi_cause)
512 end
513
514 self.object:remove()
515 end, self)
516 else
517
518 if use_cmi then
519 cmi.notify_die(self.object, cmi_cause)
520 end
521
522 self.object:remove()
523 end
524
525 effect(pos, 20, "tnt_smoke.png")
526
527 return true
528 end
529
530
531 -- check if within physical map limits (-30911 to 30927)
532 local within_limits = function(pos, radius)
533
534 if (pos.x - radius) > -30913
535 and (pos.x + radius) < 30928
536 and (pos.y - radius) > -30913
537 and (pos.y + radius) < 30928
538 and (pos.z - radius) > -30913
539 and (pos.z + radius) < 30928 then
540 return true -- within limits
541 end
542
543 return false -- beyond limits
544 end
545
546
547 -- is mob facing a cliff
548 local is_at_cliff = function(self)
549
550 if self.fear_height == 0 then -- 0 for no falling protection!
551 return false
552 end
553
554 local yaw = self.object:get_yaw()
555 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
556 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
557 local pos = self.object:get_pos()
558 local ypos = pos.y + self.collisionbox[2] -- just above floor
559
560 if minetest.line_of_sight(
561 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
562 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
563 , 1) then
564
565 return true
566 end
567
568 return false
569 end
570
571
572 -- get node but use fallback for nil or unknown
573 local node_ok = function(pos, fallback)
574
575 fallback = fallback or mobs.fallback_node
576
577 local node = minetest.get_node_or_nil(pos)
578
579 if node and minetest.registered_nodes[node.name] then
580 return node
581 end
582
583 return minetest.registered_nodes[fallback]
584 end
585
586
587 -- environmental damage (water, lava, fire, light etc.)
588 local do_env_damage = function(self)
589
590 -- feed/tame text timer (so mob 'full' messages dont spam chat)
591 if self.htimer > 0 then
592 self.htimer = self.htimer - 1
593 end
594
595 -- reset nametag after showing health stats
596 if self.htimer < 1 and self.nametag2 then
597
598 self.nametag = self.nametag2
599 self.nametag2 = nil
600
601 update_tag(self)
602 end
603
604 local pos = self.object:get_pos()
605
606 self.time_of_day = minetest.get_timeofday()
607
608 -- remove mob if beyond map limits
609 if not within_limits(pos, 0) then
610 self.object:remove()
611 return
612 end
613
614 -- bright light harms mob
615 if self.light_damage ~= 0
616 -- and pos.y > 0
617 -- and self.time_of_day > 0.2
618 -- and self.time_of_day < 0.8
619 and (minetest.get_node_light(pos) or 0) > 12 then
620
621 self.health = self.health - self.light_damage
622
623 effect(pos, 5, "tnt_smoke.png")
624
625 if check_for_death(self, "light", {type = "light"}) then return end
626 end
627 --[[
628 local y_level = self.collisionbox[2]
629
630 if self.child then
631 y_level = self.collisionbox[2] * 0.5
632 end
633
634 -- what is mob standing in?
635 pos.y = pos.y + y_level + 0.25 -- foot level
636 self.standing_in = node_ok(pos, "air").name
637 -- print ("standing in " .. self.standing_in)
638 ]]
639 -- don't fall when on ignore, just stand still
640 if self.standing_in == "ignore" then
641 self.object:setvelocity({x = 0, y = 0, z = 0})
642 end
643
644 local nodef = minetest.registered_nodes[self.standing_in]
645
646 pos.y = pos.y + 1 -- for particle effect position
647
648 -- water
649 if self.water_damage
650 and nodef.groups.water then
651
652 if self.water_damage ~= 0 then
653
654 self.health = self.health - self.water_damage
655
656 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
657
658 if check_for_death(self, "water", {type = "environment",
659 pos = pos, node = self.standing_in}) then return end
660 end
661
662 -- lava or fire
663 elseif self.lava_damage
664 and (nodef.groups.lava
665 or self.standing_in == node_fire
666 or self.standing_in == node_permanent_flame) then
667
668 if self.lava_damage ~= 0 then
669
670 self.health = self.health - self.lava_damage
671
672 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
673
674 if check_for_death(self, "lava", {type = "environment",
675 pos = pos, node = self.standing_in}) then return end
676 end
677
678 -- damage_per_second node check
679 elseif nodef.damage_per_second ~= 0 then
680
681 self.health = self.health - nodef.damage_per_second
682
683 effect(pos, 5, "tnt_smoke.png")
684
685 if check_for_death(self, "dps", {type = "environment",
686 pos = pos, node = self.standing_in}) then return end
687 end
688 --[[
689 --- suffocation inside solid node
690 if self.suffocation ~= 0
691 and nodef.walkable == true
692 and nodef.groups.disable_suffocation ~= 1
693 and nodef.drawtype == "normal" then
694
695 self.health = self.health - self.suffocation
696
697 if check_for_death(self, "suffocation", {type = "environment",
698 pos = pos, node = self.standing_in}) then return end
699 end
700 ]]
701 check_for_death(self, "", {type = "unknown"})
702 end
703
704
705 -- jump if facing a solid node (not fences or gates)
706 local do_jump = function(self)
707
708 if not self.jump
709 or self.jump_height == 0
710 or self.fly
711 or self.child
712 or self.order == "stand" then
713 return false
714 end
715
716 self.facing_fence = false
717
718 -- something stopping us while moving?
719 if self.state ~= "stand"
720 and get_velocity(self) > 0.5
721 and self.object:getvelocity().y ~= 0 then
722 return false
723 end
724
725 local pos = self.object:get_pos()
726 local yaw = self.object:get_yaw()
727
728 -- what is mob standing on?
729 pos.y = pos.y + self.collisionbox[2] - 0.2
730
731 local nod = node_ok(pos)
732
733 --print ("standing on:", nod.name, pos.y)
734
735 if minetest.registered_nodes[nod.name].walkable == false then
736 return false
737 end
738
739 -- where is front
740 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
741 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
742
743 -- what is in front of mob?
744 local nod = node_ok({
745 x = pos.x + dir_x,
746 y = pos.y + 0.5,
747 z = pos.z + dir_z
748 })
749
750 -- thin blocks that do not need to be jumped
751 if nod.name == node_snow then
752 return false
753 end
754
755 --print ("in front:", nod.name, pos.y + 0.5)
756
757 if self.walk_chance == 0
758 or minetest.registered_items[nod.name].walkable then
759
760 if not nod.name:find("fence")
761 and not nod.name:find("gate") then
762
763 local v = self.object:getvelocity()
764
765 v.y = self.jump_height
766
767 set_animation(self, "jump") -- only when defined
768
769 self.object:setvelocity(v)
770
771 -- when in air move forward
772 minetest.after(0.3, function(self, v)
773
774 if self.object:get_luaentity() then
775
776 self.object:set_acceleration({
777 x = v.x * 2,--1.5,
778 y = 0,
779 z = v.z * 2,--1.5
780 })
781 end
782 end, self, v)
783
784 if get_velocity(self) > 0 then
785 mob_sound(self, self.sounds.jump)
786 end
787 else
788 self.facing_fence = true
789 end
790
791 return true
792 end
793
794 return false
795 end
796
797
798 -- blast damage to entities nearby (modified from TNT mod)
799 local entity_physics = function(pos, radius)
800
801 radius = radius * 2
802
803 local objs = minetest.get_objects_inside_radius(pos, radius)
804 local obj_pos, dist
805
806 for n = 1, #objs do
807
808 obj_pos = objs[n]:get_pos()
809
810 dist = get_distance(pos, obj_pos)
811 if dist < 1 then dist = 1 end
812
813 local damage = floor((4 / dist) * radius)
814 local ent = objs[n]:get_luaentity()
815
816 -- punches work on entities AND players
817 objs[n]:punch(objs[n], 1.0, {
818 full_punch_interval = 1.0,
819 damage_groups = {fleshy = damage},
820 }, pos)
821 end
822 end
823
824
825 -- should mob follow what I'm holding ?
826 local follow_holding = function(self, clicker)
827
828 if mobs.invis[clicker:get_player_name()] then
829 return false
830 end
831
832 local item = clicker:get_wielded_item()
833 local t = type(self.follow)
834
835 -- single item
836 if t == "string"
837 and item:get_name() == self.follow then
838 return true
839
840 -- multiple items
841 elseif t == "table" then
842
843 for no = 1, #self.follow do
844
845 if self.follow[no] == item:get_name() then
846 return true
847 end
848 end
849 end
850
851 return false
852 end
853
854
855 -- find two animals of same type and breed if nearby and horny
856 local breed = function(self)
857
858 -- child takes 240 seconds before growing into adult
859 if self.child == true then
860
861 self.hornytimer = self.hornytimer + 1
862
863 if self.hornytimer > 240 then
864
865 self.child = false
866 self.hornytimer = 0
867
868 self.object:set_properties({
869 textures = self.base_texture,
870 mesh = self.base_mesh,
871 visual_size = self.base_size,
872 collisionbox = self.base_colbox,
873 selectionbox = self.base_selbox,
874 })
875
876 -- custom function when child grows up
877 if self.on_grown then
878 self.on_grown(self)
879 else
880 -- jump when fully grown so as not to fall into ground
881 self.object:setvelocity({
882 x = 0,
883 y = self.jump_height,
884 z = 0
885 })
886 end
887 end
888
889 return
890 end
891
892 -- horny animal can mate for 40 seconds,
893 -- afterwards horny animal cannot mate again for 200 seconds
894 if self.horny == true
895 and self.hornytimer < 240 then
896
897 self.hornytimer = self.hornytimer + 1
898
899 if self.hornytimer >= 240 then
900 self.hornytimer = 0
901 self.horny = false
902 end
903 end
904
905 -- find another same animal who is also horny and mate if nearby
906 if self.horny == true
907 and self.hornytimer <= 40 then
908
909 local pos = self.object:get_pos()
910
911 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
912
913 local objs = minetest.get_objects_inside_radius(pos, 3)
914 local num = 0
915 local ent = nil
916
917 for n = 1, #objs do
918
919 ent = objs[n]:get_luaentity()
920
921 -- check for same animal with different colour
922 local canmate = false
923
924 if ent then
925
926 if ent.name == self.name then
927 canmate = true
928 else
929 local entname = string.split(ent.name,":")
930 local selfname = string.split(self.name,":")
931
932 if entname[1] == selfname[1] then
933 entname = string.split(entname[2],"_")
934 selfname = string.split(selfname[2],"_")
935
936 if entname[1] == selfname[1] then
937 canmate = true
938 end
939 end
940 end
941 end
942
943 if ent
944 and canmate == true
945 and ent.horny == true
946 and ent.hornytimer <= 40 then
947 num = num + 1
948 end
949
950 -- found your mate? then have a baby
951 if num > 1 then
952
953 self.hornytimer = 41
954 ent.hornytimer = 41
955
956 -- spawn baby
957 minetest.after(5, function(self, ent)
958
959 if not self.object:get_luaentity() then
960 return
961 end
962
963 -- custom breed function
964 if self.on_breed then
965
966 -- when false skip going any further
967 if self.on_breed(self, ent) == false then
968 return
969 end
970 else
971 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
972 end
973
974 local mob = minetest.add_entity(pos, self.name)
975 local ent2 = mob:get_luaentity()
976 local textures = self.base_texture
977
978 -- using specific child texture (if found)
979 if self.child_texture then
980 textures = self.child_texture[1]
981 end
982
983 -- and resize to half height
984 mob:set_properties({
985 textures = textures,
986 visual_size = {
987 x = self.base_size.x * .5,
988 y = self.base_size.y * .5,
989 },
990 collisionbox = {
991 self.base_colbox[1] * .5,
992 self.base_colbox[2] * .5,
993 self.base_colbox[3] * .5,
994 self.base_colbox[4] * .5,
995 self.base_colbox[5] * .5,
996 self.base_colbox[6] * .5,
997 },
998 selectionbox = {
999 self.base_selbox[1] * .5,
1000 self.base_selbox[2] * .5,
1001 self.base_selbox[3] * .5,
1002 self.base_selbox[4] * .5,
1003 self.base_selbox[5] * .5,
1004 self.base_selbox[6] * .5,
1005 },
1006 })
1007 -- tamed and owned by parents' owner
1008 ent2.child = true
1009 ent2.tamed = true
1010 ent2.owner = self.owner
1011 end, self, ent)
1012
1013 num = 0
1014
1015 break
1016 end
1017 end
1018 end
1019 end
1020
1021
1022 -- find and replace what mob is looking for (grass, wheat etc.)
1023 local replace = function(self, pos)
1024
1025 if not mobs_griefing
1026 or not self.replace_rate
1027 or not self.replace_what
1028 or self.child == true
1029 or self.object:getvelocity().y ~= 0
1030 or random(1, self.replace_rate) > 1 then
1031 return
1032 end
1033
1034 local what, with, y_offset
1035
1036 if type(self.replace_what[1]) == "table" then
1037
1038 local num = random(#self.replace_what)
1039
1040 what = self.replace_what[num][1] or ""
1041 with = self.replace_what[num][2] or ""
1042 y_offset = self.replace_what[num][3] or 0
1043 else
1044 what = self.replace_what
1045 with = self.replace_with or ""
1046 y_offset = self.replace_offset or 0
1047 end
1048
1049 pos.y = pos.y + y_offset
1050
1051 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
1052
1053 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1054
1055 local oldnode = {name = what}
1056 local newnode = {name = with}
1057 local on_replace_return
1058
1059 if self.on_replace then
1060 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1061 end
1062
1063 if on_replace_return ~= false then
1064
1065 minetest.set_node(pos, {name = with})
1066
1067 -- when cow/sheep eats grass, replace wool and milk
1068 if self.gotten == true then
1069 self.gotten = false
1070 self.object:set_properties(self)
1071 end
1072 end
1073 end
1074 end
1075
1076
1077 -- check if daytime and also if mob is docile during daylight hours
1078 local day_docile = function(self)
1079
1080 if self.docile_by_day == false then
1081
1082 return false
1083
1084 elseif self.docile_by_day == true
1085 and self.time_of_day > 0.2
1086 and self.time_of_day < 0.8 then
1087
1088 return true
1089 end
1090 end
1091
1092
1093 local los_switcher = false
1094 local height_switcher = false
1095
1096 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1097 local smart_mobs = function(self, s, p, dist, dtime)
1098
1099 local s1 = self.path.lastpos
1100
1101 local target_pos = self.attack:get_pos()
1102
1103 -- is it becoming stuck?
1104 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
1105 self.path.stuck_timer = self.path.stuck_timer + dtime
1106 else
1107 self.path.stuck_timer = 0
1108 end
1109
1110 self.path.lastpos = {x = s.x, y = s.y, z = s.z}
1111
1112 local use_pathfind = false
1113 local has_lineofsight = minetest.line_of_sight(
1114 {x = s.x, y = (s.y) + .5, z = s.z},
1115 {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
1116
1117 -- im stuck, search for path
1118 if not has_lineofsight then
1119
1120 if los_switcher == true then
1121 use_pathfind = true
1122 los_switcher = false
1123 end -- cannot see target!
1124 else
1125 if los_switcher == false then
1126
1127 los_switcher = true
1128 use_pathfind = false
1129
1130 minetest.after(1, function(self)
1131
1132 if self.object:get_luaentity() then
1133
1134 if has_lineofsight then
1135 self.path.following = false
1136 end
1137 end
1138 end, self)
1139 end -- can see target!
1140 end
1141
1142 if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
1143
1144 use_pathfind = true
1145 self.path.stuck_timer = 0
1146
1147 minetest.after(1, function(self)
1148
1149 if self.object:get_luaentity() then
1150
1151 if has_lineofsight then
1152 self.path.following = false
1153 end
1154 end
1155 end, self)
1156 end
1157
1158 if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
1159
1160 use_pathfind = true
1161 self.path.stuck_timer = 0
1162
1163 minetest.after(1, function(self)
1164
1165 if self.object:get_luaentity() then
1166
1167 if has_lineofsight then
1168 self.path.following = false
1169 end
1170 end
1171 end, self)
1172 end
1173
1174 if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
1175
1176 if height_switcher then
1177 use_pathfind = true
1178 height_switcher = false
1179 end
1180 else
1181 if not height_switcher then
1182 use_pathfind = false
1183 height_switcher = true
1184 end
1185 end
1186
1187 if use_pathfind then
1188 -- lets try find a path, first take care of positions
1189 -- since pathfinder is very sensitive
1190 local sheight = self.collisionbox[5] - self.collisionbox[2]
1191
1192 -- round position to center of node to avoid stuck in walls
1193 -- also adjust height for player models!
1194 s.x = floor(s.x + 0.5)
1195 -- s.y = floor(s.y + 0.5) - sheight
1196 s.z = floor(s.z + 0.5)
1197
1198 local ssight, sground = minetest.line_of_sight(s, {
1199 x = s.x, y = s.y - 4, z = s.z}, 1)
1200
1201 -- determine node above ground
1202 if not ssight then
1203 s.y = sground.y + 1
1204 end
1205
1206 local p1 = self.attack:get_pos()
1207
1208 p1.x = floor(p1.x + 0.5)
1209 p1.y = floor(p1.y + 0.5)
1210 p1.z = floor(p1.z + 0.5)
1211
1212 local dropheight = 6
1213 if self.fear_height ~= 0 then dropheight = self.fear_height end
1214
1215 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1216 --[[
1217 -- show path using particles
1218 if self.path.way and #self.path.way > 0 then
1219 print ("-- path length:" .. tonumber(#self.path.way))
1220 for _,pos in pairs(self.path.way) do
1221 minetest.add_particle({
1222 pos = pos,
1223 velocity = {x=0, y=0, z=0},
1224 acceleration = {x=0, y=0, z=0},
1225 expirationtime = 1,
1226 size = 4,
1227 collisiondetection = false,
1228 vertical = false,
1229 texture = "heart.png",
1230 })
1231 end
1232 end
1233 ]]
1234
1235 self.state = ""
1236 do_attack(self, self.attack)
1237
1238 -- no path found, try something else
1239 if not self.path.way then
1240
1241 self.path.following = false
1242
1243 -- lets make way by digging/building if not accessible
1244 if self.pathfinding == 2 and mobs_griefing then
1245
1246 -- is player higher than mob?
1247 if s.y < p1.y then
1248
1249 -- build upwards
1250 if not minetest.is_protected(s, "") then
1251
1252 local ndef1 = minetest.registered_nodes[self.standing_in]
1253
1254 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1255
1256 minetest.set_node(s, {name = mobs.fallback_node})
1257 end
1258 end
1259
1260 local sheight = math.ceil(self.collisionbox[5]) + 1
1261
1262 -- assume mob is 2 blocks high so it digs above its head
1263 s.y = s.y + sheight
1264
1265 -- remove one block above to make room to jump
1266 if not minetest.is_protected(s, "") then
1267
1268 local node1 = node_ok(s, "air").name
1269 local ndef1 = minetest.registered_nodes[node1]
1270
1271 if node1 ~= "air"
1272 and node1 ~= "ignore"
1273 and ndef1
1274 and not ndef1.groups.level
1275 and not ndef1.groups.unbreakable
1276 and not ndef1.groups.liquid then
1277
1278 minetest.set_node(s, {name = "air"})
1279 minetest.add_item(s, ItemStack(node1))
1280
1281 end
1282 end
1283
1284 s.y = s.y - sheight
1285 self.object:setpos({x = s.x, y = s.y + 2, z = s.z})
1286
1287 else -- dig 2 blocks to make door toward player direction
1288
1289 local yaw1 = self.object:get_yaw() + pi / 2
1290 local p1 = {
1291 x = s.x + cos(yaw1),
1292 y = s.y,
1293 z = s.z + sin(yaw1)
1294 }
1295
1296 if not minetest.is_protected(p1, "") then
1297
1298 local node1 = node_ok(p1, "air").name
1299 local ndef1 = minetest.registered_nodes[node1]
1300
1301 if node1 ~= "air"
1302 and node1 ~= "ignore"
1303 and ndef1
1304 and not ndef1.groups.level
1305 and not ndef1.groups.unbreakable
1306 and not ndef1.groups.liquid then
1307
1308 minetest.add_item(p1, ItemStack(node1))
1309 minetest.set_node(p1, {name = "air"})
1310 end
1311
1312 p1.y = p1.y + 1
1313 node1 = node_ok(p1, "air").name
1314 ndef1 = minetest.registered_nodes[node1]
1315
1316 if node1 ~= "air"
1317 and node1 ~= "ignore"
1318 and ndef1
1319 and not ndef1.groups.level
1320 and not ndef1.groups.unbreakable
1321 and not ndef1.groups.liquid then
1322
1323 minetest.add_item(p1, ItemStack(node1))
1324 minetest.set_node(p1, {name = "air"})
1325 end
1326
1327 end
1328 end
1329 end
1330
1331 -- will try again in 2 second
1332 self.path.stuck_timer = stuck_timeout - 2
1333
1334 -- frustration! cant find the damn path :(
1335 mob_sound(self, self.sounds.random)
1336 else
1337 -- yay i found path
1338 mob_sound(self, self.sounds.war_cry)
1339 set_velocity(self, self.walk_velocity)
1340
1341 -- follow path now that it has it
1342 self.path.following = true
1343 end
1344 end
1345 end
1346
1347
1348 -- specific attacks
1349 local specific_attack = function(list, what)
1350
1351 -- no list so attack default (player, animals etc.)
1352 if list == nil then
1353 return true
1354 end
1355
1356 -- found entity on list to attack?
1357 for no = 1, #list do
1358
1359 if list[no] == what then
1360 return true
1361 end
1362 end
1363
1364 return false
1365 end
1366
1367
1368 -- monster find someone to attack
1369 local monster_attack = function(self)
1370
1371 if self.type ~= "monster"
1372 or not damage_enabled
1373 or creative
1374 or self.state == "attack"
1375 or day_docile(self) then
1376 return
1377 end
1378
1379 local s = self.object:get_pos()
1380 local p, sp, dist
1381 local player, obj, min_player
1382 local type, name = "", ""
1383 local min_dist = self.view_range + 1
1384 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1385
1386 for n = 1, #objs do
1387
1388 if objs[n]:is_player() then
1389
1390 if mobs.invis[ objs[n]:get_player_name() ] then
1391
1392 type = ""
1393 else
1394 player = objs[n]
1395 type = "player"
1396 name = "player"
1397 end
1398 else
1399 obj = objs[n]:get_luaentity()
1400
1401 if obj then
1402 player = obj.object
1403 type = obj.type
1404 name = obj.name or ""
1405 end
1406 end
1407
1408 -- find specific mob to attack, failing that attack player/npc/animal
1409 if specific_attack(self.specific_attack, name)
1410 and (type == "player" or type == "npc"
1411 or (type == "animal" and self.attack_animals == true)) then
1412
1413 p = player:get_pos()
1414 sp = s
1415
1416 dist = get_distance(p, s)
1417
1418 -- aim higher to make looking up hills more realistic
1419 p.y = p.y + 1
1420 sp.y = sp.y + 1
1421
1422
1423 -- choose closest player to attack
1424 if dist < min_dist
1425 and line_of_sight(self, sp, p, 2) == true then
1426 min_dist = dist
1427 min_player = player
1428 end
1429 end
1430 end
1431
1432 -- attack player
1433 if min_player then
1434 do_attack(self, min_player)
1435 end
1436 end
1437
1438
1439 -- npc, find closest monster to attack
1440 local npc_attack = function(self)
1441
1442 if self.type ~= "npc"
1443 or not self.attacks_monsters
1444 or self.state == "attack" then
1445 return
1446 end
1447
1448 local p, sp, obj, min_player, dist
1449 local s = self.object:get_pos()
1450 local min_dist = self.view_range + 1
1451 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1452
1453 for n = 1, #objs do
1454
1455 obj = objs[n]:get_luaentity()
1456
1457 if obj and obj.type == "monster" then
1458
1459 p = obj.object:get_pos()
1460 sp = s
1461
1462 dist = get_distance(p, s)
1463
1464 -- aim higher to make looking up hills more realistic
1465 p.y = p.y + 1
1466 sp.y = sp.y + 1
1467
1468 if dist < min_dist
1469 and line_of_sight(self, sp, p, 2) == true then
1470 min_dist = dist
1471 min_player = obj.object
1472 end
1473 end
1474 end
1475
1476 if min_player then
1477 do_attack(self, min_player)
1478 end
1479 end
1480
1481
1482 -- specific runaway
1483 local specific_runaway = function(list, what)
1484
1485 -- no list so do not run
1486 if list == nil then
1487 return false
1488 end
1489
1490 -- found entity on list to attack?
1491 for no = 1, #list do
1492
1493 if list[no] == what then
1494 return true
1495 end
1496 end
1497
1498 return false
1499 end
1500
1501
1502 -- find someone to runaway from
1503 local runaway_from = function(self)
1504
1505 if not self.runaway_from then
1506 return
1507 end
1508
1509 local s = self.object:get_pos()
1510 local p, sp, dist
1511 local player, obj, min_player
1512 local type, name = "", ""
1513 local min_dist = self.view_range + 1
1514 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1515
1516 for n = 1, #objs do
1517
1518 if objs[n]:is_player() then
1519
1520 if mobs.invis[ objs[n]:get_player_name() ]
1521 or self.owner == objs[n]:get_player_name() then
1522
1523 type = ""
1524 else
1525 player = objs[n]
1526 type = "player"
1527 name = "player"
1528 end
1529 else
1530 obj = objs[n]:get_luaentity()
1531
1532 if obj then
1533 player = obj.object
1534 type = obj.type
1535 name = obj.name or ""
1536 end
1537 end
1538
1539 -- find specific mob to runaway from
1540 if name ~= "" and name ~= self.name
1541 and specific_runaway(self.runaway_from, name) then
1542
1543 p = player:get_pos()
1544 sp = s
1545
1546 -- aim higher to make looking up hills more realistic
1547 p.y = p.y + 1
1548 sp.y = sp.y + 1
1549
1550 dist = get_distance(p, s)
1551
1552
1553 -- choose closest player/mpb to runaway from
1554 if dist < min_dist
1555 and line_of_sight(self, sp, p, 2) == true then
1556 min_dist = dist
1557 min_player = player
1558 end
1559 end
1560 end
1561
1562 if min_player then
1563
1564 local lp = player:get_pos()
1565 local vec = {
1566 x = lp.x - s.x,
1567 y = lp.y - s.y,
1568 z = lp.z - s.z
1569 }
1570
1571 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1572
1573 if lp.x > s.x then
1574 yaw = yaw + pi
1575 end
1576
1577 yaw = set_yaw(self, yaw, 4)
1578 self.state = "runaway"
1579 self.runaway_timer = 3
1580 self.following = nil
1581 end
1582 end
1583
1584
1585 -- follow player if owner or holding item, if fish outta water then flop
1586 local follow_flop = function(self)
1587
1588 -- find player to follow
1589 if (self.follow ~= ""
1590 or self.order == "follow")
1591 and not self.following
1592 and self.state ~= "attack"
1593 and self.state ~= "runaway" then
1594
1595 local s = self.object:get_pos()
1596 local players = minetest.get_connected_players()
1597
1598 for n = 1, #players do
1599
1600 if get_distance(players[n]:get_pos(), s) < self.view_range
1601 and not mobs.invis[ players[n]:get_player_name() ] then
1602
1603 self.following = players[n]
1604
1605 break
1606 end
1607 end
1608 end
1609
1610 if self.type == "npc"
1611 and self.order == "follow"
1612 and self.state ~= "attack"
1613 and self.owner ~= "" then
1614
1615 -- npc stop following player if not owner
1616 if self.following
1617 and self.owner
1618 and self.owner ~= self.following:get_player_name() then
1619 self.following = nil
1620 end
1621 else
1622 -- stop following player if not holding specific item
1623 if self.following
1624 and self.following:is_player()
1625 and follow_holding(self, self.following) == false then
1626 self.following = nil
1627 end
1628
1629 end
1630
1631 -- follow that thing
1632 if self.following then
1633
1634 local s = self.object:get_pos()
1635 local p
1636
1637 if self.following:is_player() then
1638
1639 p = self.following:get_pos()
1640
1641 elseif self.following.object then
1642
1643 p = self.following.object:get_pos()
1644 end
1645
1646 if p then
1647
1648 local dist = get_distance(p, s)
1649
1650 -- dont follow if out of range
1651 if dist > self.view_range then
1652 self.following = nil
1653 else
1654 local vec = {
1655 x = p.x - s.x,
1656 z = p.z - s.z
1657 }
1658
1659 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1660
1661 if p.x > s.x then yaw = yaw + pi end
1662
1663 yaw = set_yaw(self, yaw, 6)
1664
1665 -- anyone but standing npc's can move along
1666 if dist > self.reach
1667 and self.order ~= "stand" then
1668
1669 set_velocity(self, self.walk_velocity)
1670
1671 if self.walk_chance ~= 0 then
1672 set_animation(self, "walk")
1673 end
1674 else
1675 set_velocity(self, 0)
1676 set_animation(self, "stand")
1677 end
1678
1679 return
1680 end
1681 end
1682 end
1683
1684 -- swimmers flop when out of their element, and swim again when back in
1685 if self.fly then
1686 local s = self.object:get_pos()
1687 if not flight_check(self, s) then
1688
1689 self.state = "flop"
1690 self.object:setvelocity({x = 0, y = -5, z = 0})
1691
1692 set_animation(self, "stand")
1693
1694 return
1695 elseif self.state == "flop" then
1696 self.state = "stand"
1697 end
1698 end
1699 end
1700
1701
1702 -- dogshoot attack switch and counter function
1703 local dogswitch = function(self, dtime)
1704
1705 -- switch mode not activated
1706 if not self.dogshoot_switch
1707 or not dtime then
1708 return 0
1709 end
1710
1711 self.dogshoot_count = self.dogshoot_count + dtime
1712
1713 if (self.dogshoot_switch == 1
1714 and self.dogshoot_count > self.dogshoot_count_max)
1715 or (self.dogshoot_switch == 2
1716 and self.dogshoot_count > self.dogshoot_count2_max) then
1717
1718 self.dogshoot_count = 0
1719
1720 if self.dogshoot_switch == 1 then
1721 self.dogshoot_switch = 2
1722 else
1723 self.dogshoot_switch = 1
1724 end
1725 end
1726
1727 return self.dogshoot_switch
1728 end
1729
1730
1731 -- execute current state (stand, walk, run, attacks)
1732 local do_states = function(self, dtime)
1733
1734 local yaw = self.object:get_yaw() or 0
1735
1736 if self.state == "stand" then
1737
1738 if random(1, 4) == 1 then
1739
1740 local lp = nil
1741 local s = self.object:get_pos()
1742 local objs = minetest.get_objects_inside_radius(s, 3)
1743
1744 for n = 1, #objs do
1745
1746 if objs[n]:is_player() then
1747 lp = objs[n]:get_pos()
1748 break
1749 end
1750 end
1751
1752 -- look at any players nearby, otherwise turn randomly
1753 if lp then
1754
1755 local vec = {
1756 x = lp.x - s.x,
1757 z = lp.z - s.z
1758 }
1759
1760 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1761
1762 if lp.x > s.x then yaw = yaw + pi end
1763 else
1764 yaw = yaw + random(-0.5, 0.5)
1765 end
1766
1767 yaw = set_yaw(self, yaw, 8)
1768 end
1769
1770 set_velocity(self, 0)
1771 set_animation(self, "stand")
1772
1773 -- npc's ordered to stand stay standing
1774 if self.type ~= "npc"
1775 or self.order ~= "stand" then
1776
1777 if self.walk_chance ~= 0
1778 and self.facing_fence ~= true
1779 and random(1, 100) <= self.walk_chance
1780 and is_at_cliff(self) == false then
1781
1782 set_velocity(self, self.walk_velocity)
1783 self.state = "walk"
1784 set_animation(self, "walk")
1785
1786 --[[ fly up/down randomly for flying mobs
1787 if self.fly and random(1, 100) <= self.walk_chance then
1788
1789 local v = self.object:getvelocity()
1790 local ud = random(-1, 2) / 9
1791
1792 self.object:setvelocity({x = v.x, y = ud, z = v.z})
1793 end--]]
1794 end
1795 end
1796
1797 elseif self.state == "walk" then
1798
1799 local s = self.object:get_pos()
1800 local lp = nil
1801
1802 -- is there something I need to avoid?
1803 if self.water_damage > 0
1804 and self.lava_damage > 0 then
1805
1806 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
1807
1808 elseif self.water_damage > 0 then
1809
1810 lp = minetest.find_node_near(s, 1, {"group:water"})
1811
1812 elseif self.lava_damage > 0 then
1813
1814 lp = minetest.find_node_near(s, 1, {"group:lava"})
1815 end
1816
1817 if lp then
1818
1819 -- if mob in water or lava then look for land
1820 if (self.lava_damage
1821 and minetest.registered_nodes[self.standing_in].groups.lava)
1822 or (self.water_damage
1823 and minetest.registered_nodes[self.standing_in].groups.water) then
1824
1825 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1826 "group:sand", node_ice, node_snowblock})
1827
1828 -- did we find land?
1829 if lp then
1830
1831 local vec = {
1832 x = lp.x - s.x,
1833 z = lp.z - s.z
1834 }
1835
1836 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1837
1838 if lp.x > s.x then yaw = yaw + pi end
1839
1840 -- look towards land and jump/move in that direction
1841 yaw = set_yaw(self, yaw, 6)
1842 do_jump(self)
1843 set_velocity(self, self.walk_velocity)
1844 else
1845 yaw = yaw + random(-0.5, 0.5)
1846 end
1847
1848 else
1849
1850 local vec = {
1851 x = lp.x - s.x,
1852 z = lp.z - s.z
1853 }
1854
1855 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1856
1857 if lp.x > s.x then yaw = yaw + pi end
1858 end
1859
1860 yaw = set_yaw(self, yaw, 8)
1861
1862 -- otherwise randomly turn
1863 elseif random(1, 100) <= 30 then
1864
1865 yaw = yaw + random(-0.5, 0.5)
1866
1867 yaw = set_yaw(self, yaw, 8)
1868 end
1869
1870 -- stand for great fall in front
1871 local temp_is_cliff = is_at_cliff(self)
1872
1873 if self.facing_fence == true
1874 or temp_is_cliff
1875 or random(1, 100) <= 30 then
1876
1877 set_velocity(self, 0)
1878 self.state = "stand"
1879 set_animation(self, "stand")
1880 else
1881 set_velocity(self, self.walk_velocity)
1882
1883 if flight_check(self)
1884 and self.animation
1885 and self.animation.fly_start
1886 and self.animation.fly_end then
1887 set_animation(self, "fly")
1888 else
1889 set_animation(self, "walk")
1890 end
1891 end
1892
1893 -- runaway when punched
1894 elseif self.state == "runaway" then
1895
1896 self.runaway_timer = self.runaway_timer + 1
1897
1898 -- stop after 5 seconds or when at cliff
1899 if self.runaway_timer > 5
1900 or is_at_cliff(self) then
1901 self.runaway_timer = 0
1902 set_velocity(self, 0)
1903 self.state = "stand"
1904 set_animation(self, "stand")
1905 else
1906 set_velocity(self, self.run_velocity)
1907 set_animation(self, "walk")
1908 end
1909
1910 -- attack routines (explode, dogfight, shoot, dogshoot)
1911 elseif self.state == "attack" then
1912
1913 -- calculate distance from mob and enemy
1914 local s = self.object:get_pos()
1915 local p = self.attack:get_pos() or s
1916 local dist = get_distance(p, s)
1917
1918 -- stop attacking if player invisible or out of range
1919 if dist > self.view_range
1920 or not self.attack
1921 or not self.attack:get_pos()
1922 or self.attack:get_hp() <= 0
1923 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1924
1925 -- print(" ** stop attacking **", dist, self.view_range)
1926 self.state = "stand"
1927 set_velocity(self, 0)
1928 set_animation(self, "stand")
1929 self.attack = nil
1930 self.v_start = false
1931 self.timer = 0
1932 self.blinktimer = 0
1933 self.path.way = nil
1934
1935 return
1936 end
1937
1938 if self.attack_type == "explode" then
1939
1940 local vec = {
1941 x = p.x - s.x,
1942 z = p.z - s.z
1943 }
1944
1945 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1946
1947 if p.x > s.x then yaw = yaw + pi end
1948
1949 yaw = set_yaw(self, yaw)
1950
1951 local node_break_radius = self.explosion_radius or 1
1952 local entity_damage_radius = self.explosion_damage_radius
1953 or (node_break_radius * 2)
1954
1955 -- start timer when in reach and line of sight
1956 if not self.v_start
1957 and dist <= self.reach
1958 and line_of_sight(self, s, p, 2) then
1959
1960 self.v_start = true
1961 self.timer = 0
1962 self.blinktimer = 0
1963 mob_sound(self, self.sounds.fuse)
1964 -- print ("=== explosion timer started", self.explosion_timer)
1965
1966 -- stop timer if out of reach or direct line of sight
1967 elseif self.allow_fuse_reset
1968 and self.v_start
1969 and (dist > self.reach
1970 or not line_of_sight(self, s, p, 2)) then
1971 self.v_start = false
1972 self.timer = 0
1973 self.blinktimer = 0
1974 self.blinkstatus = false
1975 self.object:settexturemod("")
1976 end
1977
1978 -- walk right up to player unless the timer is active
1979 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1980 set_velocity(self, 0)
1981 else
1982 set_velocity(self, self.run_velocity)
1983 end
1984
1985 if self.animation and self.animation.run_start then
1986 set_animation(self, "run")
1987 else
1988 set_animation(self, "walk")
1989 end
1990
1991 if self.v_start then
1992
1993 self.timer = self.timer + dtime
1994 self.blinktimer = (self.blinktimer or 0) + dtime
1995
1996 if self.blinktimer > 0.2 then
1997
1998 self.blinktimer = 0
1999
2000 if self.blinkstatus then
2001 self.object:settexturemod("")
2002 else
2003 self.object:settexturemod("^[brighten")
2004 end
2005
2006 self.blinkstatus = not self.blinkstatus
2007 end
2008
2009 -- print ("=== explosion timer", self.timer)
2010
2011 if self.timer > self.explosion_timer then
2012
2013 local pos = self.object:get_pos()
2014
2015 -- dont damage anything if area protected or next to water
2016 if minetest.find_node_near(pos, 1, {"group:water"})
2017 or minetest.is_protected(pos, "") then
2018
2019 node_break_radius = 1
2020 end
2021
2022 self.object:remove()
2023
2024 if minetest.get_modpath("tnt") and tnt and tnt.boom
2025 and not minetest.is_protected(pos, "") then
2026
2027 tnt.boom(pos, {
2028 radius = node_break_radius,
2029 damage_radius = entity_damage_radius,
2030 sound = self.sounds.explode,
2031 })
2032 else
2033
2034 minetest.sound_play(self.sounds.explode, {
2035 pos = pos,
2036 gain = 1.0,
2037 max_hear_distance = self.sounds.distance or 32
2038 })
2039
2040 entity_physics(pos, entity_damage_radius)
2041 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2042 end
2043
2044 return
2045 end
2046 end
2047
2048 elseif self.attack_type == "dogfight"
2049 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2050 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2051
2052 if self.fly
2053 and dist > self.reach then
2054
2055 local p1 = s
2056 local me_y = floor(p1.y)
2057 local p2 = p
2058 local p_y = floor(p2.y + 1)
2059 local v = self.object:getvelocity()
2060
2061 if flight_check(self, s) then
2062
2063 if me_y < p_y then
2064
2065 self.object:setvelocity({
2066 x = v.x,
2067 y = 1 * self.walk_velocity,
2068 z = v.z
2069 })
2070
2071 elseif me_y > p_y then
2072
2073 self.object:setvelocity({
2074 x = v.x,
2075 y = -1 * self.walk_velocity,
2076 z = v.z
2077 })
2078 end
2079 else
2080 if me_y < p_y then
2081
2082 self.object:setvelocity({
2083 x = v.x,
2084 y = 0.01,
2085 z = v.z
2086 })
2087
2088 elseif me_y > p_y then
2089
2090 self.object:setvelocity({
2091 x = v.x,
2092 y = -0.01,
2093 z = v.z
2094 })
2095 end
2096 end
2097
2098 end
2099
2100 -- rnd: new movement direction
2101 if self.path.following
2102 and self.path.way
2103 and self.attack_type ~= "dogshoot" then
2104
2105 -- no paths longer than 50
2106 if #self.path.way > 50
2107 or dist < self.reach then
2108 self.path.following = false
2109 return
2110 end
2111
2112 local p1 = self.path.way[1]
2113
2114 if not p1 then
2115 self.path.following = false
2116 return
2117 end
2118
2119 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2120 -- reached waypoint, remove it from queue
2121 table.remove(self.path.way, 1)
2122 end
2123
2124 -- set new temporary target
2125 p = {x = p1.x, y = p1.y, z = p1.z}
2126 end
2127
2128 local vec = {
2129 x = p.x - s.x,
2130 z = p.z - s.z
2131 }
2132
2133 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2134
2135 if p.x > s.x then yaw = yaw + pi end
2136
2137 yaw = set_yaw(self, yaw)
2138
2139 -- move towards enemy if beyond mob reach
2140 if dist > self.reach then
2141
2142 -- path finding by rnd
2143 if self.pathfinding -- only if mob has pathfinding enabled
2144 and enable_pathfinding then
2145
2146 smart_mobs(self, s, p, dist, dtime)
2147 end
2148
2149 if is_at_cliff(self) then
2150
2151 set_velocity(self, 0)
2152 set_animation(self, "stand")
2153 else
2154
2155 if self.path.stuck then
2156 set_velocity(self, self.walk_velocity)
2157 else
2158 set_velocity(self, self.run_velocity)
2159 end
2160
2161 if self.animation and self.animation.run_start then
2162 set_animation(self, "run")
2163 else
2164 set_animation(self, "walk")
2165 end
2166 end
2167
2168 else -- rnd: if inside reach range
2169
2170 self.path.stuck = false
2171 self.path.stuck_timer = 0
2172 self.path.following = false -- not stuck anymore
2173
2174 set_velocity(self, 0)
2175
2176 if not self.custom_attack then
2177
2178 if self.timer > 1 then
2179
2180 self.timer = 0
2181
2182 if self.double_melee_attack
2183 and random(1, 2) == 1 then
2184 set_animation(self, "punch2")
2185 else
2186 set_animation(self, "punch")
2187 end
2188
2189 local p2 = p
2190 local s2 = s
2191
2192 p2.y = p2.y + .5
2193 s2.y = s2.y + .5
2194
2195 if line_of_sight(self, p2, s2) == true then
2196
2197 -- play attack sound
2198 mob_sound(self, self.sounds.attack)
2199
2200 -- punch player (or what player is attached to)
2201 local attached = self.attack:get_attach()
2202 if attached then
2203 self.attack = attached
2204 end
2205 self.attack:punch(self.object, 1.0, {
2206 full_punch_interval = 1.0,
2207 damage_groups = {fleshy = self.damage}
2208 }, nil)
2209 end
2210 end
2211 else -- call custom attack every second
2212 if self.custom_attack
2213 and self.timer > 1 then
2214
2215 self.timer = 0
2216
2217 self.custom_attack(self, p)
2218 end
2219 end
2220 end
2221
2222 elseif self.attack_type == "shoot"
2223 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2224 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2225
2226 p.y = p.y - .5
2227 s.y = s.y + .5
2228
2229 local dist = get_distance(p, s)
2230 local vec = {
2231 x = p.x - s.x,
2232 y = p.y - s.y,
2233 z = p.z - s.z
2234 }
2235
2236 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2237
2238 if p.x > s.x then yaw = yaw + pi end
2239
2240 yaw = set_yaw(self, yaw)
2241
2242 set_velocity(self, 0)
2243
2244 if self.shoot_interval
2245 and self.timer > self.shoot_interval
2246 and random(1, 100) <= 60 then
2247
2248 self.timer = 0
2249 set_animation(self, "shoot")
2250
2251 -- play shoot attack sound
2252 mob_sound(self, self.sounds.shoot_attack)
2253
2254 local p = self.object:get_pos()
2255
2256 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
2257
2258 if minetest.registered_entities[self.arrow] then
2259
2260 local obj = minetest.add_entity(p, self.arrow)
2261 local ent = obj:get_luaentity()
2262 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2263 local v = ent.velocity or 1 -- or set to default
2264
2265 ent.switch = 1
2266 ent.owner_id = tostring(self.object) -- add unique owner id to arrow
2267
2268 -- offset makes shoot aim accurate
2269 vec.y = vec.y + self.shoot_offset
2270 vec.x = vec.x * (v / amount)
2271 vec.y = vec.y * (v / amount)
2272 vec.z = vec.z * (v / amount)
2273
2274 obj:setvelocity(vec)
2275 end
2276 end
2277 end
2278 end
2279 end
2280
2281
2282 -- falling and fall damage
2283 local falling = function(self, pos)
2284
2285 if self.fly then
2286 return
2287 end
2288
2289 -- floating in water (or falling)
2290 local v = self.object:getvelocity()
2291
2292 if v.y > 0 then
2293
2294 -- apply gravity when moving up
2295 self.object:setacceleration({
2296 x = 0,
2297 y = -10,
2298 z = 0
2299 })
2300
2301 elseif v.y <= 0 and v.y > self.fall_speed then
2302
2303 -- fall downwards at set speed
2304 self.object:setacceleration({
2305 x = 0,
2306 y = self.fall_speed,
2307 z = 0
2308 })
2309 else
2310 -- stop accelerating once max fall speed hit
2311 self.object:setacceleration({x = 0, y = 0, z = 0})
2312 end
2313
2314 -- in water then float up
2315 if minetest.registered_nodes[self.standing_in].groups.water then
2316
2317 if self.floats == 1 then
2318
2319 self.object:setacceleration({
2320 x = 0,
2321 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2322 z = 0
2323 })
2324 end
2325 else
2326
2327 -- fall damage onto solid ground
2328 if self.fall_damage == 1
2329 and self.object:getvelocity().y == 0 then
2330
2331 local d = (self.old_y or 0) - self.object:get_pos().y
2332
2333 if d > 5 then
2334
2335 self.health = self.health - floor(d - 5)
2336
2337 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
2338
2339 if check_for_death(self, "fall", {type = "fall"}) then
2340 return
2341 end
2342 end
2343
2344 self.old_y = self.object:get_pos().y
2345 end
2346 end
2347 end
2348
2349
2350 -- deal damage and effects when mob punched
2351 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2352
2353 -- custom punch function
2354 if self.do_punch then
2355
2356 -- when false skip going any further
2357 if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2358 return
2359 end
2360 end
2361
2362 -- mob health check
2363 -- if self.health <= 0 then
2364 -- return
2365 -- end
2366
2367 -- error checking when mod profiling is enabled
2368 if not tool_capabilities then
2369 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2370 return
2371 end
2372
2373 -- is mob protected?
2374 if self.protected and hitter:is_player()
2375 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2376 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2377 return
2378 end
2379
2380
2381 -- weapon wear
2382 local weapon = hitter:get_wielded_item()
2383 local punch_interval = 1.4
2384
2385 -- calculate mob damage
2386 local damage = 0
2387 local armor = self.object:get_armor_groups() or {}
2388 local tmp
2389
2390 -- quick error check incase it ends up 0 (serialize.h check test)
2391 if tflp == 0 then
2392 tflp = 0.2
2393 end
2394
2395 if use_cmi then
2396 damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
2397 else
2398
2399 for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
2400
2401 tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
2402
2403 if tmp < 0 then
2404 tmp = 0.0
2405 elseif tmp > 1 then
2406 tmp = 1.0
2407 end
2408
2409 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2410 * tmp * ((armor[group] or 0) / 100.0)
2411 end
2412 end
2413
2414 -- check for tool immunity or special damage
2415 for n = 1, #self.immune_to do
2416
2417 if self.immune_to[n][1] == weapon:get_name() then
2418
2419 damage = self.immune_to[n][2] or 0
2420 break
2421
2422 -- if "all" then no tool does damage unless it's specified in list
2423 elseif self.immune_to[n][1] == "all" then
2424 damage = self.immune_to[n][2] or 0
2425 end
2426 end
2427
2428 -- healing
2429 if damage <= -1 then
2430 self.health = self.health - floor(damage)
2431 return
2432 end
2433
2434 -- print ("Mob Damage is", damage)
2435
2436 if use_cmi then
2437
2438 local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage)
2439
2440 if cancel then return end
2441 end
2442
2443 -- add weapon wear
2444 if tool_capabilities then
2445 punch_interval = tool_capabilities.full_punch_interval or 1.4
2446 end
2447
2448 if weapon:get_definition()
2449 and weapon:get_definition().tool_capabilities then
2450
2451 weapon:add_wear(floor((punch_interval / 75) * 9000))
2452 hitter:set_wielded_item(weapon)
2453 end
2454
2455 -- only play hit sound and show blood effects if damage is 1 or over
2456 if damage >= 1 then
2457
2458 -- weapon sounds
2459 if weapon:get_definition().sounds ~= nil then
2460
2461 local s = random(0, #weapon:get_definition().sounds)
2462
2463 minetest.sound_play(weapon:get_definition().sounds[s], {
2464 object = self.object, --hitter,
2465 max_hear_distance = 8
2466 })
2467 else
2468 minetest.sound_play("default_punch", {
2469 object = self.object, --hitter,
2470 max_hear_distance = 5
2471 })
2472 end
2473
2474 -- blood_particles
2475 if self.blood_amount > 0
2476 and not disable_blood then
2477
2478 local pos = self.object:get_pos()
2479
2480 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2481
2482 -- do we have a single blood texture or multiple?
2483 if type(self.blood_texture) == "table" then
2484
2485 local blood = self.blood_texture[random(1, #self.blood_texture)]
2486
2487 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2488 else
2489 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2490 end
2491 end
2492
2493 -- do damage
2494 self.health = self.health - floor(damage)
2495
2496 -- exit here if dead, special item check
2497 if weapon:get_name() == "mobs:pick_lava" then
2498 if check_for_death(self, "lava", {type = "punch",
2499 puncher = hitter}) then
2500 return
2501 end
2502 else
2503 if check_for_death(self, "hit", {type = "punch",
2504 puncher = hitter}) then
2505 return
2506 end
2507 end
2508
2509 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2510 minetest.after(0.1, function()
2511
2512 if not self.object:get_luaentity() then return end
2513
2514 self.object:settexturemod("^[colorize:#c9900070")
2515
2516 core.after(0.3, function()
2517 self.object:settexturemod("")
2518 end)
2519 end) ]]
2520
2521 -- knock back effect (only on full punch)
2522 if self.knock_back
2523 and tflp >= punch_interval then
2524
2525 local v = self.object:getvelocity()
2526 local r = 1.4 - min(punch_interval, 1.4)
2527 local kb = r * 5
2528 local up = 2
2529
2530 -- if already in air then dont go up anymore when hit
2531 if v.y > 0
2532 or self.fly then
2533 up = 0
2534 end
2535
2536 -- direction error check
2537 dir = dir or {x = 0, y = 0, z = 0}
2538
2539 -- check if tool already has specific knockback value
2540 if tool_capabilities.damage_groups["knockback"] then
2541 kb = tool_capabilities.damage_groups["knockback"]
2542 else
2543 kb = kb * 1.5
2544 end
2545
2546 self.object:setvelocity({
2547 x = dir.x * kb,
2548 y = up,
2549 z = dir.z * kb
2550 })
2551
2552 self.pause_timer = 0.25
2553 end
2554 end -- END if damage
2555
2556 -- if skittish then run away
2557 if self.runaway == true then
2558
2559 local lp = hitter:get_pos()
2560 local s = self.object:get_pos()
2561 local vec = {
2562 x = lp.x - s.x,
2563 y = lp.y - s.y,
2564 z = lp.z - s.z
2565 }
2566
2567 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2568
2569 if lp.x > s.x then
2570 yaw = yaw + pi
2571 end
2572
2573 yaw = set_yaw(self, yaw, 6)
2574 self.state = "runaway"
2575 self.runaway_timer = 0
2576 self.following = nil
2577 end
2578
2579 local name = hitter:get_player_name() or ""
2580
2581 -- attack puncher and call other mobs for help
2582 if self.passive == false
2583 and self.state ~= "flop"
2584 and self.child == false
2585 and hitter:get_player_name() ~= self.owner
2586 and not mobs.invis[ name ] then
2587
2588 -- attack whoever punched mob
2589 self.state = ""
2590 do_attack(self, hitter)
2591
2592 -- alert others to the attack
2593 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2594 local obj = nil
2595
2596 for n = 1, #objs do
2597
2598 obj = objs[n]:get_luaentity()
2599
2600 if obj then
2601
2602 -- only alert members of same mob
2603 if obj.group_attack == true
2604 and obj.state ~= "attack"
2605 and obj.owner ~= name
2606 and obj.name == self.name then
2607 do_attack(obj, hitter)
2608 end
2609
2610 -- have owned mobs attack player threat
2611 if obj.owner == name and obj.owner_loyal then
2612 do_attack(obj, self.object)
2613 end
2614 end
2615 end
2616 end
2617 end
2618
2619
2620 -- get entity staticdata
2621 local mob_staticdata = function(self)
2622
2623 -- remove mob when out of range unless tamed
2624 if remove_far
2625 and self.remove_ok
2626 and self.type ~= "npc"
2627 and self.state ~= "attack"
2628 and not self.tamed
2629 and self.lifetimer < 20000 then
2630
2631 --print ("REMOVED " .. self.name)
2632
2633 self.object:remove()
2634
2635 return ""-- nil
2636 end
2637
2638 self.remove_ok = true
2639 self.attack = nil
2640 self.following = nil
2641 self.state = "stand"
2642
2643 -- used to rotate older mobs
2644 if self.drawtype
2645 and self.drawtype == "side" then
2646 self.rotate = math.rad(90)
2647 end
2648
2649 if use_cmi then
2650 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2651 end
2652
2653 local tmp = {}
2654
2655 for _,stat in pairs(self) do
2656
2657 local t = type(stat)
2658
2659 if t ~= "function"
2660 and t ~= "nil"
2661 and t ~= "userdata"
2662 and _ ~= "_cmi_components" then
2663 tmp[_] = self[_]
2664 end
2665 end
2666
2667 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
2668 return minetest.serialize(tmp)
2669 end
2670
2671
2672 -- activate mob and reload settings
2673 local mob_activate = function(self, staticdata, def, dtime)
2674
2675 -- remove monsters in peaceful mode
2676 if self.type == "monster"
2677 and peaceful_only then
2678
2679 self.object:remove()
2680
2681 return
2682 end
2683
2684 -- load entity variables
2685 local tmp = minetest.deserialize(staticdata)
2686
2687 if tmp then
2688 for _,stat in pairs(tmp) do
2689 self[_] = stat
2690 end
2691 end
2692
2693 -- select random texture, set model and size
2694 if not self.base_texture then
2695
2696 -- compatiblity with old simple mobs textures
2697 if type(def.textures[1]) == "string" then
2698 def.textures = {def.textures}
2699 end
2700
2701 self.base_texture = def.textures[random(1, #def.textures)]
2702 self.base_mesh = def.mesh
2703 self.base_size = self.visual_size
2704 self.base_colbox = self.collisionbox
2705 self.base_selbox = self.selectionbox
2706 end
2707
2708 -- for current mobs that dont have this set
2709 if not self.base_selbox then
2710 self.base_selbox = self.selectionbox or self.base_colbox
2711 end
2712
2713 -- set texture, model and size
2714 local textures = self.base_texture
2715 local mesh = self.base_mesh
2716 local vis_size = self.base_size
2717 local colbox = self.base_colbox
2718 local selbox = self.base_selbox
2719
2720 -- specific texture if gotten
2721 if self.gotten == true
2722 and def.gotten_texture then
2723 textures = def.gotten_texture
2724 end
2725
2726 -- specific mesh if gotten
2727 if self.gotten == true
2728 and def.gotten_mesh then
2729 mesh = def.gotten_mesh
2730 end
2731
2732 -- set child objects to half size
2733 if self.child == true then
2734
2735 vis_size = {
2736 x = self.base_size.x * .5,
2737 y = self.base_size.y * .5,
2738 }
2739
2740 if def.child_texture then
2741 textures = def.child_texture[1]
2742 end
2743
2744 colbox = {
2745 self.base_colbox[1] * .5,
2746 self.base_colbox[2] * .5,
2747 self.base_colbox[3] * .5,
2748 self.base_colbox[4] * .5,
2749 self.base_colbox[5] * .5,
2750 self.base_colbox[6] * .5
2751 }
2752 selbox = {
2753 self.base_selbox[1] * .5,
2754 self.base_selbox[2] * .5,
2755 self.base_selbox[3] * .5,
2756 self.base_selbox[4] * .5,
2757 self.base_selbox[5] * .5,
2758 self.base_selbox[6] * .5
2759 }
2760 end
2761
2762 if self.health == 0 then
2763 self.health = random (self.hp_min, self.hp_max)
2764 end
2765
2766 -- pathfinding init
2767 self.path = {}
2768 self.path.way = {} -- path to follow, table of positions
2769 self.path.lastpos = {x = 0, y = 0, z = 0}
2770 self.path.stuck = false
2771 self.path.following = false -- currently following path?
2772 self.path.stuck_timer = 0 -- if stuck for too long search for path
2773
2774 -- mob defaults
2775 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
2776 self.old_y = self.object:get_pos().y
2777 self.old_health = self.health
2778 self.sounds.distance = self.sounds.distance or 10
2779 self.textures = textures
2780 self.mesh = mesh
2781 self.collisionbox = colbox
2782 self.selectionbox = selbox
2783 self.visual_size = vis_size
2784 self.standing_in = "air"
2785
2786 -- check existing nametag
2787 if not self.nametag then
2788 self.nametag = def.nametag
2789 end
2790
2791 -- set anything changed above
2792 self.object:set_properties(self)
2793 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2794 update_tag(self)
2795 set_animation(self, "stand")
2796
2797 -- run on_spawn function if found
2798 if self.on_spawn and not self.on_spawn_run then
2799 if self.on_spawn(self) then
2800 self.on_spawn_run = true -- if true, set flag to run once only
2801 end
2802 end
2803
2804 -- run after_activate
2805 if def.after_activate then
2806 def.after_activate(self, staticdata, def, dtime)
2807 end
2808
2809 if use_cmi then
2810 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
2811 cmi.notify_activate(self.object, dtime)
2812 end
2813 end
2814
2815
2816 -- main mob function
2817 local mob_step = function(self, dtime)
2818
2819 if use_cmi then
2820 cmi.notify_step(self.object, dtime)
2821 end
2822
2823 local pos = self.object:get_pos()
2824 local yaw = 0
2825
2826 -- when lifetimer expires remove mob (except npc and tamed)
2827 if self.type ~= "npc"
2828 and not self.tamed
2829 and self.state ~= "attack"
2830 and remove_far ~= true
2831 and self.lifetimer < 20000 then
2832
2833 self.lifetimer = self.lifetimer - dtime
2834
2835 if self.lifetimer <= 0 then
2836
2837 -- only despawn away from player
2838 local objs = minetest.get_objects_inside_radius(pos, 15)
2839
2840 for n = 1, #objs do
2841
2842 if objs[n]:is_player() then
2843
2844 self.lifetimer = 20
2845
2846 return
2847 end
2848 end
2849
2850 -- minetest.log("action",
2851 -- S("lifetimer expired, removed @1", self.name))
2852
2853 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
2854
2855 self.object:remove()
2856
2857 return
2858 end
2859 end
2860
2861 -- get node at foot level every quarter second
2862 self.node_timer = (self.node_timer or 0) + dtime
2863
2864 if self.node_timer > 0.25 then
2865
2866 self.node_timer = 0
2867
2868 local y_level = self.collisionbox[2]
2869
2870 if self.child then
2871 y_level = self.collisionbox[2] * 0.5
2872 end
2873
2874 -- what is mob standing in?
2875 self.standing_in = node_ok({
2876 x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
2877 -- print ("standing in " .. self.standing_in)
2878 end
2879
2880 -- check if falling, flying, floating
2881 falling(self, pos)
2882
2883 -- smooth rotation by ThomasMonroe314
2884
2885 if self.delay and self.delay > 0 then
2886
2887 local yaw = self.object:get_yaw()
2888
2889 if self.delay == 1 then
2890 yaw = self.target_yaw
2891 else
2892 local dif = abs(yaw - self.target_yaw)
2893
2894 if yaw > self.target_yaw then
2895
2896 if dif > pi then
2897 dif = 2 * pi - dif -- need to add
2898 yaw = yaw + dif / self.delay
2899 else
2900 yaw = yaw - dif / self.delay -- need to subtract
2901 end
2902
2903 elseif yaw < self.target_yaw then
2904
2905 if dif > pi then
2906 dif = 2 * pi - dif
2907 yaw = yaw - dif / self.delay -- need to subtract
2908 else
2909 yaw = yaw + dif / self.delay -- need to add
2910 end
2911 end
2912
2913 if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
2914 if yaw < 0 then yaw = yaw + (pi * 2) end
2915 end
2916
2917 self.delay = self.delay - 1
2918 self.object:set_yaw(yaw)
2919 end
2920
2921 -- end rotation
2922
2923 -- knockback timer
2924 if self.pause_timer > 0 then
2925
2926 self.pause_timer = self.pause_timer - dtime
2927
2928 return
2929 end
2930
2931 -- run custom function (defined in mob lua file)
2932 if self.do_custom then
2933
2934 -- when false skip going any further
2935 if self.do_custom(self, dtime) == false then
2936 return
2937 end
2938 end
2939
2940 -- attack timer
2941 self.timer = self.timer + dtime
2942
2943 if self.state ~= "attack" then
2944
2945 if self.timer < 1 then
2946 return
2947 end
2948
2949 self.timer = 0
2950 end
2951
2952 -- never go over 100
2953 if self.timer > 100 then
2954 self.timer = 1
2955 end
2956
2957 -- mob plays random sound at times
2958 if random(1, 100) == 1 then
2959 mob_sound(self, self.sounds.random)
2960 end
2961
2962 -- environmental damage timer (every 1 second)
2963 self.env_damage_timer = self.env_damage_timer + dtime
2964
2965 if (self.state == "attack" and self.env_damage_timer > 1)
2966 or self.state ~= "attack" then
2967
2968 self.env_damage_timer = 0
2969
2970 -- check for environmental damage (water, fire, lava etc.)
2971 do_env_damage(self)
2972
2973 -- node replace check (cow eats grass etc.)
2974 replace(self, pos)
2975 end
2976
2977 monster_attack(self)
2978
2979 npc_attack(self)
2980
2981 breed(self)
2982
2983 follow_flop(self)
2984
2985 do_states(self, dtime)
2986
2987 do_jump(self)
2988
2989 runaway_from(self)
2990
2991 end
2992
2993
2994 -- default function when mobs are blown up with TNT
2995 local do_tnt = function(obj, damage)
2996
2997 --print ("----- Damage", damage)
2998
2999 obj.object:punch(obj.object, 1.0, {
3000 full_punch_interval = 1.0,
3001 damage_groups = {fleshy = damage},
3002 }, nil)
3003
3004 return false, true, {}
3005 end
3006
3007
3008 mobs.spawning_mobs = {}
3009
3010 -- register mob entity
3011 function mobs:register_mob(name, def)
3012
3013 mobs.spawning_mobs[name] = true
3014
3015 minetest.register_entity(name, {
3016
3017 stepheight = def.stepheight or 1.1, -- was 0.6
3018 name = name,
3019 type = def.type,
3020 attack_type = def.attack_type,
3021 fly = def.fly,
3022 fly_in = def.fly_in or "air",
3023 owner = def.owner or "",
3024 order = def.order or "",
3025 on_die = def.on_die,
3026 do_custom = def.do_custom,
3027 jump_height = def.jump_height or 4, -- was 6
3028 drawtype = def.drawtype, -- DEPRECATED, use rotate instead
3029 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
3030 lifetimer = def.lifetimer or 180, -- 3 minutes
3031 hp_min = max(1, (def.hp_min or 5) * difficulty),
3032 hp_max = max(1, (def.hp_max or 10) * difficulty),
3033 physical = true,
3034 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3035 selectionbox = def.selectionbox or def.collisionbox,
3036 visual = def.visual,
3037 visual_size = def.visual_size or {x = 1, y = 1},
3038 mesh = def.mesh,
3039 makes_footstep_sound = def.makes_footstep_sound or false,
3040 view_range = def.view_range or 5,
3041 walk_velocity = def.walk_velocity or 1,
3042 run_velocity = def.run_velocity or 2,
3043 damage = max(0, (def.damage or 0) * difficulty),
3044 light_damage = def.light_damage or 0,
3045 water_damage = def.water_damage or 0,
3046 lava_damage = def.lava_damage or 0,
3047 suffocation = def.suffocation or 2,
3048 fall_damage = def.fall_damage or 1,
3049 fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
3050 drops = def.drops or {},
3051 armor = def.armor or 100,
3052 on_rightclick = def.on_rightclick,
3053 arrow = def.arrow,
3054 shoot_interval = def.shoot_interval,
3055 sounds = def.sounds or {},
3056 animation = def.animation,
3057 follow = def.follow,
3058 jump = def.jump ~= false,
3059 walk_chance = def.walk_chance or 50,
3060 attacks_monsters = def.attacks_monsters or false,
3061 group_attack = def.group_attack or false,
3062 passive = def.passive or false,
3063 knock_back = def.knock_back ~= false,
3064 blood_amount = def.blood_amount or 5,
3065 blood_texture = def.blood_texture or "mobs_blood.png",
3066 shoot_offset = def.shoot_offset or 0,
3067 floats = def.floats or 1, -- floats in water by default
3068 replace_rate = def.replace_rate,
3069 replace_what = def.replace_what,
3070 replace_with = def.replace_with,
3071 replace_offset = def.replace_offset or 0,
3072 on_replace = def.on_replace,
3073 timer = 0,
3074 env_damage_timer = 0, -- only used when state = "attack"
3075 tamed = false,
3076 pause_timer = 0,
3077 horny = false,
3078 hornytimer = 0,
3079 child = false,
3080 gotten = false,
3081 health = 0,
3082 reach = def.reach or 3,
3083 htimer = 0,
3084 texture_list = def.textures,
3085 child_texture = def.child_texture,
3086 docile_by_day = def.docile_by_day or false,
3087 time_of_day = 0.5,
3088 fear_height = def.fear_height or 0,
3089 runaway = def.runaway,
3090 runaway_timer = 0,
3091 pathfinding = def.pathfinding,
3092 immune_to = def.immune_to or {},
3093 explosion_radius = def.explosion_radius,
3094 explosion_damage_radius = def.explosion_damage_radius,
3095 explosion_timer = def.explosion_timer or 3,
3096 allow_fuse_reset = def.allow_fuse_reset ~= false,
3097 stop_to_explode = def.stop_to_explode ~= false,
3098 custom_attack = def.custom_attack,
3099 double_melee_attack = def.double_melee_attack,
3100 dogshoot_switch = def.dogshoot_switch,
3101 dogshoot_count = 0,
3102 dogshoot_count_max = def.dogshoot_count_max or 5,
3103 dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
3104 attack_animals = def.attack_animals or false,
3105 specific_attack = def.specific_attack,
3106 runaway_from = def.runaway_from,
3107 owner_loyal = def.owner_loyal,
3108 facing_fence = false,
3109 _cmi_is_mob = true,
3110
3111 on_spawn = def.on_spawn,
3112
3113 on_blast = def.on_blast or do_tnt,
3114
3115 on_step = mob_step,
3116
3117 do_punch = def.do_punch,
3118
3119 on_punch = mob_punch,
3120
3121 on_breed = def.on_breed,
3122
3123 on_grown = def.on_grown,
3124
3125 on_activate = function(self, staticdata, dtime)
3126 return mob_activate(self, staticdata, def, dtime)
3127 end,
3128
3129 get_staticdata = function(self)
3130 return mob_staticdata(self)
3131 end,
3132
3133 })
3134
3135 end -- END mobs:register_mob function
3136
3137
3138 -- count how many mobs of one type are inside an area
3139 local count_mobs = function(pos, type)
3140
3141 local num_type = 0
3142 local num_total = 0
3143 local objs = minetest.get_objects_inside_radius(pos, aoc_range)
3144
3145 for n = 1, #objs do
3146
3147 if not objs[n]:is_player() then
3148
3149 local obj = objs[n]:get_luaentity()
3150
3151 -- count mob type and add to total also
3152 if obj and obj.name and obj.name == type then
3153
3154 num_type = num_type + 1
3155 num_total = num_total + 1
3156
3157 -- add to total mobs
3158 elseif obj and obj.name and obj.health ~= nil then
3159
3160 num_total = num_total + 1
3161 end
3162 end
3163 end
3164
3165 return num_type, num_total
3166 end
3167
3168
3169 -- global functions
3170
3171 function mobs:spawn_abm_check(pos, node, name)
3172 -- global function to add additional spawn checks
3173 -- return true to stop spawning mob
3174 end
3175
3176
3177 local function player_near(pos, radius)
3178
3179 local objs = minetest.get_objects_inside_radius(pos, radius)
3180
3181 for n = 1, #objs do
3182
3183 if objs[n]:is_player() then
3184 return true
3185 end
3186 end
3187
3188 return false
3189 end
3190
3191
3192 local function daycheck(day_toggle)
3193
3194 if day_toggle ~= nil then
3195
3196 local tod = (minetest.get_timeofday() or 0) * 24000
3197
3198 if tod > 4500 and tod < 19500 then
3199
3200 if day_toggle == false then
3201 return false -- mob requires night
3202 end
3203 else
3204 if day_toggle == true then
3205 return false -- mob requires day
3206 end
3207 end
3208 end
3209
3210 return true -- mob doesn't care
3211 end
3212
3213
3214 local function is_protected(pos)
3215
3216 if not spawn_protected
3217 and minetest.is_protected(pos, "") then
3218 return true -- protected area
3219 end
3220
3221 return false -- mobs can spawn
3222 end
3223
3224
3225 local interval = 30
3226 local timer = 0
3227 local spawning_mobs = {}
3228
3229 minetest.register_globalstep(function(dtime)
3230
3231 if not mobs_spawn then
3232 return
3233 end
3234
3235 timer = timer + dtime
3236 if timer < interval then
3237 return
3238 end
3239 timer = 0
3240
3241 for _,player in ipairs(minetest.get_connected_players()) do
3242
3243 if player:get_hp() > 0 then
3244
3245 local pos = player:getpos()
3246 local area, pos2, light, obj, base
3247
3248 for _,mob in ipairs(spawning_mobs) do
3249
3250 area = nil
3251
3252 if minetest.registered_entities[mob.name]
3253 and random(1, mob.chance) == 1 then
3254
3255 area = minetest.find_nodes_in_area_under_air(
3256 {x = pos.x - 20, y = pos.y - 20, z = pos.z - 20},
3257 {x = pos.x + 20, y = pos.y + 20, z = pos.z + 20},
3258 mob.nodes)
3259 end
3260
3261 if area and #area > 0 then
3262
3263 pos2 = area[math.random(1, #area)]
3264 base = minetest.registered_entities[mob.name].collisionbox[5]
3265 pos2.y = pos2.y + 1 + base
3266
3267 light = minetest.get_node_light(pos2) or -1
3268
3269 if pos2.y >= mob.min_height
3270 and pos2.y <= mob.max_height
3271 and light >= mob.min_light
3272 and light <= mob.max_light
3273 and daycheck(mob.day_toggle)
3274 and minetest.find_node_near(pos2, 1, mob.neighbors)
3275 and count_mobs(pos2, mob.name) < mob.total
3276 and not player_near(pos2, 10)
3277 and not is_protected(pos2) then
3278
3279 print ("--- Spawned ", mob.name, minetest.pos_to_string(pos2), mob.chance)
3280
3281 obj = minetest.add_entity(pos2, mob.name)
3282
3283 if mob.on_spawn then
3284 mob.on_spawn(obj:get_luaentity(), pos2)
3285 end
3286 else
3287 print ("--- Cannot spawn ", mob.name)
3288 end
3289 end
3290 end
3291 end
3292 end
3293 end)
3294
3295
3296 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3297 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3298
3299 -- chance/spawn number override in minetest.conf for registered mob
3300 local numbers = minetest.settings:get(name)
3301
3302 if numbers then
3303 numbers = numbers:split(",")
3304 chance = tonumber(numbers[1]) or chance
3305 aoc = tonumber(numbers[2]) or aoc
3306
3307 if chance == 0 then
3308 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3309 return
3310 end
3311
3312 minetest.log("action",
3313 string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
3314 end
3315
3316 -- change old chance values to be more useable by new spawn routine
3317 if chance > 999 then
3318 chance = max(1, chance / 1000)
3319 end
3320
3321 -- adjust for mob chance multiplier
3322 chance = max(1, chance * mob_chance_multiplier)
3323
3324 -- add mob to table for spawning with routine above
3325 table.insert(spawning_mobs, {
3326 name = name,
3327 nodes = nodes,
3328 neighbors = neighbors,
3329 chance = chance,
3330 min_height = min_height,
3331 max_height = max_height,
3332 min_light = min_light,
3333 max_light = max_light,
3334 total = aoc,
3335 day_toggle = day_toggle,
3336 on_spawn = on_spawn,
3337 })
3338 end
3339
3340
3341 -- compatibility with older mob registration
3342 function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
3343
3344 mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
3345 chance, active_object_count, -31000, max_height, day_toggle)
3346 end
3347
3348
3349 -- MarkBu's spawn function
3350 function mobs:spawn(def)
3351
3352 mobs:spawn_specific(
3353 def.name,
3354 def.nodes or {"group:soil", "group:stone"},
3355 def.neighbors or {"air"},
3356 def.min_light or 0,
3357 def.max_light or 15,
3358 def.interval or 30,
3359 def.chance or 5000,
3360 def.active_object_count or 1,
3361 def.min_height or -31000,
3362 def.max_height or 31000,
3363 def.day_toggle,
3364 def.on_spawn
3365 )
3366 end
3367
3368
3369 -- register arrow for shoot attack
3370 function mobs:register_arrow(name, def)
3371
3372 if not name or not def then return end -- errorcheck
3373
3374 minetest.register_entity(name, {
3375
3376 physical = false,
3377 visual = def.visual,
3378 visual_size = def.visual_size,
3379 textures = def.textures,
3380 velocity = def.velocity,
3381 hit_player = def.hit_player,
3382 hit_node = def.hit_node,
3383 hit_mob = def.hit_mob,
3384 drop = def.drop or false, -- drops arrow as registered item when true
3385 collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
3386 timer = 0,
3387 switch = 0,
3388 owner_id = def.owner_id,
3389 rotate = def.rotate,
3390 automatic_face_movement_dir = def.rotate
3391 and (def.rotate - (pi / 180)) or false,
3392
3393 on_activate = def.on_activate,
3394
3395 on_step = def.on_step or function(self, dtime)
3396
3397 self.timer = self.timer + 1
3398
3399 local pos = self.object:get_pos()
3400
3401 if self.switch == 0
3402 or self.timer > 150
3403 or not within_limits(pos, 0) then
3404
3405 self.object:remove() ; -- print ("removed arrow")
3406
3407 return
3408 end
3409
3410 -- does arrow have a tail (fireball)
3411 if def.tail
3412 and def.tail == 1
3413 and def.tail_texture then
3414
3415 minetest.add_particle({
3416 pos = pos,
3417 velocity = {x = 0, y = 0, z = 0},
3418 acceleration = {x = 0, y = 0, z = 0},
3419 expirationtime = def.expire or 0.25,
3420 collisiondetection = false,
3421 texture = def.tail_texture,
3422 size = def.tail_size or 5,
3423 glow = def.glow or 0,
3424 })
3425 end
3426
3427 if self.hit_node then
3428
3429 local node = node_ok(pos).name
3430
3431 if minetest.registered_nodes[node].walkable then
3432
3433 self.hit_node(self, pos, node)
3434
3435 if self.drop == true then
3436
3437 pos.y = pos.y + 1
3438
3439 self.lastpos = (self.lastpos or pos)
3440
3441 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
3442 end
3443
3444 self.object:remove() ; -- print ("hit node")
3445
3446 return
3447 end
3448 end
3449
3450 if self.hit_player or self.hit_mob then
3451
3452 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
3453
3454 if self.hit_player
3455 and player:is_player() then
3456
3457 self.hit_player(self, player)
3458 self.object:remove() ; -- print ("hit player")
3459 return
3460 end
3461
3462 local entity = player:get_luaentity()
3463
3464 if entity
3465 and self.hit_mob
3466 and entity._cmi_is_mob == true
3467 and tostring(player) ~= self.owner_id
3468 and entity.name ~= self.object:get_luaentity().name then
3469
3470 self.hit_mob(self, player)
3471
3472 self.object:remove() ; --print ("hit mob")
3473
3474 return
3475 end
3476 end
3477 end
3478
3479 self.lastpos = pos
3480 end
3481 })
3482 end
3483
3484
3485 -- compatibility function
3486 function mobs:explosion(pos, radius)
3487 local self = {sounds = {}}
3488 self.sounds.explode = "tnt_explode"
3489 mobs:boom(self, pos, radius)
3490 end
3491
3492
3493 -- no damage to nodes explosion
3494 function mobs:safe_boom(self, pos, radius)
3495
3496 minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
3497 pos = pos,
3498 gain = 1.0,
3499 max_hear_distance = self.sounds and self.sounds.distance or 32
3500 })
3501
3502 entity_physics(pos, radius)
3503 effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
3504 end
3505
3506
3507 -- make explosion with protection and tnt mod check
3508 function mobs:boom(self, pos, radius)
3509
3510 if mobs_griefing
3511 and minetest.get_modpath("tnt") and tnt and tnt.boom
3512 and not minetest.is_protected(pos, "") then
3513
3514 tnt.boom(pos, {
3515 radius = radius,
3516 damage_radius = radius,
3517 sound = self.sounds and self.sounds.explode,
3518 explode_center = true,
3519 })
3520 else
3521 mobs:safe_boom(self, pos, radius)
3522 end
3523 end
3524
3525
3526 -- Register spawn eggs
3527
3528 -- Note: This also introduces the “spawn_egg” group:
3529 -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
3530 -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
3531 function mobs:register_egg(mob, desc, background, addegg, no_creative)
3532
3533 local grp = {spawn_egg = 1}
3534
3535 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3536 if creative and no_creative == true then
3537 grp.not_in_creative_inventory = 1
3538 end
3539
3540 local invimg = background
3541
3542 if addegg == 1 then
3543 invimg = "mobs_chicken_egg.png^(" .. invimg ..
3544 "^[mask:mobs_chicken_egg_overlay.png)"
3545 end
3546
3547 -- register new spawn egg containing mob information
3548 minetest.register_craftitem(mob .. "_set", {
3549
3550 description = S("@1 (Tamed)", desc),
3551 inventory_image = invimg,
3552 groups = {spawn_egg = 2, not_in_creative_inventory = 1},
3553 stack_max = 1,
3554
3555 on_place = function(itemstack, placer, pointed_thing)
3556
3557 local pos = pointed_thing.above
3558
3559 -- am I clicking on something with existing on_rightclick function?
3560 local under = minetest.get_node(pointed_thing.under)
3561 local def = minetest.registered_nodes[under.name]
3562 if def and def.on_rightclick then
3563 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3564 end
3565
3566 if pos
3567 and within_limits(pos, 0)
3568 and not minetest.is_protected(pos, placer:get_player_name()) then
3569
3570 if not minetest.registered_entities[mob] then
3571 return
3572 end
3573
3574 pos.y = pos.y + 1
3575
3576 local data = itemstack:get_metadata()
3577 local mob = minetest.add_entity(pos, mob, data)
3578 local ent = mob:get_luaentity()
3579
3580 -- set owner if not a monster
3581 if ent.type ~= "monster" then
3582 ent.owner = placer:get_player_name()
3583 ent.tamed = true
3584 end
3585
3586 -- since mob is unique we remove egg once spawned
3587 itemstack:take_item()
3588 end
3589
3590 return itemstack
3591 end,
3592 })
3593
3594
3595 -- register old stackable mob egg
3596 minetest.register_craftitem(mob, {
3597
3598 description = desc,
3599 inventory_image = invimg,
3600 groups = grp,
3601
3602 on_place = function(itemstack, placer, pointed_thing)
3603
3604 local pos = pointed_thing.above
3605
3606 -- am I clicking on something with existing on_rightclick function?
3607 local under = minetest.get_node(pointed_thing.under)
3608 local def = minetest.registered_nodes[under.name]
3609 if def and def.on_rightclick then
3610 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3611 end
3612
3613 if pos
3614 and within_limits(pos, 0)
3615 and not minetest.is_protected(pos, placer:get_player_name()) then
3616
3617 if not minetest.registered_entities[mob] then
3618 return
3619 end
3620
3621 pos.y = pos.y + 1
3622
3623 local mob = minetest.add_entity(pos, mob)
3624 local ent = mob:get_luaentity()
3625
3626 -- don't set owner if monster or sneak pressed
3627 if ent.type ~= "monster"
3628 and not placer:get_player_control().sneak then
3629 ent.owner = placer:get_player_name()
3630 ent.tamed = true
3631 end
3632
3633 -- if not in creative then take item
3634 if not mobs.is_creative(placer:get_player_name()) then
3635 itemstack:take_item()
3636 end
3637 end
3638
3639 return itemstack
3640 end,
3641 })
3642
3643 end
3644
3645
3646 -- capture critter (thanks to blert2112 for idea)
3647 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
3648
3649 if self.child
3650 or not clicker:is_player()
3651 or not clicker:get_inventory() then
3652 return false
3653 end
3654
3655 -- get name of clicked mob
3656 local mobname = self.name
3657
3658 -- if not nil change what will be added to inventory
3659 if replacewith then
3660 mobname = replacewith
3661 end
3662
3663 local name = clicker:get_player_name()
3664 local tool = clicker:get_wielded_item()
3665
3666 -- are we using hand, net or lasso to pick up mob?
3667 if tool:get_name() ~= ""
3668 and tool:get_name() ~= "mobs:net"
3669 and tool:get_name() ~= "mobs:lasso" then
3670 return false
3671 end
3672
3673 -- is mob tamed?
3674 if self.tamed == false
3675 and force_take == false then
3676
3677 minetest.chat_send_player(name, S("Not tamed!"))
3678
3679 return true -- false
3680 end
3681
3682 -- cannot pick up if not owner
3683 if self.owner ~= name
3684 and force_take == false then
3685
3686 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
3687
3688 return true -- false
3689 end
3690
3691 if clicker:get_inventory():room_for_item("main", mobname) then
3692
3693 -- was mob clicked with hand, net, or lasso?
3694 local chance = 0
3695
3696 if tool:get_name() == "" then
3697 chance = chance_hand
3698
3699 elseif tool:get_name() == "mobs:net" then
3700
3701 chance = chance_net
3702
3703 tool:add_wear(4000) -- 17 uses
3704
3705 clicker:set_wielded_item(tool)
3706
3707 elseif tool:get_name() == "mobs:lasso" then
3708
3709 chance = chance_lasso
3710
3711 tool:add_wear(650) -- 100 uses
3712
3713 clicker:set_wielded_item(tool)
3714
3715 end
3716
3717 -- calculate chance.. add to inventory if successful?
3718 if chance > 0 and random(1, 100) <= chance then
3719
3720 -- default mob egg
3721 local new_stack = ItemStack(mobname)
3722
3723 -- add special mob egg with all mob information
3724 -- unless 'replacewith' contains new item to use
3725 if not replacewith then
3726
3727 new_stack = ItemStack(mobname .. "_set")
3728
3729 local tmp = {}
3730
3731 for _,stat in pairs(self) do
3732 local t = type(stat)
3733 if t ~= "function"
3734 and t ~= "nil"
3735 and t ~= "userdata" then
3736 tmp[_] = self[_]
3737 end
3738 end
3739
3740 local data_str = minetest.serialize(tmp)
3741
3742 new_stack:set_metadata(data_str)
3743 end
3744
3745 local inv = clicker:get_inventory()
3746
3747 if inv:room_for_item("main", new_stack) then
3748 inv:add_item("main", new_stack)
3749 else
3750 minetest.add_item(clicker:get_pos(), new_stack)
3751 end
3752
3753 self.object:remove()
3754
3755 mob_sound(self, "default_place_node_hard")
3756
3757 elseif chance ~= 0 then
3758 minetest.chat_send_player(name, S("Missed!"))
3759
3760 mob_sound(self, "mobs_swing")
3761 end
3762 end
3763
3764 return true
3765 end
3766
3767
3768 -- protect tamed mob with rune item
3769 function mobs:protect(self, clicker)
3770
3771 local name = clicker:get_player_name()
3772 local tool = clicker:get_wielded_item()
3773
3774 if tool:get_name() ~= "mobs:protector" then
3775 return false
3776 end
3777
3778 if self.tamed == false then
3779 minetest.chat_send_player(name, S("Not tamed!"))
3780 return true -- false
3781 end
3782
3783 if self.protected == true then
3784 minetest.chat_send_player(name, S("Already protected!"))
3785 return true -- false
3786 end
3787
3788 if not mobs.is_creative(clicker:get_player_name()) then
3789 tool:take_item() -- take 1 protection rune
3790 clicker:set_wielded_item(tool)
3791 end
3792
3793 self.protected = true
3794
3795 local pos = self.object:get_pos()
3796 pos.y = pos.y + self.collisionbox[2] + 0.5
3797
3798 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3799
3800 mob_sound(self, "mobs_spell")
3801
3802 return true
3803 end
3804
3805
3806 local mob_obj = {}
3807 local mob_sta = {}
3808
3809 -- feeding, taming and breeding (thanks blert2112)
3810 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
3811
3812 if not self.follow then
3813 return false
3814 end
3815
3816 -- can eat/tame with item in hand
3817 if follow_holding(self, clicker) then
3818
3819 -- if not in creative then take item
3820 if not mobs.is_creative(clicker:get_player_name()) then
3821
3822 local item = clicker:get_wielded_item()
3823
3824 item:take_item()
3825
3826 clicker:set_wielded_item(item)
3827 end
3828
3829 -- increase health
3830 self.health = self.health + 4
3831
3832 if self.health >= self.hp_max then
3833
3834 self.health = self.hp_max
3835
3836 if self.htimer < 1 then
3837
3838 minetest.chat_send_player(clicker:get_player_name(),
3839 S("@1 at full health (@2)",
3840 self.name:split(":")[2], tostring(self.health)))
3841
3842 self.htimer = 5
3843 end
3844 end
3845
3846 self.object:set_hp(self.health)
3847
3848 update_tag(self)
3849
3850 -- make children grow quicker
3851 if self.child == true then
3852
3853 self.hornytimer = self.hornytimer + 20
3854
3855 return true
3856 end
3857
3858 -- feed and tame
3859 self.food = (self.food or 0) + 1
3860 if self.food >= feed_count then
3861
3862 self.food = 0
3863
3864 if breed and self.hornytimer == 0 then
3865 self.horny = true
3866 end
3867
3868 self.gotten = false
3869
3870 if tame then
3871
3872 if self.tamed == false then
3873 minetest.chat_send_player(clicker:get_player_name(),
3874 S("@1 has been tamed!",
3875 self.name:split(":")[2]))
3876 end
3877
3878 self.tamed = true
3879
3880 if not self.owner or self.owner == "" then
3881 self.owner = clicker:get_player_name()
3882 end
3883 end
3884
3885 -- make sound when fed so many times
3886 mob_sound(self, self.sounds.random)
3887 end
3888
3889 return true
3890 end
3891
3892 local item = clicker:get_wielded_item()
3893
3894 -- if mob has been tamed you can name it with a nametag
3895 if item:get_name() == "mobs:nametag"
3896 and clicker:get_player_name() == self.owner then
3897
3898 local name = clicker:get_player_name()
3899
3900 -- store mob and nametag stack in external variables
3901 mob_obj[name] = self
3902 mob_sta[name] = item
3903
3904 local tag = self.nametag or ""
3905
3906 minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
3907 .. default.gui_bg
3908 .. default.gui_bg_img
3909 .. "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape(S("Enter name:")) .. ";" .. tag .. "]"
3910 .. "button_exit[2.5,3.5;3,1;mob_rename;" .. minetest.formspec_escape(S("Rename")) .. "]")
3911 end
3912
3913 return false
3914 end
3915
3916
3917 -- inspired by blockmen's nametag mod
3918 minetest.register_on_player_receive_fields(function(player, formname, fields)
3919
3920 -- right-clicked with nametag and name entered?
3921 if formname == "mobs_nametag"
3922 and fields.name
3923 and fields.name ~= "" then
3924
3925 local name = player:get_player_name()
3926
3927 if not mob_obj[name]
3928 or not mob_obj[name].object then
3929 return
3930 end
3931
3932 -- make sure nametag is being used to name mob
3933 local item = player:get_wielded_item()
3934
3935 if item:get_name() ~= "mobs:nametag" then
3936 return
3937 end
3938
3939 -- limit name entered to 64 characters long
3940 if string.len(fields.name) > 64 then
3941 fields.name = string.sub(fields.name, 1, 64)
3942 end
3943
3944 -- update nametag
3945 mob_obj[name].nametag = fields.name
3946
3947 update_tag(mob_obj[name])
3948
3949 -- if not in creative then take item
3950 if not mobs.is_creative(name) then
3951
3952 mob_sta[name]:take_item()
3953
3954 player:set_wielded_item(mob_sta[name])
3955 end
3956
3957 -- reset external variables
3958 mob_obj[name] = nil
3959 mob_sta[name] = nil
3960 end
3961 end)
3962
3963
3964 -- compatibility function for old entities to new modpack entities
3965 function mobs:alias_mob(old_name, new_name)
3966
3967 -- spawn egg
3968 minetest.register_alias(old_name, new_name)
3969
3970 -- entity
3971 minetest.register_entity(":" .. old_name, {
3972
3973 physical = false,
3974
3975 on_step = function(self)
3976
3977 if minetest.registered_entities[new_name] then
3978 minetest.add_entity(self.object:get_pos(), new_name)
3979 end
3980
3981 self.object:remove()
3982 end
3983 })
3984 end
3030 'hp_max' has the maximum health value the mob can spawn with.
3131 'armor' holds strength of mob, 100 is normal, lower is more powerful
3232 and needs more hits and better weapons to kill.
33 'passive' when true allows animals to defend themselves when hit,
33 'passive' when false allows animals to defend themselves when hit,
3434 otherwise they amble onwards.
3535 'walk_velocity' is the speed that your mob can walk around.
3636 'run_velocity' is the speed your mob can run with, usually when attacking.
37 'stand_chance' has a 0-100 chance value your mob will stand from walking.
3738 'walk_chance' has a 0-100 chance value your mob will walk from standing,
3839 set to 0 for jumping mobs only.
40 'randomly_turn' if set to false then mob will not turn to face player or
41 randomly turn while walking or standing.
3942 'jump' when true allows your mob to jump updwards.
4043 'jump_height' holds the height your mob can jump, 0 to disable jumping.
4144 'stepheight' height of a block that your mob can easily walk up onto,
4346 'fly' when true allows your mob to fly around instead of walking.
4447 'fly_in' holds the node name that the mob flies (or swims) around
4548 in e.g. "air" or "default:water_source".
49 'keep_flying' when true mobs like birds no longer stop and stand.
50 'stay_near' when set allows mobs the chance to stay around certain nodes.
51 'nodes' string or table of nodes to stay nearby e.g. "farming:straw"
52 'chance' chance of searching for above node(s), default is 10.
4653 'runaway' if true causes animals to turn and run away when hit.
4754 'pushable' when true mobs can be pushed by player or other mobs.
4855 'view_range' how many nodes in distance the mob can see a player.
4956 'damage' how many health points the mob does to a player or another
5057 mob when melee attacking.
58 'damage_group' group in which damage is dealt, dedaults to "fleshy".
59 'damage_texture_modifier' applies texture modifier on hit e.g "^[brighten"
5160 'knock_back' when true has mobs falling backwards when hit, the greater
5261 the damage the more they move back.
5362 'fear_height' is how high a cliff or edge has to be before the mob stops
5665 'fall_damage' when true causes falling to inflict damage.
5766 'water_damage' holds the damage per second infliced to mobs when standing in
5867 water.
68 'air_damage' holds damage per second inflicted to mob when standing in air.
5969 'lava_damage' holds the damage per second inflicted to mobs when standing
60 in lava or fire or an ignition source.
70 in lava.
71 'fire_damage' holds the damage per second inflicted to mobs when standing
72 in fire.
73
6174 'light_damage' holds the damage per second inflicted to mobs when light
6275 level is between the min and max values below
6376 'light_damage_min' minimum light value when mob is affected (default: 14)
6477 'light_damage_max' maximum light value when mob is affected (default: 15)
65 'suffocation' when true causes mobs to suffocate inside solid blocks.
78 'suffocation' when > 0 mobs will suffocate inside solid blocks and will be
79 hurt by the value given every second (0 to disable).
6680 'floats' when set to 1 mob will float in water, 0 has them sink.
6781 'follow' mobs follow player when holding any of the items which appear
6882 on this table, the same items can be fed to a mob to tame or
69 breed e.g. {"farming:wheat", "default:apple"}
83 breed e.g. {"farming:wheat", "default:apple", "group:fish"}
7084
7185 'reach' is how far the mob can attack player when standing
7286 nearby, default is 3 nodes.
8296 punches when nearby.
8397 'group_attack' when true has same mob type grouping together to attack
8498 offender.
99 'group_helper' string containing mob name that attacks alongside
100 current mob when group attacking.
101 mob is attacking in groups.
85102 'attack_type' tells the api what a mob does when attacking the player
86103 or another mob:
87104 'dogfight' is a melee attack when player is within mob reach.
105122 continue chasing.
106123 'arrow' holds the pre-defined arrow object to shoot when
107124 attacking.
125 'arrow_override' function that allows tweaking of arrow entity from
126 inside mob definition (self) passed to function.
108127 'dogshoot_switch' allows switching between attack types by using timers
109128 (1 for shoot, 2 for dogfight)
110129 'dogshoot_count_max' contains how many seconds before switching from
118137 e.g. {"player", "mobs_animal:chicken"}.
119138 'runaway_from' contains a table with mob names to run away from, add
120139 "player" to list to runaway from player also.
140 'ignore_invisibility' When true mob will still be able to see and attack
141 player even if invisible (invisibility mod only).
121142 'blood_amount' contains the number of blood droplets to appear when
122143 mob is hit.
123144 'blood_texture' has the texture name to use for droplets e.g.
125146 'pathfinding' set to 1 for mobs to use pathfinder feature to locate
126147 player, set to 2 so they can build/break also (only
127148 works with dogfight attack and when 'mobs_griefing'
128 in minetest.conf is not false).
149 in minetest.conf is not false). Adding {unbreakable=1}
150 to node groups stops them being broken by mobs.
129151 'immune_to' is a table that holds specific damage when being hit by
130152 certain items e.g.
131153 {"default:sword_wood", 0} -- causes no damage.
152174 'min' minimum number of items dropped, set to 0 for rare drops.
153175 'max' maximum number of items dropped.
154176 Note: If weapon has {fire=1} damage group set then cooked items will drop.
177 Note2: A function can now be passed which can also return drops table, e.g.
178 drops = function(pos)
179 -- do something
180 return { {name = "farming:bread"}, {name = "default:dirt", chance = 2} }
181 end
155182
156183 'visual' holds the look of the mob you wish to create:
157184 'cube' looks like a normal node
169196 'child_texture' holds the texture table for when baby mobs are used.
170197 'gotten_texture' holds the texture table for when self.gotten value is
171198 true, used for milking cows or shearing sheep.
199 'texture_mods' holds a string which overlays a texture on top of the
200 mob texture e.g. "^saddle.png"
172201 'mesh' holds the name of the external object used for mob model
173202 e.g. "mobs_cow.b3d"
174203 'gotten_mesh" holds the name of the external object used for when
175204 self.gotten is true for mobs.
176205 'rotate' custom model rotation, 0 = front, 90 = side, 180 = back,
177206 270 = other side.
207 'glow' has mob glow without light source, 0 to 15 or nil to disable
178208 'double_melee_attack' when true has the api choose between 'punch' and
179209 'punch2' animations. [DEPRECATED]
180210
181 'animation' holds a table containing animation names and settings for use with mesh models:
211 'animation' holds a table containing animation names and settings for use with
212 mesh models:
182213 'stand_start' start frame for when mob stands still.
183214 'stand_end' end frame of stand animation.
184215 'stand_speed' speed of animation in frames per second.
204235 'die_end'
205236 'die_speed'
206237 'die_loop' when set to false stops the animation looping.
238 'die_rotate' if true mob spins during death animation.
207239
208240 Using '_loop = false' setting will stop any of the above animations from
209241 looping.
243275 If false is returned, the mob will not replace the node.
244276
245277 By default, replacing sets self.gotten to true and resets the object
246 properties.
278 properties. (DEPRECATED, use on_replace to make changes).
247279
248280
249281 Custom Definition Functions
272304 time_from_last_punch, tool_capabilities, direction), return
273305 false to stop punch damage and knockback from taking place.
274306 'custom_attack' when set this function is called instead of the normal mob
275 melee attack, parameters are (self, to_attack).
276 'on_die' a function that is called when mob is killed (self, pos)
307 melee attack, parameters are (self, to_attack) and if true
308 is returned normal attack function continued.
309 'on_die' a function that is called when mob is killed (self, pos), also
310 has access to self.cause_of_death table.
311 'on_flop' function called when flying or swimmimng mob is no longer in
312 air/water, (self) paramater and return true to skip the built
313 in api flop feature.
277314 'do_custom' a custom function that is called every tick while mob is
278315 active and which has access to all of the self.* variables
279316 e.g. (self.health for health or self.standing_in for node
305342 'self.order' set to "follow" or "stand" so that npc will follow owner
306343 or stand it's ground
307344 'self.nametag' contains the name of the mob which it can show above
345 'self.state' Current mob state.
346 "stand": no movement (except turning around)
347 "walk": walk or move around aimlessly
348 "attack": chase and attack enemy
349 "runaway": flee from target
350 "flop": bounce around aimlessly
351 (for swimming mobs that have stranded)
352 "die": during death
353
354
355 Adding Mobs in World
356 --------------------
357
358 mobs:add_mob(pos, {
359 name = "mobs_animal:chicken",
360 child = true,
361 owner = "singleplayer",
362 nametag = "Bessy",
363 ignore_count = true -- ignores mob count per map area
364 })
365
366 Returns false if mob could not be added, returns mob object if spawned ok.
367
368
369 Removing Mob from World
370 -----------------------
371
372 mobs:remove(self, decrease)
373
374 Removes mob 'self' from the world and if 'decrease' is true then the mob counter
375 will also be decreased by one.
308376
309377
310378 Spawning Mobs in World
311379 ----------------------
312380
313 mobs:register_spawn(name, nodes, max_light, min_light, chance,
314 active_object_count, max_height, day_toggle)
315
316 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
317 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
318
319 These functions register a spawn algorithm for the mob. Without this function
320 the call the mobs won't spawn.
381 mobs:spawn({
382 name = "mobs_monster:tree_monster",
383 nodes = {"group:leaves"},
384 max_light = 7,
385 })
386
387 Spawn functions require the following settings, some of which already have a
388 default setting and can be omitted:
321389
322390 'name' is the name of the animal/monster
323391 'nodes' is a list of nodenames on that the animal/monster can
324 spawn on top of
392 spawn on top of (defaults to {"group:dirt", "group:stone"}
325393 'neighbors' is a list of nodenames on that the animal/monster will
326 spawn beside (default is {"air"} for
327 mobs:register_spawn)
328 'max_light' is the maximum of light
329 'min_light' is the minimum of light
330 'interval' is same as in register_abm() (default is 30 for
331 mobs:register_spawn)
332 'chance' is same as in register_abm()
394 spawn beside (default is {"air"})
395 'interval' is same as in register_abm() (default is 30)
396 'chance' is same as in register_abm() (default is 5000)
397 'min_light' is the minimum light level (default is 0)
398 'max_light' is the maximum light (default is 15)
399 'min_height' is the minimum height a mob can spawn (default: -31000)
400 'max_height' is the maximum height a mob can spawn (default is 31000)
333401 'active_object_count' number of this type of mob to spawn at one time inside
334 map area
335 'min_height' is the minimum height the mob can spawn
336 'max_height' is the maximum height the mob can spawn
402 map area (default is 1)
337403 'day_toggle' true for day spawning, false for night or nil for
338404 anytime
339405 'on_spawn' is a custom function which runs after mob has spawned
340406 and gives self and pos values.
407 'on_map_load' when true mobs will have a chance of spawning only
408 when new areas of map are loaded, interval will not be
409 used.
410
411 The older spawn functions are still active and working but have no defaults like
412 the mobs:spawn, so it is recommended to use the above instead.
413
414 mobs:register_spawn(name, nodes, max_light, min_light, chance,
415 active_object_count, max_height, day_toggle)
416
417 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
418 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
341419
342420 A simpler way to handle mob spawns has been added with the mobs:spawn(def)
343421 command which uses above names to make settings clearer:
344
345 mobs:spawn({name = "mobs_monster:tree_monster",
346 nodes = {"group:leaves"},
347 max_light = 7,
348 })
349422
350423
351424 For each mob that spawns with this function is a field in mobs.spawning_mobs.
363436 'pos' holds the position of the spawning mob
364437 'node' contains the node the mob is spawning on top of
365438 'name' is the name of the animal/monster
439
440
441 Particle Effects
442 ----------------
443
444 mobs:effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
445
446 This function provides a quick way to spawn particles as an effect.
447
448 'pos' center position of particle effect.
449 'amount' how many particles.
450 'texture' texture filename to use for effect.
451 'min_size' smallest particle size.
452 'max_size' largest particle size.
453 'radius' how far particles spread outward from center.
454 'gravity' gravity applied to particles once they spawn.
455 'glow' number between 1 and 15 for glowing particles.
456 'fall' when true particles fall, false has them rising, nil has them scatter.
366457
367458
368459 Making Arrows
377468 'visual' same is in minetest.register_entity()
378469 'visual_size' same is in minetest.register_entity()
379470 'textures' same is in minetest.register_entity()
471 'physical' same is in minetest.register_entity() [default: false]
472 'collide_with_objects' same as above
380473 'velocity' the velocity of the arrow
381474 'drop' if set to true any arrows hitting a node will drop as item
382475 'hit_player' a function that is called when the arrow hits a player;
385478 'hit_mob' a function that is called when the arrow hits a mob;
386479 this function should hurt the mob, the parameters are
387480 (self, player)
481 'hit_object' a function that is called when the arrow hits an object;
482 this function parameters are (self, player)
388483 'hit_node' a function that is called when the arrow hits a node, the
389484 parameters are (self, pos, node)
390485 'tail' when set to 1 adds a trail or tail to mob arrows
398493 'on_step' is a custom function when arrow is active, nil for
399494 default.
400495 'on_punch' is a custom function when arrow is punched, nil by default
401 'collisionbox' is hitbox table for arrow, {0,0,0,0,0,0} by default.
496 'collisionbox' is hitbox table for arrow, {-.1,-.1,-.1,.1,.1,.1} by default.
497 'lifetime' contains float value for how many seconds arrow exists in
498 world before being removed (default is 4.5 seconds).
402499
403500
404501 Spawn Eggs
406503
407504 mobs:register_egg(name, description, background, addegg, no_creative)
408505
409 This function registers a spawn egg which can be used by admin to properly spawn in a mob.
506 This function registers a spawn egg which can be used to properly spawn in a mob.
507 Animals are spawned as tamed unless sneak/shift is held while spawning.
410508
411509 'name' this is the name of your new mob to spawn e.g. "mob:sheep"
412510 'description' the name of the new egg you are creating e.g. "Spawn Sheep"
452550 'force_take' take mob by force, even if tamed (true or false)
453551 'replacewith' once captured replace mob with this item instead (overrides
454552 new mob eggs with saved information)
553
554 mobs:force_capture(self, clicker)
555
556 Same as above but does no checks, it simply captures any and all mobs and places
557 inside a spawn egg containing all of the mob information.
455558
456559
457560 Feeding and Taming/Breeding
565668 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1}
566669
567670
568 mobs:line_of_sight(self, pos1, pos2, stepsize)
671 mobs:line_of_sight(self, pos1, pos2, stepsize) [DEPRECATED]
569672
570673 This function is for use within the mobs definition for special use cases and
571674 returns true if a mob can see the player or victim.
574677 'pos1' position of mob
575678 'pos2' position of vistim or player
576679 'stepsize' usually set to 1
680
681 Use this instead:
682
683 mob_class:line_of_sight(pos1, pos2, stepsize)
684
685
686 mobs:can_spawn(pos, name)
687
688 This function checks the surrounding area at [pos] to see if there is enough empty
689 space to spawn mob [name], if so then a new position is returned for use,
690 otherwise nil is returned.
577691
578692
579693 External Settings for "minetest.conf"
586700 is false)
587701 'mobs_spawn_protected' if set to false then mobs will not spawn in protected
588702 areas (default is true)
703 'mobs_spawn_monster_protected' if set to false then monsters will not spawn in
704 protected areas (default is true)
589705 'remove_far_mobs' if true then untamed mobs that are outside players
590706 visual range will be removed (default is true)
591707 'mobname' can change specific mob chance rate (0 to disable) and
604720 'mobs_griefing' when false mobs cannot break blocks when using either
605721 pathfinding level 2, replace functions or mobs:boom
606722 function.
723 'mob_nospawn_range' Minimum range a mob can spawn near player (def: 12)
724 'mob_active_limit' Number of active mobs in game, 0 for unlimited
725 'mob_area_spawn' When true will check surrounding area the size of the
726 mob for obstructions before spawning, otherwise it
727 defaults to checking the height of the mob only.
728 'mob_smooth_rotate' Enables smooth rotation when mobs turn by default.
607729
608730 Players can override the spawn chance for each mob registered by adding a line
609731 to their minetest.conf file with a new value, the lower the value the more each
610732 mob will spawn e.g.
611733
612 mobs_animal:sheep_chance 11000
613 mobs_monster:sand_monster_chance 100
734 mobs_animal:sheep 11000
735 mobs_monster:sand_monster 100
736
737 ...you can also change how many of a certain mob appear in an active mapblock by
738 adding a comma and then a new value e.g.
739
740 mobs_animal:cow = 8000,4 <-- 4 cows per mapblock at 8000 spawn chance
741 mobs_monster:dirt_monster = ,20 <-- 20 dirt monsters per mapblock
614742
615743
616744 Rideable Horse Example Mob
622750 visual_size = {x = 1.20, y = 1.20},
623751 mesh = "mobs_horse.x",
624752 collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
625 animation = {
753 animation = {
626754 speed_normal = 15,
627755 speed_run = 30,
628756 stand_start = 25,
721849 if inv:room_for_item("main", "mobs:saddle") then
722850 inv:add_item("main", "mobs:saddle")
723851 else
724 minetest.add_item(clicker.getpos(), "mobs:saddle")
852 minetest.add_item(clicker.get_pos(), "mobs:saddle")
725853 end
726854
727855 -- attach player to horse
44 minetest.register_craftitem("mobs:nametag", {
55 description = S("Name Tag"),
66 inventory_image = "mobs_nametag.png",
7 groups = {flammable = 2},
7 groups = {flammable = 2, nametag = 1}
88 })
99
1010 if minetest.get_modpath("dye") and minetest.get_modpath("farming") then
1111 minetest.register_craft({
12 type = "shapeless",
1312 output = "mobs:nametag",
14 recipe = {"default:paper", "dye:black", "farming:string"},
13 recipe = {{"default:paper", "dye:black", "farming:string"}}
1514 })
1615 end
1716
1918 minetest.register_craftitem("mobs:leather", {
2019 description = S("Leather"),
2120 inventory_image = "mobs_leather.png",
22 groups = {flammable = 2},
21 groups = {flammable = 2, leather = 1}
2322 })
2423
2524 -- raw meat
2726 description = S("Raw Meat"),
2827 inventory_image = "mobs_meat_raw.png",
2928 on_use = minetest.item_eat(3),
30 groups = {food_meat_raw = 1, flammable = 2},
29 groups = {food_meat_raw = 1, flammable = 2}
3130 })
3231
3332 -- cooked meat
3534 description = S("Meat"),
3635 inventory_image = "mobs_meat.png",
3736 on_use = minetest.item_eat(8),
38 groups = {food_meat = 1, flammable = 2},
37 groups = {food_meat = 1, flammable = 2}
3938 })
4039
4140 minetest.register_craft({
4241 type = "cooking",
4342 output = "mobs:meat",
4443 recipe = "mobs:meat_raw",
45 cooktime = 5,
44 cooktime = 5
4645 })
4746
4847 -- lasso
4948 minetest.register_tool("mobs:lasso", {
5049 description = S("Lasso (right-click animal to put in inventory)"),
5150 inventory_image = "mobs_magic_lasso.png",
52 groups = {flammable = 2},
51 groups = {flammable = 2}
5352 })
5453
5554 if minetest.get_modpath("farming") then
5857 recipe = {
5958 {"farming:string", "", "farming:string"},
6059 {"", "default:diamond", ""},
61 {"farming:string", "", "farming:string"},
60 {"farming:string", "", "farming:string"}
6261 }
6362 })
6463 end
6968 minetest.register_tool("mobs:net", {
7069 description = S("Net (right-click animal to put in inventory)"),
7170 inventory_image = "mobs_net.png",
72 groups = {flammable = 2},
71 groups = {flammable = 2}
7372 })
7473
7574 if minetest.get_modpath("farming") then
7877 recipe = {
7978 {"group:stick", "", "group:stick"},
8079 {"group:stick", "", "group:stick"},
81 {"farming:string", "group:stick", "farming:string"},
80 {"farming:string", "group:stick", "farming:string"}
8281 }
8382 })
8483 end
8786 minetest.register_tool("mobs:shears", {
8887 description = S("Steel Shears (right-click to shear)"),
8988 inventory_image = "mobs_shears.png",
90 groups = {flammable = 2},
91 })
92
93 minetest.register_craft({
94 output = 'mobs:shears',
95 recipe = {
96 {'', 'default:steel_ingot', ''},
97 {'', 'group:stick', 'default:steel_ingot'},
89 groups = {flammable = 2}
90 })
91
92 minetest.register_craft({
93 output = "mobs:shears",
94 recipe = {
95 {"", "default:steel_ingot", ""},
96 {"", "group:stick", "default:steel_ingot"}
9897 }
9998 })
10099
102101 minetest.register_craftitem("mobs:protector", {
103102 description = S("Mob Protection Rune"),
104103 inventory_image = "mobs_protector.png",
105 groups = {flammable = 2},
104 groups = {flammable = 2}
106105 })
107106
108107 minetest.register_craft({
110109 recipe = {
111110 {"default:stone", "default:stone", "default:stone"},
112111 {"default:stone", "default:goldblock", "default:stone"},
113 {"default:stone", "default:stone", "default:stone"},
112 {"default:stone", "default:stone", "default:stone"}
113 }
114 })
115
116 -- level 2 protection rune
117 minetest.register_craftitem("mobs:protector2", {
118 description = S("Mob Protection Rune (Level 2)"),
119 inventory_image = "mobs_protector2.png",
120 groups = {flammable = 2}
121 })
122
123 minetest.register_craft({
124 output = "mobs:protector2",
125 recipe = {
126 {"mobs:protector", "default:mese_crystal", "mobs:protector"},
127 {"default:mese_crystal", "default:diamondblock", "default:mese_crystal"},
128 {"mobs:protector", "default:mese_crystal", "mobs:protector"}
114129 }
115130 })
116131
118133 minetest.register_craftitem("mobs:saddle", {
119134 description = S("Saddle"),
120135 inventory_image = "mobs_saddle.png",
121 groups = {flammable = 2},
136 groups = {flammable = 2, saddle = 1}
122137 })
123138
124139 minetest.register_craft({
126141 recipe = {
127142 {"mobs:leather", "mobs:leather", "mobs:leather"},
128143 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
129 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
130 }
131 })
144 {"mobs:leather", "default:steel_ingot", "mobs:leather"}
145 }
146 })
147
148
149 -- make sure we can register fences
150 if minetest.get_modpath("default") and default.register_fence then
132151
133152 -- mob fence (looks like normal fence but collision is 2 high)
134153 default.register_fence("mobs:fence_wood", {
141160 type = "fixed",
142161 fixed = {
143162 {-0.5, -0.5, -0.5, 0.5, 1.9, 0.5},
144 },
163 }
164 }
165 })
166 end
167
168 -- mob fence top (has enlarged collisionbox to stop mobs getting over)
169 minetest.register_node("mobs:fence_top", {
170 description = S("Mob Fence Top"),
171 drawtype = "nodebox",
172 tiles = {"default_wood.png"},
173 paramtype = "light",
174 is_ground_content = false,
175 groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
176 sounds = default.node_sound_wood_defaults(),
177 node_box = {
178 type = "fixed",
179 fixed = {-0.2, -0.5, -0.2, 0.2, 0, 0.2}
145180 },
146 })
147
148 -- mob fence top (has enlarged collisionbox to stop mobs getting over)
149 minetest.register_node("mobs:fence_top", {
150 description = S("Mob Fence Top"),
151 drawtype = "nodebox",
152 tiles = {"default_wood.png"},
153 paramtype = "light",
154 is_ground_content = false,
155 groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
156 sounds = default.node_sound_wood_defaults(),
157 node_box = {
158 type = "fixed",
159 fixed = {-0.2, -0.5, -0.2, 0.2, 0, 0.2},
160 },
161 collision_box = {
162 type = "fixed",
163 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4},
164 },
165 selection_box = {
166 type = "fixed",
167 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4},
168 },
181 collision_box = {
182 type = "fixed",
183 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4}
184 },
185 selection_box = {
186 type = "fixed",
187 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4}
188 }
169189 })
170190
171191 minetest.register_craft({
172192 output = "mobs:fence_top 12",
173193 recipe = {
174194 {"group:wood", "group:wood", "group:wood"},
175 {"", "default:fence_wood", ""},
176 }
177 })
195 {"", "default:fence_wood", ""}
196 }
197 })
198
178199
179200 -- items that can be used as fuel
180201 minetest.register_craft({
181202 type = "fuel",
182203 recipe = "mobs:nametag",
183 burntime = 3,
204 burntime = 3
184205 })
185206
186207 minetest.register_craft({
187208 type = "fuel",
188209 recipe = "mobs:lasso",
189 burntime = 7,
210 burntime = 7
190211 })
191212
192213 minetest.register_craft({
193214 type = "fuel",
194215 recipe = "mobs:net",
195 burntime = 8,
216 burntime = 8
196217 })
197218
198219 minetest.register_craft({
199220 type = "fuel",
200221 recipe = "mobs:leather",
201 burntime = 4,
222 burntime = 4
202223 })
203224
204225 minetest.register_craft({
205226 type = "fuel",
206227 recipe = "mobs:saddle",
207 burntime = 7,
228 burntime = 7
208229 })
209230
210231 minetest.register_craft({
211232 type = "fuel",
212233 recipe = "mobs:fence_wood",
213 burntime = 7,
234 burntime = 7
214235 })
215236
216237 minetest.register_craft({
217238 type = "fuel",
218239 recipe = "mobs:fence_top",
219 burntime = 2,
220 })
240 burntime = 2
241 })
242
243
244 -- this tool spawns same mob and adds owner, protected, nametag info
245 -- then removes original entity, this is used for fixing any issues.
246
247 local tex_obj
248
249 minetest.register_tool(":mobs:mob_reset_stick", {
250 description = S("Mob Reset Stick"),
251 inventory_image = "default_stick.png^[colorize:#ff000050",
252 stack_max = 1,
253 groups = {not_in_creative_inventory = 1},
254
255 on_use = function(itemstack, user, pointed_thing)
256
257 if pointed_thing.type ~= "object" then
258 return
259 end
260
261 local obj = pointed_thing.ref
262
263 local control = user:get_player_control()
264 local sneak = control and control.sneak
265
266 -- spawn same mob with saved stats, with random texture
267 if obj and not sneak then
268
269 local self = obj:get_luaentity()
270 local obj2 = minetest.add_entity(obj:get_pos(), self.name)
271
272 if obj2 then
273
274 local ent2 = obj2:get_luaentity()
275
276 ent2.protected = self.protected
277 ent2.owner = self.owner
278 ent2.nametag = self.nametag
279 ent2.gotten = self.gotten
280 ent2.tamed = self.tamed
281 ent2.health = self.health
282 ent2.order = self.order
283
284 if self.child then
285 obj2:set_velocity({x = 0, y = self.jump_height, z = 0})
286 end
287
288 obj2:set_properties({nametag = self.nametag})
289
290 obj:remove()
291 end
292 end
293
294 -- display form to enter texture name ending in .png
295 if obj and sneak then
296
297 tex_obj = obj
298
299 -- get base texture
300 local bt = tex_obj:get_luaentity().base_texture[1]
301
302 if type(bt) ~= "string" then
303 bt = ""
304 end
305
306 local name = user:get_player_name()
307
308 minetest.show_formspec(name, "mobs_texture", "size[8,4]"
309 .. "field[0.5,1;7.5,0;name;"
310 .. minetest.formspec_escape(S("Enter texture:")) .. ";" .. bt .. "]"
311 .. "button_exit[2.5,3.5;3,1;mob_texture_change;"
312 .. minetest.formspec_escape(S("Change")) .. "]")
313 end
314 end
315 })
316
317 minetest.register_on_player_receive_fields(function(player, formname, fields)
318
319 -- right-clicked with nametag and name entered?
320 if formname == "mobs_texture"
321 and fields.name
322 and fields.name ~= "" then
323
324 -- does mob still exist?
325 if not tex_obj
326 or not tex_obj:get_luaentity() then
327 return
328 end
329
330 -- make sure nametag is being used to name mob
331 local item = player:get_wielded_item()
332
333 if item:get_name() ~= "mobs:mob_reset_stick" then
334 return
335 end
336
337 -- limit name entered to 64 characters long
338 if fields.name:len() > 64 then
339 fields.name = fields.name:sub(1, 64)
340 end
341
342 -- update texture
343 local self = tex_obj:get_luaentity()
344
345 self.base_texture = {fields.name}
346
347 tex_obj:set_properties({textures = {fields.name}})
348
349 -- reset external variable
350 tex_obj = nil
351 end
352 end)
353
354
355 -- Meat Block (thanks to painterlypack.net for allowing me to use these textures)
356 minetest.register_node("mobs:meatblock", {
357 description = S("Meat Block"),
358 tiles = {"mobs_meat_top.png", "mobs_meat_bottom.png", "mobs_meat_side.png"},
359 paramtype2 = "facedir",
360 groups = {choppy = 1, oddly_breakable_by_hand = 1, flammable = 2},
361 sounds = default and default.node_sound_leaves_defaults(),
362 on_place = minetest.rotate_node,
363 on_use = minetest.item_eat(20)
364 })
365
366 minetest.register_craft({
367 output = "mobs:meatblock",
368 -- type = "shapeless",
369 recipe = {
370 {"group:food_meat", "group:food_meat", "group:food_meat"},
371 {"group:food_meat", "group:food_meat", "group:food_meat"},
372 {"group:food_meat", "group:food_meat", "group:food_meat"}
373 }
374 })
0 default
0 default?
11 tnt?
22 dye?
33 farming?
66 lucky_block?
77 cmi?
88 toolranks?
9 pathfinder?
00
11 local path = minetest.get_modpath("mobs")
2
3 -- Peaceful player privilege
4 minetest.register_privilege("peaceful_player", {
5 description = "Prevents Mobs Redo mobs from attacking player",
6 give_to_singleplayer = false
7 })
28
39 -- Mob API
410 dofile(path .. "/api.lua")
0 -- Support for the old multi-load method
1 dofile(minetest.get_modpath("intllib").."/init.lua")
02
1 -- Fallback functions for when `intllib` is not installed.
2 -- Code released under Unlicense <http://unlicense.org>.
3
4 -- Get the latest version of this file at:
5 -- https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua
6
7 local function format(str, ...)
8 local args = { ... }
9 local function repl(escape, open, num, close)
10 if escape == "" then
11 local replacement = tostring(args[tonumber(num)])
12 if open == "" then
13 replacement = replacement..close
14 end
15 return replacement
16 else
17 return "@"..open..num..close
18 end
19 end
20 return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
21 end
22
23 local gettext, ngettext
24 if minetest.get_modpath("intllib") then
25 if intllib.make_gettext_pair then
26 -- New method using gettext.
27 gettext, ngettext = intllib.make_gettext_pair()
28 else
29 -- Old method using text files.
30 gettext = intllib.Getter()
31 end
32 end
33
34 -- Fill in missing functions.
35
36 gettext = gettext or function(msgid, ...)
37 return format(msgid, ...)
38 end
39
40 ngettext = ngettext or function(msgid, msgid_plural, n, ...)
41 return format(n==1 and msgid or msgid_plural, ...)
42 end
43
44 return gettext, ngettext
22 # This file is distributed under the same license as the PACKAGE package.
33 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
44 #
5 #, fuzzy
56 msgid ""
67 msgstr ""
78 "Project-Id-Version: \n"
89 "Report-Msgid-Bugs-To: \n"
910 "POT-Creation-Date: 2017-07-29 09:13+0200\n"
10 "PO-Revision-Date: 2017-07-29 09:20+0200\n"
11 "PO-Revision-Date: 2020-08-13 21:20+0500\n"
12 "Last-Translator: Olivier Dragon <odragon@protonmail.com>\n"
1113 "Language-Team: \n"
14 "Language: fr\n"
1215 "MIME-Version: 1.0\n"
1316 "Content-Type: text/plain; charset=UTF-8\n"
1417 "Content-Transfer-Encoding: 8bit\n"
15 "X-Generator: Poedit 1.8.12\n"
16 "Last-Translator: fat115 <fat115@framasoft.org>\n"
17 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
18 "Language: fr\n"
1918
2019 #: api.lua
2120 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
6362
6463 #: crafts.lua
6564 msgid "Name Tag"
66 msgstr "Étiquette pour collier"
65 msgstr "Étiquette de collier"
6766
6867 #: crafts.lua
6968 msgid "Leather"
101100 msgid "Mob Fence"
102101 msgstr "Clôture à animaux"
103102
103 #: crafts.lua
104 msgid "Mob Fence Top"
105 msgstr "Haut de clôture à animaux"
106
104107 #: spawner.lua
105108 msgid "Mob Spawner"
106109 msgstr "Générateur de mob"
107110
108111 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr "Mob MinLumière MaxLumière Quantité DistanceJoueur"
112 msgid "(mob name) (min light) (max light) (amount) (player distance) (Y offset)"
113 msgstr "(Nom) (MinLumière) (MaxLumière) (Quantité) (Distance du Joueur) (Décalage en Y)"
111114
112115 #: spawner.lua
113116 msgid "Spawner Not Active (enter settings)"
114117 msgstr "Générateur non actif (entrez les paramètres)"
118
119 #: spawner.lua
120 msgid "Command:"
121 msgstr "Commande:"
115122
116123 #: spawner.lua
117124 msgid "Spawner Active (@1)"
77 "Project-Id-Version: Italian locale file for the Mobs Redo module\n"
88 "Report-Msgid-Bugs-To: \n"
99 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
10 "PO-Revision-Date: 2017-08-18 12:18+0100\n"
11 "Last-Translator: H4mlet <h4mlet@riseup.net>\n"
10 "PO-Revision-Date: 2020-05-11 13:33+0200\n"
11 "Last-Translator: Hamlet <hamlatgitlab@riseup.net>\n"
1212 "Language-Team: \n"
1313 "Language: it\n"
1414 "MIME-Version: 1.0\n"
1515 "Content-Type: text/plain; charset=UTF-8\n"
1616 "Content-Transfer-Encoding: 8bit\n"
1717 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
18 "X-Generator: Poedit 1.6.10\n"
18 "X-Generator: Poedit 2.2.1\n"
1919
2020 #: api.lua
2121 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr ""
22 msgstr "** Modalità pacifica attiva - non comparirà nessun mostro"
2323
2424 #: api.lua
2525 msgid "Mob has been protected!"
2727
2828 #: api.lua
2929 msgid "@1 (Tamed)"
30 msgstr "@1 (Addomesticat*)"
30 msgstr "@1 (Addomesticato)"
3131
3232 #: api.lua
3333 msgid "Not tamed!"
34 msgstr "Non addomesticat*!"
34 msgstr "Non addomesticato!"
3535
3636 #: api.lua
3737 msgid "@1 is owner!"
38 msgstr "Proprietari* @1!"
38 msgstr "Il padrone è @1!"
3939
4040 #: api.lua
4141 msgid "Missed!"
42 msgstr "Mancat*!"
42 msgstr "Mancato!"
4343
4444 #: api.lua
4545 msgid "Already protected!"
46 msgstr "Già protett*!"
46 msgstr "Già protetto!"
4747
4848 #: api.lua
4949 msgid "@1 at full health (@2)"
5151
5252 #: api.lua
5353 msgid "@1 has been tamed!"
54 msgstr "@1 è stat* addomesticat*!"
54 msgstr "@1 è stato addomesticato!"
5555
5656 #: api.lua
5757 msgid "Enter name:"
5959
6060 #: api.lua
6161 msgid "Rename"
62 msgstr "Rinominare"
62 msgstr "Rinomina"
6363
6464 #: crafts.lua
6565 msgid "Name Tag"
9999
100100 #: crafts.lua
101101 msgid "Mob Fence"
102 msgstr ""
102 msgstr "Recinzione per mob"
103103
104104 #: spawner.lua
105105 msgid "Mob Spawner"
127127 "distance[1-20] y_offset[-10 to 10]”"
128128 msgstr ""
129129 "Sintassi: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 per "
130 "disabilitare] distance[1-20] y_offset[-10 to 10]”"
130 "disabilitare] distance[1-20] y_offset[-10 fino a 10]”"
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 @1 (Tamed)=@1 (Gezähmt)
3 @1 at full health (@2)=@1 bei voller Gesundheit (@2)
4 @1 has been tamed!=@1 wurde gezähmt!
5 @1 is owner!=@1 ist der Besitzer!
6 #Active Mob Limit Reached!=
7 Already protected!=Bereits geschützt!
8 #Change=
9 #Command:=
10 Enter name:=Namen eingeben:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Lasso (Rechtsklick auf Tier, um es zu nehmen)
13 Leather=Leder
14 Meat=Fleisch
15 Missed!=Daneben!
16 Mob Fence=Kreaturen Zaun
17 #Mob Fence Top=
18 Mob Protection Rune=Kreaturschutzrune
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Kreaturenspawner-Einstellungen gescheitert!
22 Mob has been protected!=Kreatur wurde geschützt!
23 Name Tag=Namensschild
24 Net (right-click animal to put in inventory)=Netz (Rechtsklick auf Tier, um es zu nehmen)
25 Not tamed!=Nicht gezähmt!
26 Raw Meat=Rohes Fleisch
27 Rename=Umbenennen
28 Saddle=Sattel
29 Spawner Active (@1)=Spawner aktiv (@1)
30 Spawner Not Active (enter settings)=Nicht aktiv (Einstellungen eingeben)
31 Steel Shears (right-click to shear)=Stahlschere (Rechtsklick zum Scheren)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 #@1 (Tamed)=
3 #@1 at full health (@2)=
4 #@1 has been tamed!=
5 #@1 is owner!=
6 #Active Mob Limit Reached!=
7 #Already protected!=
8 #Change=
9 #Command:=
10 #Enter name:=
11 #Enter texture:=
12 #Lasso (right-click animal to put in inventory)=
13 #Leather=
14 #Meat=
15 #Missed!=
16 #Mob Fence=
17 #Mob Fence Top=
18 #Mob Protection Rune=
19 #Mob Reset Stick=
20 #Mob Spawner=
21 #Mob Spawner settings failed!=
22 #Mob has been protected!=
23 #Name Tag=
24 #Net (right-click animal to put in inventory)=
25 #Not tamed!=
26 #Raw Meat=
27 #Rename=
28 #Saddle=
29 #Spawner Active (@1)=
30 #Spawner Not Active (enter settings)=
31 #Steel Shears (right-click to shear)=
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 @1 (Tamed)=@1 (Domesticado)
3 @1 at full health (@2)=@1 con salud llena (@2)
4 @1 has been tamed!=@1 ha sido domesticado!
5 @1 is owner!=@1 es el dueño!
6 #Active Mob Limit Reached!=
7 Already protected!=Ya está protegido!
8 #Change=
9 #Command:=
10 Enter name:=Ingrese nombre:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Lazo (click derecho en animal para colocar en inventario)
13 Leather=Cuero
14 Meat=Carne
15 Missed!=Perdido!
16 #Mob Fence=
17 #Mob Fence Top=
18 Mob Protection Rune=Runa de protección de Mob
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Configuracion de generador de Mob falló!
22 Mob has been protected!=El mob ha sido protegido!
23 Name Tag=Nombrar etiqueta
24 Net (right-click animal to put in inventory)=Red (click derecho en animal para colocar en inventario)
25 Not tamed!=No domesticado!
26 Raw Meat=Carne cruda
27 Rename=Renombrar
28 Saddle=Montura
29 Spawner Active (@1)=Generador activo (@1)
30 Spawner Not Active (enter settings)=Generador no activo (ingrese config)
31 Steel Shears (right-click to shear)=Tijera de acero (click derecho para esquilar)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Mode pacifique activé - aucun monstre ne sera généré
2 @1 (Tamed)=@1 (apprivoisé)
3 @1 at full health (@2)=@1 est en pleine forme (@2)
4 @1 has been tamed!=@1 a été apprivoisé !
5 @1 is owner!=Appartient à @1 !
6 Active Mob Limit Reached!=Limite atteinte du nombre des êtres vivants actifs !
7 Already protected!=Déjà protégé !
8 Change=Changer
9 Command:=Commande :
10 Enter name:=Saisissez un nom :
11 Enter texture:=Saisissez une texture :
12 Lasso (right-click animal to put in inventory)=Lasso (clic droit sur l'animal pour le mettre dans l'inventaire)
13 Leather=Cuir
14 Meat=Viande
15 Missed!=Raté !
16 Mob Fence= Clôture à animaux
17 Mob Fence Top=Haut de clôture à animaux
18 Mob Protection Rune=Rune de protection des animaux
19 Mob Reset Stick=Baguette de réinitialisation des êtres vivants
20 Mob Spawner=Créateur d'êtres vivants
21 Mob Spawner settings failed!=Échec des paramètres du créateur d'être vivants !
22 Mob has been protected!=L'animal a été protégé !
23 Name Tag=Étiquette de collier
24 Net (right-click animal to put in inventory)=Filet (clic droit sur l'animal pour le mettre dans l'inventaire)
25 Not tamed!=Non-apprivoisé !
26 Raw Meat=Viande crue
27 Rename=Renommer
28 Saddle=Selle
29 Spawner Active (@1)=Créateur actif (@1)
30 Spawner Not Active (enter settings)=Créateur non actif (entrez les paramètres)
31 Steel Shears (right-click to shear)=Ciseaux à laine (clic droit pour tondre)
32 Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=Syntaxe : «name min_lumière[0-14] max_lumière[0-14] max_être_vivant_dans_région[0 pour désactiver] distance_joueur[1-20] décalage_y[-10 to 10]»
33 lifetimer expired, removed @1=Être immortel expiré ; @1 retiré
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Modalità pacifica attiva - non comparirà nessun mostro
2 @1 (Tamed)=@1 (Addomesticato)
3 @1 at full health (@2)=@1 in piena salute (@2)
4 @1 has been tamed!=@1 è stato addomesticato!
5 @1 is owner!=Il padrone è @1!
6 #Active Mob Limit Reached!=
7 Already protected!=Già protetto!
8 #Change=
9 #Command:=
10 Enter name:=Inserire il nome:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Lazo (click di destro per mettere l'animale nell'inventario)
13 Leather=Pelle
14 Meat=Carne
15 Missed!=Mancato!
16 Mob Fence=Recinzione per mob
17 #Mob Fence Top=
18 Mob Protection Rune=Runa di protezione per mob
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Impostazioni del generatore di mob fallite!
22 Mob has been protected!=Il mob è stato protetto!
23 Name Tag=Targhetta
24 Net (right-click animal to put in inventory)=Rete (click destro per mettere l'animale nell'inventario)
25 Not tamed!=Non addomesticato!
26 Raw Meat=Carne cruda
27 Rename=Rinomina
28 Saddle=Sella
29 Spawner Active (@1)=Generatore attivo (@1)
30 Spawner Not Active (enter settings)=Generatore inattivo (inserire le impostazioni)
31 Steel Shears (right-click to shear)=Cesoie d'acciaio (click destro per tosare)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Mod Aman Diaktifkan - Tiada Raksasa Akan Muncul
2 @1 (Tamed)=@1 (Jinak)
3 @1 at full health (@2)=Mata kesihatan @1 telah penuh (@2)
4 @1 has been tamed!=@1 telah dijinakkan!
5 @1 is owner!=Ini hak milik @1!
6 #Active Mob Limit Reached!=
7 Already protected!=Telah dilindungi!
8 #Change=
9 #Command:=
10 Enter name:=Masukkan nama:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Tanjul (klik-kanan haiwan untuk masukkan ke inventori)
13 Leather=Kulit
14 Meat=Daging Bakar
15 Missed!=Terlepas!
16 Mob Fence=Pagar Mob
17 #Mob Fence Top=
18 Mob Protection Rune=Rune Perlindungan Mob
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Penetapan Pewujud Mob gagal!
22 Mob has been protected!=Mob telah pun dilindungi!
23 Name Tag=Tanda Nama
24 Net (right-click animal to put in inventory)=Jaring (klik-kanan haiwan untuk masukkan ke inventori)
25 Not tamed!=Belum dijinakkan!
26 Raw Meat=Daging Mentah
27 Rename=Namakan semula
28 Saddle=Pelana
29 Spawner Active (@1)=Pewujud Mob Aktif (@1)
30 Spawner Not Active (enter settings)=Pewujud Mob Tidak Aktif (masukkan tetapan)
31 Steel Shears (right-click to shear)=Ketam Keluli (klik-kanan untuk mengetam bulu biri-biri)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 #@1 (Tamed)=
3 @1 at full health (@2)=@1 em plena saude (@2)
4 @1 has been tamed!=@1 foi domesticado!
5 @1 is owner!=Dono @1!
6 #Active Mob Limit Reached!=
7 #Already protected!=
8 #Change=
9 #Command:=
10 Enter name:=Insira um nome:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Laço (clique-direito no animal para por no inventario)
13 Leather=Couro
14 Meat=Carne
15 Missed!=Faltou!
16 #Mob Fence=
17 #Mob Fence Top=
18 #Mob Protection Rune=
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Configuraçao de Spawnador do Mob falhou!
22 #Mob has been protected!=
23 Name Tag=Etiqueta
24 Net (right-click animal to put in inventory)=Net (clique-direito no animal para por no inventario)
25 Not tamed!=Indomesticado!
26 Raw Meat=Carne crua
27 Rename=Renomear
28 #Saddle=
29 Spawner Active (@1)=Spawnador Ativo (@1)
30 Spawner Not Active (enter settings)=Spawnador Inativo (configurar)
31 Steel Shears (right-click to shear)=Tesoura de Aço (clique-direito para tosquiar)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Мирный модус активирован - монстры не спаунятся
2 @1 (Tamed)=@1 (Прирученный)
3 @1 at full health (@2)=@1 при полном здоровье (@2)
4 @1 has been tamed!=@1 приручен
5 @1 is owner!=@1 владелец
6 #Active Mob Limit Reached!=
7 Already protected!=Уже защищен!
8 #Change=
9 #Command:=
10 Enter name:=Введите имя:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Лассо (Правый клик - положить животное в инвентарь)
13 Leather=Кожа
14 Meat=Мясо
15 Missed!=Промазал!
16 Mob Fence=Забор от мобов
17 #Mob Fence Top=
18 Mob Protection Rune=Защитная руна мобов
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Настройки спаунера моба провалились
22 Mob has been protected!=Моб защищен!
23 Name Tag=Новый тэг
24 Net (right-click animal to put in inventory)=Сеть (Правый клик - положить животное в инвентарь)
25 Not tamed!=Не прирученный
26 Raw Meat=Сырое мясо
27 Rename=Переименовать
28 Saddle=Седло
29 Spawner Active (@1)=Активные спаунер (@1)
30 Spawner Not Active (enter settings)=Спаунер не активен (введите настройки)
31 Steel Shears (right-click to shear)=Ножницы (Правый клик - подстричь)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 #@1 (Tamed)=
3 @1 at full health (@2)=@1 tam canında (@2)
4 @1 has been tamed!=@1 tamamen evcilleştirilmiştir!
5 @1 is owner!=Sahibi @1!
6 #Active Mob Limit Reached!=
7 #Already protected!=
8 #Change=
9 #Command:=
10 Enter name:=İsim gir:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Kement (hayvana sağ tıklayarak envantere koy)
13 Leather=Deri
14 Meat=Et
15 Missed!=Kaçırdın!
16 Mob Fence=Canavar Yaratıcı
17 #Mob Fence Top=
18 #Mob Protection Rune=
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Yaratıcı ayarları uygulanamadı.
22 #Mob has been protected!=
23 Name Tag=İsim etiketi
24 Net (right-click animal to put in inventory)=Ağ (hayvana sağ tıklayarak envantere koy)
25 Not tamed!=Evcil değil!
26 Raw Meat=Çiğ et
27 Rename=Yeniden adlandır
28 #Saddle=
29 Spawner Active (@1)=Yaratıcı aktif (@1)
30 Spawner Not Active (enter settings)=Yaratıcı aktif değil (ayarlara gir)
31 Steel Shears (right-click to shear)=Çelik makas (sağ tıklayarak kes)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** 和平模式已激活——没有怪物会产生
2 @1 (Tamed)=@1(已驯服)
3 @1 at full health (@2)=@1已经满血(@2)
4 @1 has been tamed!=@1已经被驯服!
5 @1 is owner!=@1 是主人
6 #Active Mob Limit Reached!=
7 Already protected!=已经被保护!
8 #Change=
9 #Command:=
10 Enter name:=输入名称:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=套索(右键单击动物以放入物品栏)
13 Leather=皮革
14 Meat=肉
15 Missed!=没抓住!
16 Mob Fence=Mob 栅栏
17 #Mob Fence Top=
18 Mob Protection Rune=Mob 保护符文
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Mob 孵化器设置失败!
22 Mob has been protected!=Mob 已经被保护了!
23 Name Tag=名称标签
24 Net (right-click animal to put in inventory)=网(右键单击动物以放入物品栏)
25 Not tamed!=没有驯服!
26 Raw Meat=生肉
27 Rename=重新命名
28 Saddle=鞍
29 Spawner Active (@1)=孵化器正在运转(@1)
30 Spawner Not Active (enter settings)=孵化器未使用(输入设置)
31 Steel Shears (right-click to shear)=钢剪(右键单击以剪切)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** 和平模式已激活——沒有怪物會產生
2 @1 (Tamed)=@1(已馴服)
3 @1 at full health (@2)=@1已經滿血(@2)
4 @1 has been tamed!=@1已經被馴服!
5 @1 is owner!=@1 是主人
6 #Active Mob Limit Reached!=
7 Already protected!=已經被保護!
8 #Change=
9 #Command:=
10 Enter name:=輸入名稱:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=套索(右鍵單擊動物以放入物品欄)
13 Leather=皮革
14 Meat=肉
15 Missed!=沒抓住!
16 Mob Fence=Mob 柵欄
17 #Mob Fence Top=
18 Mob Protection Rune=Mob 保護符文
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Mob 孵化器設置失敗!
22 Mob has been protected!=Mob 已經被保護了!
23 Name Tag=名稱標籤
24 Net (right-click animal to put in inventory)=網(右鍵單擊動物以放入物品欄)
25 Not tamed!=沒有馴服!
26 Raw Meat=生肉
27 Rename=重新命名
28 Saddle=鞍
29 Spawner Active (@1)=孵化器正在運轉(@1)
30 Spawner Not Active (enter settings)=孵化器未使用(輸入設置)
31 Steel Shears (right-click to shear)=鋼剪(右鍵單擊以剪切)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
100100 msgid "Mob Fence"
101101 msgstr ""
102102
103 #: crafts.lua
104 msgid "Mob Fence Top"
105 msgstr ""
106
103107 #: spawner.lua
104108 msgid "Mob Spawner"
105109 msgstr ""
106110
107111 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
112 msgid "(mob name) (min light) (max light) (amount) (player distance) (Y offset)"
109113 msgstr ""
110114
111115 #: spawner.lua
112116 msgid "Spawner Not Active (enter settings)"
117 msgstr ""
118
119 #@ spawner.lua
120 msgid "Command:"
113121 msgstr ""
114122
115123 #: spawner.lua
0 # SOME DESCRIPTIVE TITLE.
1 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # This file is distributed under the same license as the PACKAGE package.
3 # IFRFSX<1079092922@qq.com>, 2020.
4 #
5 #, fuzzy
6 msgid ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: \n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
18
19 #: api.lua
20 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
21 msgstr "** 和平模式已激活——没有怪物会产生"
22
23 #: api.lua
24 msgid "Mob has been protected!"
25 msgstr "Mob 已经被保护了!"
26
27 #: api.lua
28 msgid "@1 (Tamed)"
29 msgstr "@1(已驯服)"
30
31 #: api.lua
32 msgid "Not tamed!"
33 msgstr "没有驯服!"
34
35 #: api.lua
36 msgid "@1 is owner!"
37 msgstr "@1 是主人"
38
39 #: api.lua
40 msgid "Missed!"
41 msgstr "没抓住!"
42
43 #: api.lua
44 msgid "Already protected!"
45 msgstr "已经被保护!"
46
47 #: api.lua
48 msgid "@1 at full health (@2)"
49 msgstr "@1已经满血(@2)"
50
51 #: api.lua
52 msgid "@1 has been tamed!"
53 msgstr "@1已经被驯服!"
54
55 #: api.lua
56 msgid "Enter name:"
57 msgstr "输入名称:"
58
59 #: api.lua
60 msgid "Rename"
61 msgstr "重新命名"
62
63 #: crafts.lua
64 msgid "Name Tag"
65 msgstr "名称标签"
66
67 #: crafts.lua
68 msgid "Leather"
69 msgstr "皮革"
70
71 #: crafts.lua
72 msgid "Raw Meat"
73 msgstr "生肉"
74
75 #: crafts.lua
76 msgid "Meat"
77 msgstr "肉"
78
79 #: crafts.lua
80 msgid "Lasso (right-click animal to put in inventory)"
81 msgstr "套索(右键单击动物以放入物品栏)"
82
83 #: crafts.lua
84 msgid "Net (right-click animal to put in inventory)"
85 msgstr "网(右键单击动物以放入物品栏)"
86
87 #: crafts.lua
88 msgid "Steel Shears (right-click to shear)"
89 msgstr "钢剪(右键单击以剪切)"
90
91 #: crafts.lua
92 msgid "Mob Protection Rune"
93 msgstr "Mob 保护符文"
94
95 #: crafts.lua
96 msgid "Saddle"
97 msgstr "鞍"
98
99 #: crafts.lua
100 msgid "Mob Fence"
101 msgstr "Mob 栅栏"
102
103 #: spawner.lua
104 msgid "Mob Spawner"
105 msgstr "Mob 孵化器"
106
107 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
109 msgstr "Mob/最小光量/最大光量/玩家距离"
110
111 #: spawner.lua
112 msgid "Spawner Not Active (enter settings)"
113 msgstr "孵化器未使用(输入设置)"
114
115 #: spawner.lua
116 msgid "Spawner Active (@1)"
117 msgstr "孵化器正在运转(@1)"
118
119 #: spawner.lua
120 msgid "Mob Spawner settings failed!"
121 msgstr "Mob 孵化器设置失败!"
122
123 #: spawner.lua
124 msgid ""
125 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
126 "distance[1-20] y_offset[-10 to 10]”"
127 msgstr ""
128 "语法: “物品名称 最小光亮[0-14] 最大光亮[0-14] 范围内的最大Mob数量[0 to disable] "
129 "距离[1-20] y_offset[-10 to 10]”"
0 # SOME DESCRIPTIVE TITLE.
1 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # This file is distributed under the same license as the PACKAGE package.
3 # IFRFSX<1079092922@qq.com>, 2020.
4 #
5 #, fuzzy
6 msgid ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: \n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
18
19 #: api.lua
20 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
21 msgstr "** 和平模式已激活——沒有怪物會產生"
22
23 #: api.lua
24 msgid "Mob has been protected!"
25 msgstr "Mob 已經被保護了!"
26
27 #: api.lua
28 msgid "@1 (Tamed)"
29 msgstr "@1(已馴服)"
30
31 #: api.lua
32 msgid "Not tamed!"
33 msgstr "沒有馴服!"
34
35 #: api.lua
36 msgid "@1 is owner!"
37 msgstr "@1 是主人"
38
39 #: api.lua
40 msgid "Missed!"
41 msgstr "沒抓住!"
42
43 #: api.lua
44 msgid "Already protected!"
45 msgstr "已經被保護!"
46
47 #: api.lua
48 msgid "@1 at full health (@2)"
49 msgstr "@1已經滿血(@2)"
50
51 #: api.lua
52 msgid "@1 has been tamed!"
53 msgstr "@1已經被馴服!"
54
55 #: api.lua
56 msgid "Enter name:"
57 msgstr "輸入名稱:"
58
59 #: api.lua
60 msgid "Rename"
61 msgstr "重新命名"
62
63 #: crafts.lua
64 msgid "Name Tag"
65 msgstr "名稱標籤"
66
67 #: crafts.lua
68 msgid "Leather"
69 msgstr "皮革"
70
71 #: crafts.lua
72 msgid "Raw Meat"
73 msgstr "生肉"
74
75 #: crafts.lua
76 msgid "Meat"
77 msgstr "肉"
78
79 #: crafts.lua
80 msgid "Lasso (right-click animal to put in inventory)"
81 msgstr "套索(右鍵單擊動物以放入物品欄)"
82
83 #: crafts.lua
84 msgid "Net (right-click animal to put in inventory)"
85 msgstr "網(右鍵單擊動物以放入物品欄)"
86
87 #: crafts.lua
88 msgid "Steel Shears (right-click to shear)"
89 msgstr "鋼剪(右鍵單擊以剪切)"
90
91 #: crafts.lua
92 msgid "Mob Protection Rune"
93 msgstr "Mob 保護符文"
94
95 #: crafts.lua
96 msgid "Saddle"
97 msgstr "鞍"
98
99 #: crafts.lua
100 msgid "Mob Fence"
101 msgstr "Mob 柵欄"
102
103 #: spawner.lua
104 msgid "Mob Spawner"
105 msgstr "Mob 孵化器"
106
107 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
109 msgstr "Mob/最小光量/最大光量/玩家距離"
110
111 #: spawner.lua
112 msgid "Spawner Not Active (enter settings)"
113 msgstr "孵化器未使用(輸入設置)"
114
115 #: spawner.lua
116 msgid "Spawner Active (@1)"
117 msgstr "孵化器正在運轉(@1)"
118
119 #: spawner.lua
120 msgid "Mob Spawner settings failed!"
121 msgstr "Mob 孵化器設置失敗!"
122
123 #: spawner.lua
124 msgid ""
125 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
126 "distance[1-20] y_offset[-10 to 10]”"
127 msgstr ""
128 "語法: “物品名稱 最小光亮[0-14] 最大光亮[0-14] 範圍內的最大Mob數量[0 to disable] "
129 "距離[1-20] y_offset[-10 to 10]”"
1212 {"dro", {"mobs:protector"}, 1},
1313 {"dro", {"mobs:fence_wood"}, 10},
1414 {"dro", {"mobs:fence_top"}, 12},
15 {"lig"},
15 {"lig"}
1616 })
1717 end
00 name = mobs
1 depends =
2 optional_depends = default, tnt, dye, farming, invisibility, intllib, lucky_block, cmi, toolranks, pathfinder
3 description = Adds a mob api for mods to add animals or monsters etc.
0
10 -- lib_mount by Blert2112 (edited by TenPlus1)
21
3 local enable_crash = false
4 local crash_threshold = 6.5 -- ignored if enable_crash=false
5
2 local abs, cos, floor, sin, sqrt, pi =
3 math.abs, math.cos, math.floor, math.sin, math.sqrt, math.pi
64 ------------------------------------------------------------------------------
75
86 --
108 --
119
1210 local node_ok = function(pos, fallback)
13
1411 fallback = fallback or mobs.fallback_node
1512
1613 local node = minetest.get_node_or_nil(pos)
4845
4946
5047 local function get_sign(i)
51
5248 i = i or 0
5349
5450 if i == 0 then
5551 return 0
5652 else
57 return i / math.abs(i)
53 return i / abs(i)
5854 end
5955 end
6056
6157
6258 local function get_velocity(v, yaw, y)
63
64 local x = -math.sin(yaw) * v
65 local z = math.cos(yaw) * v
59 local x = -sin(yaw) * v
60 local z = cos(yaw) * v
6661
6762 return {x = x, y = y, z = z}
6863 end
6964
7065
7166 local function get_v(v)
72 return math.sqrt(v.x * v.x + v.z * v.z)
67 return sqrt(v.x * v.x + v.z * v.z)
7368 end
7469
7570
8378
8479 local entity = attached_to:get_luaentity()
8580
86 if entity.driver
81 if entity and entity.driver
8782 and entity.driver == player then
88
8983 entity.driver = nil
9084 end
9185
9286 player:set_detach()
93 default.player_attached[player:get_player_name()] = false
87 player_api.player_attached[player:get_player_name()] = false
9488 player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
95 default.player_set_animation(player, "stand" , 30)
96 player:set_properties({visual_size = {x = 1, y = 1} })
89 player_api.set_animation(player, "stand", 30)
90 player:set_properties({visual_size = {x = 1, y = 1}})
9791
9892 end
9993
10094 -------------------------------------------------------------------------------
101
10295
10396 minetest.register_on_leaveplayer(function(player)
10497 force_detach(player)
10598 end)
10699
107100 minetest.register_on_shutdown(function()
101
108102 local players = minetest.get_connected_players()
103
109104 for i = 1, #players do
110105 force_detach(players[i])
111106 end
118113
119114 -------------------------------------------------------------------------------
120115
116 -- Just for correct detaching
117 local function find_free_pos(pos)
118
119 local check = {
120 {x = 1, y = 0, z = 0},
121 {x = 1, y = 1, z = 0},
122 {x = -1, y = 0, z = 0},
123 {x = -1, y = 1, z = 0},
124 {x = 0, y = 0, z = 1},
125 {x = 0, y = 1, z = 1},
126 {x = 0, y = 0, z = -1},
127 {x = 0, y = 1, z = -1}
128 }
129
130 for _, c in pairs(check) do
131
132 local npos = {x = pos.x + c.x, y = pos.y + c.y, z = pos.z + c.z}
133 local node = minetest.get_node_or_nil(npos)
134
135 if node and node.name then
136
137 local def = minetest.registered_nodes[node.name]
138
139 if def and not def.walkable and
140 def.liquidtype == "none" then
141 return npos
142 end
143 end
144 end
145
146 return pos
147 end
148
149 -------------------------------------------------------------------------------
150
121151 function mobs.attach(entity, player)
122
123 local attach_at, eye_offset = {}, {}
124152
125153 entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
126154 entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
130158 local rot_view = 0
131159
132160 if entity.player_rotation.y == 90 then
133 rot_view = math.pi/2
134 end
135
136 attach_at = entity.driver_attach_at
137 eye_offset = entity.driver_eye_offset
161 rot_view = pi / 2
162 end
163
164 local attach_at = entity.driver_attach_at
165 local eye_offset = entity.driver_eye_offset
166
138167 entity.driver = player
139168
140169 force_detach(player)
141170
142171 player:set_attach(entity.object, "", attach_at, entity.player_rotation)
143 default.player_attached[player:get_player_name()] = true
172 player_api.player_attached[player:get_player_name()] = true
144173 player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0})
145174
146175 player:set_properties({
151180 })
152181
153182 minetest.after(0.2, function()
154 default.player_set_animation(player, "sit" , 30)
183
184 if player and player:is_player() then
185 player_api.set_animation(player, "sit", 30)
186 end
155187 end)
156188
157 --player:set_look_yaw(entity.object:get_yaw() - rot_view)
158189 player:set_look_horizontal(entity.object:get_yaw() - rot_view)
159190 end
160191
161192
162 function mobs.detach(player, offset)
163
193 function mobs.detach(player)
164194 force_detach(player)
165195
166 default.player_set_animation(player, "stand" , 30)
167
168 local pos = player:get_pos()
169
170 pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
171
172196 minetest.after(0.1, function()
173 player:set_pos(pos)
197
198 if player and player:is_player() then
199
200 local pos = find_free_pos(player:get_pos())
201
202 pos.y = pos.y + 0.5
203
204 player:set_pos(pos)
205 end
174206 end)
175207 end
176208
177209
178210 function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
179211
180 local rot_steer, rot_view = math.pi/2, 0
212 local yaw = entity.object:get_yaw() or 0
213 local rot_view = 0
181214
182215 if entity.player_rotation.y == 90 then
183 rot_steer, rot_view = 0, math.pi/2
216 rot_view = pi / 2
184217 end
185218
186219 local acce_y = 0
191224 -- process controls
192225 if entity.driver then
193226
194 --print ("---velo", get_v(velo))
195
196227 local ctrl = entity.driver:get_player_control()
197228
198229 -- move forwards
210241 entity.v = entity.v - entity.accel / 10
211242 end
212243
213 -- fix mob rotation
214 entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
244 -- mob rotation
245 local horz
246
247 if entity.alt_turn == true then
248
249 horz = yaw
250
251 if ctrl.left then
252 horz = horz + 0.05
253
254 elseif ctrl.right then
255 horz = horz - 0.05
256 end
257 else
258 horz = entity.driver:get_look_horizontal() or 0
259 end
260
261 entity.object:set_yaw(horz - entity.rotate)
215262
216263 if can_fly then
217
218264 -- fly up
219265 if ctrl.jump then
266
220267 velo.y = velo.y + 1
268
221269 if velo.y > entity.accel then velo.y = entity.accel end
222270
223271 elseif velo.y > 0 then
272
224273 velo.y = velo.y - 0.1
274
225275 if velo.y < 0 then velo.y = 0 end
226276 end
227277
228278 -- fly down
229279 if ctrl.sneak then
280
230281 velo.y = velo.y - 1
282
231283 if velo.y < -entity.accel then velo.y = -entity.accel end
232284
233285 elseif velo.y < 0 then
286
234287 velo.y = velo.y + 0.1
288
235289 if velo.y > 0 then velo.y = 0 end
236290 end
237
238291 else
239
240292 -- jump
241293 if ctrl.jump then
242294
245297 acce_y = acce_y + (acce_y * 3) + 1
246298 end
247299 end
248
249300 end
250301 end
251302
258309
259310 return
260311 end
261
312
262313 -- set moving animation
263314 if moving_anim then
264315 mobs:set_animation(entity, moving_anim)
273324
274325 entity.object:set_velocity({x = 0, y = 0, z = 0})
275326 entity.v = 0
327
276328 return
277329 end
278330
283335 max_spd = entity.max_speed_forward
284336 end
285337
286 if math.abs(entity.v) > max_spd then
338 if abs(entity.v) > max_spd then
287339 entity.v = entity.v - get_sign(entity.v)
288340 end
289341
290342 -- Set position, velocity and acceleration
291343 local p = entity.object:get_pos()
292 local new_velo = {x = 0, y = 0, z = 0}
293 local new_acce = {x = 0, y = -9.8, z = 0}
344
345 if not p then return end
346
347 local new_acce = {x = 0, y = -9.81, z = 0}
294348
295349 p.y = p.y - 0.5
296350
314368 minetest.sound_play("default_punch", {
315369 object = entity.object,
316370 max_hear_distance = 5
317 })
371 }, true)
318372
319373 entity.object:punch(entity.object, 1.0, {
320374 full_punch_interval = 1.0,
325379 end
326380 end
327381
328 if entity.terrain_type == 2
329 or entity.terrain_type == 3 then
382 local terrain_type = entity.terrain_type
383
384 if terrain_type == 2 or terrain_type == 3 then
330385
331386 new_acce.y = 0
332387 p.y = p.y + 1
341396 new_acce.y = 5
342397 end
343398 else
344 if math.abs(velo.y) < 1 then
399 if abs(velo.y) < 1 then
400
345401 local pos = entity.object:get_pos()
346 pos.y = math.floor(pos.y) + 0.5
402
403 if not pos then return end
404
405 pos.y = floor(pos.y) + 0.5
347406 entity.object:set_pos(pos)
348407 velo.y = 0
349408 end
353412 end
354413 end
355414
356 new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y)
415 local new_velo = get_velocity(v, yaw - rot_view, velo.y)
416
357417 new_acce.y = new_acce.y + acce_y
358418
359419 entity.object:set_velocity(new_velo)
360420 entity.object:set_acceleration(new_acce)
361421
362 -- CRASH!
363 if enable_crash then
364
365 local intensity = entity.v2 - v
366
367 if intensity >= crash_threshold then
368
369 --print("----------- crash", intensity)
370
371 entity.object:punch(entity.object, 1.0, {
372 full_punch_interval = 1.0,
373 damage_groups = {fleshy = intensity}
374 }, nil)
375
376 end
377 end
378
379422 entity.v2 = v
380423 end
381424
382425
383426 -- directional flying routine by D00Med (edited by TenPlus1)
384
385 function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
427 function mobs.fly(entity, _, speed, shoots, arrow, moving_anim, stand_anim)
386428
387429 local ctrl = entity.driver:get_player_control()
388430 local velo = entity.object:get_velocity()
389431 local dir = entity.driver:get_look_dir()
390432 local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands
391 local rot_steer, rot_view = math.pi / 2, 0
392
393 if entity.player_rotation.y == 90 then
394 rot_steer, rot_view = 0, math.pi / 2
395 end
433
434 if not ctrl or not velo then return end
396435
397436 if ctrl.up then
398437 entity.object:set_velocity({
402441 })
403442
404443 elseif ctrl.down then
444
405445 entity.object:set_velocity({
406446 x = -dir.x * speed,
407 y = dir.y * speed + 2,
447 y = dir.y * speed + 2,
408448 z = -dir.z * speed
409449 })
410450
412452 entity.object:set_velocity({x = 0, y = -2, z = 0})
413453 end
414454
415 entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate)
455 entity.object:set_yaw(yaw + pi + pi / 2 - entity.rotate)
416456
417457 -- firing arrows
418458 if ctrl.LMB and ctrl.sneak and shoots then
424464 z = pos.z + 0 + dir.z * 2.5}, arrow)
425465
426466 local ent = obj:get_luaentity()
467
427468 if ent then
469
428470 ent.switch = 1 -- for mob specific arrows
429471 ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding
430472 local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6}
431 local yaw = entity.driver:get_look_horizontal()
432 obj:set_yaw(yaw + math.pi / 2)
473
474 yaw = entity.driver:get_look_horizontal()
475
476 obj:set_yaw(yaw + pi / 2)
433477 obj:set_velocity(vec)
434478 else
435479 obj:remove()
438482
439483 -- change animation if stopped
440484 if velo.x == 0 and velo.y == 0 and velo.z == 0 then
441
442485 mobs:set_animation(entity, stand_anim)
443486 else
444487 -- moving animation
0
1 MOBS REDO for MINETEST
2
3 Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
4
5
6 This mod contains the API only for adding your own mobs into the world, so please use the additional modpacks to add animals, monsters etc.
7
8
9 https://forum.minetest.net/viewtopic.php?f=11&t=9917
10
11
12 Crafts:
13
14 - Nametag (paper, black dye, string) can be used right-click on a tamed mob to give them a name.
15 - Nets can be used to right-click tamed mobs to pick them up and place inside inventory as a spawn egg.
16 - Magic Lasso is similar to nets but with a better chance of picking up larger mobs.
17 - Shears are used to right-click sheep and return 1-3 wool.
18 - Protection Rune lets you protect tamed mobs from harm by other players
19 - Mob Fence and Fence Top (to stop mobs escaping/glitching through fences)
20
21 Lucky Blocks: 9
22
23
24 Changelog:
25 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
26 - 1.46- Mobs only drop rare items when killed by player (drops.min = 0 makes them rare), code tweak, pathfinding no longer sees through walkable nodes
27 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
28 - 1.44- Added ToolRanks support for swords when attacking mobs
29 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
30 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
31 - 1.41- Mob pathfinding has been updated thanks to Elkien3
32 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
33 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
34 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
35 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
36 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
37 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
38 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
39 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
40 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs
41 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
42 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
43 - 1.29- Split original Mobs Redo into a modpack to make it easier to disable mob sets (animal, monster, npc) or simply use the Api itself for your own mod
44 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
45 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
46 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
47 - 1.25- Mobs no longer spawn within 12 blocks of player or despawn within same range, spawners now have player detection, Code tidy and tweak.
48 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
49 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
50 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
51 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
52 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
53 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
54 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
55 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
56 - 1.16- Mobs follow multiple items now, Npc's can breed
57 - 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility.
58 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
59 - 1.13- Added capture function (thanks blert2112) chance of picking up mob with hand; net; magic lasso, replaced some .x models with newer .b3d one's
60 - 1.12- Added animal ownership so that players cannot steal your tamed animals
61 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
62 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
63 - 1.09- reworked breeding routine, added mob rotation value, added footstep feature, added jumping mobs with sounds feature, added magic lasso for picking up animals
64 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
65 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
66 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
67 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
68 - 1.05- Added ExeterDad's bunny's which can be picked up and tamed with 4 carrots from farming redo or farming_plus, also shears added to get wool from sheep and lastly Jordach/BSD's kitten
69 - 1.04- Added mating for sheep, cows and hogs... feed animals to make horny and hope for a baby which is half size, will grow up quick though :)
70 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
71 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
72 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
73 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
74 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
75 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
76 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
77 - 0.6 - Api now supports multi-textured mobs, e.g oerkki, dungeon master, rats and chickens have random skins when spawning (sheep fix TODO), also new Honey block
78 - 0.5 - Mobs now float in water, die from falling, and some code improvements
79 - 0.4 - Dungeon Masters and Mese Monsters have much better aim due to shoot_offset, also they can both shoot through nodes that aren't walkable (flowers, grass etc) plus new sheep sound :)
80 - 0.3 - Added LOTT's Spider mob, made Cobwebs, added KPavel's Bee with Honey and Beehives (made texture), Warthogs now have sound and can be tamed, taming of shaved sheep or milked cow with 8 wheat so it will not despawn, many bug fixes :)
81 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
82 - 0.1 - Initial Release
0
1 MOBS REDO for MINETEST
2
3 Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
4
5
6 This mod contains the API only for adding your own mobs into the world, so please use the additional modpacks to add animals, monsters etc.
7
8
9 https://forum.minetest.net/viewtopic.php?f=11&t=9917
10
11
12 Crafts:
13
14 - Nametag (paper, black dye, string) can be used right-click on a tamed mob to give them a name.
15 - Nets can be used to right-click tamed mobs to pick them up and place inside inventory as a spawn egg.
16 - Magic Lasso is similar to nets but with a better chance of picking up larger mobs.
17 - Shears are used to right-click sheep and return 1-3 wool.
18 - Protection Rune lets you protect tamed mobs from harm by other players
19 - Mob Fence and Fence Top (to stop mobs escaping/glitching through fences)
20
21 Lucky Blocks: 9
22
23
24 Changelog:
25 - 1.56 - Added arrow_override function to mob definition to tweak arrow entity settings, tamed monsters no longer despawn when outside loaded map area.
26 - 1.55 - Add 'peaceful_player' privelage and setting so mobs don't attack specific players (thanks sfence), add support for MarkBu's pathfinder mod, remove need for default mod
27 - 1.54 - Simplified animal breeding function, added editable settings (thanks Wuzzy), Child mobs now take 20 mins to grow up, reverted to simple mob spawning with setting to use area checks, on_flop added, air_damage added.
28 - 1.53 - Added 'on_map_load' settings to mobs:spawn so that mobs will only spawn when new areas of map are loaded.
29 - 1.52 - Added 'mob_active_limit' in settings to set number of mobs in game,
30 (default is 0 for unlimited), removed {immortal} from mob armor, fluid viscocity slows mobs
31 - 1.51 - Added some node checks for dangerous nodes, jumping and falling tweaks, spawn area check (thx for idea wuzzy), re-enabled mob suffocation, add 'mob_nospawn_range' setting
32 - 1.50 - Added new line_of_sight function that uses raycasting if mt5.0 is found, (thanks Astrobe), dont spawn mobs if world anchor nearby (technic or simple_anchor mods), chinese local added
33 - 1.49- Added mobs:force_capture(self, player) function, api functions now use metatables thanks to bell07
34 - 1.48- Add mobs:set_velocity(self, velocity) global function
35 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
36 - 1.46- Mobs only drop rare items when killed by player (drops.min = 0 makes them rare), code tweak, pathfinding no longer sees through walkable nodes
37 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
38 - 1.44- Added ToolRanks support for swords when attacking mobs
39 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
40 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
41 - 1.41- Mob pathfinding has been updated thanks to Elkien3
42 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
43 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
44 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
45 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
46 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
47 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
48 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
49 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
50 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs
51 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
52 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
53 - 1.29- Split original Mobs Redo into a modpack to make it easier to disable mob sets (animal, monster, npc) or simply use the Api itself for your own mod
54 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
55 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
56 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
57 - 1.25- Mobs no longer spawn within 12 blocks of player or despawn within same range, spawners now have player detection, Code tidy and tweak.
58 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
59 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
60 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
61 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
62 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
63 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
64 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
65 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
66 - 1.16- Mobs follow multiple items now, Npc's can breed
67 - 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility.
68 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
69 - 1.13- Added capture function (thanks blert2112) chance of picking up mob with hand; net; magic lasso, replaced some .x models with newer .b3d one's
70 - 1.12- Added animal ownership so that players cannot steal your tamed animals
71 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
72 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
73 - 1.09- reworked breeding routine, added mob rotation value, added footstep feature, added jumping mobs with sounds feature, added magic lasso for picking up animals
74 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
75 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
76 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
77 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
78 - 1.05- Added ExeterDad's bunny's which can be picked up and tamed with 4 carrots from farming redo or farming_plus, also shears added to get wool from sheep and lastly Jordach/BSD's kitten
79 - 1.04- Added mating for sheep, cows and hogs... feed animals to make horny and hope for a baby which is half size, will grow up quick though :)
80 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
81 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
82 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
83 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
84 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
85 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
86 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
87 - 0.6 - Api now supports multi-textured mobs, e.g oerkki, dungeon master, rats and chickens have random skins when spawning (sheep fix TODO), also new Honey block
88 - 0.5 - Mobs now float in water, die from falling, and some code improvements
89 - 0.4 - Dungeon Masters and Mese Monsters have much better aim due to shoot_offset, also they can both shoot through nodes that aren't walkable (flowers, grass etc) plus new sheep sound :)
90 - 0.3 - Added LOTT's Spider mob, made Cobwebs, added KPavel's Bee with Honey and Beehives (made texture), Warthogs now have sound and can be tamed, taming of shaved sheep or milked cow with 8 wheat so it will not despawn, many bug fixes :)
91 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
92 - 0.1 - Initial Release
1212 # If false then Mobs no longer spawn inside player protected areas
1313 mobs_spawn_protected (Spawn Mobs in protected areas) bool true
1414
15 # If false then Monsters no longer spawn inside player protected areas
16 mobs_spawn_monster_protected (Spawn Monsters in protected areas) bool true
17
1518 # If true Mobs will be removed once a map chunk is out of view
1619 remove_far_mobs (Remove far Mobs) bool true
1720
2629
2730 # When false Mob no longer drop items when killed
2831 mobs_drop_items (Mob drops) bool true
32
33 # Sets minimum distance around player that mobs cannot spawn
34 mob_nospawn_range (Mob no-spawn range) float 12.0
35
36 # Sets maximum number of active mobs in game (0 for unlimited)
37 mob_active_limit (Mob Active Limit) float 0
38
39 # Enables area check when spawning mobs
40 mob_area_spawn (Mob Area Spawn) bool false
41
42 # Enable peaceful player attack prevention
43 enable_peaceful_player (Mobs do not attack peaceful player without reason) bool false
44
45 # Enable mobs smooth rotation
46 mob_smooth_rotate (Smooth rotation for mobs) bool true
00
1 -- intllib
2 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
1 local S = mobs.intllib
42
53 -- mob spawner
64
7 local spawner_default = "mobs_animal:pumba 10 15 0 0"
5 local spawner_default = "mobs_animal:pumba 10 15 0 0 0"
86
97 minetest.register_node("mobs:spawner", {
108 tiles = {"mob_spawner.png"},
1816
1917 local meta = minetest.get_meta(pos)
2018
19 -- setup formspec
20 local head = S("(mob name) (min light) (max light) (amount)"
21 .. " (player distance) (Y offset)")
22
2123 -- text entry formspec
2224 meta:set_string("formspec",
23 "field[text;" .. S("Mob MinLight MaxLight Amount PlayerDist") .. ";${command}]")
25 "size[10,3.5]"
26 .. "label[0.15,0.5;" .. minetest.formspec_escape(head) .. "]"
27 .. "field[1,2.5;8.5,0.8;text;" .. S("Command:")
28 .. ";${command}]")
29
2430 meta:set_string("infotext", S("Spawner Not Active (enter settings)"))
2531 meta:set_string("command", spawner_default)
2632 end,
5460 local pla = tonumber(comm[5]) -- player distance (0 to disable)
5561 local yof = tonumber(comm[6]) or 0 -- Y offset to spawn mob
5662
57 if mob and mob ~= "" and mobs.spawning_mobs[mob] == true
63 if mob and mob ~= "" and mobs.spawning_mobs[mob]
5864 and num and num >= 0 and num <= 10
5965 and mlig and mlig >= 0 and mlig <= 15
6066 and xlig and xlig >= 0 and xlig <= 15
61 and pla and pla >=0 and pla <= 20
67 and pla and pla >= 0 and pla <= 20
6268 and yof and yof > -10 and yof < 10 then
6369
6470 meta:set_string("command", fields.text)
6773 else
6874 minetest.chat_send_player(name, S("Mob Spawner settings failed!"))
6975 minetest.chat_send_player(name,
70 S("Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10]”"))
76 S("Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”"))
7177 end
72 end,
78 end
7379 })
7480
7581
116122 -- check objects inside 9x9 area around spawner
117123 local objs = minetest.get_objects_inside_radius(pos, 9)
118124 local count = 0
119 local ent = nil
125 local ent
120126
121127 -- count mob objects of same type in area
122 for k, obj in ipairs(objs) do
128 for _, obj in ipairs(objs) do
123129
124130 ent = obj:get_luaentity()
125131
137143 if pla > 0 then
138144
139145 local in_range = 0
140 local objs = minetest.get_objects_inside_radius(pos, pla)
146 local objsp = minetest.get_objects_inside_radius(pos, pla)
141147
142 for _,oir in pairs(objs) do
148 for _, oir in pairs(objsp) do
143149
144150 if oir:is_player() then
145151
155161 end
156162 end
157163
164 -- set medium mob usually spawns in (defaults to air)
165 local reg = minetest.registered_entities[mob].fly_in
166
167 if not reg or type(reg) == "string" then
168 reg = {(reg or "air")}
169 end
170
158171 -- find air blocks within 5 nodes of spawner
159172 local air = minetest.find_nodes_in_area(
160173 {x = pos.x - 5, y = pos.y + yof, z = pos.z - 5},
161 {x = pos.x + 5, y = pos.y + yof, z = pos.z + 5},
162 {"air"})
174 {x = pos.x + 5, y = pos.y + yof, z = pos.z + 5}, reg)
163175
164176 -- spawn in random air block
165177 if air and #air > 0 then
175187 minetest.add_entity(pos2, mob)
176188 end
177189 end
178
179190 end
180191 })
Binary diff not shown