Codebase list minetest-mod-mobs-redo / 60c3e7a
Import upstream version 20181016+git20210722.1.9f46182 Debian Janitor 2 years ago
33 changed file(s) with 3480 addition(s) and 5623 deletion(s). Raw diff Collapse all Expand all
+2150
-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 = "20210722",
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
22402722
22412723
22422724 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2725 function mob_class:falling(pos)
2726
2727 if self.fly or self.disable_falling then
22462728 return
22472729 end
22482730
22492731 -- floating in water (or falling)
22502732 local v = self.object:get_velocity()
22512733
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
2734 -- sanity check
2735 if not v then return end
2736
2737 local fall_speed = self.fall_speed
2738
2739 -- in water then use liquid viscosity for float/sink speed
2740 if self.floats == 1 and self.standing_in
2741 and minetest.registered_nodes[self.standing_in].groups.liquid then
2742
2743 local visc = min(
2744 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2745
2746 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2747 fall_speed = -1.2 / visc
22862748 else
22872749
22882750 -- fall damage onto solid ground
22972759
22982760 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992761
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2762 if self:check_for_death({type = "fall"}) then
2763 return true
23022764 end
23032765 end
23042766
23052767 self.old_y = self.object:get_pos().y
23062768 end
23072769 end
2770
2771 -- fall at set speed
2772 self.object:set_acceleration({x = 0, y = fall_speed, z = 0})
23082773 end
23092774
23102775
23122777 local tr = minetest.get_modpath("toolranks")
23132778
23142779 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2780 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162781
23172782 -- mob health check
23182783 if self.health <= 0 then
2319 return
2784 return true
23202785 end
23212786
23222787 -- custom punch function
23232788 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2789 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2790 return true
23262791 end
23272792
23282793 -- error checking when mod profiling is enabled
23292794 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
2795
2796 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2797
2798 return true
2799 end
2800
2801 -- is mob protected
2802 if self.protected then
2803
2804 -- did player hit mob and if so is it in protected area
2805 if hitter:is_player() then
2806
2807 local player_name = hitter:get_player_name()
2808
2809 if player_name ~= self.owner
2810 and minetest.is_protected(self.object:get_pos(), player_name) then
2811
2812 minetest.chat_send_player(hitter:get_player_name(),
2813 S("Mob has been protected!"))
2814
2815 return true
2816 end
2817
2818 -- if protection is on level 2 then dont let arrows harm mobs
2819 elseif self.protected == 2 then
2820
2821 local ent = hitter and hitter:get_luaentity()
2822
2823 if ent and ent._is_arrow then
2824
2825 return true -- arrow entity
2826
2827 elseif not ent then
2828
2829 return true -- non entity
2830 end
2831 end
23392832 end
23402833
23412834 local weapon = hitter:get_wielded_item()
23672860 end
23682861
23692862 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2863 * tmp * ((armor[group] or 0) / 100.0)
23712864 end
23722865 end
23732866
23772870 if self.immune_to[n][1] == weapon_def.name then
23782871
23792872 damage = self.immune_to[n][2] or 0
2873
23802874 break
23812875
2382 -- if "all" then no tool does damage unless it's specified in list
2876 -- if "all" then no tools deal damage unless it's specified in list
23832877 elseif self.immune_to[n][1] == "all" then
23842878 damage = self.immune_to[n][2] or 0
23852879 end
23862880 end
23872881
2882 --print("Mob Damage is", damage)
2883
23882884 -- healing
23892885 if damage <= -1 then
2886
23902887 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2888
2889 return true
2890 end
23952891
23962892 if use_cmi
23972893 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2894 return true
23992895 end
24002896
24012897 -- add weapon wear
24132909 end
24142910 end
24152911
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
2912 if tr and weapon_def.original_description then
2913 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24202914 else
24212915 weapon:add_wear(wear)
24222916 end
24262920 -- only play hit sound and show blood effects if damage is 1 or over
24272921 if damage >= 1 then
24282922
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
2923 -- select tool use sound if found, or fallback to default
2924 local snd = weapon_def.sound and weapon_def.sound.use
2925 or "default_punch"
2926
2927 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442928
24452929 -- blood_particles
24462930 if not disable_blood and self.blood_amount > 0 then
24472931
24482932 local pos = self.object:get_pos()
2933 local blood = self.blood_texture
2934 local amount = self.blood_amount
24492935
24502936 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2937
2938 -- lots of damage = more blood :)
2939 if damage > 10 then
2940 amount = self.blood_amount * 2
2941 end
24512942
24522943 -- do we have a single blood texture or multiple?
24532944 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
2945 blood = self.blood_texture[random(#self.blood_texture)]
2946 end
2947
2948 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612949 end
24622950
24632951 -- do damage
24672955 local hot = tool_capabilities and tool_capabilities.damage_groups
24682956 and tool_capabilities.damage_groups.fire
24692957
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
2958 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2959 return true
2960 end
24872961 end -- END if damage
24882962
24892963 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2964 if self.knock_back and tflp >= punch_interval then
24922965
24932966 local v = self.object:get_velocity()
2967
2968 -- sanity check
2969 if not v then return true end
2970
24942971 local kb = damage or 1
24952972 local up = 2
24962973
25042981 dir = dir or {x = 0, y = 0, z = 0}
25052982
25062983 -- 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 })
2984 kb = tool_capabilities.damage_groups["knockback"] or kb
2985
2986 self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb})
25142987
25152988 self.pause_timer = 0.25
25162989 end
25202993 and self.order ~= "stand" then
25212994
25222995 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)
2996 local yaw = yaw_to_pos(self, lp, 3)
2997
25372998 self.state = "runaway"
25382999 self.runaway_timer = 0
25393000 self.following = nil
25473008 and self.child == false
25483009 and self.attack_players == true
25493010 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3011 and not is_invisible(self, name)
3012 and self.object ~= hitter then
25513013
25523014 -- attack whoever punched mob
25533015 self.state = ""
2554 do_attack(self, hitter)
3016 self:do_attack(hitter)
25553017
25563018 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3019 local objs = minetest.get_objects_inside_radius(
3020 hitter:get_pos(), self.view_range)
3021 local obj
25593022
25603023 for n = 1, #objs do
25613024
25633026
25643027 if obj and obj._cmi_is_mob then
25653028
2566 -- only alert members of same mob
3029 -- only alert members of same mob and assigned helper
25673030 if obj.group_attack == true
25683031 and obj.state ~= "attack"
25693032 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3033 and (obj.name == self.name
3034 or obj.name == self.group_helper) then
3035
3036 obj:do_attack(hitter)
25723037 end
25733038
25743039 -- have owned mobs attack player threat
25753040 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3041 obj:do_attack(self.object)
25773042 end
25783043 end
25793044 end
25823047
25833048
25843049 -- get entity staticdata
2585 local mob_staticdata = function(self)
3050 function mob_class:mob_staticdata()
3051
3052 -- this handles mob count for mobs activated, unloaded, reloaded
3053 if active_limit > 0 and self.active_toggle then
3054 active_mobs = active_mobs + self.active_toggle
3055 self.active_toggle = -self.active_toggle
3056 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3057 end
25863058
25873059 -- remove mob when out of range unless tamed
25883060 if remove_far
25923064 and not self.tamed
25933065 and self.lifetimer < 20000 then
25943066
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3067 --print("REMOVED " .. self.name)
3068
3069 remove_mob(self, true)
3070
3071 return minetest.serialize({remove_ok = true, static_save = true})
26003072 end
26013073
26023074 self.remove_ok = true
26053077 self.state = "stand"
26063078
26073079 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3080 if self.drawtype and self.drawtype == "side" then
3081 self.rotate = rad(90)
26113082 end
26123083
26133084 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3085 self.serialized_cmi_components = cmi.serialize_components(
3086 self._cmi_components)
3087 end
3088
3089 local tmp, t = {}
26183090
26193091 for _,stat in pairs(self) do
26203092
2621 local t = type(stat)
3093 t = type(stat)
26223094
26233095 if t ~= "function"
26243096 and t ~= "nil"
26253097 and t ~= "userdata"
3098 and _ ~= "object"
26263099 and _ ~= "_cmi_components" then
26273100 tmp[_] = self[_]
26283101 end
26293102 end
26303103
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3104 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3105
26323106 return minetest.serialize(tmp)
26333107 end
26343108
26353109
26363110 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
3111 function mob_class:mob_activate(staticdata, def, dtime)
3112
3113 -- if dtime == 0 then entity has just been created
3114 -- anything higher means it is respawning (thanks SorceryKid)
3115 if dtime == 0 and active_limit > 0 then
3116 self.active_toggle = 1
3117 end
3118
3119 -- remove mob if not tamed and mob total reached
3120 if active_limit > 0 and active_mobs >= active_limit and not self.tamed then
3121
3122 remove_mob(self)
3123 --print("-- mob limit reached, removing " .. self.name)
3124 return
3125 end
26383126
26393127 -- remove monsters in peaceful mode
2640 if self.type == "monster"
2641 and peaceful_only then
2642
2643 self.object:remove()
3128 if self.type == "monster" and peaceful_only then
3129
3130 remove_mob(self, true)
26443131
26453132 return
26463133 end
26493136 local tmp = minetest.deserialize(staticdata)
26503137
26513138 if tmp then
3139
3140 local t
3141
26523142 for _,stat in pairs(tmp) do
2653 self[_] = stat
2654 end
2655 end
3143
3144 t = type(stat)
3145
3146 if t ~= "function"
3147 and t ~= "nil"
3148 and t ~= "userdata" then
3149 self[_] = stat
3150 end
3151 end
3152 end
3153
3154 -- force current model into mob
3155 self.mesh = def.mesh
3156 self.base_mesh = def.mesh
3157 self.collisionbox = def.collisionbox
3158 self.selectionbox = def.selectionbox
26563159
26573160 -- select random texture, set model and size
26583161 if not self.base_texture then
26623165 def.textures = {def.textures}
26633166 end
26643167
2665 self.base_texture = def.textures and def.textures[random(1, #def.textures)]
3168 self.base_texture = def.textures and def.textures[random(#def.textures)]
26663169 self.base_mesh = def.mesh
26673170 self.base_size = self.visual_size
26683171 self.base_colbox = self.collisionbox
26823185 local selbox = self.base_selbox
26833186
26843187 -- specific texture if gotten
2685 if self.gotten == true
2686 and def.gotten_texture then
3188 if self.gotten == true and def.gotten_texture then
26873189 textures = def.gotten_texture
26883190 end
26893191
26903192 -- specific mesh if gotten
2691 if self.gotten == true
2692 and def.gotten_mesh then
3193 if self.gotten == true and def.gotten_mesh then
26933194 mesh = def.gotten_mesh
26943195 end
26953196
26963197 -- set child objects to half size
26973198 if self.child == true then
26983199
2699 vis_size = {
2700 x = self.base_size.x * .5,
2701 y = self.base_size.y * .5,
2702 }
3200 vis_size = {x = self.base_size.x * .5, y = self.base_size.y * .5}
27033201
27043202 if def.child_texture then
27053203 textures = def.child_texture[1]
27063204 end
27073205
27083206 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 }
3207 self.base_colbox[1] * .5, self.base_colbox[2] * .5,
3208 self.base_colbox[3] * .5, self.base_colbox[4] * .5,
3209 self.base_colbox[5] * .5, self.base_colbox[6] * .5}
3210
27163211 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 }
3212 self.base_selbox[1] * .5, self.base_selbox[2] * .5,
3213 self.base_selbox[3] * .5, self.base_selbox[4] * .5,
3214 self.base_selbox[5] * .5, self.base_selbox[6] * .5}
27243215 end
27253216
27263217 if self.health == 0 then
2727 self.health = random (self.hp_min, self.hp_max)
3218 self.health = random(self.hp_min, self.hp_max)
27283219 end
27293220
27303221 -- pathfinding init
27353226 self.path.following = false -- currently following path?
27363227 self.path.stuck_timer = 0 -- if stuck for too long search for path
27373228
3229 -- Armor groups (immortal = 1 for custom damage handling)
3230 local armor
3231 if type(self.armor) == "table" then
3232 armor = table_copy(self.armor)
3233 else
3234 armor = {fleshy = self.armor} -- immortal = 1
3235 end
3236 self.object:set_armor_groups(armor)
3237
27383238 -- mob defaults
2739 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
27403239 self.old_y = self.object:get_pos().y
27413240 self.old_health = self.health
27423241 self.sounds.distance = self.sounds.distance or 10
27463245 self.selectionbox = selbox
27473246 self.visual_size = vis_size
27483247 self.standing_in = "air"
3248 self.standing_on = "air"
27493249
27503250 -- check existing nametag
27513251 if not self.nametag then
27543254
27553255 -- set anything changed above
27563256 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")
3257 self:set_yaw((random(0, 360) - 180) / 180 * pi, 6)
3258 self:update_tag()
3259 self:set_animation("stand")
3260
3261 -- apply any texture mods
3262 self.object:set_texture_mod(self.texture_mods)
3263
3264 -- set 5.x flag to remove monsters when map area unloaded
3265 if remove_far and self.type == "monster" then
3266 self.static_save = false
3267 end
27603268
27613269 -- run on_spawn function if found
27623270 if self.on_spawn and not self.on_spawn_run then
27633271 if self.on_spawn(self) then
2764 self.on_spawn_run = true -- if true, set flag to run once only
3272 self.on_spawn_run = true -- if true, set flag to run once only
27653273 end
27663274 end
27673275
27713279 end
27723280
27733281 if use_cmi then
2774 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
3282 self._cmi_components = cmi.activate_components(
3283 self.serialized_cmi_components)
27753284 cmi.notify_activate(self.object, dtime)
27763285 end
27773286 end
27783287
27793288
27803289 -- handle mob lifetimer and expiration
2781 local mob_expire = function(self, pos, dtime)
3290 function mob_class:mob_expire(pos, dtime)
27823291
27833292 -- when lifetimer expires remove mob (except npc and tamed)
27843293 if self.type ~= "npc"
28093318
28103319 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
28113320
2812 self.object:remove()
3321 remove_mob(self, true)
28133322
28143323 return
28153324 end
28183327
28193328
28203329 -- main mob function
2821 local mob_step = function(self, dtime)
3330 function mob_class:on_step(dtime, moveresult)
3331
3332 if self.state == "die" then return end
28223333
28233334 if use_cmi then
28243335 cmi.notify_step(self.object, dtime)
28253336 end
28263337
28273338 local pos = self.object:get_pos()
2828 local yaw = 0
3339 local yaw = self.object:get_yaw()
3340
3341 -- early warning check, if no yaw then no entity, skip rest of function
3342 if not yaw then return end
28293343
28303344 -- get node at foot level every quarter second
28313345 self.node_timer = (self.node_timer or 0) + dtime
28433357 -- what is mob standing in?
28443358 self.standing_in = node_ok({
28453359 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)
3360
3361 self.standing_on = node_ok({
3362 x = pos.x, y = pos.y + y_level - 0.25, z = pos.z}, "air").name
3363
3364 --print("standing in " .. self.standing_in)
3365
3366 -- if standing inside solid block then jump to escape
3367 if minetest.registered_nodes[self.standing_in].walkable
3368 and minetest.registered_nodes[self.standing_in].drawtype
3369 == "normal" then
3370
3371 self.object:set_velocity({
3372 x = 0,
3373 y = self.jump_height,
3374 z = 0
3375 })
3376 end
3377
3378 -- check and stop if standing at cliff and fear of heights
3379 self.at_cliff = self:is_at_cliff()
3380
3381 if self.at_cliff then
3382 self:set_velocity(0)
3383 end
3384
3385 -- has mob expired (0.25 instead of dtime since were in a timer)
3386 self:mob_expire(pos, 0.25)
3387 end
3388
3389 -- check if falling, flying, floating and return if player died
3390 if self:falling(pos) then
3391 return
3392 end
28543393
28553394 -- smooth rotation by ThomasMonroe314
2856
28573395 if self.delay and self.delay > 0 then
2858
2859 local yaw = self.object:get_yaw()
28603396
28613397 if self.delay == 1 then
28623398 yaw = self.target_yaw
28903426 self.object:set_yaw(yaw)
28913427 end
28923428
2893 -- end rotation
2894
28953429 -- knockback timer
28963430 if self.pause_timer > 0 then
28973431
29043438 if self.do_custom then
29053439
29063440 -- when false skip going any further
2907 if self.do_custom(self, dtime) == false then
3441 if self:do_custom(dtime) == false then
29083442 return
29093443 end
29103444 end
29273461 end
29283462
29293463 -- mob plays random sound at times
2930 if random(1, 100) == 1 then
2931 mob_sound(self, self.sounds.random)
3464 if random(100) == 1 then
3465 self:mob_sound(self.sounds.random)
29323466 end
29333467
29343468 -- environmental damage timer (every 1 second)
29403474 self.env_damage_timer = 0
29413475
29423476 -- check for environmental damage (water, fire, lava etc.)
2943 do_env_damage(self)
3477 if self:do_env_damage() then return end
29443478
29453479 -- 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
3480 self:replace(pos)
3481 end
3482
3483 self:general_attack()
3484
3485 self:breed()
3486
3487 self:follow_flop()
3488
3489 if self:do_states(dtime) then return end
3490
3491 self:do_jump()
3492
3493 self:do_runaway_from(self)
3494
3495 self:do_stay_near()
29613496 end
29623497
29633498
29643499 -- 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, {
3500 function mob_class:on_blast(damage)
3501
3502 --print("-- blast damage", damage)
3503
3504 self.object:punch(self.object, 1.0, {
29703505 full_punch_interval = 1.0,
29713506 damage_groups = {fleshy = damage},
29723507 }, nil)
29733508
2974 return false, true, {}
3509 -- return no damage, no knockback, no item drops, mob api handles all
3510 return false, false, {}
29753511 end
29763512
29773513
29803516 -- register mob entity
29813517 function mobs:register_mob(name, def)
29823518
2983 mobs.spawning_mobs[name] = true
2984
2985 minetest.register_entity(name, {
2986
2987 stepheight = def.stepheight or 1.1, -- was 0.6
3519 mobs.spawning_mobs[name] = {}
3520
3521 local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
3522
3523 -- quick fix to stop mobs glitching through nodes if too small
3524 if -collisionbox[2] + collisionbox[5] < 1.01 then
3525 collisionbox[5] = collisionbox[2] + 0.99
3526 end
3527
3528 minetest.register_entity(name, setmetatable({
3529
3530 stepheight = def.stepheight,
29883531 name = name,
29893532 type = def.type,
29903533 attack_type = def.attack_type,
29913534 fly = def.fly,
2992 fly_in = def.fly_in or "air",
2993 owner = def.owner or "",
2994 order = def.order or "",
3535 fly_in = def.fly_in,
3536 keep_flying = def.keep_flying,
3537 owner = def.owner,
3538 order = def.order,
29953539 on_die = def.on_die,
3540 on_flop = def.on_flop,
29963541 do_custom = def.do_custom,
2997 jump_height = def.jump_height or 4, -- was 6
3542 jump_height = def.jump_height,
29983543 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
3544 rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2
3545 glow = def.glow,
3546 lifetimer = def.lifetimer,
30013547 hp_min = max(1, (def.hp_min or 5) * difficulty),
30023548 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,
3549 collisionbox = collisionbox, --def.collisionbox,
3550 selectionbox = def.selectionbox or collisionbox, --def.collisionbox,
30063551 visual = def.visual,
3007 visual_size = def.visual_size or {x = 1, y = 1},
3552 visual_size = def.visual_size,
30083553 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,
3554 makes_footstep_sound = def.makes_footstep_sound,
3555 view_range = def.view_range,
3556 walk_velocity = def.walk_velocity,
3557 run_velocity = def.run_velocity,
30133558 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,
3559 damage_group = def.damage_group,
3560 damage_texture_modifier = def.damage_texture_modifier,
3561 light_damage = def.light_damage,
3562 light_damage_min = def.light_damage_min,
3563 light_damage_max = def.light_damage_max,
3564 water_damage = def.water_damage,
3565 lava_damage = def.lava_damage,
3566 fire_damage = def.fire_damage,
3567 air_damage = def.air_damage,
3568 suffocation = def.suffocation,
3569 fall_damage = def.fall_damage,
3570 fall_speed = def.fall_speed,
3571 drops = def.drops,
3572 armor = def.armor,
30243573 on_rightclick = def.on_rightclick,
30253574 arrow = def.arrow,
30263575 shoot_interval = def.shoot_interval,
3027 sounds = def.sounds or {},
3576 sounds = def.sounds,
30283577 animation = def.animation,
30293578 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
3579 jump = def.jump,
3580 walk_chance = def.walk_chance,
3581 stand_chance = def.stand_chance,
3582 attack_chance = def.attack_chance,
3583 passive = def.passive,
3584 knock_back = def.knock_back,
3585 blood_amount = def.blood_amount,
3586 blood_texture = def.blood_texture,
3587 shoot_offset = def.shoot_offset,
3588 floats = def.floats,
30393589 replace_rate = def.replace_rate,
30403590 replace_what = def.replace_what,
30413591 replace_with = def.replace_with,
3042 replace_offset = def.replace_offset or 0,
3592 replace_offset = def.replace_offset,
30433593 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,
3594 reach = def.reach,
30553595 texture_list = def.textures,
3596 texture_mods = def.texture_mods or "",
30563597 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,
3598 docile_by_day = def.docile_by_day,
3599 fear_height = def.fear_height,
30603600 runaway = def.runaway,
3061 runaway_timer = 0,
30623601 pathfinding = def.pathfinding,
3063 immune_to = def.immune_to or {},
3602 immune_to = def.immune_to,
30643603 explosion_radius = def.explosion_radius,
30653604 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,
3605 explosion_timer = def.explosion_timer,
3606 allow_fuse_reset = def.allow_fuse_reset,
3607 stop_to_explode = def.stop_to_explode,
30693608 custom_attack = def.custom_attack,
30703609 double_melee_attack = def.double_melee_attack,
30713610 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,
3611 dogshoot_count_max = def.dogshoot_count_max,
3612 dogshoot_count2_max = def.dogshoot_count2_max or def.dogshoot_count_max,
3613 group_attack = def.group_attack,
3614 group_helper = def.group_helper,
3615 attack_monsters = def.attacks_monsters or def.attack_monsters,
3616 attack_animals = def.attack_animals,
3617 attack_players = def.attack_players,
3618 attack_npcs = def.attack_npcs,
30803619 specific_attack = def.specific_attack,
30813620 runaway_from = def.runaway_from,
30823621 owner_loyal = def.owner_loyal,
3083 facing_fence = false,
30843622 pushable = def.pushable,
3085 _cmi_is_mob = true,
3623 stay_near = def.stay_near,
3624 randomly_turn = def.randomly_turn ~= false,
3625 ignore_invisibility = def.ignore_invisibility,
30863626
30873627 on_spawn = def.on_spawn,
30883628
3089 on_blast = def.on_blast or do_tnt,
3090
3091 on_step = mob_step,
3629 on_blast = def.on_blast, -- class redifinition
30923630
30933631 do_punch = def.do_punch,
30943632
3095 on_punch = mob_punch,
3096
30973633 on_breed = def.on_breed,
30983634
30993635 on_grown = def.on_grown,
31003636
31013637 on_activate = function(self, staticdata, dtime)
3102 return mob_activate(self, staticdata, def, dtime)
3638 return self:mob_activate(staticdata, def, dtime)
31033639 end,
31043640
31053641 get_staticdata = function(self)
3106 return mob_staticdata(self)
3642 return self:mob_staticdata(self)
31073643 end,
31083644
3109 })
3645 }, mob_class_meta))
31103646
31113647 end -- END mobs:register_mob function
31123648
31133649
31143650 -- count how many mobs of one type are inside an area
3651 -- will also return true for second value if player is inside area
31153652 local count_mobs = function(pos, type)
31163653
31173654 local total = 0
31183655 local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2)
31193656 local ent
3657 local players
31203658
31213659 for n = 1, #objs do
31223660
31283666 if ent and ent.name and ent.name == type then
31293667 total = total + 1
31303668 end
3131 end
3132 end
3133
3134 return total
3669 else
3670 players = true
3671 end
3672 end
3673
3674 return total, players
3675 end
3676
3677
3678 -- do we have enough space to spawn mob? (thanks wuzzy)
3679 local can_spawn = function(pos, name)
3680
3681 local ent = minetest.registered_entities[name]
3682 local width_x = max(1, ceil(ent.collisionbox[4] - ent.collisionbox[1]))
3683 local min_x, max_x
3684
3685 if width_x % 2 == 0 then
3686 max_x = floor(width_x / 2)
3687 min_x = -(max_x - 1)
3688 else
3689 max_x = floor(width_x / 2)
3690 min_x = -max_x
3691 end
3692
3693 local width_z = max(1, ceil(ent.collisionbox[6] - ent.collisionbox[3]))
3694 local min_z, max_z
3695
3696 if width_z % 2 == 0 then
3697 max_z = floor(width_z / 2)
3698 min_z = -(max_z - 1)
3699 else
3700 max_z = floor(width_z / 2)
3701 min_z = -max_z
3702 end
3703
3704 local max_y = max(0, ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
3705 local pos2
3706
3707 for y = 0, max_y do
3708 for x = min_x, max_x do
3709 for z = min_z, max_z do
3710
3711 pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
3712
3713 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3714 return nil
3715 end
3716 end
3717 end
3718 end
3719
3720 -- tweak X/Z spawn pos
3721 if width_x % 2 == 0 then
3722 pos.x = pos.x + 0.5
3723 end
3724
3725 if width_z % 2 == 0 then
3726 pos.z = pos.z + 0.5
3727 end
3728
3729 return pos
3730 end
3731
3732 function mobs:can_spawn(pos, name)
3733 return can_spawn(pos, name)
31353734 end
31363735
31373736
31383737 -- global functions
3738
3739 function mobs:add_mob(pos, def)
3740
3741 -- is mob actually registered?
3742 if not mobs.spawning_mobs[def.name]
3743 or not minetest.registered_entities[def.name] then
3744 --print("--- mob doesn't exist", def.name)
3745 return
3746 end
3747
3748 -- are we over active mob limit
3749 if active_limit > 0 and active_mobs >= active_limit then
3750 --print("--- active mob limit reached", active_mobs, active_limit)
3751 return
3752 end
3753
3754 -- get total number of this mob in area
3755 local num_mob, is_pla = count_mobs(pos, def.name)
3756
3757 if not is_pla then
3758 --print("--- no players within active area, will not spawn " .. def.name)
3759 return
3760 end
3761
3762 local aoc = mobs.spawning_mobs[def.name]
3763 and mobs.spawning_mobs[def.name].aoc or 1
3764
3765 if def.ignore_count ~= true and num_mob >= aoc then
3766 --print("--- too many " .. def.name .. " in area", num_mob .. "/" .. aoc)
3767 return
3768 end
3769
3770 local mob = minetest.add_entity(pos, def.name)
3771
3772 --print("[mobs] Spawned " .. def.name .. " at " .. minetest.pos_to_string(pos))
3773
3774 local ent = mob:get_luaentity()
3775
3776 if not ent then
3777 --print("[mobs] entity not found " .. def.name)
3778 return false
3779 end
3780
3781 if def.child then
3782
3783 local textures = ent.base_texture
3784
3785 -- using specific child texture (if found)
3786 if ent.child_texture then
3787 textures = ent.child_texture[1]
3788 end
3789
3790 -- and resize to half height
3791 mob:set_properties({
3792 textures = textures,
3793 visual_size = {
3794 x = ent.base_size.x * .5,
3795 y = ent.base_size.y * .5
3796 },
3797 collisionbox = {
3798 ent.base_colbox[1] * .5,
3799 ent.base_colbox[2] * .5,
3800 ent.base_colbox[3] * .5,
3801 ent.base_colbox[4] * .5,
3802 ent.base_colbox[5] * .5,
3803 ent.base_colbox[6] * .5
3804 },
3805 selectionbox = {
3806 ent.base_selbox[1] * .5,
3807 ent.base_selbox[2] * .5,
3808 ent.base_selbox[3] * .5,
3809 ent.base_selbox[4] * .5,
3810 ent.base_selbox[5] * .5,
3811 ent.base_selbox[6] * .5
3812 },
3813 })
3814
3815 ent.child = true
3816 end
3817
3818 if def.owner then
3819 ent.tamed = true
3820 ent.owner = def.owner
3821 end
3822
3823 if def.nametag then
3824
3825 -- limit name entered to 64 characters long
3826 if def.nametag:len() > 64 then
3827 def.nametag = def.nametag:sub(1, 64)
3828 end
3829
3830 ent.nametag = def.nametag
3831
3832 ent:update_tag()
3833 end
3834
3835 return ent
3836 end
3837
31393838
31403839 function mobs:spawn_abm_check(pos, node, name)
31413840 -- global function to add additional spawn checks
31433842 end
31443843
31453844
3146 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3147 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3845 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
3846 chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load)
31483847
31493848 -- Do mobs spawn at all?
3150 if not mobs_spawn then
3849 if not mobs_spawn or not mobs.spawning_mobs[name] then
3850 --print ("--- spawning not registered for " .. name)
31513851 return
31523852 end
31533853
31543854 -- chance/spawn number override in minetest.conf for registered mob
3155 local numbers = minetest.settings:get(name)
3855 local numbers = settings:get(name)
31563856
31573857 if numbers then
3858
31583859 numbers = numbers:split(",")
31593860 chance = tonumber(numbers[1]) or chance
31603861 aoc = tonumber(numbers[2]) or aoc
31613862
31623863 if chance == 0 then
3163 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3864
3865 minetest.log("warning",
3866 string.format("[mobs] %s has spawning disabled", name))
31643867 return
31653868 end
31663869
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
3870 minetest.log("action", string.format(
3871 "[mobs] Chance setting for %s changed to %s (total: %s)",
3872 name, chance, aoc))
3873 end
3874
3875 mobs.spawning_mobs[name].aoc = aoc
3876
3877 local spawn_action = function(pos, node, active_object_count,
3878 active_object_count_wider)
3879
3880 -- use instead of abm's chance setting when using lbm
3881 if map_load and random(max(1, (chance * mob_chance_multiplier))) > 1 then
3882 return
3883 end
3884
3885 -- use instead of abm's neighbor setting when using lbm
3886 if map_load and not minetest.find_node_near(pos, 1, neighbors) then
3887 --print("--- lbm neighbors not found")
3888 return
3889 end
3890
3891 -- is mob actually registered?
3892 if not mobs.spawning_mobs[name]
3893 or not minetest.registered_entities[name] then
3894 --print("--- mob doesn't exist", name)
3895 return
3896 end
3897
3898 -- are we over active mob limit
3899 if active_limit > 0 and active_mobs >= active_limit then
3900 --print("--- active mob limit reached", active_mobs, active_limit)
3901 return
3902 end
3903
3904 -- additional custom checks for spawning mob
3905 if mobs:spawn_abm_check(pos, node, name) == true then
3906 return
3907 end
3908
3909 -- do not spawn if too many entities in area
3910 if active_object_count_wider
3911 and active_object_count_wider >= max_per_block then
31973912 --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
3913 return
3914 end
3915
3916 -- get total number of this mob in area
3917 local num_mob, is_pla = count_mobs(pos, name)
3918
3919 if not is_pla then
3920 --print("--- no players within active area, will not spawn " .. name)
3921 return
3922 end
3923
3924 if num_mob >= aoc then
3925 --print("--- too many " .. name .. " in area", num_mob .. "/" .. aoc)
3926 return
3927 end
32083928
32093929 -- 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)
3930 if day_toggle ~= nil then
3931
3932 local tod = (minetest.get_timeofday() or 0) * 24000
3933
3934 if tod > 4500 and tod < 19500 then
3935 -- daylight, but mob wants night
3936 if day_toggle == false then
3937 --print("--- mob needs night", name)
32553938 return
32563939 end
3257 end
3940 else
3941 -- night time but mob wants day
3942 if day_toggle == true then
3943 --print("--- mob needs day", name)
3944 return
3945 end
3946 end
3947 end
3948
3949 -- spawn above node
3950 pos.y = pos.y + 1
3951
3952 -- are we spawning within height limits?
3953 if pos.y > max_height
3954 or pos.y < min_height then
3955 --print("--- height limits not met", name, pos.y)
3956 return
3957 end
3958
3959 -- are light levels ok?
3960 local light = minetest.get_node_light(pos)
3961 if not light
3962 or light > max_light
3963 or light < min_light then
3964 --print("--- light limits not met", name, light)
3965 return
3966 end
3967
3968 -- check if mob can spawn inside protected areas
3969 if (spawn_protected == false
3970 or (spawn_monster_protected == false
3971 and minetest.registered_entities[name].type == "monster"))
3972 and minetest.is_protected(pos, "") then
3973 --print("--- inside protected area", name)
3974 return
3975 end
3976
3977 -- only spawn a set distance away from player
3978 local objs = minetest.get_objects_inside_radius(pos, mob_nospawn_range)
3979
3980 for n = 1, #objs do
3981
3982 if objs[n]:is_player() then
3983 --print("--- player too close", name)
3984 return
3985 end
3986 end
3987
3988 local ent = minetest.registered_entities[name]
3989
3990 -- should we check mob area for obstructions ?
3991 if mob_area_spawn ~= true then
32583992
32593993 -- 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
3994 local height = max(0, ent.collisionbox[5] - ent.collisionbox[2])
3995
3996 for n = 0, floor(height) do
32663997
32673998 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
32683999
32714002 return
32724003 end
32734004 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
4005 else
4006 -- returns position if we have enough space to spawn mob
4007 pos = can_spawn(pos, name)
4008 end
4009
4010 if pos then
4011
4012 -- adjust for mob collision box
4013 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
32844014
32854015 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 ]]
4016
4017 -- print("[mobs] Spawned " .. name .. " at "
4018 -- .. minetest.pos_to_string(pos) .. " on "
4019 -- .. node.name .. " near " .. neighbors[1])
4020
32914021 if on_spawn then
3292
3293 local ent = mob:get_luaentity()
3294
3295 on_spawn(ent, pos)
3296 end
3297 end
3298 })
4022 on_spawn(mob:get_luaentity(), pos)
4023 end
4024 else
4025 --print("--- not enough space to spawn", name)
4026 end
4027 end
4028
4029
4030 -- are we registering an abm or lbm?
4031 if map_load == true then
4032
4033 minetest.register_lbm({
4034 name = name .. "_spawning",
4035 label = name .. " spawning",
4036 nodenames = nodes,
4037 run_at_every_load = false,
4038
4039 action = function(pos, node)
4040 spawn_action(pos, node)
4041 end
4042 })
4043
4044 else
4045
4046 minetest.register_abm({
4047 label = name .. " spawning",
4048 nodenames = nodes,
4049 neighbors = neighbors,
4050 interval = interval,
4051 chance = max(1, (chance * mob_chance_multiplier)),
4052 catch_up = false,
4053
4054 action = function(pos, node, active_object_count, active_object_count_wider)
4055 spawn_action(pos, node, active_object_count, active_object_count_wider)
4056 end
4057 })
4058 end
32994059 end
33004060
33014061
33084068 end
33094069
33104070
3311 -- MarkBu's spawn function
4071 -- MarkBu's spawn function (USE this one please)
33124072 function mobs:spawn(def)
33134073
33144074 mobs:spawn_specific(
33234083 def.min_height or -31000,
33244084 def.max_height or 31000,
33254085 def.day_toggle,
3326 def.on_spawn
3327 )
4086 def.on_spawn,
4087 def.on_map_load)
33284088 end
33294089
33304090
33354095
33364096 minetest.register_entity(name, {
33374097
3338 physical = false,
4098 physical = def.physical or false,
4099 collide_with_objects = def.collide_with_objects or false,
4100 static_save = false,
4101
33394102 visual = def.visual,
33404103 visual_size = def.visual_size,
33414104 textures = def.textures,
33434106 hit_player = def.hit_player,
33444107 hit_node = def.hit_node,
33454108 hit_mob = def.hit_mob,
4109 hit_object = def.hit_object,
33464110 drop = def.drop or false, -- drops arrow as registered item when true
3347 collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
4111 collisionbox = def.collisionbox or {-.1, -.1, -.1, .1, .1, .1},
33484112 timer = 0,
4113 lifetime = def.lifetime or 4.5,
33494114 switch = 0,
33504115 owner_id = def.owner_id,
33514116 rotate = def.rotate,
33544119
33554120 on_activate = def.on_activate,
33564121
3357 on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir)
4122 on_punch = def.on_punch or function(
4123 self, hitter, tflp, tool_capabilities, dir)
33584124 end,
33594125
33604126 on_step = def.on_step or function(self, dtime)
33614127
3362 self.timer = self.timer + 1
4128 self.timer = self.timer + dtime
33634129
33644130 local pos = self.object:get_pos()
33654131
3366 if self.switch == 0
3367 or self.timer > 150 then
3368
3369 self.object:remove() ; -- print ("removed arrow")
4132 if self.switch == 0 or self.timer > self.lifetime then
4133
4134 self.object:remove() ; -- print("removed arrow")
33704135
33714136 return
33724137 end
33734138
33744139 -- does arrow have a tail (fireball)
3375 if def.tail
3376 and def.tail == 1
3377 and def.tail_texture then
4140 if def.tail and def.tail == 1 and def.tail_texture then
33784141
33794142 minetest.add_particle({
33804143 pos = pos,
33844147 collisiondetection = false,
33854148 texture = def.tail_texture,
33864149 size = def.tail_size or 5,
3387 glow = def.glow or 0,
4150 glow = def.glow or 0
33884151 })
33894152 end
33904153
33944157
33954158 if minetest.registered_nodes[node].walkable then
33964159
3397 self.hit_node(self, pos, node)
4160 self:hit_node(pos, node)
33984161
33994162 if self.drop == true then
34004163
34024165
34034166 self.lastpos = (self.lastpos or pos)
34044167
3405 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
4168 minetest.add_item(self.lastpos,
4169 self.object:get_luaentity().name)
34064170 end
34074171
3408 self.object:remove() ; -- print ("hit node")
4172 self.object:remove() ; -- print("hit node")
34094173
34104174 return
34114175 end
34124176 end
34134177
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")
4178 if self.hit_player or self.hit_mob or self.hit_object then
4179
4180 for _,player in pairs(
4181 minetest.get_objects_inside_radius(pos, 1.0)) do
4182
4183 if self.hit_player and player:is_player() then
4184
4185 self:hit_player(player)
4186
4187 self.object:remove() ; -- print("hit player")
4188
34234189 return
34244190 end
34254191
34314197 and tostring(player) ~= self.owner_id
34324198 and entity.name ~= self.object:get_luaentity().name then
34334199
3434 self.hit_mob(self, player)
3435
3436 self.object:remove() ; --print ("hit mob")
4200 self:hit_mob(player)
4201
4202 self.object:remove() ; --print("hit mob")
4203
4204 return
4205 end
4206
4207 if entity
4208 and self.hit_object
4209 and (not entity._cmi_is_mob)
4210 and tostring(player) ~= self.owner_id
4211 and entity.name ~= self.object:get_luaentity().name then
4212
4213 self:hit_object(player)
4214
4215 self.object:remove() ; -- print("hit object")
34374216
34384217 return
34394218 end
34484227
34494228 -- compatibility function
34504229 function mobs:explosion(pos, radius)
3451
3452 local self = {sounds = {explode = "tnt_explode"}}
3453
3454 mobs:boom(self, pos, radius)
4230 mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius)
34554231 end
34564232
34574233
34624238 pos = pos,
34634239 gain = 1.0,
34644240 max_hear_distance = self.sounds and self.sounds.distance or 32
3465 })
4241 }, true)
34664242
34674243 entity_physics(pos, radius)
34684244
34814257 radius = radius,
34824258 damage_radius = radius,
34834259 sound = self.sounds and self.sounds.explode,
3484 explode_center = true,
4260 explode_center = true
34854261 })
34864262 else
34874263 mobs:safe_boom(self, pos, radius)
34994275 local grp = {spawn_egg = 1}
35004276
35014277 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3502 if creative and no_creative == true then
4278 if no_creative == true then
35034279 grp.not_in_creative_inventory = 1
35044280 end
35054281
35224298
35234299 local pos = pointed_thing.above
35244300
3525 -- am I clicking on something with existing on_rightclick function?
4301 -- does existing on_rightclick function exist?
35264302 local under = minetest.get_node(pointed_thing.under)
35274303 local def = minetest.registered_nodes[under.name]
4304
35284305 if def and def.on_rightclick then
3529 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4306
4307 return def.on_rightclick(
4308 pointed_thing.under, under, placer, itemstack)
35304309 end
35314310
35324311 if pos
35394318 pos.y = pos.y + 1
35404319
35414320 local data = itemstack:get_metadata()
3542 local mob = minetest.add_entity(pos, mob, data)
3543 local ent = mob:get_luaentity()
4321 local smob = minetest.add_entity(pos, mob, data)
4322 local ent = smob and smob:get_luaentity()
4323
4324 if not ent then return end -- sanity check
35444325
35454326 -- set owner if not a monster
35464327 if ent.type ~= "monster" then
35684349
35694350 local pos = pointed_thing.above
35704351
3571 -- am I clicking on something with existing on_rightclick function?
4352 -- does existing on_rightclick function exist?
35724353 local under = minetest.get_node(pointed_thing.under)
35734354 local def = minetest.registered_nodes[under.name]
4355
35744356 if def and def.on_rightclick then
3575 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4357
4358 return def.on_rightclick(
4359 pointed_thing.under, under, placer, itemstack)
35764360 end
35774361
35784362 if pos
35824366 return
35834367 end
35844368
4369 -- have we reached active mob limit
4370 if active_limit > 0 and active_mobs >= active_limit then
4371 minetest.chat_send_player(placer:get_player_name(),
4372 S("Active Mob Limit Reached!")
4373 .. " (" .. active_mobs
4374 .. " / " .. active_limit .. ")")
4375 return
4376 end
4377
35854378 pos.y = pos.y + 1
35864379
3587 local mob = minetest.add_entity(pos, mob)
3588 local ent = mob:get_luaentity()
4380 local smob = minetest.add_entity(pos, mob)
4381 local ent = smob and smob:get_luaentity()
4382
4383 if not ent then return end -- sanity check
35894384
35904385 -- don't set owner if monster or sneak pressed
35914386 if ent.type ~= "monster"
36034398 return itemstack
36044399 end,
36054400 })
3606
4401 end
4402
4403
4404 -- force capture a mob if space available in inventory, or drop as spawn egg
4405 function mobs:force_capture(self, clicker)
4406
4407 -- add special mob egg with all mob information
4408 local new_stack = ItemStack(self.name .. "_set")
4409
4410 local tmp, t = {}
4411
4412 for _,stat in pairs(self) do
4413
4414 t = type(stat)
4415
4416 if t ~= "function"
4417 and t ~= "nil"
4418 and t ~= "userdata" then
4419 tmp[_] = self[_]
4420 end
4421 end
4422
4423 local data_str = minetest.serialize(tmp)
4424
4425 new_stack:set_metadata(data_str)
4426
4427 local inv = clicker:get_inventory()
4428
4429 if inv:room_for_item("main", new_stack) then
4430 inv:add_item("main", new_stack)
4431 else
4432 minetest.add_item(clicker:get_pos(), new_stack)
4433 end
4434
4435 self:mob_sound("default_place_node_hard")
4436
4437 remove_mob(self, true)
36074438 end
36084439
36094440
36104441 -- capture critter (thanks to blert2112 for idea)
3611 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
3612 force_take, replacewith)
4442 function mobs:capture_mob(self, clicker, chance_hand, chance_net,
4443 chance_lasso, force_take, replacewith)
36134444
36144445 if self.child
36154446 or not clicker:is_player()
36364467 end
36374468
36384469 -- is mob tamed?
3639 if self.tamed == false
3640 and force_take == false then
4470 if self.tamed == false and force_take == false then
36414471
36424472 minetest.chat_send_player(name, S("Not tamed!"))
36434473
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
4474 return false
4475 end
4476
4477 -- cannot pick up if not owner (unless player has protection_bypass priv)
4478 if not minetest.check_player_privs(name, "protection_bypass")
4479 and self.owner ~= name and force_take == false then
36504480
36514481 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
36524482
3653 return true -- false
4483 return false
36544484 end
36554485
36564486 if clicker:get_inventory():room_for_item("main", mobname) then
36804510 end
36814511
36824512 -- calculate chance.. add to inventory if successful?
3683 if chance > 0 and random(1, 100) <= chance then
4513 if chance and chance > 0 and random(100) <= chance then
36844514
36854515 -- default mob egg
36864516 local new_stack = ItemStack(mobname)
36914521
36924522 new_stack = ItemStack(mobname .. "_set")
36934523
3694 local tmp = {}
4524 local tmp, t = {}
36954525
36964526 for _,stat in pairs(self) do
3697 local t = type(stat)
4527
4528 t = type(stat)
4529
36984530 if t ~= "function"
36994531 and t ~= "nil"
37004532 and t ~= "userdata" then
37154547 minetest.add_item(clicker:get_pos(), new_stack)
37164548 end
37174549
3718 self.object:remove()
3719
3720 mob_sound(self, "default_place_node_hard")
3721
3722 elseif chance ~= 0 then
4550 self:mob_sound("default_place_node_hard")
4551
4552 remove_mob(self, true)
4553
4554 return new_stack
4555
4556 -- when chance above fails or set to 0, miss!
4557 elseif chance and chance ~= 0 then
4558
37234559 minetest.chat_send_player(name, S("Missed!"))
37244560
3725 mob_sound(self, "mobs_swing")
4561 self:mob_sound("mobs_swing")
4562
4563 return false
4564
4565 -- when chance is nil always return a miss (used for npc walk/follow)
4566 elseif not chance then
4567 return false
37264568 end
37274569 end
37284570
37354577
37364578 local name = clicker:get_player_name()
37374579 local tool = clicker:get_wielded_item()
3738
3739 if tool:get_name() ~= "mobs:protector" then
4580 local tool_name = tool:get_name()
4581
4582 if tool_name ~= "mobs:protector"
4583 and tool_name ~= "mobs:protector2" then
37404584 return false
37414585 end
37424586
3743 if self.tamed == false then
4587 if not self.tamed then
37444588 minetest.chat_send_player(name, S("Not tamed!"))
3745 return true -- false
3746 end
3747
3748 if self.protected == true then
4589 return true
4590 end
4591
4592 if (self.protected and tool_name == "mobs:protector")
4593 or (self.protected == 2 and tool_name == "mobs:protector2") then
37494594 minetest.chat_send_player(name, S("Already protected!"))
3750 return true -- false
4595 return true
37514596 end
37524597
37534598 if not mobs.is_creative(clicker:get_player_name()) then
37554600 clicker:set_wielded_item(tool)
37564601 end
37574602
3758 self.protected = true
4603 -- set protection level
4604 if tool_name == "mobs:protector" then
4605 self.protected = true
4606 else
4607 self.protected = 2 ; self.fire_damage = 0
4608 end
37594609
37604610 local pos = self.object:get_pos()
4611
37614612 pos.y = pos.y + self.collisionbox[2] + 0.5
37624613
3763 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3764
3765 mob_sound(self, "mobs_spell")
4614 effect(self.object:get_pos(), 25, "mobs_protect_particle.png",
4615 0.5, 4, 2, 15)
4616
4617 self:mob_sound("mobs_spell")
37664618
37674619 return true
37684620 end
37744626 -- feeding, taming and breeding (thanks blert2112)
37754627 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
37764628
3777 if not self.follow then
3778 return false
3779 end
3780
37814629 -- can eat/tame with item in hand
3782 if follow_holding(self, clicker) then
4630 if self.follow
4631 and self:follow_holding(clicker) then
37834632
37844633 -- if not in creative then take item
37854634 if not mobs.is_creative(clicker:get_player_name()) then
37984647
37994648 self.health = self.hp_max
38004649
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
4650 -- if self.htimer < 1 then
4651
4652 -- minetest.chat_send_player(clicker:get_player_name(),
4653 -- S("@1 at full health (@2)",
4654 -- self.name:split(":")[2], tostring(self.health)))
4655
4656 -- self.htimer = 5
4657 -- end
38094658 end
38104659
38114660 self.object:set_hp(self.health)
38124661
3813 update_tag(self)
4662 self:update_tag()
38144663
38154664 -- make children grow quicker
38164665 if self.child == true then
38174666
3818 self.hornytimer = self.hornytimer + 20
3819
4667 -- self.hornytimer = self.hornytimer + 20
4668 -- deduct 10% of the time to adulthood
4669 self.hornytimer = self.hornytimer + (
4670 (CHILD_GROW_TIME - self.hornytimer) * 0.1)
4671 --print ("====", self.hornytimer)
38204672 return true
38214673 end
38224674
38234675 -- feed and tame
38244676 self.food = (self.food or 0) + 1
4677
38254678 if self.food >= feed_count then
38264679
38274680 self.food = 0
38294682 if breed and self.hornytimer == 0 then
38304683 self.horny = true
38314684 end
3832
3833 self.gotten = false
38344685
38354686 if tame then
38364687
38484699 end
38494700
38504701 -- make sound when fed so many times
3851 mob_sound(self, self.sounds.random)
4702 self:mob_sound(self.sounds.random)
38524703 end
38534704
38544705 return true
38554706 end
38564707
38574708 local item = clicker:get_wielded_item()
4709 local name = clicker:get_player_name()
38584710
38594711 -- if mob has been tamed you can name it with a nametag
38604712 if item:get_name() == "mobs:nametag"
3861 and clicker:get_player_name() == self.owner then
3862
3863 local name = clicker:get_player_name()
4713 and (name == self.owner
4714 or minetest.check_player_privs(name, "protection_bypass")) then
38644715
38654716 -- store mob and nametag stack in external variables
38664717 mob_obj[name] = self
38674718 mob_sta[name] = item
38684719
38694720 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")) .. "]")
4721 local esc = minetest.formspec_escape
4722
4723 minetest.show_formspec(name, "mobs_nametag",
4724 "size[8,4]" ..
4725 "field[0.5,1;7.5,0;name;" ..
4726 esc(S("Enter name:")) ..
4727 ";" .. tag .. "]" ..
4728 "button_exit[2.5,3.5;3,1;mob_rename;" ..
4729 esc(S("Rename")) .. "]")
4730
4731 return true
38784732 end
38794733
38804734 return false
39044758 end
39054759
39064760 -- limit name entered to 64 characters long
3907 if string.len(fields.name) > 64 then
3908 fields.name = string.sub(fields.name, 1, 64)
4761 if fields.name:len() > 64 then
4762 fields.name = fields.name:sub(1, 64)
39094763 end
39104764
39114765 -- update nametag
39124766 mob_obj[name].nametag = fields.name
39134767
3914 update_tag(mob_obj[name])
4768 mob_obj[name]:update_tag()
39154769
39164770 -- if not in creative then take item
39174771 if not mobs.is_creative(name) then
39314785 -- compatibility function for old entities to new modpack entities
39324786 function mobs:alias_mob(old_name, new_name)
39334787
4788 -- check old_name entity doesnt already exist
4789 if minetest.registered_entities[old_name] then
4790 return
4791 end
4792
39344793 -- spawn egg
39354794 minetest.register_alias(old_name, new_name)
39364795
39374796 -- entity
39384797 minetest.register_entity(":" .. old_name, {
39394798
3940 physical = false,
3941
3942 on_activate = function(self)
4799 physical = false, static_save = false,
4800
4801 on_activate = function(self, staticdata)
39434802
39444803 if minetest.registered_entities[new_name] then
3945 minetest.add_entity(self.object:get_pos(), new_name)
3946 end
3947
3948 self.object:remove()
4804
4805 minetest.add_entity(self.object:get_pos(), new_name, staticdata)
4806 end
4807
4808 remove_mob(self)
4809 end,
4810
4811 get_staticdata = function(self)
4812 return self
39494813 end
39504814 })
39514815 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.
118135 e.g. {"player", "mobs_animal:chicken"}.
119136 'runaway_from' contains a table with mob names to run away from, add
120137 "player" to list to runaway from player also.
138 'ignore_invisibility' When true mob will still be able to see and attack
139 player even if invisible (invisibility mod only).
121140 'blood_amount' contains the number of blood droplets to appear when
122141 mob is hit.
123142 'blood_texture' has the texture name to use for droplets e.g.
125144 'pathfinding' set to 1 for mobs to use pathfinder feature to locate
126145 player, set to 2 so they can build/break also (only
127146 works with dogfight attack and when 'mobs_griefing'
128 in minetest.conf is not false).
147 in minetest.conf is not false). Adding {unbreakable=1}
148 to node groups stops them being broken by mobs.
129149 'immune_to' is a table that holds specific damage when being hit by
130150 certain items e.g.
131151 {"default:sword_wood", 0} -- causes no damage.
152172 'min' minimum number of items dropped, set to 0 for rare drops.
153173 'max' maximum number of items dropped.
154174 Note: If weapon has {fire=1} damage group set then cooked items will drop.
175 Note2: A function can now be passed which can also return drops table, e.g.
176 drops = function(pos)
177 -- do something
178 return { {name = "farming:bread"}, {name = "default:dirt", chance = 2} }
179 end
155180
156181 'visual' holds the look of the mob you wish to create:
157182 'cube' looks like a normal node
169194 'child_texture' holds the texture table for when baby mobs are used.
170195 'gotten_texture' holds the texture table for when self.gotten value is
171196 true, used for milking cows or shearing sheep.
197 'texture_mods' holds a string which overlays a texture on top of the
198 mob texture e.g. "^saddle.png"
172199 'mesh' holds the name of the external object used for mob model
173200 e.g. "mobs_cow.b3d"
174201 'gotten_mesh" holds the name of the external object used for when
175202 self.gotten is true for mobs.
176203 'rotate' custom model rotation, 0 = front, 90 = side, 180 = back,
177204 270 = other side.
205 'glow' has mob glow without light source, 0 to 15 or nil to disable
178206 'double_melee_attack' when true has the api choose between 'punch' and
179207 'punch2' animations. [DEPRECATED]
180208
181 'animation' holds a table containing animation names and settings for use with mesh models:
209 'animation' holds a table containing animation names and settings for use with
210 mesh models:
182211 'stand_start' start frame for when mob stands still.
183212 'stand_end' end frame of stand animation.
184213 'stand_speed' speed of animation in frames per second.
204233 'die_end'
205234 'die_speed'
206235 'die_loop' when set to false stops the animation looping.
236 'die_rotate' if true mob spins during death animation.
207237
208238 Using '_loop = false' setting will stop any of the above animations from
209239 looping.
243273 If false is returned, the mob will not replace the node.
244274
245275 By default, replacing sets self.gotten to true and resets the object
246 properties.
276 properties. (DEPRECATED, use on_replace to make changes).
247277
248278
249279 Custom Definition Functions
272302 time_from_last_punch, tool_capabilities, direction), return
273303 false to stop punch damage and knockback from taking place.
274304 '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)
305 melee attack, parameters are (self, to_attack) and if true
306 is returned normal attack function continued.
307 'on_die' a function that is called when mob is killed (self, pos), also
308 has access to self.cause_of_death table.
309 'on_flop' function called when flying or swimmimng mob is no longer in
310 air/water, (self) paramater and return true to skip the built
311 in api flop feature.
277312 'do_custom' a custom function that is called every tick while mob is
278313 active and which has access to all of the self.* variables
279314 e.g. (self.health for health or self.standing_in for node
305340 'self.order' set to "follow" or "stand" so that npc will follow owner
306341 or stand it's ground
307342 'self.nametag' contains the name of the mob which it can show above
343 'self.state' Current mob state.
344 "stand": no movement (except turning around)
345 "walk": walk or move around aimlessly
346 "attack": chase and attack enemy
347 "runaway": flee from target
348 "flop": bounce around aimlessly
349 (for swimming mobs that have stranded)
350 "die": during death
351
352
353 Adding Mobs in World
354 --------------------
355
356 mobs:add_mob(pos, {
357 name = "mobs_animal:chicken",
358 child = true,
359 owner = "singleplayer",
360 nametag = "Bessy",
361 ignore_count = true -- ignores mob count per map area
362 })
363
364 Returns false if mob could not be added, returns mob object if spawned ok.
365
366
367 Removing Mob from World
368 -----------------------
369
370 mobs:remove(self, decrease)
371
372 Removes mob 'self' from the world and if 'decrease' is true then the mob counter
373 will also be decreased by one.
308374
309375
310376 Spawning Mobs in World
311377 ----------------------
312378
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.
379 mobs:spawn({
380 name = "mobs_monster:tree_monster",
381 nodes = {"group:leaves"},
382 max_light = 7,
383 })
384
385 Spawn functions require the following settings, some of which already have a
386 default setting and can be omitted:
321387
322388 'name' is the name of the animal/monster
323389 'nodes' is a list of nodenames on that the animal/monster can
324 spawn on top of
390 spawn on top of (defaults to {"group:dirt", "group:stone"}
325391 '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()
392 spawn beside (default is {"air"})
393 'interval' is same as in register_abm() (default is 30)
394 'chance' is same as in register_abm() (default is 5000)
395 'min_light' is the minimum light level (default is 0)
396 'max_light' is the maximum light (default is 15)
397 'min_height' is the minimum height a mob can spawn (default: -31000)
398 'max_height' is the maximum height a mob can spawn (default is 31000)
333399 '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
400 map area (default is 1)
337401 'day_toggle' true for day spawning, false for night or nil for
338402 anytime
339403 'on_spawn' is a custom function which runs after mob has spawned
340404 and gives self and pos values.
405 'on_map_load' when true mobs will have a chance of spawning only
406 when new areas of map are loaded, interval will not be
407 used.
408
409 The older spawn functions are still active and working but have no defaults like
410 the mobs:spawn, so it is recommended to use the above instead.
411
412 mobs:register_spawn(name, nodes, max_light, min_light, chance,
413 active_object_count, max_height, day_toggle)
414
415 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
416 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
341417
342418 A simpler way to handle mob spawns has been added with the mobs:spawn(def)
343419 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 })
349420
350421
351422 For each mob that spawns with this function is a field in mobs.spawning_mobs.
363434 'pos' holds the position of the spawning mob
364435 'node' contains the node the mob is spawning on top of
365436 'name' is the name of the animal/monster
437
438
439 Particle Effects
440 ----------------
441
442 mobs:effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
443
444 This function provides a quick way to spawn particles as an effect.
445
446 'pos' center position of particle effect.
447 'amount' how many particles.
448 'texture' texture filename to use for effect.
449 'min_size' smallest particle size.
450 'max_size' largest particle size.
451 'radius' how far particles spread outward from center.
452 'gravity' gravity applied to particles once they spawn.
453 'glow' number between 1 and 15 for glowing particles.
454 'fall' when true particles fall, false has them rising, nil has them scatter.
366455
367456
368457 Making Arrows
377466 'visual' same is in minetest.register_entity()
378467 'visual_size' same is in minetest.register_entity()
379468 'textures' same is in minetest.register_entity()
469 'physical' same is in minetest.register_entity() [default: false]
470 'collide_with_objects' same as above
380471 'velocity' the velocity of the arrow
381472 'drop' if set to true any arrows hitting a node will drop as item
382473 'hit_player' a function that is called when the arrow hits a player;
385476 'hit_mob' a function that is called when the arrow hits a mob;
386477 this function should hurt the mob, the parameters are
387478 (self, player)
479 'hit_object' a function that is called when the arrow hits an object;
480 this function parameters are (self, player)
388481 'hit_node' a function that is called when the arrow hits a node, the
389482 parameters are (self, pos, node)
390483 'tail' when set to 1 adds a trail or tail to mob arrows
398491 'on_step' is a custom function when arrow is active, nil for
399492 default.
400493 '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.
494 'collisionbox' is hitbox table for arrow, {-.1,-.1,-.1,.1,.1,.1} by default.
495 'lifetime' contains float value for how many seconds arrow exists in
496 world before being removed (default is 4.5 seconds).
402497
403498
404499 Spawn Eggs
406501
407502 mobs:register_egg(name, description, background, addegg, no_creative)
408503
409 This function registers a spawn egg which can be used by admin to properly spawn in a mob.
504 This function registers a spawn egg which can be used to properly spawn in a mob.
505 Animals are spawned as tamed unless sneak/shift is held while spawning.
410506
411507 'name' this is the name of your new mob to spawn e.g. "mob:sheep"
412508 'description' the name of the new egg you are creating e.g. "Spawn Sheep"
452548 'force_take' take mob by force, even if tamed (true or false)
453549 'replacewith' once captured replace mob with this item instead (overrides
454550 new mob eggs with saved information)
551
552 mobs:force_capture(self, clicker)
553
554 Same as above but does no checks, it simply captures any and all mobs and places
555 inside a spawn egg containing all of the mob information.
455556
456557
457558 Feeding and Taming/Breeding
565666 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1}
566667
567668
568 mobs:line_of_sight(self, pos1, pos2, stepsize)
669 mobs:line_of_sight(self, pos1, pos2, stepsize) [DEPRECATED]
569670
570671 This function is for use within the mobs definition for special use cases and
571672 returns true if a mob can see the player or victim.
574675 'pos1' position of mob
575676 'pos2' position of vistim or player
576677 'stepsize' usually set to 1
678
679 Use this instead:
680
681 mob_class:line_of_sight(pos1, pos2, stepsize)
682
683
684 mobs:can_spawn(pos, name)
685
686 This function checks the surrounding area at [pos] to see if there is enough empty
687 space to spawn mob [name], if so then a new position is returned for use,
688 otherwise nil is returned.
577689
578690
579691 External Settings for "minetest.conf"
586698 is false)
587699 'mobs_spawn_protected' if set to false then mobs will not spawn in protected
588700 areas (default is true)
701 'mobs_spawn_monster_protected' if set to false then monsters will not spawn in
702 protected areas (default is true)
589703 'remove_far_mobs' if true then untamed mobs that are outside players
590704 visual range will be removed (default is true)
591705 'mobname' can change specific mob chance rate (0 to disable) and
604718 'mobs_griefing' when false mobs cannot break blocks when using either
605719 pathfinding level 2, replace functions or mobs:boom
606720 function.
721 'mob_nospawn_range' Minimum range a mob can spawn near player (def: 12)
722 'mob_active_limit' Number of active mobs in game, 0 for unlimited
723 'mob_area_spawn' When true will check surrounding area the size of the
724 mob for obstructions before spawning, otherwise it
725 defaults to checking the height of the mob only.
726 'mob_smooth_rotate' Enables smooth rotation when mobs turn by default.
607727
608728 Players can override the spawn chance for each mob registered by adding a line
609729 to their minetest.conf file with a new value, the lower the value the more each
610730 mob will spawn e.g.
611731
612 mobs_animal:sheep_chance 11000
613 mobs_monster:sand_monster_chance 100
732 mobs_animal:sheep 11000
733 mobs_monster:sand_monster 100
734
735 ...you can also change how many of a certain mob appear in an active mapblock by
736 adding a comma and then a new value e.g.
737
738 mobs_animal:cow = 8000,4 <-- 4 cows per mapblock at 8000 spawn chance
739 mobs_monster:dirt_monster = ,20 <-- 20 dirt monsters per mapblock
614740
615741
616742 Rideable Horse Example Mob
622748 visual_size = {x = 1.20, y = 1.20},
623749 mesh = "mobs_horse.x",
624750 collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
625 animation = {
751 animation = {
626752 speed_normal = 15,
627753 speed_run = 30,
628754 stand_start = 25,
721847 if inv:room_for_item("main", "mobs:saddle") then
722848 inv:add_item("main", "mobs:saddle")
723849 else
724 minetest.add_item(clicker.getpos(), "mobs:saddle")
850 minetest.add_item(clicker.get_pos(), "mobs:saddle")
725851 end
726852
727853 -- 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.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
26 - 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.
27 - 1.53 - Added 'on_map_load' settings to mobs:spawn so that mobs will only spawn when new areas of map are loaded.
28 - 1.52 - Added 'mob_active_limit' in settings to set number of mobs in game,
29 (default is 0 for unlimited), removed {immortal} from mob armor, fluid viscocity slows mobs
30 - 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
31 - 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
32 - 1.49- Added mobs:force_capture(self, player) function, api functions now use metatables thanks to bell07
33 - 1.48- Add mobs:set_velocity(self, velocity) global function
34 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
35 - 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
36 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
37 - 1.44- Added ToolRanks support for swords when attacking mobs
38 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
39 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
40 - 1.41- Mob pathfinding has been updated thanks to Elkien3
41 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
42 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
43 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
44 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
45 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
46 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
47 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
48 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
49 - 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
50 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
51 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
52 - 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
53 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
54 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
55 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
56 - 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.
57 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
58 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
59 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
60 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
61 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
62 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
63 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
64 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
65 - 1.16- Mobs follow multiple items now, Npc's can breed
66 - 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.
67 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
68 - 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
69 - 1.12- Added animal ownership so that players cannot steal your tamed animals
70 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
71 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
72 - 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
73 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
74 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
75 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
76 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
77 - 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
78 - 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 :)
79 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
80 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
81 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
82 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
83 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
84 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
85 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
86 - 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
87 - 0.5 - Mobs now float in water, die from falling, and some code improvements
88 - 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 :)
89 - 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 :)
90 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
91 - 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