Codebase list minetest-mod-mobs-redo / 114c3be
Update upstream source from tag 'upstream/20210923' Update to upstream version '20210923' with Debian dir ad375aa41d8cdf748f2d8b3afd04346a6c5d99e3 Julien Puydt 2 years ago
33 changed file(s) with 3537 addition(s) and 5627 deletion(s). Raw diff Collapse all Expand all
+2204
-1290
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 = "20210920",
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 _breed_countdown = nil,
163 _cmi_is_mob = true
164 }
165
166 local mob_class_meta = {__index = mob_class}
167
80168
81169 -- play sound
82 local mob_sound = function(self, sound)
170 function mob_class:mob_sound(sound)
171
172 local pitch = 1.0
173
174 -- higher pitch for a child
175 if self.child then pitch = pitch * 1.5 end
176
177 -- a little random pitch to be different
178 pitch = pitch + random(-10, 10) * 0.005
83179
84180 if sound then
85181 minetest.sound_play(sound, {
86182 object = self.object,
87183 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
184 max_hear_distance = self.sounds.distance,
185 pitch = pitch
186 }, true)
90187 end
91188 end
92189
93190
94191 -- attack player/mob
95 local do_attack = function(self, player)
192 function mob_class:do_attack(player)
96193
97194 if self.state == "attack" then
98195 return
102199 self.state = "attack"
103200
104201 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
202 self:mob_sound(self.sounds.war_cry)
106203 end
107204 end
108205
110207 -- calculate distance
111208 local get_distance = function(a, b)
112209
210 if not a or not b then return 50 end -- nil check
211
113212 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114213
115214 return square(x * x + y * y + z * z)
117216
118217
119218 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
219 function mob_class:collision()
121220
122221 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124222 local x, z = 0, 0
125223 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126224
127225 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128226
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
227 if object:is_player() then
131228
132229 local pos2 = object:get_pos()
133230 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
141238 end
142239
143240
241 -- check if string exists in another string or table
242 local check_for = function(look_for, look_inside)
243
244 if type(look_inside) == "string" and look_inside == look_for then
245
246 return true
247
248 elseif type(look_inside) == "table" then
249
250 for _, str in pairs(look_inside) do
251
252 if str == look_for then
253 return true
254 end
255
256 if str:find("group:") then
257
258 local group = str:split(":")[2]
259
260 if minetest.get_item_group(look_for, group) ~= 0 then
261 return true
262 end
263 end
264 end
265 end
266
267 return false
268 end
269
270
144271 -- move mob in facing direction
145 local set_velocity = function(self, v)
272 function mob_class:set_velocity(v)
273
274 -- halt mob if it has been ordered to stay
275 if self.order == "stand" then
276
277 local vel = self.object:get_velocity() or {y = 0}
278
279 self.object:set_velocity({x = 0, y = vel.y, z = 0})
280
281 return
282 end
146283
147284 local c_x, c_y = 0, 0
148285
149286 -- can mob be pushed, if so calculate direction
150287 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({
288 c_x, c_y = unpack(self:collision())
289 end
290
291 local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
292
293 -- nil check for velocity
294 v = v or 0.01
295
296 -- check if standing in liquid with max viscosity of 7
297 local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
298
299 -- only slow mob trying to move while inside a viscous fluid that
300 -- they aren't meant to be in (fish in water, spiders in cobweb etc)
301 if v > 0 and visc and visc > 0
302 and not check_for(self.standing_in, self.fly_in) then
303 v = v / (visc + 1)
304 end
305
306 -- set velocity
307 local vel = self.object:get_velocity() or 0
308
309 local new_vel = {
163310 x = (sin(yaw) * -v) + c_x,
164 y = self.object:get_velocity().y,
165 z = (cos(yaw) * v) + c_y,
166 })
311 y = vel.y,
312 z = (cos(yaw) * v) + c_y}
313
314 self.object:set_velocity(new_vel)
315 end
316
317 -- global version of above function
318 function mobs:set_velocity(entity, v)
319 mob_class.set_velocity(entity, v)
167320 end
168321
169322
170323 -- calculate mob velocity
171 local get_velocity = function(self)
324 function mob_class:get_velocity()
172325
173326 local v = self.object:get_velocity()
174327
328 if not v then return 0 end
329
175330 return (v.x * v.x + v.z * v.z) ^ 0.5
176331 end
177332
178333
179334 -- set and return valid yaw
180 local set_yaw = function(self, yaw, delay)
335 function mob_class:set_yaw(yaw, delay)
181336
182337 if not yaw or yaw ~= yaw then
183338 yaw = 0
184339 end
185340
186 delay = delay or 0
341 delay = mob_smooth_rotate and (delay or 0) or 0
187342
188343 if delay == 0 then
344
189345 self.object:set_yaw(yaw)
346
190347 return yaw
191348 end
192349
197354 end
198355
199356 -- global function to set mob yaw
200 function mobs:yaw(self, yaw, delay)
201 set_yaw(self, yaw, delay)
357 function mobs:yaw(entity, yaw, delay)
358 mob_class.set_yaw(entity, yaw, delay)
202359 end
203360
204361
205362 -- set defined animation
206 local set_animation = function(self, anim)
207
208 if not self.animation
209 or not anim then return end
363 function mob_class:set_animation(anim, force)
364
365 if not self.animation or not anim then return end
210366
211367 self.animation.current = self.animation.current or ""
212368
213 -- only set different animation for attacks when setting to same set
214 if anim ~= "punch" and anim ~= "shoot"
369 -- only use different animation for attacks when using same set
370 if force ~= true and anim ~= "punch" and anim ~= "shoot"
215371 and string.find(self.animation.current, anim) then
216372 return
217373 end
218374
219 -- check for more than one animation
220375 local num = 0
221376
377 -- check for more than one animation (max 4)
222378 for n = 1, 4 do
223379
224380 if self.animation[anim .. n .. "_start"]
244400 self.object:set_animation({
245401 x = self.animation[anim .. "_start"],
246402 y = self.animation[anim .. "_end"]},
247 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
403 self.animation[anim .. "_speed"] or
404 self.animation.speed_normal or 15,
248405 0, self.animation[anim .. "_loop"] ~= false)
249406 end
250407
251 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
408 function mobs:set_animation(entity, anim)
409 entity.set_animation(entity, anim)
410 end
411
412
413 -- check line of sight (BrunoMine)
414 local line_of_sight = function(self, pos1, pos2, stepsize)
415
416 stepsize = stepsize or 1
417
418 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
419
420 -- normal walking and flying mobs can see you through air
421 if s == true then
422 return true
423 end
424
425 -- New pos1 to be analyzed
426 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
427
428 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
429
430 -- Checks the return
431 if r == true then return true end
432
433 -- Nodename found
434 local nn = minetest.get_node(pos).name
435
436 -- Target Distance (td) to travel
437 local td = get_distance(pos1, pos2)
438
439 -- Actual Distance (ad) traveled
440 local ad = 0
441
442 -- It continues to advance in the line of sight in search of a real
443 -- obstruction which counts as 'walkable' nodebox.
444 while minetest.registered_nodes[nn]
445 and (minetest.registered_nodes[nn].walkable == false) do
446
447 -- Check if you can still move forward
448 if td < ad + stepsize then
449 return true -- Reached the target
450 end
451
452 -- Moves the analyzed pos
453 local d = get_distance(pos1, pos2)
454
455 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
456 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
457 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
458
459 -- NaN checks
460 if d == 0
461 or npos1.x ~= npos1.x
462 or npos1.y ~= npos1.y
463 or npos1.z ~= npos1.z then
464 return false
465 end
466
467 ad = ad + stepsize
468
469 -- scan again
470 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
471
472 if r == true then return true end
473
474 -- New Nodename found
475 nn = minetest.get_node(pos).name
476 end
477
478 return false
254479 end
255480
256481
257482 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
483 local new_line_of_sight = function(self, pos1, pos2, stepsize)
259484
260485 if not pos1 or not pos2 then return end
261486
262487 stepsize = stepsize or 1
263488
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
489 local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
265490
266491 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267492
280505 local nn = minetest.get_node(pos).name
281506
282507 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
508 -- obstruction which counts as 'walkable' nodebox.
284509 while minetest.registered_nodes[nn]
285510 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
511
512 npos1 = vadd(npos1, stepv)
289513
290514 if get_distance(npos1, pos2) < stepsize then return true end
291515
301525 return false
302526 end
303527
528 -- check line of sight using raycasting (thanks Astrobe)
529 local ray_line_of_sight = function(self, pos1, pos2)
530
531 local ray = minetest.raycast(pos1, pos2, true, false)
532 local thing = ray:next()
533
534 while thing do -- thing.type, thing.ref
535
536 if thing.type == "node" then
537
538 local name = minetest.get_node(thing.under).name
539
540 if minetest.registered_items[name]
541 and minetest.registered_items[name].walkable then
542 return false
543 end
544 end
545
546 thing = ray:next()
547 end
548
549 return true
550 end
551
552
553 function mob_class:line_of_sight(pos1, pos2, stepsize)
554
555 if minetest.raycast then -- only use if minetest 5.0 is detected
556 return ray_line_of_sight(self, pos1, pos2)
557 end
558
559 return line_of_sight(self, pos1, pos2, stepsize)
560 end
561
304562 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
563 function mobs:line_of_sight(entity, pos1, pos2, stepsize)
564 return entity:line_of_sight(pos1, pos2, stepsize)
565 end
566
567
568 function mob_class:attempt_flight_correction(override)
569
570 if self:flight_check() and override ~= true then return true end
571
572 -- We are not flying in what we are supposed to.
573 -- See if we can find intended flight medium and return to it
574 local pos = self.object:get_pos() ; if not pos then return true end
575 local searchnodes = self.fly_in
576
577 if type(searchnodes) == "string" then
578 searchnodes = {self.fly_in}
579 end
580
581 local flyable_nodes = minetest.find_nodes_in_area(
582 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
583 {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes)
584 -- pos.y + 0 hopefully fixes floating swimmers
585
586 if #flyable_nodes < 1 then
587 return false
588 end
589
590 local escape_target = flyable_nodes[random(#flyable_nodes)]
591 local escape_direction = vdirection(pos, escape_target)
592
593 self.object:set_velocity(
594 vmultiply(escape_direction, 1))
595
596 return true
308597 end
309598
310599
311600 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
601 function mob_class:flight_check()
313602
314603 local def = minetest.registered_nodes[self.standing_in]
315604
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
605 if not def then return false end
606
607 -- are we standing inside what we should be to fly/swim ?
608 if check_for(self.standing_in, self.fly_in) then
321609 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
332610 end
333611
334612 -- stops mobs getting stuck inside stairs and plantlike nodes
342620 end
343621
344622
623 -- turn mob to face position
624 local yaw_to_pos = function(self, target, rot)
625
626 rot = rot or 0
627
628 local pos = self.object:get_pos()
629 local vec = {x = target.x - pos.x, z = target.z - pos.z}
630 local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
631
632 if target.x > pos.x then
633 yaw = yaw + pi
634 end
635
636 yaw = self:set_yaw(yaw, rot)
637
638 return yaw
639 end
640
641 function mobs:yaw_to_pos(self, target, rot)
642 return yaw_to_pos(self, target, rot)
643 end
644
645
646 -- if stay near set then periodically check for nodes and turn towards them
647 function mob_class:do_stay_near()
648
649 if not self.stay_near then return false end
650
651 local pos = self.object:get_pos()
652 local searchnodes = self.stay_near[1]
653 local chance = self.stay_near[2] or 10
654
655 if not pos or random(chance) > 1 then
656 return false
657 end
658
659 if type(searchnodes) == "string" then
660 searchnodes = {self.stay_near[1]}
661 end
662
663 local r = self.view_range
664 local nearby_nodes = minetest.find_nodes_in_area(
665 {x = pos.x - r, y = pos.y - 1, z = pos.z - r},
666 {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
667
668 if #nearby_nodes < 1 then
669 return false
670 end
671
672 yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)])
673
674 self:set_animation("walk")
675
676 self:set_velocity(self.walk_velocity)
677
678 return true
679 end
680
681
345682 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
683 local effect = function(pos, amount, texture, min_size, max_size,
684 radius, gravity, glow, fall)
347685
348686 radius = radius or 2
349687 min_size = min_size or 0.5
351689 gravity = gravity or -10
352690 glow = glow or 0
353691
692 if fall == true then
693 fall = 0
694 elseif fall == false then
695 fall = radius
696 else
697 fall = -radius
698 end
699
354700 minetest.add_particlespawner({
355701 amount = amount,
356702 time = 0.25,
357703 minpos = pos,
358704 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
705 minvel = {x = -radius, y = fall, z = -radius},
360706 maxvel = {x = radius, y = radius, z = radius},
361707 minacc = {x = 0, y = gravity, z = 0},
362708 maxacc = {x = 0, y = gravity, z = 0},
365711 minsize = min_size,
366712 maxsize = max_size,
367713 texture = texture,
368 glow = glow,
714 glow = glow
369715 })
370716 end
371717
718 function mobs:effect(pos, amount, texture, min_size, max_size,
719 radius, gravity, glow, fall)
720
721 effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
722 end
723
724
725 -- Thanks Wuzzy for the following editable settings
726
727 local HORNY_TIME = 30
728 local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes
729 local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
730
372731
373732 -- update nametag colour
374 local update_tag = function(self)
733 function mob_class:update_tag()
375734
376735 local col = "#00FF00"
377736 local qua = self.hp_max / 4
388747 col = "#FF0000"
389748 end
390749
750 local text = ""
751
752 if self.horny == true then
753
754 text = "\nLoving: " .. (self.hornytimer - (HORNY_TIME + HORNY_AGAIN_TIME))
755
756 elseif self.child == true then
757
758 text = "\nGrowing: " .. (self.hornytimer - CHILD_GROW_TIME)
759
760 elseif self._breed_countdown then
761
762 text = "\nBreeding: " .. self._breed_countdown
763
764 end
765
766 self.infotext = "Health: " .. self.health .. " / " .. self.hp_max
767 .. "\n" .. "Owner: " .. self.owner
768 .. text
769
770 -- set changes
391771 self.object:set_properties({
392772 nametag = self.nametag,
393 nametag_color = col
773 nametag_color = col,
774 infotext = self.infotext
394775 })
395
396776 end
397777
398778
399779 -- drop items
400 local item_drop = function(self)
780 function mob_class:item_drop()
781
782 -- no drops if disabled by setting or mob is child
783 if not mobs_drop_items or self.child then return end
784
785 local pos = self.object:get_pos()
786
787 -- check for drops function
788 self.drops = type(self.drops) == "function"
789 and self.drops(pos) or self.drops
401790
402791 -- check for nil or no drops
403792 if not self.drops or #self.drops == 0 then
404793 return
405794 end
406795
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
413796 -- 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
797 local death_by_player = self.cause_of_death
798 and self.cause_of_death.puncher
799 and self.cause_of_death.puncher:is_player()
416800
417801 local obj, item, num
418 local pos = self.object:get_pos()
419802
420803 for n = 1, #self.drops do
421804
422 if random(1, self.drops[n].chance) == 1 then
805 if random(self.drops[n].chance) == 1 then
423806
424807 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425808 item = self.drops[n].name
435818 end
436819 end
437820
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
821 -- only drop rare items (drops.min = 0) if killed by player
822 if death_by_player or self.drops[n].min ~= 0 then
443823 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
444824 end
445825
448828 obj:set_velocity({
449829 x = random(-10, 10) / 9,
450830 y = 6,
451 z = random(-10, 10) / 9,
831 z = random(-10, 10) / 9
452832 })
453833
454834 elseif obj then
461841 end
462842
463843
844 -- remove mob and descrease counter
845 local remove_mob = function(self, decrease)
846
847 self.object:remove()
848
849 if decrease and active_limit > 0 then
850
851 active_mobs = active_mobs - 1
852
853 if active_mobs < 0 then
854 active_mobs = 0
855 end
856 end
857 --print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
858 end
859
860 -- global function for removing mobs
861 function mobs:remove(self, decrease)
862 remove_mob(self, decrease)
863 end
864
865
464866 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
867 function mob_class:check_for_death(cmi_cause)
868
869 -- We dead already
870 if self.state == "die" then
871 return true
872 end
466873
467874 -- has health actually changed?
468875 if self.health == self.old_health and self.health > 0 then
469 return
470 end
876 return false
877 end
878
879 local damaged = self.health < self.old_health
471880
472881 self.old_health = self.health
473882
474883 -- still got some health? play hurt sound
475884 if self.health > 0 then
476885
477 mob_sound(self, self.sounds.damage)
886 -- only play hurt sound if damaged
887 if damaged then
888 self:mob_sound(self.sounds.damage)
889 end
478890
479891 -- make sure health isn't higher than max
480892 if self.health > self.hp_max then
482894 end
483895
484896 -- 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
897 -- if not self.nametag2 then
898 -- self.nametag2 = self.nametag or ""
899 -- end
900
901 -- if show_health
902 -- and (cmi_cause and cmi_cause.type == "punch") then
903
904 -- self.htimer = 2
905 -- self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
906 self:update_tag()
907 -- end
497908
498909 return false
499910 end
501912 self.cause_of_death = cmi_cause
502913
503914 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
915 self:item_drop()
916
917 self:mob_sound(self.sounds.death)
507918
508919 local pos = self.object:get_pos()
509920
510921 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
922 if pos and self.on_die then
923
924 self:on_die(pos)
514925
515926 if use_cmi then
516927 cmi.notify_die(self.object, cmi_cause)
517928 end
518929
519 self.object:remove()
930 remove_mob(self, true)
520931
521932 return true
522933 end
523934
524 -- default death function and die animation (if defined)
935 -- check for custom death function and die animation
525936 if self.animation
526937 and self.animation.die_start
527938 and self.animation.die_end then
528939
529940 local frames = self.animation.die_end - self.animation.die_start
530941 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
942 local length = max((frames / speed), 0)
943 local rot = self.animation.die_rotate and 5
532944
533945 self.attack = nil
946 self.following = nil
534947 self.v_start = false
535948 self.timer = 0
536949 self.blinktimer = 0
537950 self.passive = true
538951 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
952 self.object:set_properties({
953 pointable = false, collide_with_objects = false,
954 automatic_rotate = rot, static_save = false
955 })
956 self:set_velocity(0)
957 self:set_animation("die")
541958
542959 minetest.after(length, function(self)
543960
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()
961 if self.object:get_luaentity() then
962
963 if use_cmi then
964 cmi.notify_die(self.object, cmi_cause)
965 end
966
967 remove_mob(self, true)
968 end
549969 end, self)
550 else
970
971 return true
972
973 elseif pos then -- otherwise remove mod and show particle effect
551974
552975 if use_cmi then
553976 cmi.notify_die(self.object, cmi_cause)
554977 end
555978
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
979 remove_mob(self, true)
980
981 effect(pos, 20, "tnt_smoke.png")
982 end
560983
561984 return true
562985 end
563986
564987
988 -- get node but use fallback for nil or unknown
989 local node_ok = function(pos, fallback)
990
991 fallback = fallback or mobs.fallback_node
992
993 local node = minetest.get_node_or_nil(pos)
994
995 if node and minetest.registered_nodes[node.name] then
996 return node
997 end
998
999 return minetest.registered_nodes[fallback]
1000 end
1001
1002
1003 -- Returns true is node can deal damage to self
1004 local is_node_dangerous = function(self, nodename)
1005
1006 if self.water_damage > 0
1007 and minetest.get_item_group(nodename, "water") ~= 0 then
1008 return true
1009 end
1010
1011 if self.lava_damage > 0
1012 and minetest.get_item_group(nodename, "lava") ~= 0 then
1013 return true
1014 end
1015
1016 if self.fire_damage > 0
1017 and minetest.get_item_group(nodename, "fire") ~= 0 then
1018 return true
1019 end
1020
1021 if minetest.registered_nodes[nodename].damage_per_second > 0 then
1022 return true
1023 end
1024
1025 return false
1026 end
1027
1028
5651029 -- is mob facing a cliff
566 local is_at_cliff = function(self)
1030 function mob_class:is_at_cliff()
5671031
5681032 if self.fear_height == 0 then -- 0 for no falling protection!
5691033 return false
5701034 end
5711035
1036 -- get yaw but if nil returned object no longer exists
5721037 local yaw = self.object:get_yaw()
1038
1039 if not yaw then return false end
1040
5731041 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
5741042 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
5751043 local pos = self.object:get_pos()
5761044 local ypos = pos.y + self.collisionbox[2] -- just above floor
5771045
578 if minetest.line_of_sight(
1046 local free_fall, blocker = minetest.line_of_sight(
5791047 {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
1048 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
1049
1050 -- check for straight drop
1051 if free_fall then
5831052 return true
5841053 end
5851054
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]
1055 local bnode = node_ok(blocker)
1056
1057 -- will we drop onto dangerous node?
1058 if is_node_dangerous(self, bnode.name) then
1059 return true
1060 end
1061
1062 local def = minetest.registered_nodes[bnode.name]
1063
1064 return (not def and def.walkable)
6021065 end
6031066
6041067
6051068 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
1069 function mob_class:do_env_damage()
6071070
6081071 -- feed/tame text timer (so mob 'full' messages dont spam chat)
6091072 if self.htimer > 0 then
6111074 end
6121075
6131076 -- 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()
1077 -- if self.htimer < 1 and self.nametag2 then
1078
1079 -- self.nametag = self.nametag2
1080 -- self.nametag2 = nil
1081
1082 self:update_tag()
1083 -- end
1084
1085 local pos = self.object:get_pos() ; if not pos then return end
6231086
6241087 self.time_of_day = minetest.get_timeofday()
6251088
626 -- remove mob if standing inside ignore node
1089 -- halt mob if standing inside ignore node
6271090 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
1091
1092 self.object:set_velocity({x = 0, y = 0, z = 0})
1093
1094 return true
1095 end
1096
1097 -- particle appears at random mob height
1098 local py = {
1099 x = pos.x,
1100 y = pos.y + random(self.collisionbox[2], self.collisionbox[5]),
1101 z = pos.z
1102 }
1103
1104 local nodef = minetest.registered_nodes[self.standing_in]
1105
1106 -- water
1107 if self.water_damage ~= 0 and nodef.groups.water then
1108
1109 self.health = self.health - self.water_damage
1110
1111 effect(py, 5, "bubble.png", nil, nil, 1, nil)
1112
1113 if self:check_for_death({type = "environment",
1114 pos = pos, node = self.standing_in}) then
1115 return true
1116 end
1117
1118 -- lava damage
1119 elseif self.lava_damage ~= 0 and nodef.groups.lava then
1120
1121 self.health = self.health - self.lava_damage
1122
1123 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1124
1125 if self:check_for_death({type = "environment", pos = pos,
1126 node = self.standing_in, hot = true}) then
1127 return true
1128 end
1129
1130 -- fire damage
1131 elseif self.fire_damage ~= 0 and nodef.groups.fire then
1132
1133 self.health = self.health - self.fire_damage
1134
1135 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1136
1137 if self:check_for_death({type = "environment", pos = pos,
1138 node = self.standing_in, hot = true}) then
1139 return true
1140 end
1141
1142 -- damage_per_second node check (not fire and lava)
1143 elseif nodef.damage_per_second ~= 0
1144 and nodef.groups.lava == nil and nodef.groups.fire == nil then
1145
1146 self.health = self.health - nodef.damage_per_second
1147
1148 effect(py, 5, "tnt_smoke.png")
1149
1150 if self:check_for_death({type = "environment",
1151 pos = pos, node = self.standing_in}) then
1152 return true
1153 end
1154 end
1155
1156 -- air damage
1157 if self.air_damage ~= 0 and self.standing_in == "air" then
1158
1159 self.health = self.health - self.air_damage
1160
1161 effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
1162
1163 if self:check_for_death({type = "environment",
1164 pos = pos, node = self.standing_in}) then
1165 return true
1166 end
6301167 end
6311168
6321169 -- is mob light sensative, or scared of the dark :P
6391176
6401177 self.health = self.health - self.light_damage
6411178
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 --[[
1179 effect(py, 5, "tnt_smoke.png")
1180
1181 if self:check_for_death({type = "light"}) then
1182 return true
1183 end
1184 end
1185 end
1186
6941187 --- 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"})
1188 if (self.suffocation and self.suffocation ~= 0)
1189 and (nodef.walkable == nil or nodef.walkable == true)
1190 and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
1191 and (nodef.node_box == nil or nodef.node_box.type == "regular")
1192 and (nodef.groups.disable_suffocation ~= 1) then
1193
1194 local damage
1195
1196 if self.suffocation == true then
1197 damage = 2
1198 else
1199 damage = (self.suffocation or 2)
1200 end
1201
1202 self.health = self.health - damage
1203
1204 if self:check_for_death({type = "suffocation",
1205 pos = pos, node = self.standing_in}) then
1206 return true
1207 end
1208 end
1209
1210 return self:check_for_death({type = "unknown"})
7071211 end
7081212
7091213
7101214 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
1215 function mob_class:do_jump()
7121216
7131217 if not self.jump
7141218 or self.jump_height == 0
7221226
7231227 -- something stopping us while moving?
7241228 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
1229 and self:get_velocity() > 0.5
7261230 and self.object:get_velocity().y ~= 0 then
7271231 return false
7281232 end
7301234 local pos = self.object:get_pos()
7311235 local yaw = self.object:get_yaw()
7321236
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
1237 -- sanity check
1238 if not yaw then return false end
1239
1240 -- we can only jump if standing on solid node
1241 if minetest.registered_nodes[self.standing_on].walkable == false then
7411242 return false
7421243 end
7431244
7451246 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
7461247 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
7471248
1249 -- set y_pos to base of mob
1250 pos.y = pos.y + self.collisionbox[2]
1251
7481252 -- what is in front of mob?
7491253 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
1254 x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
7531255 })
7541256
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
1257 -- what is above and in front?
1258 local nodt = node_ok({
1259 x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
1260 })
1261
1262 local blocked = minetest.registered_nodes[nodt.name].walkable
1263
1264 -- are we facing a fence or wall
1265 if nod.name:find("fence") or nod.name:find("gate") or nod.name:find("wall") then
1266 self.facing_fence = true
1267 end
1268 --[[
1269 print("on: " .. self.standing_on
1270 .. ", front: " .. nod.name
1271 .. ", front above: " .. nodt.name
1272 .. ", blocked: " .. (blocked and "yes" or "no")
1273 .. ", fence: " .. (self.facing_fence and "yes" or "no")
1274 )
1275 ]]
1276 -- jump if standing on solid node (not snow) and not blocked
1277 if (self.walk_chance == 0 or minetest.registered_items[nod.name].walkable)
1278 and not blocked and not self.facing_fence and nod.name ~= node_snow then
1279
1280 local v = self.object:get_velocity()
1281
1282 v.y = self.jump_height
1283
1284 self:set_animation("jump") -- only when defined
1285
1286 self.object:set_velocity(v)
1287
1288 -- when in air move forward
1289 minetest.after(0.3, function(self, v)
1290
1291 if self.object:get_luaentity() then
1292
1293 self.object:set_acceleration({
1294 x = v.x * 2,
1295 y = 0,
1296 z = v.z * 2
1297 })
1298 end
1299 end, self, v)
1300
1301 if self:get_velocity() > 0 then
1302 self:mob_sound(self.sounds.jump)
1303 end
1304
1305 self.jump_count = 0
7951306
7961307 return true
1308 end
1309
1310 -- if blocked for 3 counts then turn
1311 if not self.following and (self.facing_fence or blocked) then
1312
1313 self.jump_count = (self.jump_count or 0) + 1
1314
1315 if self.jump_count > 2 then
1316
1317 local yaw = self.object:get_yaw() or 0
1318 local turn = random(0, 2) + 1.35
1319
1320 yaw = self:set_yaw(yaw + turn, 12)
1321
1322 self.jump_count = 0
1323 end
7971324 end
7981325
7991326 return false
8131340 obj_pos = objs[n]:get_pos()
8141341
8151342 dist = get_distance(pos, obj_pos)
1343
8161344 if dist < 1 then dist = 1 end
8171345
8181346 local damage = floor((4 / dist) * radius)
8271355 end
8281356
8291357
1358 -- can mob see player
1359 local is_invisible = function(self, player_name)
1360
1361 if mobs.invis[player_name] and not self.ignore_invisibility then
1362 return true
1363 end
1364 end
1365
1366
8301367 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
1368 function mob_class:follow_holding(clicker)
1369
1370 if is_invisible(self, clicker:get_player_name()) then
8341371 return false
8351372 end
8361373
8371374 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
1375
1376 -- are we holding an item mob can follow ?
1377 if check_for(item:get_name(), self.follow) then
8431378 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
8541379 end
8551380
8561381 return false
8581383
8591384
8601385 -- 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
1386 function mob_class:breed()
1387
1388 -- child takes a long time before growing into adult
8641389 if self.child == true then
8651390
8661391 self.hornytimer = self.hornytimer + 1
8671392
868 if self.hornytimer > 240 then
1393 if self.hornytimer > CHILD_GROW_TIME then
8691394
8701395 self.child = false
8711396 self.hornytimer = 0
8751400 mesh = self.base_mesh,
8761401 visual_size = self.base_size,
8771402 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
1403 selectionbox = self.base_selbox
8791404 })
8801405
8811406 -- custom function when child grows up
8821407 if self.on_grown then
8831408 self.on_grown(self)
8841409 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 })
1410 local pos = self.object:get_pos() ; if not pos then return end
1411 local ent = self.object:get_luaentity()
1412
1413 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
1414
1415 self.object:set_pos(pos)
1416
1417 -- jump slightly when fully grown so as not to fall into ground
1418 self.object:set_velocity({x = 0, y = 0.5, z = 0 })
8911419 end
8921420 end
8931421
8941422 return
8951423 end
8961424
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
1425 -- horny animal can mate for HORNY_TIME seconds,
1426 -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
8991427 if self.horny == true
900 and self.hornytimer < 240 then
1428 and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
9011429
9021430 self.hornytimer = self.hornytimer + 1
9031431
904 if self.hornytimer >= 240 then
1432 if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
9051433 self.hornytimer = 0
9061434 self.horny = false
9071435 end
1436
1437 self:update_tag()
9081438 end
9091439
9101440 -- find another same animal who is also horny and mate if nearby
9111441 if self.horny == true
912 and self.hornytimer <= 40 then
1442 and self.hornytimer <= HORNY_TIME then
9131443
9141444 local pos = self.object:get_pos()
9151445
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
1446 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
1447 "heart.png", 3, 4, 1, 0.1)
9171448
9181449 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
1450 local ent
9211451
9221452 for n = 1, #objs do
9231453
9311461 if ent.name == self.name then
9321462 canmate = true
9331463 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
1464 local entname = ent.name:split(":")
1465 local selfname = self.name:split(":")
9361466
9371467 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
1468 entname = entname[2]:split("_")
1469 selfname = selfname[2]:split("_")
9401470
9411471 if entname[1] == selfname[1] then
9421472 canmate = true
9451475 end
9461476 end
9471477
948 if ent
1478 -- found another similar horny animal that isn't self?
1479 if ent and ent.object ~= self.object
9491480 and canmate == true
9501481 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
1482 and ent.hornytimer <= HORNY_TIME then
1483
1484 local pos2 = ent.object:get_pos()
1485
1486 -- Have mobs face one another
1487 yaw_to_pos(self, pos2)
1488 yaw_to_pos(ent, self.object:get_pos())
1489
1490 self.hornytimer = HORNY_TIME + 1
1491 ent.hornytimer = HORNY_TIME + 1
1492
1493 self:update_tag()
1494
1495 -- have we reached active mob limit
1496 if active_limit > 0 and active_mobs >= active_limit then
1497 minetest.chat_send_player(self.owner,
1498 S("Active Mob Limit Reached!")
1499 .. " (" .. active_mobs
1500 .. " / " .. active_limit .. ")")
1501 return
1502 end
9601503
9611504 -- spawn baby
9621505 minetest.after(5, function(self, ent)
9691512 if self.on_breed then
9701513
9711514 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
1515 if self:on_breed(ent) == false then
9731516 return
9741517 end
9751518 else
9761519 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
9771520 end
9781521
1522 pos.y = pos.y + 0.5 -- spawn child a little higher
1523
9791524 local mob = minetest.add_entity(pos, self.name)
9801525 local ent2 = mob:get_luaentity()
9811526 local textures = self.base_texture
9901535 textures = textures,
9911536 visual_size = {
9921537 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
1538 y = self.base_size.y * .5
9941539 },
9951540 collisionbox = {
9961541 self.base_colbox[1] * .5,
9981543 self.base_colbox[3] * .5,
9991544 self.base_colbox[4] * .5,
10001545 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1546 self.base_colbox[6] * .5
10021547 },
10031548 selectionbox = {
10041549 self.base_selbox[1] * .5,
10061551 self.base_selbox[3] * .5,
10071552 self.base_selbox[4] * .5,
10081553 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1554 self.base_selbox[6] * .5
10101555 },
10111556 })
10121557 -- tamed and owned by parents' owner
10151560 ent2.owner = self.owner
10161561 end, self, ent)
10171562
1018 num = 0
1019
10201563 break
10211564 end
10221565 end
10251568
10261569
10271570 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1571 function mob_class:replace(pos)
1572
1573 local vel = self.object:get_velocity()
1574 if not vel then return end
10291575
10301576 if not mobs_griefing
10311577 or not self.replace_rate
10321578 or not self.replace_what
10331579 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1580 or vel.y ~= 0
1581 or random(self.replace_rate) > 1 then
10361582 return
10371583 end
10381584
10551601
10561602 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
10571603
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
1604 -- print("replace node = ".. minetest.get_node(pos).name, pos.y)
10631605
10641606 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
1607
1608 local oldnode = what or ""
1609 local newnode = with
1610
1611 -- pass actual node name when using table or groups
1612 if type(oldnode) == "table"
1613 or oldnode:find("group:") then
1614 oldnode = minetest.get_node(pos).name
1615 end
1616
1617 if self:on_replace(pos, oldnode, newnode) == false then
1618 return
1619 end
1620 end
1621
1622 minetest.set_node(pos, {name = with})
10781623 end
10791624 end
10801625
10811626
10821627 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1628 function mob_class:day_docile()
10841629
10851630 if self.docile_by_day == false then
10861631
10971642
10981643 local los_switcher = false
10991644 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)
1645 local can_dig_drop = function(pos)
1646
1647 if minetest.is_protected(pos, "") then
1648 return false
1649 end
1650
1651 local node = node_ok(pos, "air").name
1652 local ndef = minetest.registered_nodes[node]
1653
1654 if node ~= "ignore"
1655 and ndef
1656 and ndef.drawtype ~= "airlike"
1657 and not ndef.groups.level
1658 and not ndef.groups.unbreakable
1659 and not ndef.groups.liquid then
1660
1661 local drops = minetest.get_node_drops(node)
1662
1663 for _, item in ipairs(drops) do
1664
1665 minetest.add_item({
1666 x = pos.x - 0.5 + random(),
1667 y = pos.y - 0.5 + random(),
1668 z = pos.z - 0.5 + random()
1669 }, item)
1670 end
1671
1672 minetest.remove_node(pos)
1673
1674 return true
1675 end
1676
1677 return false
1678 end
1679
1680
1681 local pathfinder_mod = minetest.get_modpath("pathfinder")
1682 -- path finding and smart mob routine by rnd,
1683 -- line_of_sight and other edits by Elkien3
1684 function mob_class:smart_mobs(s, p, dist, dtime)
11031685
11041686 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1687 local target_pos = p
1688
11071689
11081690 -- is it becoming stuck?
11091691 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
11761758 end, self)
11771759 end
11781760
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1761 if abs(vsubtract(s,target_pos).y) > self.stepheight then
11801762
11811763 if height_switcher then
11821764 use_pathfind = true
11891771 end
11901772 end
11911773
1774 -- lets try find a path, first take care of positions
1775 -- since pathfinder is very sensitive
11921776 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]
11961777
11971778 -- round position to center of node to avoid stuck in walls
11981779 -- also adjust height for player models!
11991780 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
12011781 s.z = floor(s.z + 0.5)
12021782
12031783 local ssight, sground = minetest.line_of_sight(s, {
12151795 p1.z = floor(p1.z + 0.5)
12161796
12171797 local dropheight = 6
1798
12181799 if self.fear_height ~= 0 then dropheight = self.fear_height end
12191800
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1221
1801 local jumpheight = 0
1802
1803 if self.jump and self.jump_height >= 4 then
1804 jumpheight = min(ceil(self.jump_height / 4), 4)
1805
1806 elseif self.stepheight > 0.5 then
1807 jumpheight = 1
1808 end
1809
1810 if pathfinder_mod then
1811 self.path.way = pathfinder.find_path(s, p1, self, dtime)
1812 else
1813 self.path.way = minetest.find_path(s, p1, 16, jumpheight,
1814 dropheight, "Dijkstra")
1815 end
12221816 --[[
12231817 -- show path using particles
12241818 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1819
1820 print("-- path length:" .. tonumber(#self.path.way))
1821
12261822 for _,pos in pairs(self.path.way) do
12271823 minetest.add_particle({
12281824 pos = pos,
12391835 ]]
12401836
12411837 self.state = ""
1242 do_attack(self, self.attack)
1838
1839 if self.attack then
1840 self:do_attack(self.attack)
1841 end
12431842
12441843 -- no path found, try something else
12451844 if not self.path.way then
12491848 -- lets make way by digging/building if not accessible
12501849 if self.pathfinding == 2 and mobs_griefing then
12511850
1252 -- is player higher than mob?
1253 if s.y < p1.y then
1851 -- is player more than 1 block higher than mob?
1852 if p1.y > (s.y + 1) then
12541853
12551854 -- build upwards
12561855 if not minetest.is_protected(s, "") then
12581857 local ndef1 = minetest.registered_nodes[self.standing_in]
12591858
12601859 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1261
1262 minetest.set_node(s, {name = mobs.fallback_node})
1860 minetest.set_node(s, {name = mobs.fallback_node})
12631861 end
12641862 end
12651863
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1864 local sheight = ceil(self.collisionbox[5]) + 1
12671865
12681866 -- assume mob is 2 blocks high so it digs above its head
12691867 s.y = s.y + sheight
12701868
12711869 -- 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
1870 can_dig_drop(s)
12891871
12901872 s.y = s.y - sheight
12911873 self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
1874
1875 -- is player more than 1 block lower than mob
1876 elseif p1.y < (s.y - 1) then
1877
1878 -- dig down
1879 s.y = s.y - self.collisionbox[4] - 0.2
1880
1881 can_dig_drop(s)
12921882
12931883 else -- dig 2 blocks to make door toward player direction
12941884
12991889 z = s.z + sin(yaw1)
13001890 }
13011891
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
1892 -- dig bottom node first incase of door
1893 can_dig_drop(p1)
1894
1895 p1.y = p1.y + 1
1896
1897 can_dig_drop(p1)
13341898 end
13351899 end
13361900
13371901 -- will try again in 2 second
13381902 self.path.stuck_timer = stuck_timeout - 2
13391903
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1904 elseif s.y < p1.y and (not self.fly) then
1905 self:do_jump() --add jump to pathfinding
1906 self.path.following = true
13421907 else
13431908 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1909 if self.attack then
1910 self:mob_sound(self.sounds.war_cry)
1911 else
1912 self:mob_sound(self.sounds.random)
1913 end
1914
1915 self:set_velocity(self.walk_velocity)
13461916
13471917 -- follow path now that it has it
13481918 self.path.following = true
13511921 end
13521922
13531923
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
1924 -- peaceful player privilege support
1925 local function is_peaceful_player(player)
1926
1927 if peaceful_player_enabled then
1928
1929 local player_name = player:get_player_name()
1930
1931 if player_name
1932 and minetest.check_player_privs(player_name, "peaceful_player") then
13661933 return true
13671934 end
13681935 end
13711938 end
13721939
13731940
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1941 -- general attack function for all mobs
1942 function mob_class:general_attack()
13761943
13771944 -- return if already attacking, passive or docile during day
13781945 if self.passive
1946 or self.state == "runaway"
13791947 or self.state == "attack"
1380 or day_docile(self) then
1948 or self:day_docile() then
13811949 return
13821950 end
13831951
1384 local s = self.object:get_pos()
1952 local s = self.object:get_pos() ; if not s then return end
13851953 local objs = minetest.get_objects_inside_radius(s, self.view_range)
13861954
13871955 -- remove entities we aren't interested in
13921960 -- are we a player?
13931961 if objs[n]:is_player() then
13941962
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1963 -- if player invisible or mob cannot attack then remove from list
1964 if not damage_enabled
1965 or self.attack_players == false
13971966 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
1967 or is_invisible(self, objs[n]:get_player_name())
1968 or (self.specific_attack
1969 and not check_for("player", self.specific_attack)) then
14001970 objs[n] = nil
14011971 --print("- pla", n)
14021972 end
14091979 or (not self.attack_animals and ent.type == "animal")
14101980 or (not self.attack_monsters and ent.type == "monster")
14111981 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1982 or (self.specific_attack
1983 and not check_for(ent.name, self.specific_attack)) then
14131984 objs[n] = nil
14141985 --print("- mob", n, self.name, ent.name)
14151986 end
14392010 -- choose closest player to attack that isnt self
14402011 if dist ~= 0
14412012 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
2013 and self:line_of_sight(sp, p, 2) == true
2014 and not is_peaceful_player(player) then
14432015 min_dist = dist
14442016 min_player = player
14452017 end
14462018 end
14472019
14482020 -- 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
2021 if min_player and random(100) > self.attack_chance then
2022 self:do_attack(min_player)
2023 end
14722024 end
14732025
14742026
14752027 -- find someone to runaway from
1476 local runaway_from = function(self)
2028 function mob_class:do_runaway_from()
14772029
14782030 if not self.runaway_from then
14792031 return
14802032 end
14812033
1482 local s = self.object:get_pos()
2034 local s = self.object:get_pos() ; if not s then return end
14832035 local p, sp, dist, pname
14842036 local player, obj, min_player, name
14852037 local min_dist = self.view_range + 1
14912043
14922044 pname = objs[n]:get_player_name()
14932045
1494 if mobs.invis[pname]
2046 if is_invisible(self, pname)
14952047 or self.owner == pname then
14962048
14972049 name = ""
15102062
15112063 -- find specific mob to runaway from
15122064 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
2065 and (self.runaway_from and check_for(name, self.runaway_from)) then
2066
15162067 sp = s
2068 p = player and player:get_pos() or s
15172069
15182070 -- aim higher to make looking up hills more realistic
15192071 p.y = p.y + 1
15232075
15242076 -- choose closest player/mob to runaway from
15252077 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
2078 and self:line_of_sight(sp, p, 2) == true then
15272079 min_dist = dist
15282080 min_player = player
15292081 end
15322084
15332085 if min_player then
15342086
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)
2087 yaw_to_pos(self, min_player:get_pos(), 3)
2088
15492089 self.state = "runaway"
15502090 self.runaway_timer = 3
15512091 self.following = nil
15542094
15552095
15562096 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
2097 function mob_class:follow_flop()
15582098
15592099 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
2100 if (self.follow ~= "" or self.order == "follow")
15622101 and not self.following
15632102 and self.state ~= "attack"
15642103 and self.state ~= "runaway" then
15652104
1566 local s = self.object:get_pos()
2105 local s = self.object:get_pos() ; if not s then return end
15672106 local players = minetest.get_connected_players()
15682107
15692108 for n = 1, #players do
15702109
15712110 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
2111 and not is_invisible(self, players[n]:get_player_name()) then
15732112
15742113 self.following = players[n]
15752114
15902129 self.following = nil
15912130 end
15922131 else
1593 -- stop following player if not holding specific item
2132 -- stop following player if not holding specific item or mob is horny
15942133 if self.following
15952134 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
2135 and (self:follow_holding(self.following) == false
2136 or self.horny) then
15972137 self.following = nil
15982138 end
15992139
16222162 if dist > self.view_range then
16232163 self.following = nil
16242164 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)
2165 yaw_to_pos(self, p)
16352166
16362167 -- anyone but standing npc's can move along
16372168 if dist > self.reach
16382169 and self.order ~= "stand" then
16392170
1640 set_velocity(self, self.walk_velocity)
2171 self:set_velocity(self.walk_velocity)
16412172
16422173 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
2174 self:set_animation("walk")
16442175 end
16452176 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
2177 self:set_velocity(0)
2178 self:set_animation("stand")
16482179 end
16492180
16502181 return
16542185
16552186 -- swimmers flop when out of their element, and swim again when back in
16562187 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
2188
2189 if not self:attempt_flight_correction() then
16592190
16602191 self.state = "flop"
2192
2193 -- do we have a custom on_flop function?
2194 if self.on_flop then
2195
2196 if self:on_flop(self) then
2197 return
2198 end
2199 end
2200
16612201 self.object:set_velocity({x = 0, y = -5, z = 0})
16622202
1663 set_animation(self, "stand")
2203 self:set_animation("stand")
16642204
16652205 return
2206
16662207 elseif self.state == "flop" then
16672208 self.state = "stand"
16682209 end
16712212
16722213
16732214 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
2215 function mob_class:dogswitch(dtime)
16752216
16762217 -- switch mode not activated
16772218 if not self.dogshoot_switch
17002241
17012242
17022243 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
2244 function mob_class:do_states(dtime)
2245
2246 local yaw = self.object:get_yaw() ; if not yaw then return end
17062247
17072248 if self.state == "stand" then
17082249
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
2250 if self.randomly_turn and random(4) == 1 then
2251
2252 local lp
17122253 local s = self.object:get_pos()
17132254 local objs = minetest.get_objects_inside_radius(s, 3)
17142255
17222263
17232264 -- look at any players nearby, otherwise turn randomly
17242265 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
2266 yaw = yaw_to_pos(self, lp)
17342267 else
17352268 yaw = yaw + random(-0.5, 0.5)
17362269 end
17372270
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
2271 yaw = self:set_yaw(yaw, 8)
2272 end
2273
2274 self:set_velocity(0)
2275 self:set_animation("stand")
17432276
17442277 -- mobs ordered to stand stay standing
17452278 if self.order ~= "stand"
17462279 and self.walk_chance ~= 0
17472280 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)
2281 and random(100) <= self.walk_chance
2282 and self.at_cliff == false then
2283
2284 self:set_velocity(self.walk_velocity)
17522285 self.state = "walk"
1753 set_animation(self, "walk")
2286 self:set_animation("walk")
17542287 end
17552288
17562289 elseif self.state == "walk" then
17572290
17582291 local s = self.object:get_pos()
1759 local lp = nil
2292 local lp
17602293
17612294 -- is there something I need to avoid?
17622295 if self.water_damage > 0
17632296 and self.lava_damage > 0 then
17642297
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
2298 lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"})
17662299
17672300 elseif self.water_damage > 0 then
17682301
17702303
17712304 elseif self.lava_damage > 0 then
17722305
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
2306 lp = minetest.find_node_near(s, 1, {"group:igniter"})
17742307 end
17752308
17762309 if lp then
17772310
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})
2311 -- if mob in dangerous node then look for land
2312 if not is_node_dangerous(self, self.standing_in) then
2313
2314 lp = minetest.find_nodes_in_area_under_air(
2315 {s.x - 5, s.y - 1, s.z - 5},
2316 {s.x + 5, s.y + 2, s.z + 5},
2317 {"group:soil", "group:stone", "group:sand",
2318 node_ice, node_snowblock})
2319
2320 -- select position of random block to climb onto
2321 lp = #lp > 0 and lp[random(#lp)]
17862322
17872323 -- did we find land?
17882324 if lp then
17892325
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)
2326 yaw = yaw_to_pos(self, lp)
2327
2328 self:do_jump()
2329 self:set_velocity(self.walk_velocity)
18032330 else
18042331 yaw = yaw + random(-0.5, 0.5)
18052332 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)
2333 end
2334
2335 yaw = self:set_yaw(yaw, 8)
18202336
18212337 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
2338 elseif self.randomly_turn and random(100) <= 30 then
18232339
18242340 yaw = yaw + random(-0.5, 0.5)
18252341
1826 yaw = set_yaw(self, yaw, 8)
2342 yaw = self:set_yaw(yaw, 8)
2343
2344 -- for flying/swimming mobs randomly move up and down also
2345 if self.fly_in
2346 and not self.following then
2347 self:attempt_flight_correction(true)
2348 end
18272349 end
18282350
18292351 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
18322352 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")
2353 or self.at_cliff
2354 or random(100) <= self.stand_chance then
2355
2356 -- don't stand if mob flies and keep_flying set
2357 if (self.fly and not self.keep_flying)
2358 or not self.fly then
2359
2360 self:set_velocity(0)
2361 self.state = "stand"
2362 self:set_animation("stand", true)
2363 end
18392364 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
2365 self:set_velocity(self.walk_velocity)
2366
2367 if self:flight_check()
18432368 and self.animation
18442369 and self.animation.fly_start
18452370 and self.animation.fly_end then
1846 set_animation(self, "fly")
2371 self:set_animation("fly")
18472372 else
1848 set_animation(self, "walk")
2373 self:set_animation("walk")
18492374 end
18502375 end
18512376
18562381
18572382 -- stop after 5 seconds or when at cliff
18582383 if self.runaway_timer > 5
1859 or is_at_cliff(self)
2384 or self.at_cliff
18602385 or self.order == "stand" then
18612386 self.runaway_timer = 0
1862 set_velocity(self, 0)
2387 self:set_velocity(0)
18632388 self.state = "stand"
1864 set_animation(self, "stand")
2389 self:set_animation("stand")
18652390 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
2391 self:set_velocity(self.run_velocity)
2392 self:set_animation("walk")
18682393 end
18692394
18702395 -- attack routines (explode, dogfight, shoot, dogshoot)
18712396 elseif self.state == "attack" then
18722397
1873 -- calculate distance from mob and enemy
2398 -- get mob and enemy positions and distance between
18742399 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
2400 local p = self.attack and self.attack:get_pos()
2401 local dist = p and get_distance(p, s) or 500
2402
2403 -- stop attacking if player out of range or invisible
18792404 if dist > self.view_range
18802405 or not self.attack
18812406 or not self.attack:get_pos()
18822407 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)
2408 or (self.attack:is_player()
2409 and is_invisible(self, self.attack:get_player_name())) then
2410
2411 --print(" ** stop attacking **", dist, self.view_range)
2412
18862413 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
2414 self:set_velocity(0)
2415 self:set_animation("stand")
18892416 self.attack = nil
18902417 self.v_start = false
18912418 self.timer = 0
18972424
18982425 if self.attack_type == "explode" then
18992426
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)
2427 yaw = yaw_to_pos(self, p)
19102428
19112429 local node_break_radius = self.explosion_radius or 1
19122430 local entity_damage_radius = self.explosion_damage_radius
19132431 or (node_break_radius * 2)
19142432
2433 -- look a little higher to fix raycast
2434 s.y = s.y + 0.5 ; p.y = p.y + 0.5
2435
19152436 -- start timer when in reach and line of sight
19162437 if not self.v_start
19172438 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
2439 and self:line_of_sight(s, p, 2) then
19192440
19202441 self.v_start = true
19212442 self.timer = 0
19222443 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
2444 self:mob_sound(self.sounds.fuse)
2445
2446 --print("=== explosion timer started", self.explosion_timer)
19252447
19262448 -- stop timer if out of reach or direct line of sight
19272449 elseif self.allow_fuse_reset
19282450 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
2451 and (dist > self.reach or not self:line_of_sight(s, p, 2)) then
2452
2453 --print("=== explosion timer stopped")
2454
19312455 self.v_start = false
19322456 self.timer = 0
19332457 self.blinktimer = 0
19342458 self.blinkstatus = false
1935 self.object:settexturemod("")
2459 self.object:set_texture_mod("")
19362460 end
19372461
19382462 -- walk right up to player unless the timer is active
19392463 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
2464 self:set_velocity(0)
19412465 else
1942 set_velocity(self, self.run_velocity)
2466 self:set_velocity(self.run_velocity)
19432467 end
19442468
19452469 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
2470 self:set_animation("run")
19472471 else
1948 set_animation(self, "walk")
2472 self:set_animation("walk")
19492473 end
19502474
19512475 if self.v_start then
19582482 self.blinktimer = 0
19592483
19602484 if self.blinkstatus then
1961 self.object:settexturemod("")
2485
2486 self.object:set_texture_mod(self.texture_mods)
19622487 else
1963 self.object:settexturemod("^[brighten")
2488
2489 self.object:set_texture_mod(self.texture_mods
2490 .. "^[brighten")
19642491 end
19652492
19662493 self.blinkstatus = not self.blinkstatus
19672494 end
19682495
1969 -- print ("=== explosion timer", self.timer)
2496 --print("=== explosion timer", self.timer)
19702497
19712498 if self.timer > self.explosion_timer then
19722499
19792506 node_break_radius = 1
19802507 end
19812508
1982 self.object:remove()
2509 remove_mob(self, true)
19832510
19842511 if minetest.get_modpath("tnt") and tnt and tnt.boom
19852512 and not minetest.is_protected(pos, "") then
19872514 tnt.boom(pos, {
19882515 radius = node_break_radius,
19892516 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
2517 sound = self.sounds.explode
19912518 })
19922519 else
19932520
19982525 })
19992526
20002527 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2528
2529 effect(pos, 32, "tnt_smoke.png", nil, nil,
2530 node_break_radius, 1, 0)
20022531 end
20032532
2004 return
2533 return true
20052534 end
20062535 end
20072536
20082537 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
2538 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
2539 or (self.attack_type == "dogshoot" and dist <= self.reach
2540 and self:dogswitch() == 0) then
20112541
20122542 if self.fly
20132543 and dist > self.reach then
20182548 local p_y = floor(p2.y + 1)
20192549 local v = self.object:get_velocity()
20202550
2021 if flight_check(self, s) then
2551 if self:flight_check() then
20222552
20232553 if me_y < p_y then
20242554
20542584 })
20552585 end
20562586 end
2057
20582587 end
20592588
20602589 -- rnd: new movement direction
20762605 return
20772606 end
20782607
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2608 if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
20802609 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2610 table_remove(self.path.way, 1)
20822611 end
20832612
20842613 -- set new temporary target
20852614 p = {x = p1.x, y = p1.y, z = p1.z}
20862615 end
20872616
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)
2617 yaw = yaw_to_pos(self, p)
20982618
20992619 -- move towards enemy if beyond mob reach
21002620 if dist > self.reach then
21032623 if self.pathfinding -- only if mob has pathfinding enabled
21042624 and enable_pathfinding then
21052625
2106 smart_mobs(self, s, p, dist, dtime)
2626 self:smart_mobs(s, p, dist, dtime)
21072627 end
21082628
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2629 -- distance padding to stop spinning mob
2630 local pad = abs(p.x - s.x) + abs(p.z - s.z)
2631
2632 if self.at_cliff or pad < 0.2 then
2633
2634 self:set_velocity(0)
2635 self:set_animation("stand")
21132636 else
21142637
21152638 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2639 self:set_velocity(self.walk_velocity)
21172640 else
2118 set_velocity(self, self.run_velocity)
2641 self:set_velocity(self.run_velocity)
21192642 end
21202643
21212644 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2645 self:set_animation("run")
21232646 else
2124 set_animation(self, "walk")
2647 self:set_animation("walk")
21252648 end
21262649 end
2127
21282650 else -- rnd: if inside reach range
21292651
21302652 self.path.stuck = false
21312653 self.path.stuck_timer = 0
21322654 self.path.following = false -- not stuck anymore
21332655
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2656 self:set_velocity(0)
2657
2658 if self.timer > 1 then
2659
2660 -- no custom attack or custom attack returns true to continue
2661 if not self.custom_attack
2662 or self:custom_attack(self, p) == true then
21392663
21402664 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
2665 self:set_animation("punch")
21482666
21492667 local p2 = p
21502668 local s2 = s
21522670 p2.y = p2.y + .5
21532671 s2.y = s2.y + .5
21542672
2155 if line_of_sight(self, p2, s2) == true then
2673 if self:line_of_sight(p2, s2) == true then
21562674
21572675 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2676 self:mob_sound(self.sounds.attack)
21592677
21602678 -- punch player (or what player is attached to)
21612679 local attached = self.attack:get_attach()
2680
21622681 if attached then
21632682 self.attack = attached
21642683 end
2684
2685 local dgroup = self.damage_group or "fleshy"
2686
21652687 self.attack:punch(self.object, 1.0, {
21662688 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2689 damage_groups = {[dgroup] = self.damage}
21682690 }, nil)
21692691 end
21702692 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
21792693 end
21802694 end
21812695
21822696 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
2697 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
2698 or (self.attack_type == "dogshoot" and dist > self.reach and
2699 self:dogswitch() == 0) then
21852700
21862701 p.y = p.y - .5
21872702 s.y = s.y + .5
21882703
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)
2704 local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
2705
2706 yaw = yaw_to_pos(self, p)
2707
2708 self:set_velocity(0)
22032709
22042710 if self.shoot_interval
22052711 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2712 and random(100) <= 60 then
22072713
22082714 self.timer = 0
2209 set_animation(self, "shoot")
2715 self:set_animation("shoot")
22102716
22112717 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2718 self:mob_sound(self.sounds.shoot_attack)
22132719
22142720 local p = self.object:get_pos()
22152721
22202726 local obj = minetest.add_entity(p, self.arrow)
22212727 local ent = obj:get_luaentity()
22222728 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2729
2730 -- check for custom override for arrow
2731 if self.arrow_override then
2732 self.arrow_override(ent)
2733 end
2734
22232735 local v = ent.velocity or 1 -- or set to default
22242736
22252737 ent.switch = 1
22402752
22412753
22422754 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2755 function mob_class:falling(pos)
2756
2757 if self.fly or self.disable_falling then
22462758 return
22472759 end
22482760
22492761 -- floating in water (or falling)
22502762 local v = self.object:get_velocity()
22512763
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
2764 -- sanity check
2765 if not v then return end
2766
2767 local fall_speed = self.fall_speed
2768
2769 -- in water then use liquid viscosity for float/sink speed
2770 if self.floats == 1 and self.standing_in
2771 and minetest.registered_nodes[self.standing_in].groups.liquid then
2772
2773 local visc = min(
2774 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2775
2776 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2777 fall_speed = -1.2 / visc
22862778 else
22872779
22882780 -- fall damage onto solid ground
22972789
22982790 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992791
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2792 if self:check_for_death({type = "fall"}) then
2793 return true
23022794 end
23032795 end
23042796
23052797 self.old_y = self.object:get_pos().y
23062798 end
23072799 end
2800
2801 -- fall at set speed
2802 self.object:set_acceleration({x = 0, y = fall_speed, z = 0})
23082803 end
23092804
23102805
23122807 local tr = minetest.get_modpath("toolranks")
23132808
23142809 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2810 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162811
23172812 -- mob health check
23182813 if self.health <= 0 then
2319 return
2814 return true
23202815 end
23212816
23222817 -- custom punch function
23232818 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2819 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2820 return true
23262821 end
23272822
23282823 -- error checking when mod profiling is enabled
23292824 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
2825
2826 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2827
2828 return true
2829 end
2830
2831 -- is mob protected
2832 if self.protected then
2833
2834 -- did player hit mob and if so is it in protected area
2835 if hitter:is_player() then
2836
2837 local player_name = hitter:get_player_name()
2838
2839 if player_name ~= self.owner
2840 and minetest.is_protected(self.object:get_pos(), player_name) then
2841
2842 minetest.chat_send_player(hitter:get_player_name(),
2843 S("Mob has been protected!"))
2844
2845 return true
2846 end
2847
2848 -- if protection is on level 2 then dont let arrows harm mobs
2849 elseif self.protected == 2 then
2850
2851 local ent = hitter and hitter:get_luaentity()
2852
2853 if ent and ent._is_arrow then
2854
2855 return true -- arrow entity
2856
2857 elseif not ent then
2858
2859 return true -- non entity
2860 end
2861 end
23392862 end
23402863
23412864 local weapon = hitter:get_wielded_item()
23672890 end
23682891
23692892 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2893 * tmp * ((armor[group] or 0) / 100.0)
23712894 end
23722895 end
23732896
23772900 if self.immune_to[n][1] == weapon_def.name then
23782901
23792902 damage = self.immune_to[n][2] or 0
2903
23802904 break
23812905
2382 -- if "all" then no tool does damage unless it's specified in list
2906 -- if "all" then no tools deal damage unless it's specified in list
23832907 elseif self.immune_to[n][1] == "all" then
23842908 damage = self.immune_to[n][2] or 0
23852909 end
23862910 end
23872911
2912 --print("Mob Damage is", damage)
2913
23882914 -- healing
23892915 if damage <= -1 then
2916
23902917 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2918
2919 return true
2920 end
23952921
23962922 if use_cmi
23972923 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2924 return true
23992925 end
24002926
24012927 -- add weapon wear
24132939 end
24142940 end
24152941
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
2942 if tr and weapon_def.original_description then
2943 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24202944 else
24212945 weapon:add_wear(wear)
24222946 end
24262950 -- only play hit sound and show blood effects if damage is 1 or over
24272951 if damage >= 1 then
24282952
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
2953 -- select tool use sound if found, or fallback to default
2954 local snd = weapon_def.sound and weapon_def.sound.use
2955 or "default_punch"
2956
2957 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442958
24452959 -- blood_particles
24462960 if not disable_blood and self.blood_amount > 0 then
24472961
24482962 local pos = self.object:get_pos()
2963 local blood = self.blood_texture
2964 local amount = self.blood_amount
24492965
24502966 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2967
2968 -- lots of damage = more blood :)
2969 if damage > 10 then
2970 amount = self.blood_amount * 2
2971 end
24512972
24522973 -- do we have a single blood texture or multiple?
24532974 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
2975 blood = self.blood_texture[random(#self.blood_texture)]
2976 end
2977
2978 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612979 end
24622980
24632981 -- do damage
24672985 local hot = tool_capabilities and tool_capabilities.damage_groups
24682986 and tool_capabilities.damage_groups.fire
24692987
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
2988 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2989 return true
2990 end
24872991 end -- END if damage
24882992
24892993 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2994 if self.knock_back and tflp >= punch_interval then
24922995
24932996 local v = self.object:get_velocity()
2997
2998 -- sanity check
2999 if not v then return true end
3000
24943001 local kb = damage or 1
24953002 local up = 2
24963003
25043011 dir = dir or {x = 0, y = 0, z = 0}
25053012
25063013 -- 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 })
3014 kb = tool_capabilities.damage_groups["knockback"] or kb
3015
3016 self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb})
25143017
25153018 self.pause_timer = 0.25
25163019 end
25203023 and self.order ~= "stand" then
25213024
25223025 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)
3026 local yaw = yaw_to_pos(self, lp, 3)
3027
25373028 self.state = "runaway"
25383029 self.runaway_timer = 0
25393030 self.following = nil
25473038 and self.child == false
25483039 and self.attack_players == true
25493040 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3041 and not is_invisible(self, name)
3042 and self.object ~= hitter then
25513043
25523044 -- attack whoever punched mob
25533045 self.state = ""
2554 do_attack(self, hitter)
3046 self:do_attack(hitter)
25553047
25563048 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3049 local objs = minetest.get_objects_inside_radius(
3050 hitter:get_pos(), self.view_range)
3051 local obj
25593052
25603053 for n = 1, #objs do
25613054
25633056
25643057 if obj and obj._cmi_is_mob then
25653058
2566 -- only alert members of same mob
3059 -- only alert members of same mob and assigned helper
25673060 if obj.group_attack == true
25683061 and obj.state ~= "attack"
25693062 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3063 and (obj.name == self.name
3064 or obj.name == self.group_helper) then
3065
3066 obj:do_attack(hitter)
25723067 end
25733068
25743069 -- have owned mobs attack player threat
25753070 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3071 obj:do_attack(self.object)
25773072 end
25783073 end
25793074 end
25823077
25833078
25843079 -- get entity staticdata
2585 local mob_staticdata = function(self)
3080 function mob_class:mob_staticdata()
3081
3082 -- this handles mob count for mobs activated, unloaded, reloaded
3083 if active_limit > 0 and self.active_toggle then
3084 active_mobs = active_mobs + self.active_toggle
3085 self.active_toggle = -self.active_toggle
3086 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3087 end
25863088
25873089 -- remove mob when out of range unless tamed
25883090 if remove_far
25923094 and not self.tamed
25933095 and self.lifetimer < 20000 then
25943096
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3097 --print("REMOVED " .. self.name)
3098
3099 remove_mob(self, true)
3100
3101 return minetest.serialize({remove_ok = true, static_save = true})
26003102 end
26013103
26023104 self.remove_ok = true
26053107 self.state = "stand"
26063108
26073109 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3110 if self.drawtype and self.drawtype == "side" then
3111 self.rotate = rad(90)
26113112 end
26123113
26133114 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3115 self.serialized_cmi_components = cmi.serialize_components(
3116 self._cmi_components)
3117 end
3118
3119 local tmp, t = {}
26183120
26193121 for _,stat in pairs(self) do
26203122
2621 local t = type(stat)
3123 t = type(stat)
26223124
26233125 if t ~= "function"
26243126 and t ~= "nil"
26253127 and t ~= "userdata"
3128 and _ ~= "object"
26263129 and _ ~= "_cmi_components" then
26273130 tmp[_] = self[_]
26283131 end
26293132 end
26303133
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3134 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3135
26323136 return minetest.serialize(tmp)
26333137 end
26343138
26353139
26363140 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
3141 function mob_class:mob_activate(staticdata, def, dtime)
3142
3143 -- if dtime == 0 then entity has just been created
3144 -- anything higher means it is respawning (thanks SorceryKid)
3145 if dtime == 0 and active_limit > 0 then
3146 self.active_toggle = 1
3147 end
3148
3149 -- remove mob if not tamed and mob total reached
3150 if active_limit > 0 and active_mobs >= active_limit and not self.tamed then
3151
3152 remove_mob(self)
3153 --print("-- mob limit reached, removing " .. self.name)
3154 return
3155 end
26383156
26393157 -- remove monsters in peaceful mode
2640 if self.type == "monster"
2641 and peaceful_only then
2642
2643 self.object:remove()
3158 if self.type == "monster" and peaceful_only then
3159
3160 remove_mob(self, true)
26443161
26453162 return
26463163 end
26493166 local tmp = minetest.deserialize(staticdata)
26503167
26513168 if tmp then
3169
3170 local t
3171
26523172 for _,stat in pairs(tmp) do
2653 self[_] = stat
2654 end
2655 end
3173
3174 t = type(stat)
3175
3176 if t ~= "function"
3177 and t ~= "nil"
3178 and t ~= "userdata" then
3179 self[_] = stat
3180 end
3181 end
3182 end
3183
3184 -- force current model into mob
3185 self.mesh = def.mesh
3186 self.base_mesh = def.mesh
3187 self.collisionbox = def.collisionbox
3188 self.selectionbox = def.selectionbox
26563189
26573190 -- select random texture, set model and size
26583191 if not self.base_texture then
26623195 def.textures = {def.textures}
26633196 end
26643197
2665 self.base_texture = def.textures and def.textures[random(1, #def.textures)]
3198 self.base_texture = def.textures and def.textures[random(#def.textures)]
26663199 self.base_mesh = def.mesh
26673200 self.base_size = self.visual_size
26683201 self.base_colbox = self.collisionbox
26823215 local selbox = self.base_selbox
26833216
26843217 -- specific texture if gotten
2685 if self.gotten == true
2686 and def.gotten_texture then
3218 if self.gotten == true and def.gotten_texture then
26873219 textures = def.gotten_texture
26883220 end
26893221
26903222 -- specific mesh if gotten
2691 if self.gotten == true
2692 and def.gotten_mesh then
3223 if self.gotten == true and def.gotten_mesh then
26933224 mesh = def.gotten_mesh
26943225 end
26953226
26963227 -- set child objects to half size
26973228 if self.child == true then
26983229
2699 vis_size = {
2700 x = self.base_size.x * .5,
2701 y = self.base_size.y * .5,
2702 }
3230 vis_size = {x = self.base_size.x * .5, y = self.base_size.y * .5}
27033231
27043232 if def.child_texture then
27053233 textures = def.child_texture[1]
27063234 end
27073235
27083236 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 }
3237 self.base_colbox[1] * .5, self.base_colbox[2] * .5,
3238 self.base_colbox[3] * .5, self.base_colbox[4] * .5,
3239 self.base_colbox[5] * .5, self.base_colbox[6] * .5}
3240
27163241 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 }
3242 self.base_selbox[1] * .5, self.base_selbox[2] * .5,
3243 self.base_selbox[3] * .5, self.base_selbox[4] * .5,
3244 self.base_selbox[5] * .5, self.base_selbox[6] * .5}
27243245 end
27253246
27263247 if self.health == 0 then
2727 self.health = random (self.hp_min, self.hp_max)
3248 self.health = random(self.hp_min, self.hp_max)
27283249 end
27293250
27303251 -- pathfinding init
27353256 self.path.following = false -- currently following path?
27363257 self.path.stuck_timer = 0 -- if stuck for too long search for path
27373258
3259 -- Armor groups (immortal = 1 for custom damage handling)
3260 local armor
3261 if type(self.armor) == "table" then
3262 armor = table_copy(self.armor)
3263 else
3264 armor = {fleshy = self.armor} -- immortal = 1
3265 end
3266 self.object:set_armor_groups(armor)
3267
27383268 -- mob defaults
2739 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
27403269 self.old_y = self.object:get_pos().y
27413270 self.old_health = self.health
27423271 self.sounds.distance = self.sounds.distance or 10
27463275 self.selectionbox = selbox
27473276 self.visual_size = vis_size
27483277 self.standing_in = "air"
3278 self.standing_on = "air"
27493279
27503280 -- check existing nametag
27513281 if not self.nametag then
27543284
27553285 -- set anything changed above
27563286 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")
3287 self:set_yaw((random(0, 360) - 180) / 180 * pi, 6)
3288 self:update_tag()
3289 self:set_animation("stand")
3290
3291 -- apply any texture mods
3292 self.object:set_texture_mod(self.texture_mods)
3293
3294 -- set 5.x flag to remove monsters when map area unloaded
3295 if remove_far and self.type == "monster" and not self.tamed then
3296 self.static_save = false
3297 end
27603298
27613299 -- run on_spawn function if found
27623300 if self.on_spawn and not self.on_spawn_run then
27633301 if self.on_spawn(self) then
2764 self.on_spawn_run = true -- if true, set flag to run once only
3302 self.on_spawn_run = true -- if true, set flag to run once only
27653303 end
27663304 end
27673305
27713309 end
27723310
27733311 if use_cmi then
2774 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
3312 self._cmi_components = cmi.activate_components(
3313 self.serialized_cmi_components)
27753314 cmi.notify_activate(self.object, dtime)
27763315 end
27773316 end
27783317
27793318
27803319 -- handle mob lifetimer and expiration
2781 local mob_expire = function(self, pos, dtime)
3320 function mob_class:mob_expire(pos, dtime)
27823321
27833322 -- when lifetimer expires remove mob (except npc and tamed)
27843323 if self.type ~= "npc"
28093348
28103349 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
28113350
2812 self.object:remove()
3351 remove_mob(self, true)
28133352
28143353 return
28153354 end
28183357
28193358
28203359 -- main mob function
2821 local mob_step = function(self, dtime)
3360 function mob_class:on_step(dtime, moveresult)
3361
3362 if self.state == "die" then return end
28223363
28233364 if use_cmi then
28243365 cmi.notify_step(self.object, dtime)
28253366 end
28263367
28273368 local pos = self.object:get_pos()
2828 local yaw = 0
3369 local yaw = self.object:get_yaw()
3370
3371 -- early warning check, if no yaw then no entity, skip rest of function
3372 if not yaw then return end
28293373
28303374 -- get node at foot level every quarter second
28313375 self.node_timer = (self.node_timer or 0) + dtime
28433387 -- what is mob standing in?
28443388 self.standing_in = node_ok({
28453389 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)
3390
3391 self.standing_on = node_ok({
3392 x = pos.x, y = pos.y + y_level - 0.25, z = pos.z}, "air").name
3393
3394 --print("standing in " .. self.standing_in)
3395
3396 -- if standing inside solid block then jump to escape
3397 if minetest.registered_nodes[self.standing_in].walkable
3398 and minetest.registered_nodes[self.standing_in].drawtype
3399 == "normal" then
3400
3401 self.object:set_velocity({
3402 x = 0,
3403 y = self.jump_height,
3404 z = 0
3405 })
3406 end
3407
3408 -- check and stop if standing at cliff and fear of heights
3409 self.at_cliff = self:is_at_cliff()
3410
3411 if self.at_cliff then
3412 self:set_velocity(0)
3413 end
3414
3415 -- has mob expired (0.25 instead of dtime since were in a timer)
3416 self:mob_expire(pos, 0.25)
3417 end
3418
3419 -- check if falling, flying, floating and return if player died
3420 if self:falling(pos) then
3421 return
3422 end
28543423
28553424 -- smooth rotation by ThomasMonroe314
2856
28573425 if self.delay and self.delay > 0 then
2858
2859 local yaw = self.object:get_yaw()
28603426
28613427 if self.delay == 1 then
28623428 yaw = self.target_yaw
28903456 self.object:set_yaw(yaw)
28913457 end
28923458
2893 -- end rotation
2894
28953459 -- knockback timer
28963460 if self.pause_timer > 0 then
28973461
29043468 if self.do_custom then
29053469
29063470 -- when false skip going any further
2907 if self.do_custom(self, dtime) == false then
3471 if self:do_custom(dtime) == false then
29083472 return
29093473 end
29103474 end
29273491 end
29283492
29293493 -- mob plays random sound at times
2930 if random(1, 100) == 1 then
2931 mob_sound(self, self.sounds.random)
3494 if random(100) == 1 then
3495 self:mob_sound(self.sounds.random)
29323496 end
29333497
29343498 -- environmental damage timer (every 1 second)
29403504 self.env_damage_timer = 0
29413505
29423506 -- check for environmental damage (water, fire, lava etc.)
2943 do_env_damage(self)
3507 if self:do_env_damage() then return end
29443508
29453509 -- 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
3510 self:replace(pos)
3511 end
3512
3513 self:general_attack()
3514
3515 self:breed()
3516
3517 self:follow_flop()
3518
3519 if self:do_states(dtime) then return end
3520
3521 self:do_jump()
3522
3523 self:do_runaway_from(self)
3524
3525 self:do_stay_near()
29613526 end
29623527
29633528
29643529 -- 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, {
3530 function mob_class:on_blast(damage)
3531
3532 --print("-- blast damage", damage)
3533
3534 self.object:punch(self.object, 1.0, {
29703535 full_punch_interval = 1.0,
29713536 damage_groups = {fleshy = damage},
29723537 }, nil)
29733538
2974 return false, true, {}
3539 -- return no damage, no knockback, no item drops, mob api handles all
3540 return false, false, {}
29753541 end
29763542
29773543
29803546 -- register mob entity
29813547 function mobs:register_mob(name, def)
29823548
2983 mobs.spawning_mobs[name] = true
2984
2985 minetest.register_entity(name, {
2986
2987 stepheight = def.stepheight or 1.1, -- was 0.6
3549 mobs.spawning_mobs[name] = {}
3550
3551 local collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}
3552
3553 -- quick fix to stop mobs glitching through nodes if too small
3554 if -collisionbox[2] + collisionbox[5] < 1.01 then
3555 collisionbox[5] = collisionbox[2] + 0.99
3556 end
3557
3558 minetest.register_entity(name, setmetatable({
3559
3560 stepheight = def.stepheight,
29883561 name = name,
29893562 type = def.type,
29903563 attack_type = def.attack_type,
29913564 fly = def.fly,
2992 fly_in = def.fly_in or "air",
2993 owner = def.owner or "",
2994 order = def.order or "",
3565 fly_in = def.fly_in,
3566 keep_flying = def.keep_flying,
3567 owner = def.owner,
3568 order = def.order,
29953569 on_die = def.on_die,
3570 on_flop = def.on_flop,
29963571 do_custom = def.do_custom,
2997 jump_height = def.jump_height or 4, -- was 6
3572 jump_height = def.jump_height,
29983573 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
3574 rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2
3575 glow = def.glow,
3576 lifetimer = def.lifetimer,
30013577 hp_min = max(1, (def.hp_min or 5) * difficulty),
30023578 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,
3579 collisionbox = collisionbox, --def.collisionbox,
3580 selectionbox = def.selectionbox or collisionbox, --def.collisionbox,
30063581 visual = def.visual,
3007 visual_size = def.visual_size or {x = 1, y = 1},
3582 visual_size = def.visual_size,
30083583 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,
3584 makes_footstep_sound = def.makes_footstep_sound,
3585 view_range = def.view_range,
3586 walk_velocity = def.walk_velocity,
3587 run_velocity = def.run_velocity,
30133588 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,
3589 damage_group = def.damage_group,
3590 damage_texture_modifier = def.damage_texture_modifier,
3591 light_damage = def.light_damage,
3592 light_damage_min = def.light_damage_min,
3593 light_damage_max = def.light_damage_max,
3594 water_damage = def.water_damage,
3595 lava_damage = def.lava_damage,
3596 fire_damage = def.fire_damage,
3597 air_damage = def.air_damage,
3598 suffocation = def.suffocation,
3599 fall_damage = def.fall_damage,
3600 fall_speed = def.fall_speed,
3601 drops = def.drops,
3602 armor = def.armor,
30243603 on_rightclick = def.on_rightclick,
30253604 arrow = def.arrow,
3605 arrow_override = def.arrow_override,
30263606 shoot_interval = def.shoot_interval,
3027 sounds = def.sounds or {},
3607 sounds = def.sounds,
30283608 animation = def.animation,
30293609 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
3610 jump = def.jump,
3611 walk_chance = def.walk_chance,
3612 stand_chance = def.stand_chance,
3613 attack_chance = def.attack_chance,
3614 passive = def.passive,
3615 knock_back = def.knock_back,
3616 blood_amount = def.blood_amount,
3617 blood_texture = def.blood_texture,
3618 shoot_offset = def.shoot_offset,
3619 floats = def.floats,
30393620 replace_rate = def.replace_rate,
30403621 replace_what = def.replace_what,
30413622 replace_with = def.replace_with,
3042 replace_offset = def.replace_offset or 0,
3623 replace_offset = def.replace_offset,
30433624 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,
3625 reach = def.reach,
30553626 texture_list = def.textures,
3627 texture_mods = def.texture_mods or "",
30563628 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,
3629 docile_by_day = def.docile_by_day,
3630 fear_height = def.fear_height,
30603631 runaway = def.runaway,
3061 runaway_timer = 0,
30623632 pathfinding = def.pathfinding,
3063 immune_to = def.immune_to or {},
3633 immune_to = def.immune_to,
30643634 explosion_radius = def.explosion_radius,
30653635 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,
3636 explosion_timer = def.explosion_timer,
3637 allow_fuse_reset = def.allow_fuse_reset,
3638 stop_to_explode = def.stop_to_explode,
30693639 custom_attack = def.custom_attack,
30703640 double_melee_attack = def.double_melee_attack,
30713641 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,
3642 dogshoot_count_max = def.dogshoot_count_max,
3643 dogshoot_count2_max = def.dogshoot_count2_max or def.dogshoot_count_max,
3644 group_attack = def.group_attack,
3645 group_helper = def.group_helper,
3646 attack_monsters = def.attacks_monsters or def.attack_monsters,
3647 attack_animals = def.attack_animals,
3648 attack_players = def.attack_players,
3649 attack_npcs = def.attack_npcs,
30803650 specific_attack = def.specific_attack,
30813651 runaway_from = def.runaway_from,
30823652 owner_loyal = def.owner_loyal,
3083 facing_fence = false,
30843653 pushable = def.pushable,
3085 _cmi_is_mob = true,
3654 stay_near = def.stay_near,
3655 randomly_turn = def.randomly_turn ~= false,
3656 ignore_invisibility = def.ignore_invisibility,
30863657
30873658 on_spawn = def.on_spawn,
30883659
3089 on_blast = def.on_blast or do_tnt,
3090
3091 on_step = mob_step,
3660 on_blast = def.on_blast, -- class redifinition
30923661
30933662 do_punch = def.do_punch,
30943663
3095 on_punch = mob_punch,
3096
30973664 on_breed = def.on_breed,
30983665
30993666 on_grown = def.on_grown,
31003667
31013668 on_activate = function(self, staticdata, dtime)
3102 return mob_activate(self, staticdata, def, dtime)
3669 return self:mob_activate(staticdata, def, dtime)
31033670 end,
31043671
31053672 get_staticdata = function(self)
3106 return mob_staticdata(self)
3673 return self:mob_staticdata(self)
31073674 end,
31083675
3109 })
3676 }, mob_class_meta))
31103677
31113678 end -- END mobs:register_mob function
31123679
31133680
31143681 -- count how many mobs of one type are inside an area
3682 -- will also return true for second value if player is inside area
31153683 local count_mobs = function(pos, type)
31163684
31173685 local total = 0
31183686 local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2)
31193687 local ent
3688 local players
31203689
31213690 for n = 1, #objs do
31223691
31283697 if ent and ent.name and ent.name == type then
31293698 total = total + 1
31303699 end
3131 end
3132 end
3133
3134 return total
3700 else
3701 players = true
3702 end
3703 end
3704
3705 return total, players
3706 end
3707
3708
3709 -- do we have enough space to spawn mob? (thanks wuzzy)
3710 local can_spawn = function(pos, name)
3711
3712 local ent = minetest.registered_entities[name]
3713 local width_x = max(1, ceil(ent.collisionbox[4] - ent.collisionbox[1]))
3714 local min_x, max_x
3715
3716 if width_x % 2 == 0 then
3717 max_x = floor(width_x / 2)
3718 min_x = -(max_x - 1)
3719 else
3720 max_x = floor(width_x / 2)
3721 min_x = -max_x
3722 end
3723
3724 local width_z = max(1, ceil(ent.collisionbox[6] - ent.collisionbox[3]))
3725 local min_z, max_z
3726
3727 if width_z % 2 == 0 then
3728 max_z = floor(width_z / 2)
3729 min_z = -(max_z - 1)
3730 else
3731 max_z = floor(width_z / 2)
3732 min_z = -max_z
3733 end
3734
3735 local max_y = max(0, ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
3736 local pos2
3737
3738 for y = 0, max_y do
3739 for x = min_x, max_x do
3740 for z = min_z, max_z do
3741
3742 pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
3743
3744 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3745 return nil
3746 end
3747 end
3748 end
3749 end
3750
3751 -- tweak X/Z spawn pos
3752 if width_x % 2 == 0 then
3753 pos.x = pos.x + 0.5
3754 end
3755
3756 if width_z % 2 == 0 then
3757 pos.z = pos.z + 0.5
3758 end
3759
3760 return pos
3761 end
3762
3763 function mobs:can_spawn(pos, name)
3764 return can_spawn(pos, name)
31353765 end
31363766
31373767
31383768 -- global functions
3769
3770 function mobs:add_mob(pos, def)
3771
3772 -- is mob actually registered?
3773 if not mobs.spawning_mobs[def.name]
3774 or not minetest.registered_entities[def.name] then
3775 --print("--- mob doesn't exist", def.name)
3776 return
3777 end
3778
3779 -- are we over active mob limit
3780 if active_limit > 0 and active_mobs >= active_limit then
3781 --print("--- active mob limit reached", active_mobs, active_limit)
3782 return
3783 end
3784
3785 -- get total number of this mob in area
3786 local num_mob, is_pla = count_mobs(pos, def.name)
3787
3788 if not is_pla then
3789 --print("--- no players within active area, will not spawn " .. def.name)
3790 return
3791 end
3792
3793 local aoc = mobs.spawning_mobs[def.name]
3794 and mobs.spawning_mobs[def.name].aoc or 1
3795
3796 if def.ignore_count ~= true and num_mob >= aoc then
3797 --print("--- too many " .. def.name .. " in area", num_mob .. "/" .. aoc)
3798 return
3799 end
3800
3801 local mob = minetest.add_entity(pos, def.name)
3802
3803 --print("[mobs] Spawned " .. def.name .. " at " .. minetest.pos_to_string(pos))
3804
3805 local ent = mob:get_luaentity()
3806
3807 if not ent then
3808 --print("[mobs] entity not found " .. def.name)
3809 return false
3810 end
3811
3812 if def.child then
3813
3814 local textures = ent.base_texture
3815
3816 -- using specific child texture (if found)
3817 if ent.child_texture then
3818 textures = ent.child_texture[1]
3819 end
3820
3821 -- and resize to half height
3822 mob:set_properties({
3823 textures = textures,
3824 visual_size = {
3825 x = ent.base_size.x * .5,
3826 y = ent.base_size.y * .5
3827 },
3828 collisionbox = {
3829 ent.base_colbox[1] * .5,
3830 ent.base_colbox[2] * .5,
3831 ent.base_colbox[3] * .5,
3832 ent.base_colbox[4] * .5,
3833 ent.base_colbox[5] * .5,
3834 ent.base_colbox[6] * .5
3835 },
3836 selectionbox = {
3837 ent.base_selbox[1] * .5,
3838 ent.base_selbox[2] * .5,
3839 ent.base_selbox[3] * .5,
3840 ent.base_selbox[4] * .5,
3841 ent.base_selbox[5] * .5,
3842 ent.base_selbox[6] * .5
3843 },
3844 })
3845
3846 ent.child = true
3847 end
3848
3849 if def.owner then
3850 ent.tamed = true
3851 ent.owner = def.owner
3852 end
3853
3854 if def.nametag then
3855
3856 -- limit name entered to 64 characters long
3857 if def.nametag:len() > 64 then
3858 def.nametag = def.nametag:sub(1, 64)
3859 end
3860
3861 ent.nametag = def.nametag
3862
3863 ent:update_tag()
3864 end
3865
3866 return ent
3867 end
3868
31393869
31403870 function mobs:spawn_abm_check(pos, node, name)
31413871 -- global function to add additional spawn checks
31433873 end
31443874
31453875
3146 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3147 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3876 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
3877 chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load)
31483878
31493879 -- Do mobs spawn at all?
3150 if not mobs_spawn then
3880 if not mobs_spawn or not mobs.spawning_mobs[name] then
3881 --print ("--- spawning not registered for " .. name)
31513882 return
31523883 end
31533884
31543885 -- chance/spawn number override in minetest.conf for registered mob
3155 local numbers = minetest.settings:get(name)
3886 local numbers = settings:get(name)
31563887
31573888 if numbers then
3889
31583890 numbers = numbers:split(",")
31593891 chance = tonumber(numbers[1]) or chance
31603892 aoc = tonumber(numbers[2]) or aoc
31613893
31623894 if chance == 0 then
3163 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3895
3896 minetest.log("warning",
3897 string.format("[mobs] %s has spawning disabled", name))
31643898 return
31653899 end
31663900
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
3901 minetest.log("action", string.format(
3902 "[mobs] Chance setting for %s changed to %s (total: %s)",
3903 name, chance, aoc))
3904 end
3905
3906 mobs.spawning_mobs[name].aoc = aoc
3907
3908 local spawn_action = function(pos, node, active_object_count,
3909 active_object_count_wider)
3910
3911 -- use instead of abm's chance setting when using lbm
3912 if map_load and random(max(1, (chance * mob_chance_multiplier))) > 1 then
3913 return
3914 end
3915
3916 -- use instead of abm's neighbor setting when using lbm
3917 if map_load and not minetest.find_node_near(pos, 1, neighbors) then
3918 --print("--- lbm neighbors not found")
3919 return
3920 end
3921
3922 -- is mob actually registered?
3923 if not mobs.spawning_mobs[name]
3924 or not minetest.registered_entities[name] then
3925 --print("--- mob doesn't exist", name)
3926 return
3927 end
3928
3929 -- are we over active mob limit
3930 if active_limit > 0 and active_mobs >= active_limit then
3931 --print("--- active mob limit reached", active_mobs, active_limit)
3932 return
3933 end
3934
3935 -- additional custom checks for spawning mob
3936 if mobs:spawn_abm_check(pos, node, name) == true then
3937 return
3938 end
3939
3940 -- do not spawn if too many entities in area
3941 if active_object_count_wider
3942 and active_object_count_wider >= max_per_block then
31973943 --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
3944 return
3945 end
3946
3947 -- get total number of this mob in area
3948 local num_mob, is_pla = count_mobs(pos, name)
3949
3950 if not is_pla then
3951 --print("--- no players within active area, will not spawn " .. name)
3952 return
3953 end
3954
3955 if num_mob >= aoc then
3956 --print("--- too many " .. name .. " in area", num_mob .. "/" .. aoc)
3957 return
3958 end
32083959
32093960 -- 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)
3961 if day_toggle ~= nil then
3962
3963 local tod = (minetest.get_timeofday() or 0) * 24000
3964
3965 if tod > 4500 and tod < 19500 then
3966 -- daylight, but mob wants night
3967 if day_toggle == false then
3968 --print("--- mob needs night", name)
32553969 return
32563970 end
3257 end
3971 else
3972 -- night time but mob wants day
3973 if day_toggle == true then
3974 --print("--- mob needs day", name)
3975 return
3976 end
3977 end
3978 end
3979
3980 -- spawn above node
3981 pos.y = pos.y + 1
3982
3983 -- are we spawning within height limits?
3984 if pos.y > max_height
3985 or pos.y < min_height then
3986 --print("--- height limits not met", name, pos.y)
3987 return
3988 end
3989
3990 -- are light levels ok?
3991 local light = minetest.get_node_light(pos)
3992 if not light
3993 or light > max_light
3994 or light < min_light then
3995 --print("--- light limits not met", name, light)
3996 return
3997 end
3998
3999 -- check if mob can spawn inside protected areas
4000 if (spawn_protected == false
4001 or (spawn_monster_protected == false
4002 and minetest.registered_entities[name].type == "monster"))
4003 and minetest.is_protected(pos, "") then
4004 --print("--- inside protected area", name)
4005 return
4006 end
4007
4008 -- only spawn a set distance away from player
4009 local objs = minetest.get_objects_inside_radius(pos, mob_nospawn_range)
4010
4011 for n = 1, #objs do
4012
4013 if objs[n]:is_player() then
4014 --print("--- player too close", name)
4015 return
4016 end
4017 end
4018
4019 local ent = minetest.registered_entities[name]
4020
4021 -- should we check mob area for obstructions ?
4022 if mob_area_spawn ~= true then
32584023
32594024 -- 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
4025 local height = max(0, ent.collisionbox[5] - ent.collisionbox[2])
4026
4027 for n = 0, floor(height) do
32664028
32674029 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
32684030
32714033 return
32724034 end
32734035 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
4036 else
4037 -- returns position if we have enough space to spawn mob
4038 pos = can_spawn(pos, name)
4039 end
4040
4041 if pos then
4042
4043 -- adjust for mob collision box
4044 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
32844045
32854046 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 ]]
4047
4048 -- print("[mobs] Spawned " .. name .. " at "
4049 -- .. minetest.pos_to_string(pos) .. " on "
4050 -- .. node.name .. " near " .. neighbors[1])
4051
32914052 if on_spawn then
3292
3293 local ent = mob:get_luaentity()
3294
3295 on_spawn(ent, pos)
3296 end
3297 end
3298 })
4053 on_spawn(mob:get_luaentity(), pos)
4054 end
4055 else
4056 --print("--- not enough space to spawn", name)
4057 end
4058 end
4059
4060
4061 -- are we registering an abm or lbm?
4062 if map_load == true then
4063
4064 minetest.register_lbm({
4065 name = name .. "_spawning",
4066 label = name .. " spawning",
4067 nodenames = nodes,
4068 run_at_every_load = false,
4069
4070 action = function(pos, node)
4071 spawn_action(pos, node)
4072 end
4073 })
4074
4075 else
4076
4077 minetest.register_abm({
4078 label = name .. " spawning",
4079 nodenames = nodes,
4080 neighbors = neighbors,
4081 interval = interval,
4082 chance = max(1, (chance * mob_chance_multiplier)),
4083 catch_up = false,
4084
4085 action = function(pos, node, active_object_count, active_object_count_wider)
4086 spawn_action(pos, node, active_object_count, active_object_count_wider)
4087 end
4088 })
4089 end
32994090 end
33004091
33014092
33084099 end
33094100
33104101
3311 -- MarkBu's spawn function
4102 -- MarkBu's spawn function (USE this one please)
33124103 function mobs:spawn(def)
33134104
33144105 mobs:spawn_specific(
33234114 def.min_height or -31000,
33244115 def.max_height or 31000,
33254116 def.day_toggle,
3326 def.on_spawn
3327 )
4117 def.on_spawn,
4118 def.on_map_load)
33284119 end
33294120
33304121
33354126
33364127 minetest.register_entity(name, {
33374128
3338 physical = false,
4129 physical = def.physical or false,
4130 collide_with_objects = def.collide_with_objects or false,
4131 static_save = false,
4132
33394133 visual = def.visual,
33404134 visual_size = def.visual_size,
33414135 textures = def.textures,
33434137 hit_player = def.hit_player,
33444138 hit_node = def.hit_node,
33454139 hit_mob = def.hit_mob,
4140 hit_object = def.hit_object,
33464141 drop = def.drop or false, -- drops arrow as registered item when true
3347 collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
4142 collisionbox = def.collisionbox or {-.1, -.1, -.1, .1, .1, .1},
33484143 timer = 0,
4144 lifetime = def.lifetime or 4.5,
33494145 switch = 0,
33504146 owner_id = def.owner_id,
33514147 rotate = def.rotate,
33544150
33554151 on_activate = def.on_activate,
33564152
3357 on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir)
4153 on_punch = def.on_punch or function(
4154 self, hitter, tflp, tool_capabilities, dir)
33584155 end,
33594156
33604157 on_step = def.on_step or function(self, dtime)
33614158
3362 self.timer = self.timer + 1
4159 self.timer = self.timer + dtime
33634160
33644161 local pos = self.object:get_pos()
33654162
3366 if self.switch == 0
3367 or self.timer > 150 then
3368
3369 self.object:remove() ; -- print ("removed arrow")
4163 if self.switch == 0 or self.timer > self.lifetime then
4164
4165 self.object:remove() ; -- print("removed arrow")
33704166
33714167 return
33724168 end
33734169
33744170 -- does arrow have a tail (fireball)
3375 if def.tail
3376 and def.tail == 1
3377 and def.tail_texture then
4171 if def.tail and def.tail == 1 and def.tail_texture then
33784172
33794173 minetest.add_particle({
33804174 pos = pos,
33844178 collisiondetection = false,
33854179 texture = def.tail_texture,
33864180 size = def.tail_size or 5,
3387 glow = def.glow or 0,
4181 glow = def.glow or 0
33884182 })
33894183 end
33904184
33944188
33954189 if minetest.registered_nodes[node].walkable then
33964190
3397 self.hit_node(self, pos, node)
4191 self:hit_node(pos, node)
33984192
33994193 if self.drop == true then
34004194
34024196
34034197 self.lastpos = (self.lastpos or pos)
34044198
3405 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
4199 minetest.add_item(self.lastpos,
4200 self.object:get_luaentity().name)
34064201 end
34074202
3408 self.object:remove() ; -- print ("hit node")
4203 self.object:remove() ; -- print("hit node")
34094204
34104205 return
34114206 end
34124207 end
34134208
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")
4209 if self.hit_player or self.hit_mob or self.hit_object then
4210
4211 for _,player in pairs(
4212 minetest.get_objects_inside_radius(pos, 1.0)) do
4213
4214 if self.hit_player and player:is_player() then
4215
4216 self:hit_player(player)
4217
4218 self.object:remove() ; -- print("hit player")
4219
34234220 return
34244221 end
34254222
34314228 and tostring(player) ~= self.owner_id
34324229 and entity.name ~= self.object:get_luaentity().name then
34334230
3434 self.hit_mob(self, player)
3435
3436 self.object:remove() ; --print ("hit mob")
4231 self:hit_mob(player)
4232
4233 self.object:remove() ; --print("hit mob")
4234
4235 return
4236 end
4237
4238 if entity
4239 and self.hit_object
4240 and (not entity._cmi_is_mob)
4241 and tostring(player) ~= self.owner_id
4242 and entity.name ~= self.object:get_luaentity().name then
4243
4244 self:hit_object(player)
4245
4246 self.object:remove() ; -- print("hit object")
34374247
34384248 return
34394249 end
34484258
34494259 -- compatibility function
34504260 function mobs:explosion(pos, radius)
3451
3452 local self = {sounds = {explode = "tnt_explode"}}
3453
3454 mobs:boom(self, pos, radius)
4261 mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius)
34554262 end
34564263
34574264
34624269 pos = pos,
34634270 gain = 1.0,
34644271 max_hear_distance = self.sounds and self.sounds.distance or 32
3465 })
4272 }, true)
34664273
34674274 entity_physics(pos, radius)
34684275
34814288 radius = radius,
34824289 damage_radius = radius,
34834290 sound = self.sounds and self.sounds.explode,
3484 explode_center = true,
4291 explode_center = true
34854292 })
34864293 else
34874294 mobs:safe_boom(self, pos, radius)
34994306 local grp = {spawn_egg = 1}
35004307
35014308 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3502 if creative and no_creative == true then
4309 if no_creative == true then
35034310 grp.not_in_creative_inventory = 1
35044311 end
35054312
35224329
35234330 local pos = pointed_thing.above
35244331
3525 -- am I clicking on something with existing on_rightclick function?
4332 -- does existing on_rightclick function exist?
35264333 local under = minetest.get_node(pointed_thing.under)
35274334 local def = minetest.registered_nodes[under.name]
4335
35284336 if def and def.on_rightclick then
3529 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4337
4338 return def.on_rightclick(
4339 pointed_thing.under, under, placer, itemstack)
35304340 end
35314341
35324342 if pos
35394349 pos.y = pos.y + 1
35404350
35414351 local data = itemstack:get_metadata()
3542 local mob = minetest.add_entity(pos, mob, data)
3543 local ent = mob:get_luaentity()
4352 local smob = minetest.add_entity(pos, mob, data)
4353 local ent = smob and smob:get_luaentity()
4354
4355 if not ent then return end -- sanity check
35444356
35454357 -- set owner if not a monster
35464358 if ent.type ~= "monster" then
35684380
35694381 local pos = pointed_thing.above
35704382
3571 -- am I clicking on something with existing on_rightclick function?
4383 -- does existing on_rightclick function exist?
35724384 local under = minetest.get_node(pointed_thing.under)
35734385 local def = minetest.registered_nodes[under.name]
4386
35744387 if def and def.on_rightclick then
3575 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4388
4389 return def.on_rightclick(
4390 pointed_thing.under, under, placer, itemstack)
35764391 end
35774392
35784393 if pos
35824397 return
35834398 end
35844399
4400 -- have we reached active mob limit
4401 if active_limit > 0 and active_mobs >= active_limit then
4402 minetest.chat_send_player(placer:get_player_name(),
4403 S("Active Mob Limit Reached!")
4404 .. " (" .. active_mobs
4405 .. " / " .. active_limit .. ")")
4406 return
4407 end
4408
35854409 pos.y = pos.y + 1
35864410
3587 local mob = minetest.add_entity(pos, mob)
3588 local ent = mob:get_luaentity()
4411 local smob = minetest.add_entity(pos, mob)
4412 local ent = smob and smob:get_luaentity()
4413
4414 if not ent then return end -- sanity check
35894415
35904416 -- don't set owner if monster or sneak pressed
35914417 if ent.type ~= "monster"
36034429 return itemstack
36044430 end,
36054431 })
3606
4432 end
4433
4434
4435 -- force capture a mob if space available in inventory, or drop as spawn egg
4436 function mobs:force_capture(self, clicker)
4437
4438 -- add special mob egg with all mob information
4439 local new_stack = ItemStack(self.name .. "_set")
4440
4441 local tmp, t = {}
4442
4443 for _,stat in pairs(self) do
4444
4445 t = type(stat)
4446
4447 if t ~= "function"
4448 and t ~= "nil"
4449 and t ~= "userdata" then
4450 tmp[_] = self[_]
4451 end
4452 end
4453
4454 local data_str = minetest.serialize(tmp)
4455
4456 new_stack:set_metadata(data_str)
4457
4458 local inv = clicker:get_inventory()
4459
4460 if inv:room_for_item("main", new_stack) then
4461 inv:add_item("main", new_stack)
4462 else
4463 minetest.add_item(clicker:get_pos(), new_stack)
4464 end
4465
4466 self:mob_sound("default_place_node_hard")
4467
4468 remove_mob(self, true)
36074469 end
36084470
36094471
36104472 -- capture critter (thanks to blert2112 for idea)
3611 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
3612 force_take, replacewith)
3613
3614 if self.child
4473 function mobs:capture_mob(self, clicker, chance_hand, chance_net,
4474 chance_lasso, force_take, replacewith)
4475
4476 if not self --self.child
36154477 or not clicker:is_player()
36164478 or not clicker:get_inventory() then
36174479 return false
36364498 end
36374499
36384500 -- is mob tamed?
3639 if self.tamed == false
3640 and force_take == false then
4501 if self.tamed == false and force_take == false then
36414502
36424503 minetest.chat_send_player(name, S("Not tamed!"))
36434504
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
4505 return false
4506 end
4507
4508 -- cannot pick up if not owner (unless player has protection_bypass priv)
4509 if not minetest.check_player_privs(name, "protection_bypass")
4510 and self.owner ~= name and force_take == false then
36504511
36514512 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
36524513
3653 return true -- false
4514 return false
36544515 end
36554516
36564517 if clicker:get_inventory():room_for_item("main", mobname) then
36804541 end
36814542
36824543 -- calculate chance.. add to inventory if successful?
3683 if chance > 0 and random(1, 100) <= chance then
4544 if chance and chance > 0 and random(100) <= chance then
36844545
36854546 -- default mob egg
36864547 local new_stack = ItemStack(mobname)
36914552
36924553 new_stack = ItemStack(mobname .. "_set")
36934554
3694 local tmp = {}
4555 local tmp, t = {}
36954556
36964557 for _,stat in pairs(self) do
3697 local t = type(stat)
4558
4559 t = type(stat)
4560
36984561 if t ~= "function"
36994562 and t ~= "nil"
37004563 and t ~= "userdata" then
37154578 minetest.add_item(clicker:get_pos(), new_stack)
37164579 end
37174580
3718 self.object:remove()
3719
3720 mob_sound(self, "default_place_node_hard")
3721
3722 elseif chance ~= 0 then
4581 self:mob_sound("default_place_node_hard")
4582
4583 remove_mob(self, true)
4584
4585 return new_stack
4586
4587 -- when chance above fails or set to 0, miss!
4588 elseif chance and chance ~= 0 then
4589
37234590 minetest.chat_send_player(name, S("Missed!"))
37244591
3725 mob_sound(self, "mobs_swing")
4592 self:mob_sound("mobs_swing")
4593
4594 return false
4595
4596 -- when chance is nil always return a miss (used for npc walk/follow)
4597 elseif not chance then
4598 return false
37264599 end
37274600 end
37284601
37354608
37364609 local name = clicker:get_player_name()
37374610 local tool = clicker:get_wielded_item()
3738
3739 if tool:get_name() ~= "mobs:protector" then
4611 local tool_name = tool:get_name()
4612
4613 if tool_name ~= "mobs:protector"
4614 and tool_name ~= "mobs:protector2" then
37404615 return false
37414616 end
37424617
3743 if self.tamed == false then
4618 if not self.tamed then
37444619 minetest.chat_send_player(name, S("Not tamed!"))
3745 return true -- false
3746 end
3747
3748 if self.protected == true then
4620 return true
4621 end
4622
4623 if (self.protected and tool_name == "mobs:protector")
4624 or (self.protected == 2 and tool_name == "mobs:protector2") then
37494625 minetest.chat_send_player(name, S("Already protected!"))
3750 return true -- false
4626 return true
37514627 end
37524628
37534629 if not mobs.is_creative(clicker:get_player_name()) then
37554631 clicker:set_wielded_item(tool)
37564632 end
37574633
3758 self.protected = true
4634 -- set protection level
4635 if tool_name == "mobs:protector" then
4636 self.protected = true
4637 else
4638 self.protected = 2 ; self.fire_damage = 0
4639 end
37594640
37604641 local pos = self.object:get_pos()
4642
37614643 pos.y = pos.y + self.collisionbox[2] + 0.5
37624644
3763 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3764
3765 mob_sound(self, "mobs_spell")
4645 effect(self.object:get_pos(), 25, "mobs_protect_particle.png",
4646 0.5, 4, 2, 15)
4647
4648 self:mob_sound("mobs_spell")
37664649
37674650 return true
37684651 end
37744657 -- feeding, taming and breeding (thanks blert2112)
37754658 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
37764659
3777 if not self.follow then
3778 return false
3779 end
3780
37814660 -- can eat/tame with item in hand
3782 if follow_holding(self, clicker) then
4661 if self.follow
4662 and self:follow_holding(clicker) then
37834663
37844664 -- if not in creative then take item
37854665 if not mobs.is_creative(clicker:get_player_name()) then
37984678
37994679 self.health = self.hp_max
38004680
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
4681 -- if self.htimer < 1 then
4682
4683 -- minetest.chat_send_player(clicker:get_player_name(),
4684 -- S("@1 at full health (@2)",
4685 -- self.name:split(":")[2], tostring(self.health)))
4686
4687 -- self.htimer = 5
4688 -- end
38094689 end
38104690
38114691 self.object:set_hp(self.health)
3812
3813 update_tag(self)
38144692
38154693 -- make children grow quicker
38164694 if self.child == true then
38174695
3818 self.hornytimer = self.hornytimer + 20
3819
4696 -- self.hornytimer = self.hornytimer + 20
4697 -- deduct 10% of the time to adulthood
4698 self.hornytimer = math.floor(self.hornytimer + (
4699 (CHILD_GROW_TIME - self.hornytimer) * 0.1))
4700 --print ("====", self.hornytimer)
38204701 return true
38214702 end
38224703
38234704 -- feed and tame
38244705 self.food = (self.food or 0) + 1
4706 self._breed_countdown = feed_count - self.food
4707
38254708 if self.food >= feed_count then
38264709
38274710 self.food = 0
4711 self._breed_countdown = nil
38284712
38294713 if breed and self.hornytimer == 0 then
38304714 self.horny = true
38314715 end
3832
3833 self.gotten = false
38344716
38354717 if tame then
38364718
38414723 end
38424724
38434725 self.tamed = true
4726 self.static_save = true
38444727
38454728 if not self.owner or self.owner == "" then
38464729 self.owner = clicker:get_player_name()
38484731 end
38494732
38504733 -- make sound when fed so many times
3851 mob_sound(self, self.sounds.random)
3852 end
4734 self:mob_sound(self.sounds.random)
4735 end
4736
4737 self:update_tag()
38534738
38544739 return true
38554740 end
38564741
38574742 local item = clicker:get_wielded_item()
4743 local name = clicker:get_player_name()
38584744
38594745 -- if mob has been tamed you can name it with a nametag
38604746 if item:get_name() == "mobs:nametag"
3861 and clicker:get_player_name() == self.owner then
3862
3863 local name = clicker:get_player_name()
4747 and (name == self.owner
4748 or minetest.check_player_privs(name, "protection_bypass")) then
38644749
38654750 -- store mob and nametag stack in external variables
38664751 mob_obj[name] = self
38674752 mob_sta[name] = item
38684753
38694754 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")) .. "]")
4755 local esc = minetest.formspec_escape
4756
4757 minetest.show_formspec(name, "mobs_nametag",
4758 "size[8,4]" ..
4759 "field[0.5,1;7.5,0;name;" ..
4760 esc(S("Enter name:")) ..
4761 ";" .. tag .. "]" ..
4762 "button_exit[2.5,3.5;3,1;mob_rename;" ..
4763 esc(S("Rename")) .. "]")
4764
4765 return true
4766 end
4767
4768 -- if mob follows items and user right clicks while holding sneak it shows info
4769 if self.follow then
4770
4771 if clicker:get_player_control().sneak then
4772
4773 if type(self.follow) == "string" then
4774 self.follow = {self.follow}
4775 end
4776
4777 minetest.chat_send_player(clicker:get_player_name(),
4778 S("@1 follows:\n- @2",
4779 self.name:split(":")[2],
4780 table.concat(self.follow, "\n- ")))
4781 end
38784782 end
38794783
38804784 return false
39044808 end
39054809
39064810 -- limit name entered to 64 characters long
3907 if string.len(fields.name) > 64 then
3908 fields.name = string.sub(fields.name, 1, 64)
4811 if fields.name:len() > 64 then
4812 fields.name = fields.name:sub(1, 64)
39094813 end
39104814
39114815 -- update nametag
39124816 mob_obj[name].nametag = fields.name
39134817
3914 update_tag(mob_obj[name])
4818 mob_obj[name]:update_tag()
39154819
39164820 -- if not in creative then take item
39174821 if not mobs.is_creative(name) then
39314835 -- compatibility function for old entities to new modpack entities
39324836 function mobs:alias_mob(old_name, new_name)
39334837
4838 -- check old_name entity doesnt already exist
4839 if minetest.registered_entities[old_name] then
4840 return
4841 end
4842
39344843 -- spawn egg
39354844 minetest.register_alias(old_name, new_name)
39364845
39374846 -- entity
39384847 minetest.register_entity(":" .. old_name, {
39394848
3940 physical = false,
3941
3942 on_activate = function(self)
4849 physical = false, static_save = false,
4850
4851 on_activate = function(self, staticdata)
39434852
39444853 if minetest.registered_entities[new_name] then
3945 minetest.add_entity(self.object:get_pos(), new_name)
3946 end
3947
3948 self.object:remove()
4854
4855 minetest.add_entity(self.object:get_pos(), new_name, staticdata)
4856 end
4857
4858 remove_mob(self)
4859 end,
4860
4861 get_staticdata = function(self)
4862 return self
39494863 end
39504864 })
39514865 end
+0
-3985
api.lua_testspawn less more
0
1 -- Mobs Api
2
3 mobs = {}
4 mobs.mod = "redo"
5 mobs.version = "20180623"
6
7
8 -- Intllib
9 local MP = minetest.get_modpath(minetest.get_current_modname())
10 local S, NS = dofile(MP .. "/intllib.lua")
11 mobs.intllib = S
12
13
14 -- CMI support check
15 local use_cmi = minetest.global_exists("cmi")
16
17
18 -- Invisibility mod check
19 mobs.invis = {}
20 if minetest.global_exists("invisibility") then
21 mobs.invis = invisibility
22 end
23
24
25 -- creative check
26 local creative_mode_cache = minetest.settings:get_bool("creative_mode")
27 function mobs.is_creative(name)
28 return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
29 end
30
31
32 -- localize math functions
33 local pi = math.pi
34 local square = math.sqrt
35 local sin = math.sin
36 local cos = math.cos
37 local abs = math.abs
38 local min = math.min
39 local max = math.max
40 local atann = math.atan
41 local random = math.random
42 local floor = math.floor
43 local atan = function(x)
44 if not x or x ~= x then
45 --error("atan bassed NaN")
46 return 0
47 else
48 return atann(x)
49 end
50 end
51
52
53 -- Load settings
54 local damage_enabled = minetest.settings:get_bool("enable_damage")
55 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
56 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
57 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
58 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
59 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
60 local creative = minetest.settings:get_bool("creative_mode")
61 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
62 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
63 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
64 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
65 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
66 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
67
68 -- Peaceful mode message so players will know there are no monsters
69 if peaceful_only then
70 minetest.register_on_joinplayer(function(player)
71 minetest.chat_send_player(player:get_player_name(),
72 S("** Peaceful Mode Active - No Monsters Will Spawn"))
73 end)
74 end
75
76 -- calculate aoc range for mob count
77 local aosrb = tonumber(minetest.settings:get("active_object_send_range_blocks"))
78 local abr = tonumber(minetest.settings:get("active_block_range"))
79 local aoc_range = max(aosrb, abr) * 16
80
81 -- pathfinding settings
82 local enable_pathfinding = true
83 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
84 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
85
86 -- default nodes
87 local node_fire = "fire:basic_flame"
88 local node_permanent_flame = "fire:permanent_flame"
89 local node_ice = "default:ice"
90 local node_snowblock = "default:snowblock"
91 local node_snow = "default:snow"
92 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
93
94
95 -- play sound
96 local mob_sound = function(self, sound)
97
98 if sound then
99 minetest.sound_play(sound, {
100 object = self.object,
101 gain = 1.0,
102 max_hear_distance = self.sounds.distance
103 })
104 end
105 end
106
107
108 -- attack player/mob
109 local do_attack = function(self, player)
110
111 if self.state == "attack" then
112 return
113 end
114
115 self.attack = player
116 self.state = "attack"
117
118 if random(0, 100) < 90 then
119 mob_sound(self, self.sounds.war_cry)
120 end
121 end
122
123
124 -- move mob in facing direction
125 local set_velocity = function(self, v)
126
127 -- do not move if mob has been ordered to stay
128 if self.order == "stand" then
129 self.object:setvelocity({x = 0, y = 0, z = 0})
130 return
131 end
132
133 local yaw = (self.object:get_yaw() or 0) + self.rotate
134
135 self.object:setvelocity({
136 x = sin(yaw) * -v,
137 y = self.object:getvelocity().y,
138 z = cos(yaw) * v
139 })
140 end
141
142
143 -- calculate mob velocity
144 local get_velocity = function(self)
145
146 local v = self.object:getvelocity()
147
148 return (v.x * v.x + v.z * v.z) ^ 0.5
149 end
150
151
152 -- set and return valid yaw
153 local set_yaw = function(self, yaw, delay)
154
155 if not yaw or yaw ~= yaw then
156 yaw = 0
157 end
158
159 delay = delay or 0
160
161 if delay == 0 then
162 self.object:set_yaw(yaw)
163 return yaw
164 end
165
166 self.target_yaw = yaw
167 self.delay = delay
168
169 return self.target_yaw
170 end
171
172 -- global function to set mob yaw
173 function mobs:yaw(self, yaw, delay)
174 set_yaw(self, yaw, delay)
175 end
176
177
178 -- set defined animation
179 local set_animation = function(self, anim)
180
181 if not self.animation
182 or not anim then return end
183
184 self.animation.current = self.animation.current or ""
185
186 if anim == self.animation.current
187 or not self.animation[anim .. "_start"]
188 or not self.animation[anim .. "_end"] then
189 return
190 end
191
192 self.animation.current = anim
193
194 self.object:set_animation({
195 x = self.animation[anim .. "_start"],
196 y = self.animation[anim .. "_end"]},
197 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
198 0, self.animation[anim .. "_loop"] ~= false)
199 end
200
201
202 -- above function exported for mount.lua
203 function mobs:set_animation(self, anim)
204 set_animation(self, anim)
205 end
206
207
208 -- calculate distance
209 local get_distance = function(a, b)
210
211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
212
213 return square(x * x + y * y + z * z)
214 end
215
216
217 -- check line of sight (BrunoMine)
218 local line_of_sight = function(self, pos1, pos2, stepsize)
219
220 stepsize = stepsize or 1
221
222 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
223
224 -- normal walking and flying mobs can see you through air
225 if s == true then
226 return true
227 end
228
229 -- New pos1 to be analyzed
230 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
231
232 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
233
234 -- Checks the return
235 if r == true then return true end
236
237 -- Nodename found
238 local nn = minetest.get_node(pos).name
239
240 -- Target Distance (td) to travel
241 local td = get_distance(pos1, pos2)
242
243 -- Actual Distance (ad) traveled
244 local ad = 0
245
246 -- It continues to advance in the line of sight in search of a real
247 -- obstruction which counts as 'normal' nodebox.
248 while minetest.registered_nodes[nn]
249 and (minetest.registered_nodes[nn].walkable == false
250 or minetest.registered_nodes[nn].drawtype == "nodebox") do
251
252 -- Check if you can still move forward
253 if td < ad + stepsize then
254 return true -- Reached the target
255 end
256
257 -- Moves the analyzed pos
258 local d = get_distance(pos1, pos2)
259
260 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
261 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
262 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
263
264 -- NaN checks
265 if d == 0
266 or npos1.x ~= npos1.x
267 or npos1.y ~= npos1.y
268 or npos1.z ~= npos1.z then
269 return false
270 end
271
272 ad = ad + stepsize
273
274 -- scan again
275 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
276
277 if r == true then return true end
278
279 -- New Nodename found
280 nn = minetest.get_node(pos).name
281
282 end
283
284 return false
285 end
286
287
288 -- are we flying in what we are suppose to? (taikedz)
289 local flight_check = function(self, pos_w)
290
291 local def = minetest.registered_nodes[self.standing_in]
292
293 if not def then return false end -- nil check
294
295 if type(self.fly_in) == "string"
296 and self.standing_in == self.fly_in then
297
298 return true
299
300 elseif type(self.fly_in) == "table" then
301
302 for _,fly_in in pairs(self.fly_in) do
303
304 if self.standing_in == fly_in then
305
306 return true
307 end
308 end
309 end
310
311 -- stops mobs getting stuck inside stairs and plantlike nodes
312 if def.drawtype ~= "airlike"
313 and def.drawtype ~= "liquid"
314 and def.drawtype ~= "flowingliquid" then
315 return true
316 end
317
318 return false
319 end
320
321
322 -- custom particle effects
323 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
324
325 radius = radius or 2
326 min_size = min_size or 0.5
327 max_size = max_size or 1
328 gravity = gravity or -10
329 glow = glow or 0
330
331 minetest.add_particlespawner({
332 amount = amount,
333 time = 0.25,
334 minpos = pos,
335 maxpos = pos,
336 minvel = {x = -radius, y = -radius, z = -radius},
337 maxvel = {x = radius, y = radius, z = radius},
338 minacc = {x = 0, y = gravity, z = 0},
339 maxacc = {x = 0, y = gravity, z = 0},
340 minexptime = 0.1,
341 maxexptime = 1,
342 minsize = min_size,
343 maxsize = max_size,
344 texture = texture,
345 glow = glow,
346 })
347 end
348
349
350 -- update nametag colour
351 local update_tag = function(self)
352
353 local col = "#00FF00"
354 local qua = self.hp_max / 4
355
356 if self.health <= floor(qua * 3) then
357 col = "#FFFF00"
358 end
359
360 if self.health <= floor(qua * 2) then
361 col = "#FF6600"
362 end
363
364 if self.health <= floor(qua) then
365 col = "#FF0000"
366 end
367
368 self.object:set_properties({
369 nametag = self.nametag,
370 nametag_color = col
371 })
372
373 end
374
375
376 -- drop items
377 local item_drop = function(self, cooked)
378
379 -- no drops if disabled by setting
380 if not mobs_drop_items then return end
381
382 -- no drops for child mobs
383 if self.child then return end
384
385 local obj, item, num
386 local pos = self.object:get_pos()
387
388 self.drops = self.drops or {} -- nil check
389
390 for n = 1, #self.drops do
391
392 if random(1, self.drops[n].chance) == 1 then
393
394 num = random(self.drops[n].min or 1, self.drops[n].max or 1)
395 item = self.drops[n].name
396
397 -- cook items when true
398 if cooked then
399
400 local output = minetest.get_craft_result({
401 method = "cooking", width = 1, items = {item}})
402
403 if output and output.item and not output.item:is_empty() then
404 item = output.item:get_name()
405 end
406 end
407
408 -- add item if it exists
409 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
410
411 if obj and obj:get_luaentity() then
412
413 obj:setvelocity({
414 x = random(-10, 10) / 9,
415 y = 6,
416 z = random(-10, 10) / 9,
417 })
418 elseif obj then
419 obj:remove() -- item does not exist
420 end
421 end
422 end
423
424 self.drops = {}
425 end
426
427
428 -- check if mob is dead or only hurt
429 local check_for_death = function(self, cause, cmi_cause)
430
431 -- has health actually changed?
432 if self.health == self.old_health and self.health > 0 then
433 return
434 end
435
436 self.old_health = self.health
437
438 -- still got some health? play hurt sound
439 if self.health > 0 then
440
441 mob_sound(self, self.sounds.damage)
442
443 -- make sure health isn't higher than max
444 if self.health > self.hp_max then
445 self.health = self.hp_max
446 end
447
448 -- backup nametag so we can show health stats
449 if not self.nametag2 then
450 self.nametag2 = self.nametag or ""
451 end
452
453 if show_health
454 and (cmi_cause and cmi_cause.type == "punch") then
455
456 self.htimer = 2
457 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
458
459 update_tag(self)
460 end
461
462 return false
463 end
464
465 -- dropped cooked item if mob died in lava
466 if cause == "lava" then
467 item_drop(self, true)
468 else
469 item_drop(self, nil)
470 end
471
472 mob_sound(self, self.sounds.death)
473
474 local pos = self.object:get_pos()
475
476 -- execute custom death function
477 if self.on_die then
478
479 self.on_die(self, pos)
480
481 if use_cmi then
482 cmi.notify_die(self.object, cmi_cause)
483 end
484
485 self.object:remove()
486
487 return true
488 end
489
490 -- default death function and die animation (if defined)
491 if self.animation
492 and self.animation.die_start
493 and self.animation.die_end then
494
495 local frames = self.animation.die_end - self.animation.die_start
496 local speed = self.animation.die_speed or 15
497 local length = max(frames / speed, 0)
498
499 self.attack = nil
500 self.v_start = false
501 self.timer = 0
502 self.blinktimer = 0
503 self.passive = true
504 self.state = "die"
505 set_velocity(self, 0)
506 set_animation(self, "die")
507
508 minetest.after(length, function(self)
509
510 if use_cmi and self.object:get_luaentity() then
511 cmi.notify_die(self.object, cmi_cause)
512 end
513
514 self.object:remove()
515 end, self)
516 else
517
518 if use_cmi then
519 cmi.notify_die(self.object, cmi_cause)
520 end
521
522 self.object:remove()
523 end
524
525 effect(pos, 20, "tnt_smoke.png")
526
527 return true
528 end
529
530
531 -- check if within physical map limits (-30911 to 30927)
532 local within_limits = function(pos, radius)
533
534 if (pos.x - radius) > -30913
535 and (pos.x + radius) < 30928
536 and (pos.y - radius) > -30913
537 and (pos.y + radius) < 30928
538 and (pos.z - radius) > -30913
539 and (pos.z + radius) < 30928 then
540 return true -- within limits
541 end
542
543 return false -- beyond limits
544 end
545
546
547 -- is mob facing a cliff
548 local is_at_cliff = function(self)
549
550 if self.fear_height == 0 then -- 0 for no falling protection!
551 return false
552 end
553
554 local yaw = self.object:get_yaw()
555 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
556 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
557 local pos = self.object:get_pos()
558 local ypos = pos.y + self.collisionbox[2] -- just above floor
559
560 if minetest.line_of_sight(
561 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
562 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
563 , 1) then
564
565 return true
566 end
567
568 return false
569 end
570
571
572 -- get node but use fallback for nil or unknown
573 local node_ok = function(pos, fallback)
574
575 fallback = fallback or mobs.fallback_node
576
577 local node = minetest.get_node_or_nil(pos)
578
579 if node and minetest.registered_nodes[node.name] then
580 return node
581 end
582
583 return minetest.registered_nodes[fallback]
584 end
585
586
587 -- environmental damage (water, lava, fire, light etc.)
588 local do_env_damage = function(self)
589
590 -- feed/tame text timer (so mob 'full' messages dont spam chat)
591 if self.htimer > 0 then
592 self.htimer = self.htimer - 1
593 end
594
595 -- reset nametag after showing health stats
596 if self.htimer < 1 and self.nametag2 then
597
598 self.nametag = self.nametag2
599 self.nametag2 = nil
600
601 update_tag(self)
602 end
603
604 local pos = self.object:get_pos()
605
606 self.time_of_day = minetest.get_timeofday()
607
608 -- remove mob if beyond map limits
609 if not within_limits(pos, 0) then
610 self.object:remove()
611 return
612 end
613
614 -- bright light harms mob
615 if self.light_damage ~= 0
616 -- and pos.y > 0
617 -- and self.time_of_day > 0.2
618 -- and self.time_of_day < 0.8
619 and (minetest.get_node_light(pos) or 0) > 12 then
620
621 self.health = self.health - self.light_damage
622
623 effect(pos, 5, "tnt_smoke.png")
624
625 if check_for_death(self, "light", {type = "light"}) then return end
626 end
627 --[[
628 local y_level = self.collisionbox[2]
629
630 if self.child then
631 y_level = self.collisionbox[2] * 0.5
632 end
633
634 -- what is mob standing in?
635 pos.y = pos.y + y_level + 0.25 -- foot level
636 self.standing_in = node_ok(pos, "air").name
637 -- print ("standing in " .. self.standing_in)
638 ]]
639 -- don't fall when on ignore, just stand still
640 if self.standing_in == "ignore" then
641 self.object:setvelocity({x = 0, y = 0, z = 0})
642 end
643
644 local nodef = minetest.registered_nodes[self.standing_in]
645
646 pos.y = pos.y + 1 -- for particle effect position
647
648 -- water
649 if self.water_damage
650 and nodef.groups.water then
651
652 if self.water_damage ~= 0 then
653
654 self.health = self.health - self.water_damage
655
656 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
657
658 if check_for_death(self, "water", {type = "environment",
659 pos = pos, node = self.standing_in}) then return end
660 end
661
662 -- lava or fire
663 elseif self.lava_damage
664 and (nodef.groups.lava
665 or self.standing_in == node_fire
666 or self.standing_in == node_permanent_flame) then
667
668 if self.lava_damage ~= 0 then
669
670 self.health = self.health - self.lava_damage
671
672 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
673
674 if check_for_death(self, "lava", {type = "environment",
675 pos = pos, node = self.standing_in}) then return end
676 end
677
678 -- damage_per_second node check
679 elseif nodef.damage_per_second ~= 0 then
680
681 self.health = self.health - nodef.damage_per_second
682
683 effect(pos, 5, "tnt_smoke.png")
684
685 if check_for_death(self, "dps", {type = "environment",
686 pos = pos, node = self.standing_in}) then return end
687 end
688 --[[
689 --- suffocation inside solid node
690 if self.suffocation ~= 0
691 and nodef.walkable == true
692 and nodef.groups.disable_suffocation ~= 1
693 and nodef.drawtype == "normal" then
694
695 self.health = self.health - self.suffocation
696
697 if check_for_death(self, "suffocation", {type = "environment",
698 pos = pos, node = self.standing_in}) then return end
699 end
700 ]]
701 check_for_death(self, "", {type = "unknown"})
702 end
703
704
705 -- jump if facing a solid node (not fences or gates)
706 local do_jump = function(self)
707
708 if not self.jump
709 or self.jump_height == 0
710 or self.fly
711 or self.child
712 or self.order == "stand" then
713 return false
714 end
715
716 self.facing_fence = false
717
718 -- something stopping us while moving?
719 if self.state ~= "stand"
720 and get_velocity(self) > 0.5
721 and self.object:getvelocity().y ~= 0 then
722 return false
723 end
724
725 local pos = self.object:get_pos()
726 local yaw = self.object:get_yaw()
727
728 -- what is mob standing on?
729 pos.y = pos.y + self.collisionbox[2] - 0.2
730
731 local nod = node_ok(pos)
732
733 --print ("standing on:", nod.name, pos.y)
734
735 if minetest.registered_nodes[nod.name].walkable == false then
736 return false
737 end
738
739 -- where is front
740 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
741 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
742
743 -- what is in front of mob?
744 local nod = node_ok({
745 x = pos.x + dir_x,
746 y = pos.y + 0.5,
747 z = pos.z + dir_z
748 })
749
750 -- thin blocks that do not need to be jumped
751 if nod.name == node_snow then
752 return false
753 end
754
755 --print ("in front:", nod.name, pos.y + 0.5)
756
757 if self.walk_chance == 0
758 or minetest.registered_items[nod.name].walkable then
759
760 if not nod.name:find("fence")
761 and not nod.name:find("gate") then
762
763 local v = self.object:getvelocity()
764
765 v.y = self.jump_height
766
767 set_animation(self, "jump") -- only when defined
768
769 self.object:setvelocity(v)
770
771 -- when in air move forward
772 minetest.after(0.3, function(self, v)
773
774 if self.object:get_luaentity() then
775
776 self.object:set_acceleration({
777 x = v.x * 2,--1.5,
778 y = 0,
779 z = v.z * 2,--1.5
780 })
781 end
782 end, self, v)
783
784 if get_velocity(self) > 0 then
785 mob_sound(self, self.sounds.jump)
786 end
787 else
788 self.facing_fence = true
789 end
790
791 return true
792 end
793
794 return false
795 end
796
797
798 -- blast damage to entities nearby (modified from TNT mod)
799 local entity_physics = function(pos, radius)
800
801 radius = radius * 2
802
803 local objs = minetest.get_objects_inside_radius(pos, radius)
804 local obj_pos, dist
805
806 for n = 1, #objs do
807
808 obj_pos = objs[n]:get_pos()
809
810 dist = get_distance(pos, obj_pos)
811 if dist < 1 then dist = 1 end
812
813 local damage = floor((4 / dist) * radius)
814 local ent = objs[n]:get_luaentity()
815
816 -- punches work on entities AND players
817 objs[n]:punch(objs[n], 1.0, {
818 full_punch_interval = 1.0,
819 damage_groups = {fleshy = damage},
820 }, pos)
821 end
822 end
823
824
825 -- should mob follow what I'm holding ?
826 local follow_holding = function(self, clicker)
827
828 if mobs.invis[clicker:get_player_name()] then
829 return false
830 end
831
832 local item = clicker:get_wielded_item()
833 local t = type(self.follow)
834
835 -- single item
836 if t == "string"
837 and item:get_name() == self.follow then
838 return true
839
840 -- multiple items
841 elseif t == "table" then
842
843 for no = 1, #self.follow do
844
845 if self.follow[no] == item:get_name() then
846 return true
847 end
848 end
849 end
850
851 return false
852 end
853
854
855 -- find two animals of same type and breed if nearby and horny
856 local breed = function(self)
857
858 -- child takes 240 seconds before growing into adult
859 if self.child == true then
860
861 self.hornytimer = self.hornytimer + 1
862
863 if self.hornytimer > 240 then
864
865 self.child = false
866 self.hornytimer = 0
867
868 self.object:set_properties({
869 textures = self.base_texture,
870 mesh = self.base_mesh,
871 visual_size = self.base_size,
872 collisionbox = self.base_colbox,
873 selectionbox = self.base_selbox,
874 })
875
876 -- custom function when child grows up
877 if self.on_grown then
878 self.on_grown(self)
879 else
880 -- jump when fully grown so as not to fall into ground
881 self.object:setvelocity({
882 x = 0,
883 y = self.jump_height,
884 z = 0
885 })
886 end
887 end
888
889 return
890 end
891
892 -- horny animal can mate for 40 seconds,
893 -- afterwards horny animal cannot mate again for 200 seconds
894 if self.horny == true
895 and self.hornytimer < 240 then
896
897 self.hornytimer = self.hornytimer + 1
898
899 if self.hornytimer >= 240 then
900 self.hornytimer = 0
901 self.horny = false
902 end
903 end
904
905 -- find another same animal who is also horny and mate if nearby
906 if self.horny == true
907 and self.hornytimer <= 40 then
908
909 local pos = self.object:get_pos()
910
911 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
912
913 local objs = minetest.get_objects_inside_radius(pos, 3)
914 local num = 0
915 local ent = nil
916
917 for n = 1, #objs do
918
919 ent = objs[n]:get_luaentity()
920
921 -- check for same animal with different colour
922 local canmate = false
923
924 if ent then
925
926 if ent.name == self.name then
927 canmate = true
928 else
929 local entname = string.split(ent.name,":")
930 local selfname = string.split(self.name,":")
931
932 if entname[1] == selfname[1] then
933 entname = string.split(entname[2],"_")
934 selfname = string.split(selfname[2],"_")
935
936 if entname[1] == selfname[1] then
937 canmate = true
938 end
939 end
940 end
941 end
942
943 if ent
944 and canmate == true
945 and ent.horny == true
946 and ent.hornytimer <= 40 then
947 num = num + 1
948 end
949
950 -- found your mate? then have a baby
951 if num > 1 then
952
953 self.hornytimer = 41
954 ent.hornytimer = 41
955
956 -- spawn baby
957 minetest.after(5, function(self, ent)
958
959 if not self.object:get_luaentity() then
960 return
961 end
962
963 -- custom breed function
964 if self.on_breed then
965
966 -- when false skip going any further
967 if self.on_breed(self, ent) == false then
968 return
969 end
970 else
971 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
972 end
973
974 local mob = minetest.add_entity(pos, self.name)
975 local ent2 = mob:get_luaentity()
976 local textures = self.base_texture
977
978 -- using specific child texture (if found)
979 if self.child_texture then
980 textures = self.child_texture[1]
981 end
982
983 -- and resize to half height
984 mob:set_properties({
985 textures = textures,
986 visual_size = {
987 x = self.base_size.x * .5,
988 y = self.base_size.y * .5,
989 },
990 collisionbox = {
991 self.base_colbox[1] * .5,
992 self.base_colbox[2] * .5,
993 self.base_colbox[3] * .5,
994 self.base_colbox[4] * .5,
995 self.base_colbox[5] * .5,
996 self.base_colbox[6] * .5,
997 },
998 selectionbox = {
999 self.base_selbox[1] * .5,
1000 self.base_selbox[2] * .5,
1001 self.base_selbox[3] * .5,
1002 self.base_selbox[4] * .5,
1003 self.base_selbox[5] * .5,
1004 self.base_selbox[6] * .5,
1005 },
1006 })
1007 -- tamed and owned by parents' owner
1008 ent2.child = true
1009 ent2.tamed = true
1010 ent2.owner = self.owner
1011 end, self, ent)
1012
1013 num = 0
1014
1015 break
1016 end
1017 end
1018 end
1019 end
1020
1021
1022 -- find and replace what mob is looking for (grass, wheat etc.)
1023 local replace = function(self, pos)
1024
1025 if not mobs_griefing
1026 or not self.replace_rate
1027 or not self.replace_what
1028 or self.child == true
1029 or self.object:getvelocity().y ~= 0
1030 or random(1, self.replace_rate) > 1 then
1031 return
1032 end
1033
1034 local what, with, y_offset
1035
1036 if type(self.replace_what[1]) == "table" then
1037
1038 local num = random(#self.replace_what)
1039
1040 what = self.replace_what[num][1] or ""
1041 with = self.replace_what[num][2] or ""
1042 y_offset = self.replace_what[num][3] or 0
1043 else
1044 what = self.replace_what
1045 with = self.replace_with or ""
1046 y_offset = self.replace_offset or 0
1047 end
1048
1049 pos.y = pos.y + y_offset
1050
1051 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
1052
1053 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1054
1055 local oldnode = {name = what}
1056 local newnode = {name = with}
1057 local on_replace_return
1058
1059 if self.on_replace then
1060 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1061 end
1062
1063 if on_replace_return ~= false then
1064
1065 minetest.set_node(pos, {name = with})
1066
1067 -- when cow/sheep eats grass, replace wool and milk
1068 if self.gotten == true then
1069 self.gotten = false
1070 self.object:set_properties(self)
1071 end
1072 end
1073 end
1074 end
1075
1076
1077 -- check if daytime and also if mob is docile during daylight hours
1078 local day_docile = function(self)
1079
1080 if self.docile_by_day == false then
1081
1082 return false
1083
1084 elseif self.docile_by_day == true
1085 and self.time_of_day > 0.2
1086 and self.time_of_day < 0.8 then
1087
1088 return true
1089 end
1090 end
1091
1092
1093 local los_switcher = false
1094 local height_switcher = false
1095
1096 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1097 local smart_mobs = function(self, s, p, dist, dtime)
1098
1099 local s1 = self.path.lastpos
1100
1101 local target_pos = self.attack:get_pos()
1102
1103 -- is it becoming stuck?
1104 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
1105 self.path.stuck_timer = self.path.stuck_timer + dtime
1106 else
1107 self.path.stuck_timer = 0
1108 end
1109
1110 self.path.lastpos = {x = s.x, y = s.y, z = s.z}
1111
1112 local use_pathfind = false
1113 local has_lineofsight = minetest.line_of_sight(
1114 {x = s.x, y = (s.y) + .5, z = s.z},
1115 {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
1116
1117 -- im stuck, search for path
1118 if not has_lineofsight then
1119
1120 if los_switcher == true then
1121 use_pathfind = true
1122 los_switcher = false
1123 end -- cannot see target!
1124 else
1125 if los_switcher == false then
1126
1127 los_switcher = true
1128 use_pathfind = false
1129
1130 minetest.after(1, function(self)
1131
1132 if self.object:get_luaentity() then
1133
1134 if has_lineofsight then
1135 self.path.following = false
1136 end
1137 end
1138 end, self)
1139 end -- can see target!
1140 end
1141
1142 if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
1143
1144 use_pathfind = true
1145 self.path.stuck_timer = 0
1146
1147 minetest.after(1, function(self)
1148
1149 if self.object:get_luaentity() then
1150
1151 if has_lineofsight then
1152 self.path.following = false
1153 end
1154 end
1155 end, self)
1156 end
1157
1158 if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
1159
1160 use_pathfind = true
1161 self.path.stuck_timer = 0
1162
1163 minetest.after(1, function(self)
1164
1165 if self.object:get_luaentity() then
1166
1167 if has_lineofsight then
1168 self.path.following = false
1169 end
1170 end
1171 end, self)
1172 end
1173
1174 if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
1175
1176 if height_switcher then
1177 use_pathfind = true
1178 height_switcher = false
1179 end
1180 else
1181 if not height_switcher then
1182 use_pathfind = false
1183 height_switcher = true
1184 end
1185 end
1186
1187 if use_pathfind then
1188 -- lets try find a path, first take care of positions
1189 -- since pathfinder is very sensitive
1190 local sheight = self.collisionbox[5] - self.collisionbox[2]
1191
1192 -- round position to center of node to avoid stuck in walls
1193 -- also adjust height for player models!
1194 s.x = floor(s.x + 0.5)
1195 -- s.y = floor(s.y + 0.5) - sheight
1196 s.z = floor(s.z + 0.5)
1197
1198 local ssight, sground = minetest.line_of_sight(s, {
1199 x = s.x, y = s.y - 4, z = s.z}, 1)
1200
1201 -- determine node above ground
1202 if not ssight then
1203 s.y = sground.y + 1
1204 end
1205
1206 local p1 = self.attack:get_pos()
1207
1208 p1.x = floor(p1.x + 0.5)
1209 p1.y = floor(p1.y + 0.5)
1210 p1.z = floor(p1.z + 0.5)
1211
1212 local dropheight = 6
1213 if self.fear_height ~= 0 then dropheight = self.fear_height end
1214
1215 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1216 --[[
1217 -- show path using particles
1218 if self.path.way and #self.path.way > 0 then
1219 print ("-- path length:" .. tonumber(#self.path.way))
1220 for _,pos in pairs(self.path.way) do
1221 minetest.add_particle({
1222 pos = pos,
1223 velocity = {x=0, y=0, z=0},
1224 acceleration = {x=0, y=0, z=0},
1225 expirationtime = 1,
1226 size = 4,
1227 collisiondetection = false,
1228 vertical = false,
1229 texture = "heart.png",
1230 })
1231 end
1232 end
1233 ]]
1234
1235 self.state = ""
1236 do_attack(self, self.attack)
1237
1238 -- no path found, try something else
1239 if not self.path.way then
1240
1241 self.path.following = false
1242
1243 -- lets make way by digging/building if not accessible
1244 if self.pathfinding == 2 and mobs_griefing then
1245
1246 -- is player higher than mob?
1247 if s.y < p1.y then
1248
1249 -- build upwards
1250 if not minetest.is_protected(s, "") then
1251
1252 local ndef1 = minetest.registered_nodes[self.standing_in]
1253
1254 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1255
1256 minetest.set_node(s, {name = mobs.fallback_node})
1257 end
1258 end
1259
1260 local sheight = math.ceil(self.collisionbox[5]) + 1
1261
1262 -- assume mob is 2 blocks high so it digs above its head
1263 s.y = s.y + sheight
1264
1265 -- remove one block above to make room to jump
1266 if not minetest.is_protected(s, "") then
1267
1268 local node1 = node_ok(s, "air").name
1269 local ndef1 = minetest.registered_nodes[node1]
1270
1271 if node1 ~= "air"
1272 and node1 ~= "ignore"
1273 and ndef1
1274 and not ndef1.groups.level
1275 and not ndef1.groups.unbreakable
1276 and not ndef1.groups.liquid then
1277
1278 minetest.set_node(s, {name = "air"})
1279 minetest.add_item(s, ItemStack(node1))
1280
1281 end
1282 end
1283
1284 s.y = s.y - sheight
1285 self.object:setpos({x = s.x, y = s.y + 2, z = s.z})
1286
1287 else -- dig 2 blocks to make door toward player direction
1288
1289 local yaw1 = self.object:get_yaw() + pi / 2
1290 local p1 = {
1291 x = s.x + cos(yaw1),
1292 y = s.y,
1293 z = s.z + sin(yaw1)
1294 }
1295
1296 if not minetest.is_protected(p1, "") then
1297
1298 local node1 = node_ok(p1, "air").name
1299 local ndef1 = minetest.registered_nodes[node1]
1300
1301 if node1 ~= "air"
1302 and node1 ~= "ignore"
1303 and ndef1
1304 and not ndef1.groups.level
1305 and not ndef1.groups.unbreakable
1306 and not ndef1.groups.liquid then
1307
1308 minetest.add_item(p1, ItemStack(node1))
1309 minetest.set_node(p1, {name = "air"})
1310 end
1311
1312 p1.y = p1.y + 1
1313 node1 = node_ok(p1, "air").name
1314 ndef1 = minetest.registered_nodes[node1]
1315
1316 if node1 ~= "air"
1317 and node1 ~= "ignore"
1318 and ndef1
1319 and not ndef1.groups.level
1320 and not ndef1.groups.unbreakable
1321 and not ndef1.groups.liquid then
1322
1323 minetest.add_item(p1, ItemStack(node1))
1324 minetest.set_node(p1, {name = "air"})
1325 end
1326
1327 end
1328 end
1329 end
1330
1331 -- will try again in 2 second
1332 self.path.stuck_timer = stuck_timeout - 2
1333
1334 -- frustration! cant find the damn path :(
1335 mob_sound(self, self.sounds.random)
1336 else
1337 -- yay i found path
1338 mob_sound(self, self.sounds.war_cry)
1339 set_velocity(self, self.walk_velocity)
1340
1341 -- follow path now that it has it
1342 self.path.following = true
1343 end
1344 end
1345 end
1346
1347
1348 -- specific attacks
1349 local specific_attack = function(list, what)
1350
1351 -- no list so attack default (player, animals etc.)
1352 if list == nil then
1353 return true
1354 end
1355
1356 -- found entity on list to attack?
1357 for no = 1, #list do
1358
1359 if list[no] == what then
1360 return true
1361 end
1362 end
1363
1364 return false
1365 end
1366
1367
1368 -- monster find someone to attack
1369 local monster_attack = function(self)
1370
1371 if self.type ~= "monster"
1372 or not damage_enabled
1373 or creative
1374 or self.state == "attack"
1375 or day_docile(self) then
1376 return
1377 end
1378
1379 local s = self.object:get_pos()
1380 local p, sp, dist
1381 local player, obj, min_player
1382 local type, name = "", ""
1383 local min_dist = self.view_range + 1
1384 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1385
1386 for n = 1, #objs do
1387
1388 if objs[n]:is_player() then
1389
1390 if mobs.invis[ objs[n]:get_player_name() ] then
1391
1392 type = ""
1393 else
1394 player = objs[n]
1395 type = "player"
1396 name = "player"
1397 end
1398 else
1399 obj = objs[n]:get_luaentity()
1400
1401 if obj then
1402 player = obj.object
1403 type = obj.type
1404 name = obj.name or ""
1405 end
1406 end
1407
1408 -- find specific mob to attack, failing that attack player/npc/animal
1409 if specific_attack(self.specific_attack, name)
1410 and (type == "player" or type == "npc"
1411 or (type == "animal" and self.attack_animals == true)) then
1412
1413 p = player:get_pos()
1414 sp = s
1415
1416 dist = get_distance(p, s)
1417
1418 -- aim higher to make looking up hills more realistic
1419 p.y = p.y + 1
1420 sp.y = sp.y + 1
1421
1422
1423 -- choose closest player to attack
1424 if dist < min_dist
1425 and line_of_sight(self, sp, p, 2) == true then
1426 min_dist = dist
1427 min_player = player
1428 end
1429 end
1430 end
1431
1432 -- attack player
1433 if min_player then
1434 do_attack(self, min_player)
1435 end
1436 end
1437
1438
1439 -- npc, find closest monster to attack
1440 local npc_attack = function(self)
1441
1442 if self.type ~= "npc"
1443 or not self.attacks_monsters
1444 or self.state == "attack" then
1445 return
1446 end
1447
1448 local p, sp, obj, min_player, dist
1449 local s = self.object:get_pos()
1450 local min_dist = self.view_range + 1
1451 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1452
1453 for n = 1, #objs do
1454
1455 obj = objs[n]:get_luaentity()
1456
1457 if obj and obj.type == "monster" then
1458
1459 p = obj.object:get_pos()
1460 sp = s
1461
1462 dist = get_distance(p, s)
1463
1464 -- aim higher to make looking up hills more realistic
1465 p.y = p.y + 1
1466 sp.y = sp.y + 1
1467
1468 if dist < min_dist
1469 and line_of_sight(self, sp, p, 2) == true then
1470 min_dist = dist
1471 min_player = obj.object
1472 end
1473 end
1474 end
1475
1476 if min_player then
1477 do_attack(self, min_player)
1478 end
1479 end
1480
1481
1482 -- specific runaway
1483 local specific_runaway = function(list, what)
1484
1485 -- no list so do not run
1486 if list == nil then
1487 return false
1488 end
1489
1490 -- found entity on list to attack?
1491 for no = 1, #list do
1492
1493 if list[no] == what then
1494 return true
1495 end
1496 end
1497
1498 return false
1499 end
1500
1501
1502 -- find someone to runaway from
1503 local runaway_from = function(self)
1504
1505 if not self.runaway_from then
1506 return
1507 end
1508
1509 local s = self.object:get_pos()
1510 local p, sp, dist
1511 local player, obj, min_player
1512 local type, name = "", ""
1513 local min_dist = self.view_range + 1
1514 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1515
1516 for n = 1, #objs do
1517
1518 if objs[n]:is_player() then
1519
1520 if mobs.invis[ objs[n]:get_player_name() ]
1521 or self.owner == objs[n]:get_player_name() then
1522
1523 type = ""
1524 else
1525 player = objs[n]
1526 type = "player"
1527 name = "player"
1528 end
1529 else
1530 obj = objs[n]:get_luaentity()
1531
1532 if obj then
1533 player = obj.object
1534 type = obj.type
1535 name = obj.name or ""
1536 end
1537 end
1538
1539 -- find specific mob to runaway from
1540 if name ~= "" and name ~= self.name
1541 and specific_runaway(self.runaway_from, name) then
1542
1543 p = player:get_pos()
1544 sp = s
1545
1546 -- aim higher to make looking up hills more realistic
1547 p.y = p.y + 1
1548 sp.y = sp.y + 1
1549
1550 dist = get_distance(p, s)
1551
1552
1553 -- choose closest player/mpb to runaway from
1554 if dist < min_dist
1555 and line_of_sight(self, sp, p, 2) == true then
1556 min_dist = dist
1557 min_player = player
1558 end
1559 end
1560 end
1561
1562 if min_player then
1563
1564 local lp = player:get_pos()
1565 local vec = {
1566 x = lp.x - s.x,
1567 y = lp.y - s.y,
1568 z = lp.z - s.z
1569 }
1570
1571 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1572
1573 if lp.x > s.x then
1574 yaw = yaw + pi
1575 end
1576
1577 yaw = set_yaw(self, yaw, 4)
1578 self.state = "runaway"
1579 self.runaway_timer = 3
1580 self.following = nil
1581 end
1582 end
1583
1584
1585 -- follow player if owner or holding item, if fish outta water then flop
1586 local follow_flop = function(self)
1587
1588 -- find player to follow
1589 if (self.follow ~= ""
1590 or self.order == "follow")
1591 and not self.following
1592 and self.state ~= "attack"
1593 and self.state ~= "runaway" then
1594
1595 local s = self.object:get_pos()
1596 local players = minetest.get_connected_players()
1597
1598 for n = 1, #players do
1599
1600 if get_distance(players[n]:get_pos(), s) < self.view_range
1601 and not mobs.invis[ players[n]:get_player_name() ] then
1602
1603 self.following = players[n]
1604
1605 break
1606 end
1607 end
1608 end
1609
1610 if self.type == "npc"
1611 and self.order == "follow"
1612 and self.state ~= "attack"
1613 and self.owner ~= "" then
1614
1615 -- npc stop following player if not owner
1616 if self.following
1617 and self.owner
1618 and self.owner ~= self.following:get_player_name() then
1619 self.following = nil
1620 end
1621 else
1622 -- stop following player if not holding specific item
1623 if self.following
1624 and self.following:is_player()
1625 and follow_holding(self, self.following) == false then
1626 self.following = nil
1627 end
1628
1629 end
1630
1631 -- follow that thing
1632 if self.following then
1633
1634 local s = self.object:get_pos()
1635 local p
1636
1637 if self.following:is_player() then
1638
1639 p = self.following:get_pos()
1640
1641 elseif self.following.object then
1642
1643 p = self.following.object:get_pos()
1644 end
1645
1646 if p then
1647
1648 local dist = get_distance(p, s)
1649
1650 -- dont follow if out of range
1651 if dist > self.view_range then
1652 self.following = nil
1653 else
1654 local vec = {
1655 x = p.x - s.x,
1656 z = p.z - s.z
1657 }
1658
1659 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1660
1661 if p.x > s.x then yaw = yaw + pi end
1662
1663 yaw = set_yaw(self, yaw, 6)
1664
1665 -- anyone but standing npc's can move along
1666 if dist > self.reach
1667 and self.order ~= "stand" then
1668
1669 set_velocity(self, self.walk_velocity)
1670
1671 if self.walk_chance ~= 0 then
1672 set_animation(self, "walk")
1673 end
1674 else
1675 set_velocity(self, 0)
1676 set_animation(self, "stand")
1677 end
1678
1679 return
1680 end
1681 end
1682 end
1683
1684 -- swimmers flop when out of their element, and swim again when back in
1685 if self.fly then
1686 local s = self.object:get_pos()
1687 if not flight_check(self, s) then
1688
1689 self.state = "flop"
1690 self.object:setvelocity({x = 0, y = -5, z = 0})
1691
1692 set_animation(self, "stand")
1693
1694 return
1695 elseif self.state == "flop" then
1696 self.state = "stand"
1697 end
1698 end
1699 end
1700
1701
1702 -- dogshoot attack switch and counter function
1703 local dogswitch = function(self, dtime)
1704
1705 -- switch mode not activated
1706 if not self.dogshoot_switch
1707 or not dtime then
1708 return 0
1709 end
1710
1711 self.dogshoot_count = self.dogshoot_count + dtime
1712
1713 if (self.dogshoot_switch == 1
1714 and self.dogshoot_count > self.dogshoot_count_max)
1715 or (self.dogshoot_switch == 2
1716 and self.dogshoot_count > self.dogshoot_count2_max) then
1717
1718 self.dogshoot_count = 0
1719
1720 if self.dogshoot_switch == 1 then
1721 self.dogshoot_switch = 2
1722 else
1723 self.dogshoot_switch = 1
1724 end
1725 end
1726
1727 return self.dogshoot_switch
1728 end
1729
1730
1731 -- execute current state (stand, walk, run, attacks)
1732 local do_states = function(self, dtime)
1733
1734 local yaw = self.object:get_yaw() or 0
1735
1736 if self.state == "stand" then
1737
1738 if random(1, 4) == 1 then
1739
1740 local lp = nil
1741 local s = self.object:get_pos()
1742 local objs = minetest.get_objects_inside_radius(s, 3)
1743
1744 for n = 1, #objs do
1745
1746 if objs[n]:is_player() then
1747 lp = objs[n]:get_pos()
1748 break
1749 end
1750 end
1751
1752 -- look at any players nearby, otherwise turn randomly
1753 if lp then
1754
1755 local vec = {
1756 x = lp.x - s.x,
1757 z = lp.z - s.z
1758 }
1759
1760 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1761
1762 if lp.x > s.x then yaw = yaw + pi end
1763 else
1764 yaw = yaw + random(-0.5, 0.5)
1765 end
1766
1767 yaw = set_yaw(self, yaw, 8)
1768 end
1769
1770 set_velocity(self, 0)
1771 set_animation(self, "stand")
1772
1773 -- npc's ordered to stand stay standing
1774 if self.type ~= "npc"
1775 or self.order ~= "stand" then
1776
1777 if self.walk_chance ~= 0
1778 and self.facing_fence ~= true
1779 and random(1, 100) <= self.walk_chance
1780 and is_at_cliff(self) == false then
1781
1782 set_velocity(self, self.walk_velocity)
1783 self.state = "walk"
1784 set_animation(self, "walk")
1785
1786 --[[ fly up/down randomly for flying mobs
1787 if self.fly and random(1, 100) <= self.walk_chance then
1788
1789 local v = self.object:getvelocity()
1790 local ud = random(-1, 2) / 9
1791
1792 self.object:setvelocity({x = v.x, y = ud, z = v.z})
1793 end--]]
1794 end
1795 end
1796
1797 elseif self.state == "walk" then
1798
1799 local s = self.object:get_pos()
1800 local lp = nil
1801
1802 -- is there something I need to avoid?
1803 if self.water_damage > 0
1804 and self.lava_damage > 0 then
1805
1806 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
1807
1808 elseif self.water_damage > 0 then
1809
1810 lp = minetest.find_node_near(s, 1, {"group:water"})
1811
1812 elseif self.lava_damage > 0 then
1813
1814 lp = minetest.find_node_near(s, 1, {"group:lava"})
1815 end
1816
1817 if lp then
1818
1819 -- if mob in water or lava then look for land
1820 if (self.lava_damage
1821 and minetest.registered_nodes[self.standing_in].groups.lava)
1822 or (self.water_damage
1823 and minetest.registered_nodes[self.standing_in].groups.water) then
1824
1825 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1826 "group:sand", node_ice, node_snowblock})
1827
1828 -- did we find land?
1829 if lp then
1830
1831 local vec = {
1832 x = lp.x - s.x,
1833 z = lp.z - s.z
1834 }
1835
1836 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1837
1838 if lp.x > s.x then yaw = yaw + pi end
1839
1840 -- look towards land and jump/move in that direction
1841 yaw = set_yaw(self, yaw, 6)
1842 do_jump(self)
1843 set_velocity(self, self.walk_velocity)
1844 else
1845 yaw = yaw + random(-0.5, 0.5)
1846 end
1847
1848 else
1849
1850 local vec = {
1851 x = lp.x - s.x,
1852 z = lp.z - s.z
1853 }
1854
1855 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1856
1857 if lp.x > s.x then yaw = yaw + pi end
1858 end
1859
1860 yaw = set_yaw(self, yaw, 8)
1861
1862 -- otherwise randomly turn
1863 elseif random(1, 100) <= 30 then
1864
1865 yaw = yaw + random(-0.5, 0.5)
1866
1867 yaw = set_yaw(self, yaw, 8)
1868 end
1869
1870 -- stand for great fall in front
1871 local temp_is_cliff = is_at_cliff(self)
1872
1873 if self.facing_fence == true
1874 or temp_is_cliff
1875 or random(1, 100) <= 30 then
1876
1877 set_velocity(self, 0)
1878 self.state = "stand"
1879 set_animation(self, "stand")
1880 else
1881 set_velocity(self, self.walk_velocity)
1882
1883 if flight_check(self)
1884 and self.animation
1885 and self.animation.fly_start
1886 and self.animation.fly_end then
1887 set_animation(self, "fly")
1888 else
1889 set_animation(self, "walk")
1890 end
1891 end
1892
1893 -- runaway when punched
1894 elseif self.state == "runaway" then
1895
1896 self.runaway_timer = self.runaway_timer + 1
1897
1898 -- stop after 5 seconds or when at cliff
1899 if self.runaway_timer > 5
1900 or is_at_cliff(self) then
1901 self.runaway_timer = 0
1902 set_velocity(self, 0)
1903 self.state = "stand"
1904 set_animation(self, "stand")
1905 else
1906 set_velocity(self, self.run_velocity)
1907 set_animation(self, "walk")
1908 end
1909
1910 -- attack routines (explode, dogfight, shoot, dogshoot)
1911 elseif self.state == "attack" then
1912
1913 -- calculate distance from mob and enemy
1914 local s = self.object:get_pos()
1915 local p = self.attack:get_pos() or s
1916 local dist = get_distance(p, s)
1917
1918 -- stop attacking if player invisible or out of range
1919 if dist > self.view_range
1920 or not self.attack
1921 or not self.attack:get_pos()
1922 or self.attack:get_hp() <= 0
1923 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1924
1925 -- print(" ** stop attacking **", dist, self.view_range)
1926 self.state = "stand"
1927 set_velocity(self, 0)
1928 set_animation(self, "stand")
1929 self.attack = nil
1930 self.v_start = false
1931 self.timer = 0
1932 self.blinktimer = 0
1933 self.path.way = nil
1934
1935 return
1936 end
1937
1938 if self.attack_type == "explode" then
1939
1940 local vec = {
1941 x = p.x - s.x,
1942 z = p.z - s.z
1943 }
1944
1945 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1946
1947 if p.x > s.x then yaw = yaw + pi end
1948
1949 yaw = set_yaw(self, yaw)
1950
1951 local node_break_radius = self.explosion_radius or 1
1952 local entity_damage_radius = self.explosion_damage_radius
1953 or (node_break_radius * 2)
1954
1955 -- start timer when in reach and line of sight
1956 if not self.v_start
1957 and dist <= self.reach
1958 and line_of_sight(self, s, p, 2) then
1959
1960 self.v_start = true
1961 self.timer = 0
1962 self.blinktimer = 0
1963 mob_sound(self, self.sounds.fuse)
1964 -- print ("=== explosion timer started", self.explosion_timer)
1965
1966 -- stop timer if out of reach or direct line of sight
1967 elseif self.allow_fuse_reset
1968 and self.v_start
1969 and (dist > self.reach
1970 or not line_of_sight(self, s, p, 2)) then
1971 self.v_start = false
1972 self.timer = 0
1973 self.blinktimer = 0
1974 self.blinkstatus = false
1975 self.object:settexturemod("")
1976 end
1977
1978 -- walk right up to player unless the timer is active
1979 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1980 set_velocity(self, 0)
1981 else
1982 set_velocity(self, self.run_velocity)
1983 end
1984
1985 if self.animation and self.animation.run_start then
1986 set_animation(self, "run")
1987 else
1988 set_animation(self, "walk")
1989 end
1990
1991 if self.v_start then
1992
1993 self.timer = self.timer + dtime
1994 self.blinktimer = (self.blinktimer or 0) + dtime
1995
1996 if self.blinktimer > 0.2 then
1997
1998 self.blinktimer = 0
1999
2000 if self.blinkstatus then
2001 self.object:settexturemod("")
2002 else
2003 self.object:settexturemod("^[brighten")
2004 end
2005
2006 self.blinkstatus = not self.blinkstatus
2007 end
2008
2009 -- print ("=== explosion timer", self.timer)
2010
2011 if self.timer > self.explosion_timer then
2012
2013 local pos = self.object:get_pos()
2014
2015 -- dont damage anything if area protected or next to water
2016 if minetest.find_node_near(pos, 1, {"group:water"})
2017 or minetest.is_protected(pos, "") then
2018
2019 node_break_radius = 1
2020 end
2021
2022 self.object:remove()
2023
2024 if minetest.get_modpath("tnt") and tnt and tnt.boom
2025 and not minetest.is_protected(pos, "") then
2026
2027 tnt.boom(pos, {
2028 radius = node_break_radius,
2029 damage_radius = entity_damage_radius,
2030 sound = self.sounds.explode,
2031 })
2032 else
2033
2034 minetest.sound_play(self.sounds.explode, {
2035 pos = pos,
2036 gain = 1.0,
2037 max_hear_distance = self.sounds.distance or 32
2038 })
2039
2040 entity_physics(pos, entity_damage_radius)
2041 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2042 end
2043
2044 return
2045 end
2046 end
2047
2048 elseif self.attack_type == "dogfight"
2049 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2050 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2051
2052 if self.fly
2053 and dist > self.reach then
2054
2055 local p1 = s
2056 local me_y = floor(p1.y)
2057 local p2 = p
2058 local p_y = floor(p2.y + 1)
2059 local v = self.object:getvelocity()
2060
2061 if flight_check(self, s) then
2062
2063 if me_y < p_y then
2064
2065 self.object:setvelocity({
2066 x = v.x,
2067 y = 1 * self.walk_velocity,
2068 z = v.z
2069 })
2070
2071 elseif me_y > p_y then
2072
2073 self.object:setvelocity({
2074 x = v.x,
2075 y = -1 * self.walk_velocity,
2076 z = v.z
2077 })
2078 end
2079 else
2080 if me_y < p_y then
2081
2082 self.object:setvelocity({
2083 x = v.x,
2084 y = 0.01,
2085 z = v.z
2086 })
2087
2088 elseif me_y > p_y then
2089
2090 self.object:setvelocity({
2091 x = v.x,
2092 y = -0.01,
2093 z = v.z
2094 })
2095 end
2096 end
2097
2098 end
2099
2100 -- rnd: new movement direction
2101 if self.path.following
2102 and self.path.way
2103 and self.attack_type ~= "dogshoot" then
2104
2105 -- no paths longer than 50
2106 if #self.path.way > 50
2107 or dist < self.reach then
2108 self.path.following = false
2109 return
2110 end
2111
2112 local p1 = self.path.way[1]
2113
2114 if not p1 then
2115 self.path.following = false
2116 return
2117 end
2118
2119 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2120 -- reached waypoint, remove it from queue
2121 table.remove(self.path.way, 1)
2122 end
2123
2124 -- set new temporary target
2125 p = {x = p1.x, y = p1.y, z = p1.z}
2126 end
2127
2128 local vec = {
2129 x = p.x - s.x,
2130 z = p.z - s.z
2131 }
2132
2133 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2134
2135 if p.x > s.x then yaw = yaw + pi end
2136
2137 yaw = set_yaw(self, yaw)
2138
2139 -- move towards enemy if beyond mob reach
2140 if dist > self.reach then
2141
2142 -- path finding by rnd
2143 if self.pathfinding -- only if mob has pathfinding enabled
2144 and enable_pathfinding then
2145
2146 smart_mobs(self, s, p, dist, dtime)
2147 end
2148
2149 if is_at_cliff(self) then
2150
2151 set_velocity(self, 0)
2152 set_animation(self, "stand")
2153 else
2154
2155 if self.path.stuck then
2156 set_velocity(self, self.walk_velocity)
2157 else
2158 set_velocity(self, self.run_velocity)
2159 end
2160
2161 if self.animation and self.animation.run_start then
2162 set_animation(self, "run")
2163 else
2164 set_animation(self, "walk")
2165 end
2166 end
2167
2168 else -- rnd: if inside reach range
2169
2170 self.path.stuck = false
2171 self.path.stuck_timer = 0
2172 self.path.following = false -- not stuck anymore
2173
2174 set_velocity(self, 0)
2175
2176 if not self.custom_attack then
2177
2178 if self.timer > 1 then
2179
2180 self.timer = 0
2181
2182 if self.double_melee_attack
2183 and random(1, 2) == 1 then
2184 set_animation(self, "punch2")
2185 else
2186 set_animation(self, "punch")
2187 end
2188
2189 local p2 = p
2190 local s2 = s
2191
2192 p2.y = p2.y + .5
2193 s2.y = s2.y + .5
2194
2195 if line_of_sight(self, p2, s2) == true then
2196
2197 -- play attack sound
2198 mob_sound(self, self.sounds.attack)
2199
2200 -- punch player (or what player is attached to)
2201 local attached = self.attack:get_attach()
2202 if attached then
2203 self.attack = attached
2204 end
2205 self.attack:punch(self.object, 1.0, {
2206 full_punch_interval = 1.0,
2207 damage_groups = {fleshy = self.damage}
2208 }, nil)
2209 end
2210 end
2211 else -- call custom attack every second
2212 if self.custom_attack
2213 and self.timer > 1 then
2214
2215 self.timer = 0
2216
2217 self.custom_attack(self, p)
2218 end
2219 end
2220 end
2221
2222 elseif self.attack_type == "shoot"
2223 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2224 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2225
2226 p.y = p.y - .5
2227 s.y = s.y + .5
2228
2229 local dist = get_distance(p, s)
2230 local vec = {
2231 x = p.x - s.x,
2232 y = p.y - s.y,
2233 z = p.z - s.z
2234 }
2235
2236 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2237
2238 if p.x > s.x then yaw = yaw + pi end
2239
2240 yaw = set_yaw(self, yaw)
2241
2242 set_velocity(self, 0)
2243
2244 if self.shoot_interval
2245 and self.timer > self.shoot_interval
2246 and random(1, 100) <= 60 then
2247
2248 self.timer = 0
2249 set_animation(self, "shoot")
2250
2251 -- play shoot attack sound
2252 mob_sound(self, self.sounds.shoot_attack)
2253
2254 local p = self.object:get_pos()
2255
2256 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
2257
2258 if minetest.registered_entities[self.arrow] then
2259
2260 local obj = minetest.add_entity(p, self.arrow)
2261 local ent = obj:get_luaentity()
2262 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2263 local v = ent.velocity or 1 -- or set to default
2264
2265 ent.switch = 1
2266 ent.owner_id = tostring(self.object) -- add unique owner id to arrow
2267
2268 -- offset makes shoot aim accurate
2269 vec.y = vec.y + self.shoot_offset
2270 vec.x = vec.x * (v / amount)
2271 vec.y = vec.y * (v / amount)
2272 vec.z = vec.z * (v / amount)
2273
2274 obj:setvelocity(vec)
2275 end
2276 end
2277 end
2278 end
2279 end
2280
2281
2282 -- falling and fall damage
2283 local falling = function(self, pos)
2284
2285 if self.fly then
2286 return
2287 end
2288
2289 -- floating in water (or falling)
2290 local v = self.object:getvelocity()
2291
2292 if v.y > 0 then
2293
2294 -- apply gravity when moving up
2295 self.object:setacceleration({
2296 x = 0,
2297 y = -10,
2298 z = 0
2299 })
2300
2301 elseif v.y <= 0 and v.y > self.fall_speed then
2302
2303 -- fall downwards at set speed
2304 self.object:setacceleration({
2305 x = 0,
2306 y = self.fall_speed,
2307 z = 0
2308 })
2309 else
2310 -- stop accelerating once max fall speed hit
2311 self.object:setacceleration({x = 0, y = 0, z = 0})
2312 end
2313
2314 -- in water then float up
2315 if minetest.registered_nodes[self.standing_in].groups.water then
2316
2317 if self.floats == 1 then
2318
2319 self.object:setacceleration({
2320 x = 0,
2321 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2322 z = 0
2323 })
2324 end
2325 else
2326
2327 -- fall damage onto solid ground
2328 if self.fall_damage == 1
2329 and self.object:getvelocity().y == 0 then
2330
2331 local d = (self.old_y or 0) - self.object:get_pos().y
2332
2333 if d > 5 then
2334
2335 self.health = self.health - floor(d - 5)
2336
2337 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
2338
2339 if check_for_death(self, "fall", {type = "fall"}) then
2340 return
2341 end
2342 end
2343
2344 self.old_y = self.object:get_pos().y
2345 end
2346 end
2347 end
2348
2349
2350 -- deal damage and effects when mob punched
2351 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2352
2353 -- custom punch function
2354 if self.do_punch then
2355
2356 -- when false skip going any further
2357 if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2358 return
2359 end
2360 end
2361
2362 -- mob health check
2363 -- if self.health <= 0 then
2364 -- return
2365 -- end
2366
2367 -- error checking when mod profiling is enabled
2368 if not tool_capabilities then
2369 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2370 return
2371 end
2372
2373 -- is mob protected?
2374 if self.protected and hitter:is_player()
2375 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2376 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2377 return
2378 end
2379
2380
2381 -- weapon wear
2382 local weapon = hitter:get_wielded_item()
2383 local punch_interval = 1.4
2384
2385 -- calculate mob damage
2386 local damage = 0
2387 local armor = self.object:get_armor_groups() or {}
2388 local tmp
2389
2390 -- quick error check incase it ends up 0 (serialize.h check test)
2391 if tflp == 0 then
2392 tflp = 0.2
2393 end
2394
2395 if use_cmi then
2396 damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
2397 else
2398
2399 for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
2400
2401 tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
2402
2403 if tmp < 0 then
2404 tmp = 0.0
2405 elseif tmp > 1 then
2406 tmp = 1.0
2407 end
2408
2409 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2410 * tmp * ((armor[group] or 0) / 100.0)
2411 end
2412 end
2413
2414 -- check for tool immunity or special damage
2415 for n = 1, #self.immune_to do
2416
2417 if self.immune_to[n][1] == weapon:get_name() then
2418
2419 damage = self.immune_to[n][2] or 0
2420 break
2421
2422 -- if "all" then no tool does damage unless it's specified in list
2423 elseif self.immune_to[n][1] == "all" then
2424 damage = self.immune_to[n][2] or 0
2425 end
2426 end
2427
2428 -- healing
2429 if damage <= -1 then
2430 self.health = self.health - floor(damage)
2431 return
2432 end
2433
2434 -- print ("Mob Damage is", damage)
2435
2436 if use_cmi then
2437
2438 local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage)
2439
2440 if cancel then return end
2441 end
2442
2443 -- add weapon wear
2444 if tool_capabilities then
2445 punch_interval = tool_capabilities.full_punch_interval or 1.4
2446 end
2447
2448 if weapon:get_definition()
2449 and weapon:get_definition().tool_capabilities then
2450
2451 weapon:add_wear(floor((punch_interval / 75) * 9000))
2452 hitter:set_wielded_item(weapon)
2453 end
2454
2455 -- only play hit sound and show blood effects if damage is 1 or over
2456 if damage >= 1 then
2457
2458 -- weapon sounds
2459 if weapon:get_definition().sounds ~= nil then
2460
2461 local s = random(0, #weapon:get_definition().sounds)
2462
2463 minetest.sound_play(weapon:get_definition().sounds[s], {
2464 object = self.object, --hitter,
2465 max_hear_distance = 8
2466 })
2467 else
2468 minetest.sound_play("default_punch", {
2469 object = self.object, --hitter,
2470 max_hear_distance = 5
2471 })
2472 end
2473
2474 -- blood_particles
2475 if self.blood_amount > 0
2476 and not disable_blood then
2477
2478 local pos = self.object:get_pos()
2479
2480 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2481
2482 -- do we have a single blood texture or multiple?
2483 if type(self.blood_texture) == "table" then
2484
2485 local blood = self.blood_texture[random(1, #self.blood_texture)]
2486
2487 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2488 else
2489 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2490 end
2491 end
2492
2493 -- do damage
2494 self.health = self.health - floor(damage)
2495
2496 -- exit here if dead, special item check
2497 if weapon:get_name() == "mobs:pick_lava" then
2498 if check_for_death(self, "lava", {type = "punch",
2499 puncher = hitter}) then
2500 return
2501 end
2502 else
2503 if check_for_death(self, "hit", {type = "punch",
2504 puncher = hitter}) then
2505 return
2506 end
2507 end
2508
2509 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2510 minetest.after(0.1, function()
2511
2512 if not self.object:get_luaentity() then return end
2513
2514 self.object:settexturemod("^[colorize:#c9900070")
2515
2516 core.after(0.3, function()
2517 self.object:settexturemod("")
2518 end)
2519 end) ]]
2520
2521 -- knock back effect (only on full punch)
2522 if self.knock_back
2523 and tflp >= punch_interval then
2524
2525 local v = self.object:getvelocity()
2526 local r = 1.4 - min(punch_interval, 1.4)
2527 local kb = r * 5
2528 local up = 2
2529
2530 -- if already in air then dont go up anymore when hit
2531 if v.y > 0
2532 or self.fly then
2533 up = 0
2534 end
2535
2536 -- direction error check
2537 dir = dir or {x = 0, y = 0, z = 0}
2538
2539 -- check if tool already has specific knockback value
2540 if tool_capabilities.damage_groups["knockback"] then
2541 kb = tool_capabilities.damage_groups["knockback"]
2542 else
2543 kb = kb * 1.5
2544 end
2545
2546 self.object:setvelocity({
2547 x = dir.x * kb,
2548 y = up,
2549 z = dir.z * kb
2550 })
2551
2552 self.pause_timer = 0.25
2553 end
2554 end -- END if damage
2555
2556 -- if skittish then run away
2557 if self.runaway == true then
2558
2559 local lp = hitter:get_pos()
2560 local s = self.object:get_pos()
2561 local vec = {
2562 x = lp.x - s.x,
2563 y = lp.y - s.y,
2564 z = lp.z - s.z
2565 }
2566
2567 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2568
2569 if lp.x > s.x then
2570 yaw = yaw + pi
2571 end
2572
2573 yaw = set_yaw(self, yaw, 6)
2574 self.state = "runaway"
2575 self.runaway_timer = 0
2576 self.following = nil
2577 end
2578
2579 local name = hitter:get_player_name() or ""
2580
2581 -- attack puncher and call other mobs for help
2582 if self.passive == false
2583 and self.state ~= "flop"
2584 and self.child == false
2585 and hitter:get_player_name() ~= self.owner
2586 and not mobs.invis[ name ] then
2587
2588 -- attack whoever punched mob
2589 self.state = ""
2590 do_attack(self, hitter)
2591
2592 -- alert others to the attack
2593 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2594 local obj = nil
2595
2596 for n = 1, #objs do
2597
2598 obj = objs[n]:get_luaentity()
2599
2600 if obj then
2601
2602 -- only alert members of same mob
2603 if obj.group_attack == true
2604 and obj.state ~= "attack"
2605 and obj.owner ~= name
2606 and obj.name == self.name then
2607 do_attack(obj, hitter)
2608 end
2609
2610 -- have owned mobs attack player threat
2611 if obj.owner == name and obj.owner_loyal then
2612 do_attack(obj, self.object)
2613 end
2614 end
2615 end
2616 end
2617 end
2618
2619
2620 -- get entity staticdata
2621 local mob_staticdata = function(self)
2622
2623 -- remove mob when out of range unless tamed
2624 if remove_far
2625 and self.remove_ok
2626 and self.type ~= "npc"
2627 and self.state ~= "attack"
2628 and not self.tamed
2629 and self.lifetimer < 20000 then
2630
2631 --print ("REMOVED " .. self.name)
2632
2633 self.object:remove()
2634
2635 return ""-- nil
2636 end
2637
2638 self.remove_ok = true
2639 self.attack = nil
2640 self.following = nil
2641 self.state = "stand"
2642
2643 -- used to rotate older mobs
2644 if self.drawtype
2645 and self.drawtype == "side" then
2646 self.rotate = math.rad(90)
2647 end
2648
2649 if use_cmi then
2650 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2651 end
2652
2653 local tmp = {}
2654
2655 for _,stat in pairs(self) do
2656
2657 local t = type(stat)
2658
2659 if t ~= "function"
2660 and t ~= "nil"
2661 and t ~= "userdata"
2662 and _ ~= "_cmi_components" then
2663 tmp[_] = self[_]
2664 end
2665 end
2666
2667 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
2668 return minetest.serialize(tmp)
2669 end
2670
2671
2672 -- activate mob and reload settings
2673 local mob_activate = function(self, staticdata, def, dtime)
2674
2675 -- remove monsters in peaceful mode
2676 if self.type == "monster"
2677 and peaceful_only then
2678
2679 self.object:remove()
2680
2681 return
2682 end
2683
2684 -- load entity variables
2685 local tmp = minetest.deserialize(staticdata)
2686
2687 if tmp then
2688 for _,stat in pairs(tmp) do
2689 self[_] = stat
2690 end
2691 end
2692
2693 -- select random texture, set model and size
2694 if not self.base_texture then
2695
2696 -- compatiblity with old simple mobs textures
2697 if type(def.textures[1]) == "string" then
2698 def.textures = {def.textures}
2699 end
2700
2701 self.base_texture = def.textures[random(1, #def.textures)]
2702 self.base_mesh = def.mesh
2703 self.base_size = self.visual_size
2704 self.base_colbox = self.collisionbox
2705 self.base_selbox = self.selectionbox
2706 end
2707
2708 -- for current mobs that dont have this set
2709 if not self.base_selbox then
2710 self.base_selbox = self.selectionbox or self.base_colbox
2711 end
2712
2713 -- set texture, model and size
2714 local textures = self.base_texture
2715 local mesh = self.base_mesh
2716 local vis_size = self.base_size
2717 local colbox = self.base_colbox
2718 local selbox = self.base_selbox
2719
2720 -- specific texture if gotten
2721 if self.gotten == true
2722 and def.gotten_texture then
2723 textures = def.gotten_texture
2724 end
2725
2726 -- specific mesh if gotten
2727 if self.gotten == true
2728 and def.gotten_mesh then
2729 mesh = def.gotten_mesh
2730 end
2731
2732 -- set child objects to half size
2733 if self.child == true then
2734
2735 vis_size = {
2736 x = self.base_size.x * .5,
2737 y = self.base_size.y * .5,
2738 }
2739
2740 if def.child_texture then
2741 textures = def.child_texture[1]
2742 end
2743
2744 colbox = {
2745 self.base_colbox[1] * .5,
2746 self.base_colbox[2] * .5,
2747 self.base_colbox[3] * .5,
2748 self.base_colbox[4] * .5,
2749 self.base_colbox[5] * .5,
2750 self.base_colbox[6] * .5
2751 }
2752 selbox = {
2753 self.base_selbox[1] * .5,
2754 self.base_selbox[2] * .5,
2755 self.base_selbox[3] * .5,
2756 self.base_selbox[4] * .5,
2757 self.base_selbox[5] * .5,
2758 self.base_selbox[6] * .5
2759 }
2760 end
2761
2762 if self.health == 0 then
2763 self.health = random (self.hp_min, self.hp_max)
2764 end
2765
2766 -- pathfinding init
2767 self.path = {}
2768 self.path.way = {} -- path to follow, table of positions
2769 self.path.lastpos = {x = 0, y = 0, z = 0}
2770 self.path.stuck = false
2771 self.path.following = false -- currently following path?
2772 self.path.stuck_timer = 0 -- if stuck for too long search for path
2773
2774 -- mob defaults
2775 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
2776 self.old_y = self.object:get_pos().y
2777 self.old_health = self.health
2778 self.sounds.distance = self.sounds.distance or 10
2779 self.textures = textures
2780 self.mesh = mesh
2781 self.collisionbox = colbox
2782 self.selectionbox = selbox
2783 self.visual_size = vis_size
2784 self.standing_in = "air"
2785
2786 -- check existing nametag
2787 if not self.nametag then
2788 self.nametag = def.nametag
2789 end
2790
2791 -- set anything changed above
2792 self.object:set_properties(self)
2793 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2794 update_tag(self)
2795 set_animation(self, "stand")
2796
2797 -- run on_spawn function if found
2798 if self.on_spawn and not self.on_spawn_run then
2799 if self.on_spawn(self) then
2800 self.on_spawn_run = true -- if true, set flag to run once only
2801 end
2802 end
2803
2804 -- run after_activate
2805 if def.after_activate then
2806 def.after_activate(self, staticdata, def, dtime)
2807 end
2808
2809 if use_cmi then
2810 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
2811 cmi.notify_activate(self.object, dtime)
2812 end
2813 end
2814
2815
2816 -- main mob function
2817 local mob_step = function(self, dtime)
2818
2819 if use_cmi then
2820 cmi.notify_step(self.object, dtime)
2821 end
2822
2823 local pos = self.object:get_pos()
2824 local yaw = 0
2825
2826 -- when lifetimer expires remove mob (except npc and tamed)
2827 if self.type ~= "npc"
2828 and not self.tamed
2829 and self.state ~= "attack"
2830 and remove_far ~= true
2831 and self.lifetimer < 20000 then
2832
2833 self.lifetimer = self.lifetimer - dtime
2834
2835 if self.lifetimer <= 0 then
2836
2837 -- only despawn away from player
2838 local objs = minetest.get_objects_inside_radius(pos, 15)
2839
2840 for n = 1, #objs do
2841
2842 if objs[n]:is_player() then
2843
2844 self.lifetimer = 20
2845
2846 return
2847 end
2848 end
2849
2850 -- minetest.log("action",
2851 -- S("lifetimer expired, removed @1", self.name))
2852
2853 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
2854
2855 self.object:remove()
2856
2857 return
2858 end
2859 end
2860
2861 -- get node at foot level every quarter second
2862 self.node_timer = (self.node_timer or 0) + dtime
2863
2864 if self.node_timer > 0.25 then
2865
2866 self.node_timer = 0
2867
2868 local y_level = self.collisionbox[2]
2869
2870 if self.child then
2871 y_level = self.collisionbox[2] * 0.5
2872 end
2873
2874 -- what is mob standing in?
2875 self.standing_in = node_ok({
2876 x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
2877 -- print ("standing in " .. self.standing_in)
2878 end
2879
2880 -- check if falling, flying, floating
2881 falling(self, pos)
2882
2883 -- smooth rotation by ThomasMonroe314
2884
2885 if self.delay and self.delay > 0 then
2886
2887 local yaw = self.object:get_yaw()
2888
2889 if self.delay == 1 then
2890 yaw = self.target_yaw
2891 else
2892 local dif = abs(yaw - self.target_yaw)
2893
2894 if yaw > self.target_yaw then
2895
2896 if dif > pi then
2897 dif = 2 * pi - dif -- need to add
2898 yaw = yaw + dif / self.delay
2899 else
2900 yaw = yaw - dif / self.delay -- need to subtract
2901 end
2902
2903 elseif yaw < self.target_yaw then
2904
2905 if dif > pi then
2906 dif = 2 * pi - dif
2907 yaw = yaw - dif / self.delay -- need to subtract
2908 else
2909 yaw = yaw + dif / self.delay -- need to add
2910 end
2911 end
2912
2913 if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
2914 if yaw < 0 then yaw = yaw + (pi * 2) end
2915 end
2916
2917 self.delay = self.delay - 1
2918 self.object:set_yaw(yaw)
2919 end
2920
2921 -- end rotation
2922
2923 -- knockback timer
2924 if self.pause_timer > 0 then
2925
2926 self.pause_timer = self.pause_timer - dtime
2927
2928 return
2929 end
2930
2931 -- run custom function (defined in mob lua file)
2932 if self.do_custom then
2933
2934 -- when false skip going any further
2935 if self.do_custom(self, dtime) == false then
2936 return
2937 end
2938 end
2939
2940 -- attack timer
2941 self.timer = self.timer + dtime
2942
2943 if self.state ~= "attack" then
2944
2945 if self.timer < 1 then
2946 return
2947 end
2948
2949 self.timer = 0
2950 end
2951
2952 -- never go over 100
2953 if self.timer > 100 then
2954 self.timer = 1
2955 end
2956
2957 -- mob plays random sound at times
2958 if random(1, 100) == 1 then
2959 mob_sound(self, self.sounds.random)
2960 end
2961
2962 -- environmental damage timer (every 1 second)
2963 self.env_damage_timer = self.env_damage_timer + dtime
2964
2965 if (self.state == "attack" and self.env_damage_timer > 1)
2966 or self.state ~= "attack" then
2967
2968 self.env_damage_timer = 0
2969
2970 -- check for environmental damage (water, fire, lava etc.)
2971 do_env_damage(self)
2972
2973 -- node replace check (cow eats grass etc.)
2974 replace(self, pos)
2975 end
2976
2977 monster_attack(self)
2978
2979 npc_attack(self)
2980
2981 breed(self)
2982
2983 follow_flop(self)
2984
2985 do_states(self, dtime)
2986
2987 do_jump(self)
2988
2989 runaway_from(self)
2990
2991 end
2992
2993
2994 -- default function when mobs are blown up with TNT
2995 local do_tnt = function(obj, damage)
2996
2997 --print ("----- Damage", damage)
2998
2999 obj.object:punch(obj.object, 1.0, {
3000 full_punch_interval = 1.0,
3001 damage_groups = {fleshy = damage},
3002 }, nil)
3003
3004 return false, true, {}
3005 end
3006
3007
3008 mobs.spawning_mobs = {}
3009
3010 -- register mob entity
3011 function mobs:register_mob(name, def)
3012
3013 mobs.spawning_mobs[name] = true
3014
3015 minetest.register_entity(name, {
3016
3017 stepheight = def.stepheight or 1.1, -- was 0.6
3018 name = name,
3019 type = def.type,
3020 attack_type = def.attack_type,
3021 fly = def.fly,
3022 fly_in = def.fly_in or "air",
3023 owner = def.owner or "",
3024 order = def.order or "",
3025 on_die = def.on_die,
3026 do_custom = def.do_custom,
3027 jump_height = def.jump_height or 4, -- was 6
3028 drawtype = def.drawtype, -- DEPRECATED, use rotate instead
3029 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
3030 lifetimer = def.lifetimer or 180, -- 3 minutes
3031 hp_min = max(1, (def.hp_min or 5) * difficulty),
3032 hp_max = max(1, (def.hp_max or 10) * difficulty),
3033 physical = true,
3034 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3035 selectionbox = def.selectionbox or def.collisionbox,
3036 visual = def.visual,
3037 visual_size = def.visual_size or {x = 1, y = 1},
3038 mesh = def.mesh,
3039 makes_footstep_sound = def.makes_footstep_sound or false,
3040 view_range = def.view_range or 5,
3041 walk_velocity = def.walk_velocity or 1,
3042 run_velocity = def.run_velocity or 2,
3043 damage = max(0, (def.damage or 0) * difficulty),
3044 light_damage = def.light_damage or 0,
3045 water_damage = def.water_damage or 0,
3046 lava_damage = def.lava_damage or 0,
3047 suffocation = def.suffocation or 2,
3048 fall_damage = def.fall_damage or 1,
3049 fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
3050 drops = def.drops or {},
3051 armor = def.armor or 100,
3052 on_rightclick = def.on_rightclick,
3053 arrow = def.arrow,
3054 shoot_interval = def.shoot_interval,
3055 sounds = def.sounds or {},
3056 animation = def.animation,
3057 follow = def.follow,
3058 jump = def.jump ~= false,
3059 walk_chance = def.walk_chance or 50,
3060 attacks_monsters = def.attacks_monsters or false,
3061 group_attack = def.group_attack or false,
3062 passive = def.passive or false,
3063 knock_back = def.knock_back ~= false,
3064 blood_amount = def.blood_amount or 5,
3065 blood_texture = def.blood_texture or "mobs_blood.png",
3066 shoot_offset = def.shoot_offset or 0,
3067 floats = def.floats or 1, -- floats in water by default
3068 replace_rate = def.replace_rate,
3069 replace_what = def.replace_what,
3070 replace_with = def.replace_with,
3071 replace_offset = def.replace_offset or 0,
3072 on_replace = def.on_replace,
3073 timer = 0,
3074 env_damage_timer = 0, -- only used when state = "attack"
3075 tamed = false,
3076 pause_timer = 0,
3077 horny = false,
3078 hornytimer = 0,
3079 child = false,
3080 gotten = false,
3081 health = 0,
3082 reach = def.reach or 3,
3083 htimer = 0,
3084 texture_list = def.textures,
3085 child_texture = def.child_texture,
3086 docile_by_day = def.docile_by_day or false,
3087 time_of_day = 0.5,
3088 fear_height = def.fear_height or 0,
3089 runaway = def.runaway,
3090 runaway_timer = 0,
3091 pathfinding = def.pathfinding,
3092 immune_to = def.immune_to or {},
3093 explosion_radius = def.explosion_radius,
3094 explosion_damage_radius = def.explosion_damage_radius,
3095 explosion_timer = def.explosion_timer or 3,
3096 allow_fuse_reset = def.allow_fuse_reset ~= false,
3097 stop_to_explode = def.stop_to_explode ~= false,
3098 custom_attack = def.custom_attack,
3099 double_melee_attack = def.double_melee_attack,
3100 dogshoot_switch = def.dogshoot_switch,
3101 dogshoot_count = 0,
3102 dogshoot_count_max = def.dogshoot_count_max or 5,
3103 dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
3104 attack_animals = def.attack_animals or false,
3105 specific_attack = def.specific_attack,
3106 runaway_from = def.runaway_from,
3107 owner_loyal = def.owner_loyal,
3108 facing_fence = false,
3109 _cmi_is_mob = true,
3110
3111 on_spawn = def.on_spawn,
3112
3113 on_blast = def.on_blast or do_tnt,
3114
3115 on_step = mob_step,
3116
3117 do_punch = def.do_punch,
3118
3119 on_punch = mob_punch,
3120
3121 on_breed = def.on_breed,
3122
3123 on_grown = def.on_grown,
3124
3125 on_activate = function(self, staticdata, dtime)
3126 return mob_activate(self, staticdata, def, dtime)
3127 end,
3128
3129 get_staticdata = function(self)
3130 return mob_staticdata(self)
3131 end,
3132
3133 })
3134
3135 end -- END mobs:register_mob function
3136
3137
3138 -- count how many mobs of one type are inside an area
3139 local count_mobs = function(pos, type)
3140
3141 local num_type = 0
3142 local num_total = 0
3143 local objs = minetest.get_objects_inside_radius(pos, aoc_range)
3144
3145 for n = 1, #objs do
3146
3147 if not objs[n]:is_player() then
3148
3149 local obj = objs[n]:get_luaentity()
3150
3151 -- count mob type and add to total also
3152 if obj and obj.name and obj.name == type then
3153
3154 num_type = num_type + 1
3155 num_total = num_total + 1
3156
3157 -- add to total mobs
3158 elseif obj and obj.name and obj.health ~= nil then
3159
3160 num_total = num_total + 1
3161 end
3162 end
3163 end
3164
3165 return num_type, num_total
3166 end
3167
3168
3169 -- global functions
3170
3171 function mobs:spawn_abm_check(pos, node, name)
3172 -- global function to add additional spawn checks
3173 -- return true to stop spawning mob
3174 end
3175
3176
3177 local function player_near(pos, radius)
3178
3179 local objs = minetest.get_objects_inside_radius(pos, radius)
3180
3181 for n = 1, #objs do
3182
3183 if objs[n]:is_player() then
3184 return true
3185 end
3186 end
3187
3188 return false
3189 end
3190
3191
3192 local function daycheck(day_toggle)
3193
3194 if day_toggle ~= nil then
3195
3196 local tod = (minetest.get_timeofday() or 0) * 24000
3197
3198 if tod > 4500 and tod < 19500 then
3199
3200 if day_toggle == false then
3201 return false -- mob requires night
3202 end
3203 else
3204 if day_toggle == true then
3205 return false -- mob requires day
3206 end
3207 end
3208 end
3209
3210 return true -- mob doesn't care
3211 end
3212
3213
3214 local function is_protected(pos)
3215
3216 if not spawn_protected
3217 and minetest.is_protected(pos, "") then
3218 return true -- protected area
3219 end
3220
3221 return false -- mobs can spawn
3222 end
3223
3224
3225 local interval = 30
3226 local timer = 0
3227 local spawning_mobs = {}
3228
3229 minetest.register_globalstep(function(dtime)
3230
3231 if not mobs_spawn then
3232 return
3233 end
3234
3235 timer = timer + dtime
3236 if timer < interval then
3237 return
3238 end
3239 timer = 0
3240
3241 for _,player in ipairs(minetest.get_connected_players()) do
3242
3243 if player:get_hp() > 0 then
3244
3245 local pos = player:getpos()
3246 local area, pos2, light, obj, base
3247
3248 for _,mob in ipairs(spawning_mobs) do
3249
3250 area = nil
3251
3252 if minetest.registered_entities[mob.name]
3253 and random(1, mob.chance) == 1 then
3254
3255 area = minetest.find_nodes_in_area_under_air(
3256 {x = pos.x - 20, y = pos.y - 20, z = pos.z - 20},
3257 {x = pos.x + 20, y = pos.y + 20, z = pos.z + 20},
3258 mob.nodes)
3259 end
3260
3261 if area and #area > 0 then
3262
3263 pos2 = area[math.random(1, #area)]
3264 base = minetest.registered_entities[mob.name].collisionbox[5]
3265 pos2.y = pos2.y + 1 + base
3266
3267 light = minetest.get_node_light(pos2) or -1
3268
3269 if pos2.y >= mob.min_height
3270 and pos2.y <= mob.max_height
3271 and light >= mob.min_light
3272 and light <= mob.max_light
3273 and daycheck(mob.day_toggle)
3274 and minetest.find_node_near(pos2, 1, mob.neighbors)
3275 and count_mobs(pos2, mob.name) < mob.total
3276 and not player_near(pos2, 10)
3277 and not is_protected(pos2) then
3278
3279 print ("--- Spawned ", mob.name, minetest.pos_to_string(pos2), mob.chance)
3280
3281 obj = minetest.add_entity(pos2, mob.name)
3282
3283 if mob.on_spawn then
3284 mob.on_spawn(obj:get_luaentity(), pos2)
3285 end
3286 else
3287 print ("--- Cannot spawn ", mob.name)
3288 end
3289 end
3290 end
3291 end
3292 end
3293 end)
3294
3295
3296 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3297 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3298
3299 -- chance/spawn number override in minetest.conf for registered mob
3300 local numbers = minetest.settings:get(name)
3301
3302 if numbers then
3303 numbers = numbers:split(",")
3304 chance = tonumber(numbers[1]) or chance
3305 aoc = tonumber(numbers[2]) or aoc
3306
3307 if chance == 0 then
3308 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3309 return
3310 end
3311
3312 minetest.log("action",
3313 string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
3314 end
3315
3316 -- change old chance values to be more useable by new spawn routine
3317 if chance > 999 then
3318 chance = max(1, chance / 1000)
3319 end
3320
3321 -- adjust for mob chance multiplier
3322 chance = max(1, chance * mob_chance_multiplier)
3323
3324 -- add mob to table for spawning with routine above
3325 table.insert(spawning_mobs, {
3326 name = name,
3327 nodes = nodes,
3328 neighbors = neighbors,
3329 chance = chance,
3330 min_height = min_height,
3331 max_height = max_height,
3332 min_light = min_light,
3333 max_light = max_light,
3334 total = aoc,
3335 day_toggle = day_toggle,
3336 on_spawn = on_spawn,
3337 })
3338 end
3339
3340
3341 -- compatibility with older mob registration
3342 function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
3343
3344 mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
3345 chance, active_object_count, -31000, max_height, day_toggle)
3346 end
3347
3348
3349 -- MarkBu's spawn function
3350 function mobs:spawn(def)
3351
3352 mobs:spawn_specific(
3353 def.name,
3354 def.nodes or {"group:soil", "group:stone"},
3355 def.neighbors or {"air"},
3356 def.min_light or 0,
3357 def.max_light or 15,
3358 def.interval or 30,
3359 def.chance or 5000,
3360 def.active_object_count or 1,
3361 def.min_height or -31000,
3362 def.max_height or 31000,
3363 def.day_toggle,
3364 def.on_spawn
3365 )
3366 end
3367
3368
3369 -- register arrow for shoot attack
3370 function mobs:register_arrow(name, def)
3371
3372 if not name or not def then return end -- errorcheck
3373
3374 minetest.register_entity(name, {
3375
3376 physical = false,
3377 visual = def.visual,
3378 visual_size = def.visual_size,
3379 textures = def.textures,
3380 velocity = def.velocity,
3381 hit_player = def.hit_player,
3382 hit_node = def.hit_node,
3383 hit_mob = def.hit_mob,
3384 drop = def.drop or false, -- drops arrow as registered item when true
3385 collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
3386 timer = 0,
3387 switch = 0,
3388 owner_id = def.owner_id,
3389 rotate = def.rotate,
3390 automatic_face_movement_dir = def.rotate
3391 and (def.rotate - (pi / 180)) or false,
3392
3393 on_activate = def.on_activate,
3394
3395 on_step = def.on_step or function(self, dtime)
3396
3397 self.timer = self.timer + 1
3398
3399 local pos = self.object:get_pos()
3400
3401 if self.switch == 0
3402 or self.timer > 150
3403 or not within_limits(pos, 0) then
3404
3405 self.object:remove() ; -- print ("removed arrow")
3406
3407 return
3408 end
3409
3410 -- does arrow have a tail (fireball)
3411 if def.tail
3412 and def.tail == 1
3413 and def.tail_texture then
3414
3415 minetest.add_particle({
3416 pos = pos,
3417 velocity = {x = 0, y = 0, z = 0},
3418 acceleration = {x = 0, y = 0, z = 0},
3419 expirationtime = def.expire or 0.25,
3420 collisiondetection = false,
3421 texture = def.tail_texture,
3422 size = def.tail_size or 5,
3423 glow = def.glow or 0,
3424 })
3425 end
3426
3427 if self.hit_node then
3428
3429 local node = node_ok(pos).name
3430
3431 if minetest.registered_nodes[node].walkable then
3432
3433 self.hit_node(self, pos, node)
3434
3435 if self.drop == true then
3436
3437 pos.y = pos.y + 1
3438
3439 self.lastpos = (self.lastpos or pos)
3440
3441 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
3442 end
3443
3444 self.object:remove() ; -- print ("hit node")
3445
3446 return
3447 end
3448 end
3449
3450 if self.hit_player or self.hit_mob then
3451
3452 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
3453
3454 if self.hit_player
3455 and player:is_player() then
3456
3457 self.hit_player(self, player)
3458 self.object:remove() ; -- print ("hit player")
3459 return
3460 end
3461
3462 local entity = player:get_luaentity()
3463
3464 if entity
3465 and self.hit_mob
3466 and entity._cmi_is_mob == true
3467 and tostring(player) ~= self.owner_id
3468 and entity.name ~= self.object:get_luaentity().name then
3469
3470 self.hit_mob(self, player)
3471
3472 self.object:remove() ; --print ("hit mob")
3473
3474 return
3475 end
3476 end
3477 end
3478
3479 self.lastpos = pos
3480 end
3481 })
3482 end
3483
3484
3485 -- compatibility function
3486 function mobs:explosion(pos, radius)
3487 local self = {sounds = {}}
3488 self.sounds.explode = "tnt_explode"
3489 mobs:boom(self, pos, radius)
3490 end
3491
3492
3493 -- no damage to nodes explosion
3494 function mobs:safe_boom(self, pos, radius)
3495
3496 minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
3497 pos = pos,
3498 gain = 1.0,
3499 max_hear_distance = self.sounds and self.sounds.distance or 32
3500 })
3501
3502 entity_physics(pos, radius)
3503 effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
3504 end
3505
3506
3507 -- make explosion with protection and tnt mod check
3508 function mobs:boom(self, pos, radius)
3509
3510 if mobs_griefing
3511 and minetest.get_modpath("tnt") and tnt and tnt.boom
3512 and not minetest.is_protected(pos, "") then
3513
3514 tnt.boom(pos, {
3515 radius = radius,
3516 damage_radius = radius,
3517 sound = self.sounds and self.sounds.explode,
3518 explode_center = true,
3519 })
3520 else
3521 mobs:safe_boom(self, pos, radius)
3522 end
3523 end
3524
3525
3526 -- Register spawn eggs
3527
3528 -- Note: This also introduces the “spawn_egg” group:
3529 -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
3530 -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
3531 function mobs:register_egg(mob, desc, background, addegg, no_creative)
3532
3533 local grp = {spawn_egg = 1}
3534
3535 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3536 if creative and no_creative == true then
3537 grp.not_in_creative_inventory = 1
3538 end
3539
3540 local invimg = background
3541
3542 if addegg == 1 then
3543 invimg = "mobs_chicken_egg.png^(" .. invimg ..
3544 "^[mask:mobs_chicken_egg_overlay.png)"
3545 end
3546
3547 -- register new spawn egg containing mob information
3548 minetest.register_craftitem(mob .. "_set", {
3549
3550 description = S("@1 (Tamed)", desc),
3551 inventory_image = invimg,
3552 groups = {spawn_egg = 2, not_in_creative_inventory = 1},
3553 stack_max = 1,
3554
3555 on_place = function(itemstack, placer, pointed_thing)
3556
3557 local pos = pointed_thing.above
3558
3559 -- am I clicking on something with existing on_rightclick function?
3560 local under = minetest.get_node(pointed_thing.under)
3561 local def = minetest.registered_nodes[under.name]
3562 if def and def.on_rightclick then
3563 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3564 end
3565
3566 if pos
3567 and within_limits(pos, 0)
3568 and not minetest.is_protected(pos, placer:get_player_name()) then
3569
3570 if not minetest.registered_entities[mob] then
3571 return
3572 end
3573
3574 pos.y = pos.y + 1
3575
3576 local data = itemstack:get_metadata()
3577 local mob = minetest.add_entity(pos, mob, data)
3578 local ent = mob:get_luaentity()
3579
3580 -- set owner if not a monster
3581 if ent.type ~= "monster" then
3582 ent.owner = placer:get_player_name()
3583 ent.tamed = true
3584 end
3585
3586 -- since mob is unique we remove egg once spawned
3587 itemstack:take_item()
3588 end
3589
3590 return itemstack
3591 end,
3592 })
3593
3594
3595 -- register old stackable mob egg
3596 minetest.register_craftitem(mob, {
3597
3598 description = desc,
3599 inventory_image = invimg,
3600 groups = grp,
3601
3602 on_place = function(itemstack, placer, pointed_thing)
3603
3604 local pos = pointed_thing.above
3605
3606 -- am I clicking on something with existing on_rightclick function?
3607 local under = minetest.get_node(pointed_thing.under)
3608 local def = minetest.registered_nodes[under.name]
3609 if def and def.on_rightclick then
3610 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3611 end
3612
3613 if pos
3614 and within_limits(pos, 0)
3615 and not minetest.is_protected(pos, placer:get_player_name()) then
3616
3617 if not minetest.registered_entities[mob] then
3618 return
3619 end
3620
3621 pos.y = pos.y + 1
3622
3623 local mob = minetest.add_entity(pos, mob)
3624 local ent = mob:get_luaentity()
3625
3626 -- don't set owner if monster or sneak pressed
3627 if ent.type ~= "monster"
3628 and not placer:get_player_control().sneak then
3629 ent.owner = placer:get_player_name()
3630 ent.tamed = true
3631 end
3632
3633 -- if not in creative then take item
3634 if not mobs.is_creative(placer:get_player_name()) then
3635 itemstack:take_item()
3636 end
3637 end
3638
3639 return itemstack
3640 end,
3641 })
3642
3643 end
3644
3645
3646 -- capture critter (thanks to blert2112 for idea)
3647 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
3648
3649 if self.child
3650 or not clicker:is_player()
3651 or not clicker:get_inventory() then
3652 return false
3653 end
3654
3655 -- get name of clicked mob
3656 local mobname = self.name
3657
3658 -- if not nil change what will be added to inventory
3659 if replacewith then
3660 mobname = replacewith
3661 end
3662
3663 local name = clicker:get_player_name()
3664 local tool = clicker:get_wielded_item()
3665
3666 -- are we using hand, net or lasso to pick up mob?
3667 if tool:get_name() ~= ""
3668 and tool:get_name() ~= "mobs:net"
3669 and tool:get_name() ~= "mobs:lasso" then
3670 return false
3671 end
3672
3673 -- is mob tamed?
3674 if self.tamed == false
3675 and force_take == false then
3676
3677 minetest.chat_send_player(name, S("Not tamed!"))
3678
3679 return true -- false
3680 end
3681
3682 -- cannot pick up if not owner
3683 if self.owner ~= name
3684 and force_take == false then
3685
3686 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
3687
3688 return true -- false
3689 end
3690
3691 if clicker:get_inventory():room_for_item("main", mobname) then
3692
3693 -- was mob clicked with hand, net, or lasso?
3694 local chance = 0
3695
3696 if tool:get_name() == "" then
3697 chance = chance_hand
3698
3699 elseif tool:get_name() == "mobs:net" then
3700
3701 chance = chance_net
3702
3703 tool:add_wear(4000) -- 17 uses
3704
3705 clicker:set_wielded_item(tool)
3706
3707 elseif tool:get_name() == "mobs:lasso" then
3708
3709 chance = chance_lasso
3710
3711 tool:add_wear(650) -- 100 uses
3712
3713 clicker:set_wielded_item(tool)
3714
3715 end
3716
3717 -- calculate chance.. add to inventory if successful?
3718 if chance > 0 and random(1, 100) <= chance then
3719
3720 -- default mob egg
3721 local new_stack = ItemStack(mobname)
3722
3723 -- add special mob egg with all mob information
3724 -- unless 'replacewith' contains new item to use
3725 if not replacewith then
3726
3727 new_stack = ItemStack(mobname .. "_set")
3728
3729 local tmp = {}
3730
3731 for _,stat in pairs(self) do
3732 local t = type(stat)
3733 if t ~= "function"
3734 and t ~= "nil"
3735 and t ~= "userdata" then
3736 tmp[_] = self[_]
3737 end
3738 end
3739
3740 local data_str = minetest.serialize(tmp)
3741
3742 new_stack:set_metadata(data_str)
3743 end
3744
3745 local inv = clicker:get_inventory()
3746
3747 if inv:room_for_item("main", new_stack) then
3748 inv:add_item("main", new_stack)
3749 else
3750 minetest.add_item(clicker:get_pos(), new_stack)
3751 end
3752
3753 self.object:remove()
3754
3755 mob_sound(self, "default_place_node_hard")
3756
3757 elseif chance ~= 0 then
3758 minetest.chat_send_player(name, S("Missed!"))
3759
3760 mob_sound(self, "mobs_swing")
3761 end
3762 end
3763
3764 return true
3765 end
3766
3767
3768 -- protect tamed mob with rune item
3769 function mobs:protect(self, clicker)
3770
3771 local name = clicker:get_player_name()
3772 local tool = clicker:get_wielded_item()
3773
3774 if tool:get_name() ~= "mobs:protector" then
3775 return false
3776 end
3777
3778 if self.tamed == false then
3779 minetest.chat_send_player(name, S("Not tamed!"))
3780 return true -- false
3781 end
3782
3783 if self.protected == true then
3784 minetest.chat_send_player(name, S("Already protected!"))
3785 return true -- false
3786 end
3787
3788 if not mobs.is_creative(clicker:get_player_name()) then
3789 tool:take_item() -- take 1 protection rune
3790 clicker:set_wielded_item(tool)
3791 end
3792
3793 self.protected = true
3794
3795 local pos = self.object:get_pos()
3796 pos.y = pos.y + self.collisionbox[2] + 0.5
3797
3798 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3799
3800 mob_sound(self, "mobs_spell")
3801
3802 return true
3803 end
3804
3805
3806 local mob_obj = {}
3807 local mob_sta = {}
3808
3809 -- feeding, taming and breeding (thanks blert2112)
3810 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
3811
3812 if not self.follow then
3813 return false
3814 end
3815
3816 -- can eat/tame with item in hand
3817 if follow_holding(self, clicker) then
3818
3819 -- if not in creative then take item
3820 if not mobs.is_creative(clicker:get_player_name()) then
3821
3822 local item = clicker:get_wielded_item()
3823
3824 item:take_item()
3825
3826 clicker:set_wielded_item(item)
3827 end
3828
3829 -- increase health
3830 self.health = self.health + 4
3831
3832 if self.health >= self.hp_max then
3833
3834 self.health = self.hp_max
3835
3836 if self.htimer < 1 then
3837
3838 minetest.chat_send_player(clicker:get_player_name(),
3839 S("@1 at full health (@2)",
3840 self.name:split(":")[2], tostring(self.health)))
3841
3842 self.htimer = 5
3843 end
3844 end
3845
3846 self.object:set_hp(self.health)
3847
3848 update_tag(self)
3849
3850 -- make children grow quicker
3851 if self.child == true then
3852
3853 self.hornytimer = self.hornytimer + 20
3854
3855 return true
3856 end
3857
3858 -- feed and tame
3859 self.food = (self.food or 0) + 1
3860 if self.food >= feed_count then
3861
3862 self.food = 0
3863
3864 if breed and self.hornytimer == 0 then
3865 self.horny = true
3866 end
3867
3868 self.gotten = false
3869
3870 if tame then
3871
3872 if self.tamed == false then
3873 minetest.chat_send_player(clicker:get_player_name(),
3874 S("@1 has been tamed!",
3875 self.name:split(":")[2]))
3876 end
3877
3878 self.tamed = true
3879
3880 if not self.owner or self.owner == "" then
3881 self.owner = clicker:get_player_name()
3882 end
3883 end
3884
3885 -- make sound when fed so many times
3886 mob_sound(self, self.sounds.random)
3887 end
3888
3889 return true
3890 end
3891
3892 local item = clicker:get_wielded_item()
3893
3894 -- if mob has been tamed you can name it with a nametag
3895 if item:get_name() == "mobs:nametag"
3896 and clicker:get_player_name() == self.owner then
3897
3898 local name = clicker:get_player_name()
3899
3900 -- store mob and nametag stack in external variables
3901 mob_obj[name] = self
3902 mob_sta[name] = item
3903
3904 local tag = self.nametag or ""
3905
3906 minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
3907 .. default.gui_bg
3908 .. default.gui_bg_img
3909 .. "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape(S("Enter name:")) .. ";" .. tag .. "]"
3910 .. "button_exit[2.5,3.5;3,1;mob_rename;" .. minetest.formspec_escape(S("Rename")) .. "]")
3911 end
3912
3913 return false
3914 end
3915
3916
3917 -- inspired by blockmen's nametag mod
3918 minetest.register_on_player_receive_fields(function(player, formname, fields)
3919
3920 -- right-clicked with nametag and name entered?
3921 if formname == "mobs_nametag"
3922 and fields.name
3923 and fields.name ~= "" then
3924
3925 local name = player:get_player_name()
3926
3927 if not mob_obj[name]
3928 or not mob_obj[name].object then
3929 return
3930 end
3931
3932 -- make sure nametag is being used to name mob
3933 local item = player:get_wielded_item()
3934
3935 if item:get_name() ~= "mobs:nametag" then
3936 return
3937 end
3938
3939 -- limit name entered to 64 characters long
3940 if string.len(fields.name) > 64 then
3941 fields.name = string.sub(fields.name, 1, 64)
3942 end
3943
3944 -- update nametag
3945 mob_obj[name].nametag = fields.name
3946
3947 update_tag(mob_obj[name])
3948
3949 -- if not in creative then take item
3950 if not mobs.is_creative(name) then
3951
3952 mob_sta[name]:take_item()
3953
3954 player:set_wielded_item(mob_sta[name])
3955 end
3956
3957 -- reset external variables
3958 mob_obj[name] = nil
3959 mob_sta[name] = nil
3960 end
3961 end)
3962
3963
3964 -- compatibility function for old entities to new modpack entities
3965 function mobs:alias_mob(old_name, new_name)
3966
3967 -- spawn egg
3968 minetest.register_alias(old_name, new_name)
3969
3970 -- entity
3971 minetest.register_entity(":" .. old_name, {
3972
3973 physical = false,
3974
3975 on_step = function(self)
3976
3977 if minetest.registered_entities[new_name] then
3978 minetest.add_entity(self.object:get_pos(), new_name)
3979 end
3980
3981 self.object:remove()
3982 end
3983 })
3984 end
3030 'hp_max' has the maximum health value the mob can spawn with.
3131 'armor' holds strength of mob, 100 is normal, lower is more powerful
3232 and needs more hits and better weapons to kill.
33 'passive' when true allows animals to defend themselves when hit,
33 'passive' when false allows animals to defend themselves when hit,
3434 otherwise they amble onwards.
3535 'walk_velocity' is the speed that your mob can walk around.
3636 'run_velocity' is the speed your mob can run with, usually when attacking.
37 'stand_chance' has a 0-100 chance value your mob will stand from walking.
3738 'walk_chance' has a 0-100 chance value your mob will walk from standing,
3839 set to 0 for jumping mobs only.
40 'randomly_turn' if set to false then mob will not turn to face player or
41 randomly turn while walking or standing.
3942 'jump' when true allows your mob to jump updwards.
4043 'jump_height' holds the height your mob can jump, 0 to disable jumping.
4144 'stepheight' height of a block that your mob can easily walk up onto,
4346 'fly' when true allows your mob to fly around instead of walking.
4447 'fly_in' holds the node name that the mob flies (or swims) around
4548 in e.g. "air" or "default:water_source".
49 'keep_flying' when true mobs like birds no longer stop and stand.
50 'stay_near' when set allows mobs the chance to stay around certain nodes.
51 'nodes' string or table of nodes to stay nearby e.g. "farming:straw"
52 'chance' chance of searching for above node(s), default is 10.
4653 'runaway' if true causes animals to turn and run away when hit.
4754 'pushable' when true mobs can be pushed by player or other mobs.
4855 'view_range' how many nodes in distance the mob can see a player.
4956 'damage' how many health points the mob does to a player or another
5057 mob when melee attacking.
58 'damage_group' group in which damage is dealt, dedaults to "fleshy".
59 'damage_texture_modifier' applies texture modifier on hit e.g "^[brighten"
5160 'knock_back' when true has mobs falling backwards when hit, the greater
5261 the damage the more they move back.
5362 'fear_height' is how high a cliff or edge has to be before the mob stops
5665 'fall_damage' when true causes falling to inflict damage.
5766 'water_damage' holds the damage per second infliced to mobs when standing in
5867 water.
68 'air_damage' holds damage per second inflicted to mob when standing in air.
5969 'lava_damage' holds the damage per second inflicted to mobs when standing
60 in lava or fire or an ignition source.
70 in lava.
71 'fire_damage' holds the damage per second inflicted to mobs when standing
72 in fire.
73
6174 'light_damage' holds the damage per second inflicted to mobs when light
6275 level is between the min and max values below
6376 'light_damage_min' minimum light value when mob is affected (default: 14)
6477 'light_damage_max' maximum light value when mob is affected (default: 15)
65 'suffocation' when true causes mobs to suffocate inside solid blocks.
78 'suffocation' when > 0 mobs will suffocate inside solid blocks and will be
79 hurt by the value given every second (0 to disable).
6680 'floats' when set to 1 mob will float in water, 0 has them sink.
6781 'follow' mobs follow player when holding any of the items which appear
6882 on this table, the same items can be fed to a mob to tame or
69 breed e.g. {"farming:wheat", "default:apple"}
83 breed e.g. {"farming:wheat", "default:apple", "group:fish"}
7084
7185 'reach' is how far the mob can attack player when standing
7286 nearby, default is 3 nodes.
8296 punches when nearby.
8397 'group_attack' when true has same mob type grouping together to attack
8498 offender.
99 'group_helper' string containing mob name that attacks alongside
100 current mob when group attacking.
101 mob is attacking in groups.
85102 'attack_type' tells the api what a mob does when attacking the player
86103 or another mob:
87104 'dogfight' is a melee attack when player is within mob reach.
105122 continue chasing.
106123 'arrow' holds the pre-defined arrow object to shoot when
107124 attacking.
125 'arrow_override' function that allows tweaking of arrow entity from
126 inside mob definition (self) passed to function.
108127 'dogshoot_switch' allows switching between attack types by using timers
109128 (1 for shoot, 2 for dogfight)
110129 'dogshoot_count_max' contains how many seconds before switching from
118137 e.g. {"player", "mobs_animal:chicken"}.
119138 'runaway_from' contains a table with mob names to run away from, add
120139 "player" to list to runaway from player also.
140 'ignore_invisibility' When true mob will still be able to see and attack
141 player even if invisible (invisibility mod only).
121142 'blood_amount' contains the number of blood droplets to appear when
122143 mob is hit.
123144 'blood_texture' has the texture name to use for droplets e.g.
125146 'pathfinding' set to 1 for mobs to use pathfinder feature to locate
126147 player, set to 2 so they can build/break also (only
127148 works with dogfight attack and when 'mobs_griefing'
128 in minetest.conf is not false).
149 in minetest.conf is not false). Adding {unbreakable=1}
150 to node groups stops them being broken by mobs.
129151 'immune_to' is a table that holds specific damage when being hit by
130152 certain items e.g.
131153 {"default:sword_wood", 0} -- causes no damage.
152174 'min' minimum number of items dropped, set to 0 for rare drops.
153175 'max' maximum number of items dropped.
154176 Note: If weapon has {fire=1} damage group set then cooked items will drop.
177 Note2: A function can now be passed which can also return drops table, e.g.
178 drops = function(pos)
179 -- do something
180 return { {name = "farming:bread"}, {name = "default:dirt", chance = 2} }
181 end
155182
156183 'visual' holds the look of the mob you wish to create:
157184 'cube' looks like a normal node
169196 'child_texture' holds the texture table for when baby mobs are used.
170197 'gotten_texture' holds the texture table for when self.gotten value is
171198 true, used for milking cows or shearing sheep.
199 'texture_mods' holds a string which overlays a texture on top of the
200 mob texture e.g. "^saddle.png"
172201 'mesh' holds the name of the external object used for mob model
173202 e.g. "mobs_cow.b3d"
174203 'gotten_mesh" holds the name of the external object used for when
175204 self.gotten is true for mobs.
176205 'rotate' custom model rotation, 0 = front, 90 = side, 180 = back,
177206 270 = other side.
207 'glow' has mob glow without light source, 0 to 15 or nil to disable
178208 'double_melee_attack' when true has the api choose between 'punch' and
179209 'punch2' animations. [DEPRECATED]
180210
181 'animation' holds a table containing animation names and settings for use with mesh models:
211 'animation' holds a table containing animation names and settings for use with
212 mesh models:
182213 'stand_start' start frame for when mob stands still.
183214 'stand_end' end frame of stand animation.
184215 'stand_speed' speed of animation in frames per second.
204235 'die_end'
205236 'die_speed'
206237 'die_loop' when set to false stops the animation looping.
238 'die_rotate' if true mob spins during death animation.
207239
208240 Using '_loop = false' setting will stop any of the above animations from
209241 looping.
243275 If false is returned, the mob will not replace the node.
244276
245277 By default, replacing sets self.gotten to true and resets the object
246 properties.
278 properties. (DEPRECATED, use on_replace to make changes).
247279
248280
249281 Custom Definition Functions
272304 time_from_last_punch, tool_capabilities, direction), return
273305 false to stop punch damage and knockback from taking place.
274306 'custom_attack' when set this function is called instead of the normal mob
275 melee attack, parameters are (self, to_attack).
276 'on_die' a function that is called when mob is killed (self, pos)
307 melee attack, parameters are (self, to_attack) and if true
308 is returned normal attack function continued.
309 'on_die' a function that is called when mob is killed (self, pos), also
310 has access to self.cause_of_death table.
311 'on_flop' function called when flying or swimmimng mob is no longer in
312 air/water, (self) paramater and return true to skip the built
313 in api flop feature.
277314 'do_custom' a custom function that is called every tick while mob is
278315 active and which has access to all of the self.* variables
279316 e.g. (self.health for health or self.standing_in for node
305342 'self.order' set to "follow" or "stand" so that npc will follow owner
306343 or stand it's ground
307344 'self.nametag' contains the name of the mob which it can show above
345 'self.state' Current mob state.
346 "stand": no movement (except turning around)
347 "walk": walk or move around aimlessly
348 "attack": chase and attack enemy
349 "runaway": flee from target
350 "flop": bounce around aimlessly
351 (for swimming mobs that have stranded)
352 "die": during death
353
354
355 Adding Mobs in World
356 --------------------
357
358 mobs:add_mob(pos, {
359 name = "mobs_animal:chicken",
360 child = true,
361 owner = "singleplayer",
362 nametag = "Bessy",
363 ignore_count = true -- ignores mob count per map area
364 })
365
366 Returns false if mob could not be added, returns mob object if spawned ok.
367
368
369 Removing Mob from World
370 -----------------------
371
372 mobs:remove(self, decrease)
373
374 Removes mob 'self' from the world and if 'decrease' is true then the mob counter
375 will also be decreased by one.
308376
309377
310378 Spawning Mobs in World
311379 ----------------------
312380
313 mobs:register_spawn(name, nodes, max_light, min_light, chance,
314 active_object_count, max_height, day_toggle)
315
316 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
317 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
318
319 These functions register a spawn algorithm for the mob. Without this function
320 the call the mobs won't spawn.
381 mobs:spawn({
382 name = "mobs_monster:tree_monster",
383 nodes = {"group:leaves"},
384 max_light = 7,
385 })
386
387 Spawn functions require the following settings, some of which already have a
388 default setting and can be omitted:
321389
322390 'name' is the name of the animal/monster
323391 'nodes' is a list of nodenames on that the animal/monster can
324 spawn on top of
392 spawn on top of (defaults to {"group:dirt", "group:stone"}
325393 'neighbors' is a list of nodenames on that the animal/monster will
326 spawn beside (default is {"air"} for
327 mobs:register_spawn)
328 'max_light' is the maximum of light
329 'min_light' is the minimum of light
330 'interval' is same as in register_abm() (default is 30 for
331 mobs:register_spawn)
332 'chance' is same as in register_abm()
394 spawn beside (default is {"air"})
395 'interval' is same as in register_abm() (default is 30)
396 'chance' is same as in register_abm() (default is 5000)
397 'min_light' is the minimum light level (default is 0)
398 'max_light' is the maximum light (default is 15)
399 'min_height' is the minimum height a mob can spawn (default: -31000)
400 'max_height' is the maximum height a mob can spawn (default is 31000)
333401 'active_object_count' number of this type of mob to spawn at one time inside
334 map area
335 'min_height' is the minimum height the mob can spawn
336 'max_height' is the maximum height the mob can spawn
402 map area (default is 1)
337403 'day_toggle' true for day spawning, false for night or nil for
338404 anytime
339405 'on_spawn' is a custom function which runs after mob has spawned
340406 and gives self and pos values.
407 'on_map_load' when true mobs will have a chance of spawning only
408 when new areas of map are loaded, interval will not be
409 used.
410
411 The older spawn functions are still active and working but have no defaults like
412 the mobs:spawn, so it is recommended to use the above instead.
413
414 mobs:register_spawn(name, nodes, max_light, min_light, chance,
415 active_object_count, max_height, day_toggle)
416
417 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
418 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
341419
342420 A simpler way to handle mob spawns has been added with the mobs:spawn(def)
343421 command which uses above names to make settings clearer:
344
345 mobs:spawn({name = "mobs_monster:tree_monster",
346 nodes = {"group:leaves"},
347 max_light = 7,
348 })
349422
350423
351424 For each mob that spawns with this function is a field in mobs.spawning_mobs.
363436 'pos' holds the position of the spawning mob
364437 'node' contains the node the mob is spawning on top of
365438 'name' is the name of the animal/monster
439
440
441 Particle Effects
442 ----------------
443
444 mobs:effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
445
446 This function provides a quick way to spawn particles as an effect.
447
448 'pos' center position of particle effect.
449 'amount' how many particles.
450 'texture' texture filename to use for effect.
451 'min_size' smallest particle size.
452 'max_size' largest particle size.
453 'radius' how far particles spread outward from center.
454 'gravity' gravity applied to particles once they spawn.
455 'glow' number between 1 and 15 for glowing particles.
456 'fall' when true particles fall, false has them rising, nil has them scatter.
366457
367458
368459 Making Arrows
377468 'visual' same is in minetest.register_entity()
378469 'visual_size' same is in minetest.register_entity()
379470 'textures' same is in minetest.register_entity()
471 'physical' same is in minetest.register_entity() [default: false]
472 'collide_with_objects' same as above
380473 'velocity' the velocity of the arrow
381474 'drop' if set to true any arrows hitting a node will drop as item
382475 'hit_player' a function that is called when the arrow hits a player;
385478 'hit_mob' a function that is called when the arrow hits a mob;
386479 this function should hurt the mob, the parameters are
387480 (self, player)
481 'hit_object' a function that is called when the arrow hits an object;
482 this function parameters are (self, player)
388483 'hit_node' a function that is called when the arrow hits a node, the
389484 parameters are (self, pos, node)
390485 'tail' when set to 1 adds a trail or tail to mob arrows
398493 'on_step' is a custom function when arrow is active, nil for
399494 default.
400495 'on_punch' is a custom function when arrow is punched, nil by default
401 'collisionbox' is hitbox table for arrow, {0,0,0,0,0,0} by default.
496 'collisionbox' is hitbox table for arrow, {-.1,-.1,-.1,.1,.1,.1} by default.
497 'lifetime' contains float value for how many seconds arrow exists in
498 world before being removed (default is 4.5 seconds).
402499
403500
404501 Spawn Eggs
406503
407504 mobs:register_egg(name, description, background, addegg, no_creative)
408505
409 This function registers a spawn egg which can be used by admin to properly spawn in a mob.
506 This function registers a spawn egg which can be used to properly spawn in a mob.
507 Animals are spawned as tamed unless sneak/shift is held while spawning.
410508
411509 'name' this is the name of your new mob to spawn e.g. "mob:sheep"
412510 'description' the name of the new egg you are creating e.g. "Spawn Sheep"
452550 'force_take' take mob by force, even if tamed (true or false)
453551 'replacewith' once captured replace mob with this item instead (overrides
454552 new mob eggs with saved information)
553
554 mobs:force_capture(self, clicker)
555
556 Same as above but does no checks, it simply captures any and all mobs and places
557 inside a spawn egg containing all of the mob information.
455558
456559
457560 Feeding and Taming/Breeding
565668 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1}
566669
567670
568 mobs:line_of_sight(self, pos1, pos2, stepsize)
671 mobs:line_of_sight(self, pos1, pos2, stepsize) [DEPRECATED]
569672
570673 This function is for use within the mobs definition for special use cases and
571674 returns true if a mob can see the player or victim.
574677 'pos1' position of mob
575678 'pos2' position of vistim or player
576679 'stepsize' usually set to 1
680
681 Use this instead:
682
683 mob_class:line_of_sight(pos1, pos2, stepsize)
684
685
686 mobs:can_spawn(pos, name)
687
688 This function checks the surrounding area at [pos] to see if there is enough empty
689 space to spawn mob [name], if so then a new position is returned for use,
690 otherwise nil is returned.
577691
578692
579693 External Settings for "minetest.conf"
586700 is false)
587701 'mobs_spawn_protected' if set to false then mobs will not spawn in protected
588702 areas (default is true)
703 'mobs_spawn_monster_protected' if set to false then monsters will not spawn in
704 protected areas (default is true)
589705 'remove_far_mobs' if true then untamed mobs that are outside players
590706 visual range will be removed (default is true)
591707 'mobname' can change specific mob chance rate (0 to disable) and
604720 'mobs_griefing' when false mobs cannot break blocks when using either
605721 pathfinding level 2, replace functions or mobs:boom
606722 function.
723 'mob_nospawn_range' Minimum range a mob can spawn near player (def: 12)
724 'mob_active_limit' Number of active mobs in game, 0 for unlimited
725 'mob_area_spawn' When true will check surrounding area the size of the
726 mob for obstructions before spawning, otherwise it
727 defaults to checking the height of the mob only.
728 'mob_smooth_rotate' Enables smooth rotation when mobs turn by default.
607729
608730 Players can override the spawn chance for each mob registered by adding a line
609731 to their minetest.conf file with a new value, the lower the value the more each
610732 mob will spawn e.g.
611733
612 mobs_animal:sheep_chance 11000
613 mobs_monster:sand_monster_chance 100
734 mobs_animal:sheep 11000
735 mobs_monster:sand_monster 100
736
737 ...you can also change how many of a certain mob appear in an active mapblock by
738 adding a comma and then a new value e.g.
739
740 mobs_animal:cow = 8000,4 <-- 4 cows per mapblock at 8000 spawn chance
741 mobs_monster:dirt_monster = ,20 <-- 20 dirt monsters per mapblock
614742
615743
616744 Rideable Horse Example Mob
622750 visual_size = {x = 1.20, y = 1.20},
623751 mesh = "mobs_horse.x",
624752 collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
625 animation = {
753 animation = {
626754 speed_normal = 15,
627755 speed_run = 30,
628756 stand_start = 25,
721849 if inv:room_for_item("main", "mobs:saddle") then
722850 inv:add_item("main", "mobs:saddle")
723851 else
724 minetest.add_item(clicker.getpos(), "mobs:saddle")
852 minetest.add_item(clicker.get_pos(), "mobs:saddle")
725853 end
726854
727855 -- attach player to horse
44 minetest.register_craftitem("mobs:nametag", {
55 description = S("Name Tag"),
66 inventory_image = "mobs_nametag.png",
7 groups = {flammable = 2},
7 groups = {flammable = 2, nametag = 1}
88 })
99
1010 if minetest.get_modpath("dye") and minetest.get_modpath("farming") then
1111 minetest.register_craft({
12 type = "shapeless",
1312 output = "mobs:nametag",
14 recipe = {"default:paper", "dye:black", "farming:string"},
13 recipe = {{"default:paper", "dye:black", "farming:string"}}
1514 })
1615 end
1716
1918 minetest.register_craftitem("mobs:leather", {
2019 description = S("Leather"),
2120 inventory_image = "mobs_leather.png",
22 groups = {flammable = 2},
21 groups = {flammable = 2, leather = 1}
2322 })
2423
2524 -- raw meat
2726 description = S("Raw Meat"),
2827 inventory_image = "mobs_meat_raw.png",
2928 on_use = minetest.item_eat(3),
30 groups = {food_meat_raw = 1, flammable = 2},
29 groups = {food_meat_raw = 1, flammable = 2}
3130 })
3231
3332 -- cooked meat
3534 description = S("Meat"),
3635 inventory_image = "mobs_meat.png",
3736 on_use = minetest.item_eat(8),
38 groups = {food_meat = 1, flammable = 2},
37 groups = {food_meat = 1, flammable = 2}
3938 })
4039
4140 minetest.register_craft({
4241 type = "cooking",
4342 output = "mobs:meat",
4443 recipe = "mobs:meat_raw",
45 cooktime = 5,
44 cooktime = 5
4645 })
4746
4847 -- lasso
4948 minetest.register_tool("mobs:lasso", {
5049 description = S("Lasso (right-click animal to put in inventory)"),
5150 inventory_image = "mobs_magic_lasso.png",
52 groups = {flammable = 2},
51 groups = {flammable = 2}
5352 })
5453
5554 if minetest.get_modpath("farming") then
5857 recipe = {
5958 {"farming:string", "", "farming:string"},
6059 {"", "default:diamond", ""},
61 {"farming:string", "", "farming:string"},
60 {"farming:string", "", "farming:string"}
6261 }
6362 })
6463 end
6968 minetest.register_tool("mobs:net", {
7069 description = S("Net (right-click animal to put in inventory)"),
7170 inventory_image = "mobs_net.png",
72 groups = {flammable = 2},
71 groups = {flammable = 2}
7372 })
7473
7574 if minetest.get_modpath("farming") then
7877 recipe = {
7978 {"group:stick", "", "group:stick"},
8079 {"group:stick", "", "group:stick"},
81 {"farming:string", "group:stick", "farming:string"},
80 {"farming:string", "group:stick", "farming:string"}
8281 }
8382 })
8483 end
8786 minetest.register_tool("mobs:shears", {
8887 description = S("Steel Shears (right-click to shear)"),
8988 inventory_image = "mobs_shears.png",
90 groups = {flammable = 2},
91 })
92
93 minetest.register_craft({
94 output = 'mobs:shears',
95 recipe = {
96 {'', 'default:steel_ingot', ''},
97 {'', 'group:stick', 'default:steel_ingot'},
89 groups = {flammable = 2}
90 })
91
92 minetest.register_craft({
93 output = "mobs:shears",
94 recipe = {
95 {"", "default:steel_ingot", ""},
96 {"", "group:stick", "default:steel_ingot"}
9897 }
9998 })
10099
102101 minetest.register_craftitem("mobs:protector", {
103102 description = S("Mob Protection Rune"),
104103 inventory_image = "mobs_protector.png",
105 groups = {flammable = 2},
104 groups = {flammable = 2}
106105 })
107106
108107 minetest.register_craft({
110109 recipe = {
111110 {"default:stone", "default:stone", "default:stone"},
112111 {"default:stone", "default:goldblock", "default:stone"},
113 {"default:stone", "default:stone", "default:stone"},
112 {"default:stone", "default:stone", "default:stone"}
113 }
114 })
115
116 -- level 2 protection rune
117 minetest.register_craftitem("mobs:protector2", {
118 description = S("Mob Protection Rune (Level 2)"),
119 inventory_image = "mobs_protector2.png",
120 groups = {flammable = 2}
121 })
122
123 minetest.register_craft({
124 output = "mobs:protector2",
125 recipe = {
126 {"mobs:protector", "default:mese_crystal", "mobs:protector"},
127 {"default:mese_crystal", "default:diamondblock", "default:mese_crystal"},
128 {"mobs:protector", "default:mese_crystal", "mobs:protector"}
114129 }
115130 })
116131
118133 minetest.register_craftitem("mobs:saddle", {
119134 description = S("Saddle"),
120135 inventory_image = "mobs_saddle.png",
121 groups = {flammable = 2},
136 groups = {flammable = 2, saddle = 1}
122137 })
123138
124139 minetest.register_craft({
126141 recipe = {
127142 {"mobs:leather", "mobs:leather", "mobs:leather"},
128143 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
129 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
130 }
131 })
144 {"mobs:leather", "default:steel_ingot", "mobs:leather"}
145 }
146 })
147
148
149 -- make sure we can register fences
150 if minetest.get_modpath("default") and default.register_fence then
132151
133152 -- mob fence (looks like normal fence but collision is 2 high)
134153 default.register_fence("mobs:fence_wood", {
141160 type = "fixed",
142161 fixed = {
143162 {-0.5, -0.5, -0.5, 0.5, 1.9, 0.5},
144 },
163 }
164 }
165 })
166 end
167
168 -- mob fence top (has enlarged collisionbox to stop mobs getting over)
169 minetest.register_node("mobs:fence_top", {
170 description = S("Mob Fence Top"),
171 drawtype = "nodebox",
172 tiles = {"default_wood.png"},
173 paramtype = "light",
174 is_ground_content = false,
175 groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
176 sounds = default.node_sound_wood_defaults(),
177 node_box = {
178 type = "fixed",
179 fixed = {-0.2, -0.5, -0.2, 0.2, 0, 0.2}
145180 },
146 })
147
148 -- mob fence top (has enlarged collisionbox to stop mobs getting over)
149 minetest.register_node("mobs:fence_top", {
150 description = S("Mob Fence Top"),
151 drawtype = "nodebox",
152 tiles = {"default_wood.png"},
153 paramtype = "light",
154 is_ground_content = false,
155 groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
156 sounds = default.node_sound_wood_defaults(),
157 node_box = {
158 type = "fixed",
159 fixed = {-0.2, -0.5, -0.2, 0.2, 0, 0.2},
160 },
161 collision_box = {
162 type = "fixed",
163 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4},
164 },
165 selection_box = {
166 type = "fixed",
167 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4},
168 },
181 collision_box = {
182 type = "fixed",
183 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4}
184 },
185 selection_box = {
186 type = "fixed",
187 fixed = {-0.4, -1.5, -0.4, 0.4, 0, 0.4}
188 }
169189 })
170190
171191 minetest.register_craft({
172192 output = "mobs:fence_top 12",
173193 recipe = {
174194 {"group:wood", "group:wood", "group:wood"},
175 {"", "default:fence_wood", ""},
176 }
177 })
195 {"", "default:fence_wood", ""}
196 }
197 })
198
178199
179200 -- items that can be used as fuel
180201 minetest.register_craft({
181202 type = "fuel",
182203 recipe = "mobs:nametag",
183 burntime = 3,
204 burntime = 3
184205 })
185206
186207 minetest.register_craft({
187208 type = "fuel",
188209 recipe = "mobs:lasso",
189 burntime = 7,
210 burntime = 7
190211 })
191212
192213 minetest.register_craft({
193214 type = "fuel",
194215 recipe = "mobs:net",
195 burntime = 8,
216 burntime = 8
196217 })
197218
198219 minetest.register_craft({
199220 type = "fuel",
200221 recipe = "mobs:leather",
201 burntime = 4,
222 burntime = 4
202223 })
203224
204225 minetest.register_craft({
205226 type = "fuel",
206227 recipe = "mobs:saddle",
207 burntime = 7,
228 burntime = 7
208229 })
209230
210231 minetest.register_craft({
211232 type = "fuel",
212233 recipe = "mobs:fence_wood",
213 burntime = 7,
234 burntime = 7
214235 })
215236
216237 minetest.register_craft({
217238 type = "fuel",
218239 recipe = "mobs:fence_top",
219 burntime = 2,
220 })
240 burntime = 2
241 })
242
243
244 -- this tool spawns same mob and adds owner, protected, nametag info
245 -- then removes original entity, this is used for fixing any issues.
246
247 local tex_obj
248
249 minetest.register_tool(":mobs:mob_reset_stick", {
250 description = S("Mob Reset Stick"),
251 inventory_image = "default_stick.png^[colorize:#ff000050",
252 stack_max = 1,
253 groups = {not_in_creative_inventory = 1},
254
255 on_use = function(itemstack, user, pointed_thing)
256
257 if pointed_thing.type ~= "object" then
258 return
259 end
260
261 local obj = pointed_thing.ref
262
263 local control = user:get_player_control()
264 local sneak = control and control.sneak
265
266 -- spawn same mob with saved stats, with random texture
267 if obj and not sneak then
268
269 local self = obj:get_luaentity()
270 local obj2 = minetest.add_entity(obj:get_pos(), self.name)
271
272 if obj2 then
273
274 local ent2 = obj2:get_luaentity()
275
276 ent2.protected = self.protected
277 ent2.owner = self.owner
278 ent2.nametag = self.nametag
279 ent2.gotten = self.gotten
280 ent2.tamed = self.tamed
281 ent2.health = self.health
282 ent2.order = self.order
283
284 if self.child then
285 obj2:set_velocity({x = 0, y = self.jump_height, z = 0})
286 end
287
288 obj2:set_properties({nametag = self.nametag})
289
290 obj:remove()
291 end
292 end
293
294 -- display form to enter texture name ending in .png
295 if obj and sneak then
296
297 tex_obj = obj
298
299 -- get base texture
300 local bt = tex_obj:get_luaentity().base_texture[1]
301
302 if type(bt) ~= "string" then
303 bt = ""
304 end
305
306 local name = user:get_player_name()
307
308 minetest.show_formspec(name, "mobs_texture", "size[8,4]"
309 .. "field[0.5,1;7.5,0;name;"
310 .. minetest.formspec_escape(S("Enter texture:")) .. ";" .. bt .. "]"
311 .. "button_exit[2.5,3.5;3,1;mob_texture_change;"
312 .. minetest.formspec_escape(S("Change")) .. "]")
313 end
314 end
315 })
316
317 minetest.register_on_player_receive_fields(function(player, formname, fields)
318
319 -- right-clicked with nametag and name entered?
320 if formname == "mobs_texture"
321 and fields.name
322 and fields.name ~= "" then
323
324 -- does mob still exist?
325 if not tex_obj
326 or not tex_obj:get_luaentity() then
327 return
328 end
329
330 -- make sure nametag is being used to name mob
331 local item = player:get_wielded_item()
332
333 if item:get_name() ~= "mobs:mob_reset_stick" then
334 return
335 end
336
337 -- limit name entered to 64 characters long
338 if fields.name:len() > 64 then
339 fields.name = fields.name:sub(1, 64)
340 end
341
342 -- update texture
343 local self = tex_obj:get_luaentity()
344
345 self.base_texture = {fields.name}
346
347 tex_obj:set_properties({textures = {fields.name}})
348
349 -- reset external variable
350 tex_obj = nil
351 end
352 end)
353
354
355 -- Meat Block (thanks to painterlypack.net for allowing me to use these textures)
356 minetest.register_node("mobs:meatblock", {
357 description = S("Meat Block"),
358 tiles = {"mobs_meat_top.png", "mobs_meat_bottom.png", "mobs_meat_side.png"},
359 paramtype2 = "facedir",
360 groups = {choppy = 1, oddly_breakable_by_hand = 1, flammable = 2},
361 sounds = default and default.node_sound_leaves_defaults(),
362 on_place = minetest.rotate_node,
363 on_use = minetest.item_eat(20)
364 })
365
366 minetest.register_craft({
367 output = "mobs:meatblock",
368 -- type = "shapeless",
369 recipe = {
370 {"group:food_meat", "group:food_meat", "group:food_meat"},
371 {"group:food_meat", "group:food_meat", "group:food_meat"},
372 {"group:food_meat", "group:food_meat", "group:food_meat"}
373 }
374 })
0 default
0 default?
11 tnt?
22 dye?
33 farming?
66 lucky_block?
77 cmi?
88 toolranks?
9 pathfinder?
00
11 local path = minetest.get_modpath("mobs")
2
3 -- Peaceful player privilege
4 minetest.register_privilege("peaceful_player", {
5 description = "Prevents Mobs Redo mobs from attacking player",
6 give_to_singleplayer = false
7 })
28
39 -- Mob API
410 dofile(path .. "/api.lua")
0 -- Support for the old multi-load method
1 dofile(minetest.get_modpath("intllib").."/init.lua")
02
1 -- Fallback functions for when `intllib` is not installed.
2 -- Code released under Unlicense <http://unlicense.org>.
3
4 -- Get the latest version of this file at:
5 -- https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua
6
7 local function format(str, ...)
8 local args = { ... }
9 local function repl(escape, open, num, close)
10 if escape == "" then
11 local replacement = tostring(args[tonumber(num)])
12 if open == "" then
13 replacement = replacement..close
14 end
15 return replacement
16 else
17 return "@"..open..num..close
18 end
19 end
20 return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
21 end
22
23 local gettext, ngettext
24 if minetest.get_modpath("intllib") then
25 if intllib.make_gettext_pair then
26 -- New method using gettext.
27 gettext, ngettext = intllib.make_gettext_pair()
28 else
29 -- Old method using text files.
30 gettext = intllib.Getter()
31 end
32 end
33
34 -- Fill in missing functions.
35
36 gettext = gettext or function(msgid, ...)
37 return format(msgid, ...)
38 end
39
40 ngettext = ngettext or function(msgid, msgid_plural, n, ...)
41 return format(n==1 and msgid or msgid_plural, ...)
42 end
43
44 return gettext, ngettext
22 # This file is distributed under the same license as the PACKAGE package.
33 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
44 #
5 #, fuzzy
56 msgid ""
67 msgstr ""
78 "Project-Id-Version: \n"
89 "Report-Msgid-Bugs-To: \n"
910 "POT-Creation-Date: 2017-07-29 09:13+0200\n"
10 "PO-Revision-Date: 2017-07-29 09:20+0200\n"
11 "PO-Revision-Date: 2020-08-13 21:20+0500\n"
12 "Last-Translator: Olivier Dragon <odragon@protonmail.com>\n"
1113 "Language-Team: \n"
14 "Language: fr\n"
1215 "MIME-Version: 1.0\n"
1316 "Content-Type: text/plain; charset=UTF-8\n"
1417 "Content-Transfer-Encoding: 8bit\n"
15 "X-Generator: Poedit 1.8.12\n"
16 "Last-Translator: fat115 <fat115@framasoft.org>\n"
17 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
18 "Language: fr\n"
1918
2019 #: api.lua
2120 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
6362
6463 #: crafts.lua
6564 msgid "Name Tag"
66 msgstr "Étiquette pour collier"
65 msgstr "Étiquette de collier"
6766
6867 #: crafts.lua
6968 msgid "Leather"
101100 msgid "Mob Fence"
102101 msgstr "Clôture à animaux"
103102
103 #: crafts.lua
104 msgid "Mob Fence Top"
105 msgstr "Haut de clôture à animaux"
106
104107 #: spawner.lua
105108 msgid "Mob Spawner"
106109 msgstr "Générateur de mob"
107110
108111 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr "Mob MinLumière MaxLumière Quantité DistanceJoueur"
112 msgid "(mob name) (min light) (max light) (amount) (player distance) (Y offset)"
113 msgstr "(Nom) (MinLumière) (MaxLumière) (Quantité) (Distance du Joueur) (Décalage en Y)"
111114
112115 #: spawner.lua
113116 msgid "Spawner Not Active (enter settings)"
114117 msgstr "Générateur non actif (entrez les paramètres)"
118
119 #: spawner.lua
120 msgid "Command:"
121 msgstr "Commande:"
115122
116123 #: spawner.lua
117124 msgid "Spawner Active (@1)"
77 "Project-Id-Version: Italian locale file for the Mobs Redo module\n"
88 "Report-Msgid-Bugs-To: \n"
99 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
10 "PO-Revision-Date: 2017-08-18 12:18+0100\n"
11 "Last-Translator: H4mlet <h4mlet@riseup.net>\n"
10 "PO-Revision-Date: 2020-05-11 13:33+0200\n"
11 "Last-Translator: Hamlet <hamlatgitlab@riseup.net>\n"
1212 "Language-Team: \n"
1313 "Language: it\n"
1414 "MIME-Version: 1.0\n"
1515 "Content-Type: text/plain; charset=UTF-8\n"
1616 "Content-Transfer-Encoding: 8bit\n"
1717 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
18 "X-Generator: Poedit 1.6.10\n"
18 "X-Generator: Poedit 2.2.1\n"
1919
2020 #: api.lua
2121 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr ""
22 msgstr "** Modalità pacifica attiva - non comparirà nessun mostro"
2323
2424 #: api.lua
2525 msgid "Mob has been protected!"
2727
2828 #: api.lua
2929 msgid "@1 (Tamed)"
30 msgstr "@1 (Addomesticat*)"
30 msgstr "@1 (Addomesticato)"
3131
3232 #: api.lua
3333 msgid "Not tamed!"
34 msgstr "Non addomesticat*!"
34 msgstr "Non addomesticato!"
3535
3636 #: api.lua
3737 msgid "@1 is owner!"
38 msgstr "Proprietari* @1!"
38 msgstr "Il padrone è @1!"
3939
4040 #: api.lua
4141 msgid "Missed!"
42 msgstr "Mancat*!"
42 msgstr "Mancato!"
4343
4444 #: api.lua
4545 msgid "Already protected!"
46 msgstr "Già protett*!"
46 msgstr "Già protetto!"
4747
4848 #: api.lua
4949 msgid "@1 at full health (@2)"
5151
5252 #: api.lua
5353 msgid "@1 has been tamed!"
54 msgstr "@1 è stat* addomesticat*!"
54 msgstr "@1 è stato addomesticato!"
5555
5656 #: api.lua
5757 msgid "Enter name:"
5959
6060 #: api.lua
6161 msgid "Rename"
62 msgstr "Rinominare"
62 msgstr "Rinomina"
6363
6464 #: crafts.lua
6565 msgid "Name Tag"
9999
100100 #: crafts.lua
101101 msgid "Mob Fence"
102 msgstr ""
102 msgstr "Recinzione per mob"
103103
104104 #: spawner.lua
105105 msgid "Mob Spawner"
127127 "distance[1-20] y_offset[-10 to 10]”"
128128 msgstr ""
129129 "Sintassi: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 per "
130 "disabilitare] distance[1-20] y_offset[-10 to 10]”"
130 "disabilitare] distance[1-20] y_offset[-10 fino a 10]”"
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 @1 (Tamed)=@1 (Gezähmt)
3 @1 at full health (@2)=@1 bei voller Gesundheit (@2)
4 @1 has been tamed!=@1 wurde gezähmt!
5 @1 is owner!=@1 ist der Besitzer!
6 #Active Mob Limit Reached!=
7 Already protected!=Bereits geschützt!
8 #Change=
9 #Command:=
10 Enter name:=Namen eingeben:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Lasso (Rechtsklick auf Tier, um es zu nehmen)
13 Leather=Leder
14 Meat=Fleisch
15 Missed!=Daneben!
16 Mob Fence=Kreaturen Zaun
17 #Mob Fence Top=
18 Mob Protection Rune=Kreaturschutzrune
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Kreaturenspawner-Einstellungen gescheitert!
22 Mob has been protected!=Kreatur wurde geschützt!
23 Name Tag=Namensschild
24 Net (right-click animal to put in inventory)=Netz (Rechtsklick auf Tier, um es zu nehmen)
25 Not tamed!=Nicht gezähmt!
26 Raw Meat=Rohes Fleisch
27 Rename=Umbenennen
28 Saddle=Sattel
29 Spawner Active (@1)=Spawner aktiv (@1)
30 Spawner Not Active (enter settings)=Nicht aktiv (Einstellungen eingeben)
31 Steel Shears (right-click to shear)=Stahlschere (Rechtsklick zum Scheren)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 #@1 (Tamed)=
3 #@1 at full health (@2)=
4 #@1 has been tamed!=
5 #@1 is owner!=
6 #Active Mob Limit Reached!=
7 #Already protected!=
8 #Change=
9 #Command:=
10 #Enter name:=
11 #Enter texture:=
12 #Lasso (right-click animal to put in inventory)=
13 #Leather=
14 #Meat=
15 #Missed!=
16 #Mob Fence=
17 #Mob Fence Top=
18 #Mob Protection Rune=
19 #Mob Reset Stick=
20 #Mob Spawner=
21 #Mob Spawner settings failed!=
22 #Mob has been protected!=
23 #Name Tag=
24 #Net (right-click animal to put in inventory)=
25 #Not tamed!=
26 #Raw Meat=
27 #Rename=
28 #Saddle=
29 #Spawner Active (@1)=
30 #Spawner Not Active (enter settings)=
31 #Steel Shears (right-click to shear)=
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 @1 (Tamed)=@1 (Domesticado)
3 @1 at full health (@2)=@1 con salud llena (@2)
4 @1 has been tamed!=@1 ha sido domesticado!
5 @1 is owner!=@1 es el dueño!
6 #Active Mob Limit Reached!=
7 Already protected!=Ya está protegido!
8 #Change=
9 #Command:=
10 Enter name:=Ingrese nombre:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Lazo (click derecho en animal para colocar en inventario)
13 Leather=Cuero
14 Meat=Carne
15 Missed!=Perdido!
16 #Mob Fence=
17 #Mob Fence Top=
18 Mob Protection Rune=Runa de protección de Mob
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Configuracion de generador de Mob falló!
22 Mob has been protected!=El mob ha sido protegido!
23 Name Tag=Nombrar etiqueta
24 Net (right-click animal to put in inventory)=Red (click derecho en animal para colocar en inventario)
25 Not tamed!=No domesticado!
26 Raw Meat=Carne cruda
27 Rename=Renombrar
28 Saddle=Montura
29 Spawner Active (@1)=Generador activo (@1)
30 Spawner Not Active (enter settings)=Generador no activo (ingrese config)
31 Steel Shears (right-click to shear)=Tijera de acero (click derecho para esquilar)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Mode pacifique activé - aucun monstre ne sera généré
2 @1 (Tamed)=@1 (apprivoisé)
3 @1 at full health (@2)=@1 est en pleine forme (@2)
4 @1 has been tamed!=@1 a été apprivoisé !
5 @1 is owner!=Appartient à @1 !
6 Active Mob Limit Reached!=Limite atteinte du nombre des êtres vivants actifs !
7 Already protected!=Déjà protégé !
8 Change=Changer
9 Command:=Commande :
10 Enter name:=Saisissez un nom :
11 Enter texture:=Saisissez une texture :
12 Lasso (right-click animal to put in inventory)=Lasso (clic droit sur l'animal pour le mettre dans l'inventaire)
13 Leather=Cuir
14 Meat=Viande
15 Missed!=Raté !
16 Mob Fence= Clôture à animaux
17 Mob Fence Top=Haut de clôture à animaux
18 Mob Protection Rune=Rune de protection des animaux
19 Mob Reset Stick=Baguette de réinitialisation des êtres vivants
20 Mob Spawner=Créateur d'êtres vivants
21 Mob Spawner settings failed!=Échec des paramètres du créateur d'être vivants !
22 Mob has been protected!=L'animal a été protégé !
23 Name Tag=Étiquette de collier
24 Net (right-click animal to put in inventory)=Filet (clic droit sur l'animal pour le mettre dans l'inventaire)
25 Not tamed!=Non-apprivoisé !
26 Raw Meat=Viande crue
27 Rename=Renommer
28 Saddle=Selle
29 Spawner Active (@1)=Créateur actif (@1)
30 Spawner Not Active (enter settings)=Créateur non actif (entrez les paramètres)
31 Steel Shears (right-click to shear)=Ciseaux à laine (clic droit pour tondre)
32 Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=Syntaxe : «name min_lumière[0-14] max_lumière[0-14] max_être_vivant_dans_région[0 pour désactiver] distance_joueur[1-20] décalage_y[-10 to 10]»
33 lifetimer expired, removed @1=Être immortel expiré ; @1 retiré
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Modalità pacifica attiva - non comparirà nessun mostro
2 @1 (Tamed)=@1 (Addomesticato)
3 @1 at full health (@2)=@1 in piena salute (@2)
4 @1 has been tamed!=@1 è stato addomesticato!
5 @1 is owner!=Il padrone è @1!
6 #Active Mob Limit Reached!=
7 Already protected!=Già protetto!
8 #Change=
9 #Command:=
10 Enter name:=Inserire il nome:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Lazo (click di destro per mettere l'animale nell'inventario)
13 Leather=Pelle
14 Meat=Carne
15 Missed!=Mancato!
16 Mob Fence=Recinzione per mob
17 #Mob Fence Top=
18 Mob Protection Rune=Runa di protezione per mob
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Impostazioni del generatore di mob fallite!
22 Mob has been protected!=Il mob è stato protetto!
23 Name Tag=Targhetta
24 Net (right-click animal to put in inventory)=Rete (click destro per mettere l'animale nell'inventario)
25 Not tamed!=Non addomesticato!
26 Raw Meat=Carne cruda
27 Rename=Rinomina
28 Saddle=Sella
29 Spawner Active (@1)=Generatore attivo (@1)
30 Spawner Not Active (enter settings)=Generatore inattivo (inserire le impostazioni)
31 Steel Shears (right-click to shear)=Cesoie d'acciaio (click destro per tosare)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Mod Aman Diaktifkan - Tiada Raksasa Akan Muncul
2 @1 (Tamed)=@1 (Jinak)
3 @1 at full health (@2)=Mata kesihatan @1 telah penuh (@2)
4 @1 has been tamed!=@1 telah dijinakkan!
5 @1 is owner!=Ini hak milik @1!
6 #Active Mob Limit Reached!=
7 Already protected!=Telah dilindungi!
8 #Change=
9 #Command:=
10 Enter name:=Masukkan nama:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Tanjul (klik-kanan haiwan untuk masukkan ke inventori)
13 Leather=Kulit
14 Meat=Daging Bakar
15 Missed!=Terlepas!
16 Mob Fence=Pagar Mob
17 #Mob Fence Top=
18 Mob Protection Rune=Rune Perlindungan Mob
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Penetapan Pewujud Mob gagal!
22 Mob has been protected!=Mob telah pun dilindungi!
23 Name Tag=Tanda Nama
24 Net (right-click animal to put in inventory)=Jaring (klik-kanan haiwan untuk masukkan ke inventori)
25 Not tamed!=Belum dijinakkan!
26 Raw Meat=Daging Mentah
27 Rename=Namakan semula
28 Saddle=Pelana
29 Spawner Active (@1)=Pewujud Mob Aktif (@1)
30 Spawner Not Active (enter settings)=Pewujud Mob Tidak Aktif (masukkan tetapan)
31 Steel Shears (right-click to shear)=Ketam Keluli (klik-kanan untuk mengetam bulu biri-biri)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 #@1 (Tamed)=
3 @1 at full health (@2)=@1 em plena saude (@2)
4 @1 has been tamed!=@1 foi domesticado!
5 @1 is owner!=Dono @1!
6 #Active Mob Limit Reached!=
7 #Already protected!=
8 #Change=
9 #Command:=
10 Enter name:=Insira um nome:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Laço (clique-direito no animal para por no inventario)
13 Leather=Couro
14 Meat=Carne
15 Missed!=Faltou!
16 #Mob Fence=
17 #Mob Fence Top=
18 #Mob Protection Rune=
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Configuraçao de Spawnador do Mob falhou!
22 #Mob has been protected!=
23 Name Tag=Etiqueta
24 Net (right-click animal to put in inventory)=Net (clique-direito no animal para por no inventario)
25 Not tamed!=Indomesticado!
26 Raw Meat=Carne crua
27 Rename=Renomear
28 #Saddle=
29 Spawner Active (@1)=Spawnador Ativo (@1)
30 Spawner Not Active (enter settings)=Spawnador Inativo (configurar)
31 Steel Shears (right-click to shear)=Tesoura de Aço (clique-direito para tosquiar)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** Мирный модус активирован - монстры не спаунятся
2 @1 (Tamed)=@1 (Прирученный)
3 @1 at full health (@2)=@1 при полном здоровье (@2)
4 @1 has been tamed!=@1 приручен
5 @1 is owner!=@1 владелец
6 #Active Mob Limit Reached!=
7 Already protected!=Уже защищен!
8 #Change=
9 #Command:=
10 Enter name:=Введите имя:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Лассо (Правый клик - положить животное в инвентарь)
13 Leather=Кожа
14 Meat=Мясо
15 Missed!=Промазал!
16 Mob Fence=Забор от мобов
17 #Mob Fence Top=
18 Mob Protection Rune=Защитная руна мобов
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Настройки спаунера моба провалились
22 Mob has been protected!=Моб защищен!
23 Name Tag=Новый тэг
24 Net (right-click animal to put in inventory)=Сеть (Правый клик - положить животное в инвентарь)
25 Not tamed!=Не прирученный
26 Raw Meat=Сырое мясо
27 Rename=Переименовать
28 Saddle=Седло
29 Spawner Active (@1)=Активные спаунер (@1)
30 Spawner Not Active (enter settings)=Спаунер не активен (введите настройки)
31 Steel Shears (right-click to shear)=Ножницы (Правый клик - подстричь)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 #** Peaceful Mode Active - No Monsters Will Spawn=
2 #@1 (Tamed)=
3 @1 at full health (@2)=@1 tam canında (@2)
4 @1 has been tamed!=@1 tamamen evcilleştirilmiştir!
5 @1 is owner!=Sahibi @1!
6 #Active Mob Limit Reached!=
7 #Already protected!=
8 #Change=
9 #Command:=
10 Enter name:=İsim gir:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=Kement (hayvana sağ tıklayarak envantere koy)
13 Leather=Deri
14 Meat=Et
15 Missed!=Kaçırdın!
16 Mob Fence=Canavar Yaratıcı
17 #Mob Fence Top=
18 #Mob Protection Rune=
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Yaratıcı ayarları uygulanamadı.
22 #Mob has been protected!=
23 Name Tag=İsim etiketi
24 Net (right-click animal to put in inventory)=Ağ (hayvana sağ tıklayarak envantere koy)
25 Not tamed!=Evcil değil!
26 Raw Meat=Çiğ et
27 Rename=Yeniden adlandır
28 #Saddle=
29 Spawner Active (@1)=Yaratıcı aktif (@1)
30 Spawner Not Active (enter settings)=Yaratıcı aktif değil (ayarlara gir)
31 Steel Shears (right-click to shear)=Çelik makas (sağ tıklayarak kes)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** 和平模式已激活——没有怪物会产生
2 @1 (Tamed)=@1(已驯服)
3 @1 at full health (@2)=@1已经满血(@2)
4 @1 has been tamed!=@1已经被驯服!
5 @1 is owner!=@1 是主人
6 #Active Mob Limit Reached!=
7 Already protected!=已经被保护!
8 #Change=
9 #Command:=
10 Enter name:=输入名称:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=套索(右键单击动物以放入物品栏)
13 Leather=皮革
14 Meat=肉
15 Missed!=没抓住!
16 Mob Fence=Mob 栅栏
17 #Mob Fence Top=
18 Mob Protection Rune=Mob 保护符文
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Mob 孵化器设置失败!
22 Mob has been protected!=Mob 已经被保护了!
23 Name Tag=名称标签
24 Net (right-click animal to put in inventory)=网(右键单击动物以放入物品栏)
25 Not tamed!=没有驯服!
26 Raw Meat=生肉
27 Rename=重新命名
28 Saddle=鞍
29 Spawner Active (@1)=孵化器正在运转(@1)
30 Spawner Not Active (enter settings)=孵化器未使用(输入设置)
31 Steel Shears (right-click to shear)=钢剪(右键单击以剪切)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
0 # textdomain:mobs
1 ** Peaceful Mode Active - No Monsters Will Spawn=** 和平模式已激活——沒有怪物會產生
2 @1 (Tamed)=@1(已馴服)
3 @1 at full health (@2)=@1已經滿血(@2)
4 @1 has been tamed!=@1已經被馴服!
5 @1 is owner!=@1 是主人
6 #Active Mob Limit Reached!=
7 Already protected!=已經被保護!
8 #Change=
9 #Command:=
10 Enter name:=輸入名稱:
11 #Enter texture:=
12 Lasso (right-click animal to put in inventory)=套索(右鍵單擊動物以放入物品欄)
13 Leather=皮革
14 Meat=肉
15 Missed!=沒抓住!
16 Mob Fence=Mob 柵欄
17 #Mob Fence Top=
18 Mob Protection Rune=Mob 保護符文
19 #Mob Reset Stick=
20 #Mob Spawner=
21 Mob Spawner settings failed!=Mob 孵化器設置失敗!
22 Mob has been protected!=Mob 已經被保護了!
23 Name Tag=名稱標籤
24 Net (right-click animal to put in inventory)=網(右鍵單擊動物以放入物品欄)
25 Not tamed!=沒有馴服!
26 Raw Meat=生肉
27 Rename=重新命名
28 Saddle=鞍
29 Spawner Active (@1)=孵化器正在運轉(@1)
30 Spawner Not Active (enter settings)=孵化器未使用(輸入設置)
31 Steel Shears (right-click to shear)=鋼剪(右鍵單擊以剪切)
32 #Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”=
33 #lifetimer expired, removed @1=
100100 msgid "Mob Fence"
101101 msgstr ""
102102
103 #: crafts.lua
104 msgid "Mob Fence Top"
105 msgstr ""
106
103107 #: spawner.lua
104108 msgid "Mob Spawner"
105109 msgstr ""
106110
107111 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
112 msgid "(mob name) (min light) (max light) (amount) (player distance) (Y offset)"
109113 msgstr ""
110114
111115 #: spawner.lua
112116 msgid "Spawner Not Active (enter settings)"
117 msgstr ""
118
119 #@ spawner.lua
120 msgid "Command:"
113121 msgstr ""
114122
115123 #: spawner.lua
0 # SOME DESCRIPTIVE TITLE.
1 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # This file is distributed under the same license as the PACKAGE package.
3 # IFRFSX<1079092922@qq.com>, 2020.
4 #
5 #, fuzzy
6 msgid ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: \n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
18
19 #: api.lua
20 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
21 msgstr "** 和平模式已激活——没有怪物会产生"
22
23 #: api.lua
24 msgid "Mob has been protected!"
25 msgstr "Mob 已经被保护了!"
26
27 #: api.lua
28 msgid "@1 (Tamed)"
29 msgstr "@1(已驯服)"
30
31 #: api.lua
32 msgid "Not tamed!"
33 msgstr "没有驯服!"
34
35 #: api.lua
36 msgid "@1 is owner!"
37 msgstr "@1 是主人"
38
39 #: api.lua
40 msgid "Missed!"
41 msgstr "没抓住!"
42
43 #: api.lua
44 msgid "Already protected!"
45 msgstr "已经被保护!"
46
47 #: api.lua
48 msgid "@1 at full health (@2)"
49 msgstr "@1已经满血(@2)"
50
51 #: api.lua
52 msgid "@1 has been tamed!"
53 msgstr "@1已经被驯服!"
54
55 #: api.lua
56 msgid "Enter name:"
57 msgstr "输入名称:"
58
59 #: api.lua
60 msgid "Rename"
61 msgstr "重新命名"
62
63 #: crafts.lua
64 msgid "Name Tag"
65 msgstr "名称标签"
66
67 #: crafts.lua
68 msgid "Leather"
69 msgstr "皮革"
70
71 #: crafts.lua
72 msgid "Raw Meat"
73 msgstr "生肉"
74
75 #: crafts.lua
76 msgid "Meat"
77 msgstr "肉"
78
79 #: crafts.lua
80 msgid "Lasso (right-click animal to put in inventory)"
81 msgstr "套索(右键单击动物以放入物品栏)"
82
83 #: crafts.lua
84 msgid "Net (right-click animal to put in inventory)"
85 msgstr "网(右键单击动物以放入物品栏)"
86
87 #: crafts.lua
88 msgid "Steel Shears (right-click to shear)"
89 msgstr "钢剪(右键单击以剪切)"
90
91 #: crafts.lua
92 msgid "Mob Protection Rune"
93 msgstr "Mob 保护符文"
94
95 #: crafts.lua
96 msgid "Saddle"
97 msgstr "鞍"
98
99 #: crafts.lua
100 msgid "Mob Fence"
101 msgstr "Mob 栅栏"
102
103 #: spawner.lua
104 msgid "Mob Spawner"
105 msgstr "Mob 孵化器"
106
107 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
109 msgstr "Mob/最小光量/最大光量/玩家距离"
110
111 #: spawner.lua
112 msgid "Spawner Not Active (enter settings)"
113 msgstr "孵化器未使用(输入设置)"
114
115 #: spawner.lua
116 msgid "Spawner Active (@1)"
117 msgstr "孵化器正在运转(@1)"
118
119 #: spawner.lua
120 msgid "Mob Spawner settings failed!"
121 msgstr "Mob 孵化器设置失败!"
122
123 #: spawner.lua
124 msgid ""
125 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
126 "distance[1-20] y_offset[-10 to 10]”"
127 msgstr ""
128 "语法: “物品名称 最小光亮[0-14] 最大光亮[0-14] 范围内的最大Mob数量[0 to disable] "
129 "距离[1-20] y_offset[-10 to 10]”"
0 # SOME DESCRIPTIVE TITLE.
1 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # This file is distributed under the same license as the PACKAGE package.
3 # IFRFSX<1079092922@qq.com>, 2020.
4 #
5 #, fuzzy
6 msgid ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: \n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
18
19 #: api.lua
20 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
21 msgstr "** 和平模式已激活——沒有怪物會產生"
22
23 #: api.lua
24 msgid "Mob has been protected!"
25 msgstr "Mob 已經被保護了!"
26
27 #: api.lua
28 msgid "@1 (Tamed)"
29 msgstr "@1(已馴服)"
30
31 #: api.lua
32 msgid "Not tamed!"
33 msgstr "沒有馴服!"
34
35 #: api.lua
36 msgid "@1 is owner!"
37 msgstr "@1 是主人"
38
39 #: api.lua
40 msgid "Missed!"
41 msgstr "沒抓住!"
42
43 #: api.lua
44 msgid "Already protected!"
45 msgstr "已經被保護!"
46
47 #: api.lua
48 msgid "@1 at full health (@2)"
49 msgstr "@1已經滿血(@2)"
50
51 #: api.lua
52 msgid "@1 has been tamed!"
53 msgstr "@1已經被馴服!"
54
55 #: api.lua
56 msgid "Enter name:"
57 msgstr "輸入名稱:"
58
59 #: api.lua
60 msgid "Rename"
61 msgstr "重新命名"
62
63 #: crafts.lua
64 msgid "Name Tag"
65 msgstr "名稱標籤"
66
67 #: crafts.lua
68 msgid "Leather"
69 msgstr "皮革"
70
71 #: crafts.lua
72 msgid "Raw Meat"
73 msgstr "生肉"
74
75 #: crafts.lua
76 msgid "Meat"
77 msgstr "肉"
78
79 #: crafts.lua
80 msgid "Lasso (right-click animal to put in inventory)"
81 msgstr "套索(右鍵單擊動物以放入物品欄)"
82
83 #: crafts.lua
84 msgid "Net (right-click animal to put in inventory)"
85 msgstr "網(右鍵單擊動物以放入物品欄)"
86
87 #: crafts.lua
88 msgid "Steel Shears (right-click to shear)"
89 msgstr "鋼剪(右鍵單擊以剪切)"
90
91 #: crafts.lua
92 msgid "Mob Protection Rune"
93 msgstr "Mob 保護符文"
94
95 #: crafts.lua
96 msgid "Saddle"
97 msgstr "鞍"
98
99 #: crafts.lua
100 msgid "Mob Fence"
101 msgstr "Mob 柵欄"
102
103 #: spawner.lua
104 msgid "Mob Spawner"
105 msgstr "Mob 孵化器"
106
107 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
109 msgstr "Mob/最小光量/最大光量/玩家距離"
110
111 #: spawner.lua
112 msgid "Spawner Not Active (enter settings)"
113 msgstr "孵化器未使用(輸入設置)"
114
115 #: spawner.lua
116 msgid "Spawner Active (@1)"
117 msgstr "孵化器正在運轉(@1)"
118
119 #: spawner.lua
120 msgid "Mob Spawner settings failed!"
121 msgstr "Mob 孵化器設置失敗!"
122
123 #: spawner.lua
124 msgid ""
125 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
126 "distance[1-20] y_offset[-10 to 10]”"
127 msgstr ""
128 "語法: “物品名稱 最小光亮[0-14] 最大光亮[0-14] 範圍內的最大Mob數量[0 to disable] "
129 "距離[1-20] y_offset[-10 to 10]”"
1212 {"dro", {"mobs:protector"}, 1},
1313 {"dro", {"mobs:fence_wood"}, 10},
1414 {"dro", {"mobs:fence_top"}, 12},
15 {"lig"},
15 {"lig"}
1616 })
1717 end
00 name = mobs
1 depends =
2 optional_depends = default, tnt, dye, farming, invisibility, intllib, lucky_block, cmi, toolranks, pathfinder
3 description = Adds a mob api for mods to add animals or monsters etc.
0
10 -- lib_mount by Blert2112 (edited by TenPlus1)
21
3 local enable_crash = false
4 local crash_threshold = 6.5 -- ignored if enable_crash=false
5
2 local abs, cos, floor, sin, sqrt, pi =
3 math.abs, math.cos, math.floor, math.sin, math.sqrt, math.pi
64 ------------------------------------------------------------------------------
75
86 --
108 --
119
1210 local node_ok = function(pos, fallback)
13
1411 fallback = fallback or mobs.fallback_node
1512
1613 local node = minetest.get_node_or_nil(pos)
4845
4946
5047 local function get_sign(i)
51
5248 i = i or 0
5349
5450 if i == 0 then
5551 return 0
5652 else
57 return i / math.abs(i)
53 return i / abs(i)
5854 end
5955 end
6056
6157
6258 local function get_velocity(v, yaw, y)
63
64 local x = -math.sin(yaw) * v
65 local z = math.cos(yaw) * v
59 local x = -sin(yaw) * v
60 local z = cos(yaw) * v
6661
6762 return {x = x, y = y, z = z}
6863 end
6964
7065
7166 local function get_v(v)
72 return math.sqrt(v.x * v.x + v.z * v.z)
67 return sqrt(v.x * v.x + v.z * v.z)
7368 end
7469
7570
8378
8479 local entity = attached_to:get_luaentity()
8580
86 if entity.driver
81 if entity and entity.driver
8782 and entity.driver == player then
88
8983 entity.driver = nil
9084 end
9185
9286 player:set_detach()
93 default.player_attached[player:get_player_name()] = false
87 player_api.player_attached[player:get_player_name()] = false
9488 player:set_eye_offset({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
95 default.player_set_animation(player, "stand" , 30)
96 player:set_properties({visual_size = {x = 1, y = 1} })
89 player_api.set_animation(player, "stand", 30)
90 player:set_properties({visual_size = {x = 1, y = 1}})
9791
9892 end
9993
10094 -------------------------------------------------------------------------------
101
10295
10396 minetest.register_on_leaveplayer(function(player)
10497 force_detach(player)
10598 end)
10699
107100 minetest.register_on_shutdown(function()
101
108102 local players = minetest.get_connected_players()
103
109104 for i = 1, #players do
110105 force_detach(players[i])
111106 end
118113
119114 -------------------------------------------------------------------------------
120115
116 -- Just for correct detaching
117 local function find_free_pos(pos)
118
119 local check = {
120 {x = 1, y = 0, z = 0},
121 {x = 1, y = 1, z = 0},
122 {x = -1, y = 0, z = 0},
123 {x = -1, y = 1, z = 0},
124 {x = 0, y = 0, z = 1},
125 {x = 0, y = 1, z = 1},
126 {x = 0, y = 0, z = -1},
127 {x = 0, y = 1, z = -1}
128 }
129
130 for _, c in pairs(check) do
131
132 local npos = {x = pos.x + c.x, y = pos.y + c.y, z = pos.z + c.z}
133 local node = minetest.get_node_or_nil(npos)
134
135 if node and node.name then
136
137 local def = minetest.registered_nodes[node.name]
138
139 if def and not def.walkable and
140 def.liquidtype == "none" then
141 return npos
142 end
143 end
144 end
145
146 return pos
147 end
148
149 -------------------------------------------------------------------------------
150
121151 function mobs.attach(entity, player)
122
123 local attach_at, eye_offset = {}, {}
124152
125153 entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
126154 entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
130158 local rot_view = 0
131159
132160 if entity.player_rotation.y == 90 then
133 rot_view = math.pi/2
134 end
135
136 attach_at = entity.driver_attach_at
137 eye_offset = entity.driver_eye_offset
161 rot_view = pi / 2
162 end
163
164 local attach_at = entity.driver_attach_at
165 local eye_offset = entity.driver_eye_offset
166
138167 entity.driver = player
139168
140169 force_detach(player)
141170
142171 player:set_attach(entity.object, "", attach_at, entity.player_rotation)
143 default.player_attached[player:get_player_name()] = true
172 player_api.player_attached[player:get_player_name()] = true
144173 player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0})
145174
146175 player:set_properties({
151180 })
152181
153182 minetest.after(0.2, function()
154 default.player_set_animation(player, "sit" , 30)
183
184 if player and player:is_player() then
185 player_api.set_animation(player, "sit", 30)
186 end
155187 end)
156188
157 --player:set_look_yaw(entity.object:get_yaw() - rot_view)
158189 player:set_look_horizontal(entity.object:get_yaw() - rot_view)
159190 end
160191
161192
162 function mobs.detach(player, offset)
163
193 function mobs.detach(player)
164194 force_detach(player)
165195
166 default.player_set_animation(player, "stand" , 30)
167
168 local pos = player:get_pos()
169
170 pos = {x = pos.x + offset.x, y = pos.y + 0.2 + offset.y, z = pos.z + offset.z}
171
172196 minetest.after(0.1, function()
173 player:set_pos(pos)
197
198 if player and player:is_player() then
199
200 local pos = find_free_pos(player:get_pos())
201
202 pos.y = pos.y + 0.5
203
204 player:set_pos(pos)
205 end
174206 end)
175207 end
176208
177209
178210 function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
179211
180 local rot_steer, rot_view = math.pi/2, 0
212 local yaw = entity.object:get_yaw() or 0
213 local rot_view = 0
181214
182215 if entity.player_rotation.y == 90 then
183 rot_steer, rot_view = 0, math.pi/2
216 rot_view = pi / 2
184217 end
185218
186219 local acce_y = 0
191224 -- process controls
192225 if entity.driver then
193226
194 --print ("---velo", get_v(velo))
195
196227 local ctrl = entity.driver:get_player_control()
197228
198229 -- move forwards
210241 entity.v = entity.v - entity.accel / 10
211242 end
212243
213 -- fix mob rotation
214 entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
244 -- mob rotation
245 local horz
246
247 if entity.alt_turn == true then
248
249 horz = yaw
250
251 if ctrl.left then
252 horz = horz + 0.05
253
254 elseif ctrl.right then
255 horz = horz - 0.05
256 end
257 else
258 horz = entity.driver:get_look_horizontal() or 0
259 end
260
261 entity.object:set_yaw(horz - entity.rotate)
215262
216263 if can_fly then
217
218264 -- fly up
219265 if ctrl.jump then
266
220267 velo.y = velo.y + 1
268
221269 if velo.y > entity.accel then velo.y = entity.accel end
222270
223271 elseif velo.y > 0 then
272
224273 velo.y = velo.y - 0.1
274
225275 if velo.y < 0 then velo.y = 0 end
226276 end
227277
228278 -- fly down
229279 if ctrl.sneak then
280
230281 velo.y = velo.y - 1
282
231283 if velo.y < -entity.accel then velo.y = -entity.accel end
232284
233285 elseif velo.y < 0 then
286
234287 velo.y = velo.y + 0.1
288
235289 if velo.y > 0 then velo.y = 0 end
236290 end
237
238291 else
239
240292 -- jump
241293 if ctrl.jump then
242294
245297 acce_y = acce_y + (acce_y * 3) + 1
246298 end
247299 end
248
249300 end
250301 end
251302
258309
259310 return
260311 end
261
312
262313 -- set moving animation
263314 if moving_anim then
264315 mobs:set_animation(entity, moving_anim)
273324
274325 entity.object:set_velocity({x = 0, y = 0, z = 0})
275326 entity.v = 0
327
276328 return
277329 end
278330
283335 max_spd = entity.max_speed_forward
284336 end
285337
286 if math.abs(entity.v) > max_spd then
338 if abs(entity.v) > max_spd then
287339 entity.v = entity.v - get_sign(entity.v)
288340 end
289341
290342 -- Set position, velocity and acceleration
291343 local p = entity.object:get_pos()
292 local new_velo = {x = 0, y = 0, z = 0}
293 local new_acce = {x = 0, y = -9.8, z = 0}
344
345 if not p then return end
346
347 local new_acce = {x = 0, y = -9.81, z = 0}
294348
295349 p.y = p.y - 0.5
296350
314368 minetest.sound_play("default_punch", {
315369 object = entity.object,
316370 max_hear_distance = 5
317 })
371 }, true)
318372
319373 entity.object:punch(entity.object, 1.0, {
320374 full_punch_interval = 1.0,
325379 end
326380 end
327381
328 if entity.terrain_type == 2
329 or entity.terrain_type == 3 then
382 local terrain_type = entity.terrain_type
383
384 if terrain_type == 2 or terrain_type == 3 then
330385
331386 new_acce.y = 0
332387 p.y = p.y + 1
341396 new_acce.y = 5
342397 end
343398 else
344 if math.abs(velo.y) < 1 then
399 if abs(velo.y) < 1 then
400
345401 local pos = entity.object:get_pos()
346 pos.y = math.floor(pos.y) + 0.5
402
403 if not pos then return end
404
405 pos.y = floor(pos.y) + 0.5
347406 entity.object:set_pos(pos)
348407 velo.y = 0
349408 end
353412 end
354413 end
355414
356 new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y)
415 local new_velo = get_velocity(v, yaw - rot_view, velo.y)
416
357417 new_acce.y = new_acce.y + acce_y
358418
359419 entity.object:set_velocity(new_velo)
360420 entity.object:set_acceleration(new_acce)
361421
362 -- CRASH!
363 if enable_crash then
364
365 local intensity = entity.v2 - v
366
367 if intensity >= crash_threshold then
368
369 --print("----------- crash", intensity)
370
371 entity.object:punch(entity.object, 1.0, {
372 full_punch_interval = 1.0,
373 damage_groups = {fleshy = intensity}
374 }, nil)
375
376 end
377 end
378
379422 entity.v2 = v
380423 end
381424
382425
383426 -- directional flying routine by D00Med (edited by TenPlus1)
384
385 function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
427 function mobs.fly(entity, _, speed, shoots, arrow, moving_anim, stand_anim)
386428
387429 local ctrl = entity.driver:get_player_control()
388430 local velo = entity.object:get_velocity()
389431 local dir = entity.driver:get_look_dir()
390432 local yaw = entity.driver:get_look_horizontal() + 1.57 -- offset fix between old and new commands
391 local rot_steer, rot_view = math.pi / 2, 0
392
393 if entity.player_rotation.y == 90 then
394 rot_steer, rot_view = 0, math.pi / 2
395 end
433
434 if not ctrl or not velo then return end
396435
397436 if ctrl.up then
398437 entity.object:set_velocity({
402441 })
403442
404443 elseif ctrl.down then
444
405445 entity.object:set_velocity({
406446 x = -dir.x * speed,
407 y = dir.y * speed + 2,
447 y = dir.y * speed + 2,
408448 z = -dir.z * speed
409449 })
410450
412452 entity.object:set_velocity({x = 0, y = -2, z = 0})
413453 end
414454
415 entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate)
455 entity.object:set_yaw(yaw + pi + pi / 2 - entity.rotate)
416456
417457 -- firing arrows
418458 if ctrl.LMB and ctrl.sneak and shoots then
424464 z = pos.z + 0 + dir.z * 2.5}, arrow)
425465
426466 local ent = obj:get_luaentity()
467
427468 if ent then
469
428470 ent.switch = 1 -- for mob specific arrows
429471 ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding
430472 local vec = {x = dir.x * 6, y = dir.y * 6, z = dir.z * 6}
431 local yaw = entity.driver:get_look_horizontal()
432 obj:set_yaw(yaw + math.pi / 2)
473
474 yaw = entity.driver:get_look_horizontal()
475
476 obj:set_yaw(yaw + pi / 2)
433477 obj:set_velocity(vec)
434478 else
435479 obj:remove()
438482
439483 -- change animation if stopped
440484 if velo.x == 0 and velo.y == 0 and velo.z == 0 then
441
442485 mobs:set_animation(entity, stand_anim)
443486 else
444487 -- moving animation
0
1 MOBS REDO for MINETEST
2
3 Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
4
5
6 This mod contains the API only for adding your own mobs into the world, so please use the additional modpacks to add animals, monsters etc.
7
8
9 https://forum.minetest.net/viewtopic.php?f=11&t=9917
10
11
12 Crafts:
13
14 - Nametag (paper, black dye, string) can be used right-click on a tamed mob to give them a name.
15 - Nets can be used to right-click tamed mobs to pick them up and place inside inventory as a spawn egg.
16 - Magic Lasso is similar to nets but with a better chance of picking up larger mobs.
17 - Shears are used to right-click sheep and return 1-3 wool.
18 - Protection Rune lets you protect tamed mobs from harm by other players
19 - Mob Fence and Fence Top (to stop mobs escaping/glitching through fences)
20
21 Lucky Blocks: 9
22
23
24 Changelog:
25 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
26 - 1.46- Mobs only drop rare items when killed by player (drops.min = 0 makes them rare), code tweak, pathfinding no longer sees through walkable nodes
27 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
28 - 1.44- Added ToolRanks support for swords when attacking mobs
29 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
30 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
31 - 1.41- Mob pathfinding has been updated thanks to Elkien3
32 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
33 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
34 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
35 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
36 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
37 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
38 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
39 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
40 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs
41 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
42 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
43 - 1.29- Split original Mobs Redo into a modpack to make it easier to disable mob sets (animal, monster, npc) or simply use the Api itself for your own mod
44 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
45 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
46 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
47 - 1.25- Mobs no longer spawn within 12 blocks of player or despawn within same range, spawners now have player detection, Code tidy and tweak.
48 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
49 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
50 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
51 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
52 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
53 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
54 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
55 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
56 - 1.16- Mobs follow multiple items now, Npc's can breed
57 - 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility.
58 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
59 - 1.13- Added capture function (thanks blert2112) chance of picking up mob with hand; net; magic lasso, replaced some .x models with newer .b3d one's
60 - 1.12- Added animal ownership so that players cannot steal your tamed animals
61 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
62 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
63 - 1.09- reworked breeding routine, added mob rotation value, added footstep feature, added jumping mobs with sounds feature, added magic lasso for picking up animals
64 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
65 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
66 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
67 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
68 - 1.05- Added ExeterDad's bunny's which can be picked up and tamed with 4 carrots from farming redo or farming_plus, also shears added to get wool from sheep and lastly Jordach/BSD's kitten
69 - 1.04- Added mating for sheep, cows and hogs... feed animals to make horny and hope for a baby which is half size, will grow up quick though :)
70 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
71 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
72 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
73 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
74 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
75 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
76 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
77 - 0.6 - Api now supports multi-textured mobs, e.g oerkki, dungeon master, rats and chickens have random skins when spawning (sheep fix TODO), also new Honey block
78 - 0.5 - Mobs now float in water, die from falling, and some code improvements
79 - 0.4 - Dungeon Masters and Mese Monsters have much better aim due to shoot_offset, also they can both shoot through nodes that aren't walkable (flowers, grass etc) plus new sheep sound :)
80 - 0.3 - Added LOTT's Spider mob, made Cobwebs, added KPavel's Bee with Honey and Beehives (made texture), Warthogs now have sound and can be tamed, taming of shaved sheep or milked cow with 8 wheat so it will not despawn, many bug fixes :)
81 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
82 - 0.1 - Initial Release
0
1 MOBS REDO for MINETEST
2
3 Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
4
5
6 This mod contains the API only for adding your own mobs into the world, so please use the additional modpacks to add animals, monsters etc.
7
8
9 https://forum.minetest.net/viewtopic.php?f=11&t=9917
10
11
12 Crafts:
13
14 - Nametag (paper, black dye, string) can be used right-click on a tamed mob to give them a name.
15 - Nets can be used to right-click tamed mobs to pick them up and place inside inventory as a spawn egg.
16 - Magic Lasso is similar to nets but with a better chance of picking up larger mobs.
17 - Shears are used to right-click sheep and return 1-3 wool.
18 - Protection Rune lets you protect tamed mobs from harm by other players
19 - Mob Fence and Fence Top (to stop mobs escaping/glitching through fences)
20
21 Lucky Blocks: 9
22
23
24 Changelog:
25 - 1.56 - Added arrow_override function to mob definition to tweak arrow entity settings, tamed monsters no longer despawn when outside loaded map area.
26 - 1.55 - Add 'peaceful_player' privelage and setting so mobs don't attack specific players (thanks sfence), add support for MarkBu's pathfinder mod, remove need for default mod
27 - 1.54 - Simplified animal breeding function, added editable settings (thanks Wuzzy), Child mobs now take 20 mins to grow up, reverted to simple mob spawning with setting to use area checks, on_flop added, air_damage added.
28 - 1.53 - Added 'on_map_load' settings to mobs:spawn so that mobs will only spawn when new areas of map are loaded.
29 - 1.52 - Added 'mob_active_limit' in settings to set number of mobs in game,
30 (default is 0 for unlimited), removed {immortal} from mob armor, fluid viscocity slows mobs
31 - 1.51 - Added some node checks for dangerous nodes, jumping and falling tweaks, spawn area check (thx for idea wuzzy), re-enabled mob suffocation, add 'mob_nospawn_range' setting
32 - 1.50 - Added new line_of_sight function that uses raycasting if mt5.0 is found, (thanks Astrobe), dont spawn mobs if world anchor nearby (technic or simple_anchor mods), chinese local added
33 - 1.49- Added mobs:force_capture(self, player) function, api functions now use metatables thanks to bell07
34 - 1.48- Add mobs:set_velocity(self, velocity) global function
35 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
36 - 1.46- Mobs only drop rare items when killed by player (drops.min = 0 makes them rare), code tweak, pathfinding no longer sees through walkable nodes
37 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
38 - 1.44- Added ToolRanks support for swords when attacking mobs
39 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
40 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
41 - 1.41- Mob pathfinding has been updated thanks to Elkien3
42 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
43 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
44 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
45 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
46 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
47 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
48 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
49 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
50 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs
51 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
52 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
53 - 1.29- Split original Mobs Redo into a modpack to make it easier to disable mob sets (animal, monster, npc) or simply use the Api itself for your own mod
54 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
55 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
56 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
57 - 1.25- Mobs no longer spawn within 12 blocks of player or despawn within same range, spawners now have player detection, Code tidy and tweak.
58 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
59 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
60 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
61 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
62 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
63 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
64 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
65 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
66 - 1.16- Mobs follow multiple items now, Npc's can breed
67 - 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility.
68 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
69 - 1.13- Added capture function (thanks blert2112) chance of picking up mob with hand; net; magic lasso, replaced some .x models with newer .b3d one's
70 - 1.12- Added animal ownership so that players cannot steal your tamed animals
71 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
72 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
73 - 1.09- reworked breeding routine, added mob rotation value, added footstep feature, added jumping mobs with sounds feature, added magic lasso for picking up animals
74 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
75 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
76 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
77 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
78 - 1.05- Added ExeterDad's bunny's which can be picked up and tamed with 4 carrots from farming redo or farming_plus, also shears added to get wool from sheep and lastly Jordach/BSD's kitten
79 - 1.04- Added mating for sheep, cows and hogs... feed animals to make horny and hope for a baby which is half size, will grow up quick though :)
80 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
81 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
82 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
83 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
84 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
85 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
86 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
87 - 0.6 - Api now supports multi-textured mobs, e.g oerkki, dungeon master, rats and chickens have random skins when spawning (sheep fix TODO), also new Honey block
88 - 0.5 - Mobs now float in water, die from falling, and some code improvements
89 - 0.4 - Dungeon Masters and Mese Monsters have much better aim due to shoot_offset, also they can both shoot through nodes that aren't walkable (flowers, grass etc) plus new sheep sound :)
90 - 0.3 - Added LOTT's Spider mob, made Cobwebs, added KPavel's Bee with Honey and Beehives (made texture), Warthogs now have sound and can be tamed, taming of shaved sheep or milked cow with 8 wheat so it will not despawn, many bug fixes :)
91 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
92 - 0.1 - Initial Release
1212 # If false then Mobs no longer spawn inside player protected areas
1313 mobs_spawn_protected (Spawn Mobs in protected areas) bool true
1414
15 # If false then Monsters no longer spawn inside player protected areas
16 mobs_spawn_monster_protected (Spawn Monsters in protected areas) bool true
17
1518 # If true Mobs will be removed once a map chunk is out of view
1619 remove_far_mobs (Remove far Mobs) bool true
1720
2629
2730 # When false Mob no longer drop items when killed
2831 mobs_drop_items (Mob drops) bool true
32
33 # Sets minimum distance around player that mobs cannot spawn
34 mob_nospawn_range (Mob no-spawn range) float 12.0
35
36 # Sets maximum number of active mobs in game (0 for unlimited)
37 mob_active_limit (Mob Active Limit) float 0
38
39 # Enables area check when spawning mobs
40 mob_area_spawn (Mob Area Spawn) bool false
41
42 # Enable peaceful player attack prevention
43 enable_peaceful_player (Mobs do not attack peaceful player without reason) bool false
44
45 # Enable mobs smooth rotation
46 mob_smooth_rotate (Smooth rotation for mobs) bool true
00
1 -- intllib
2 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
1 local S = mobs.intllib
42
53 -- mob spawner
64
7 local spawner_default = "mobs_animal:pumba 10 15 0 0"
5 local spawner_default = "mobs_animal:pumba 10 15 0 0 0"
86
97 minetest.register_node("mobs:spawner", {
108 tiles = {"mob_spawner.png"},
1816
1917 local meta = minetest.get_meta(pos)
2018
19 -- setup formspec
20 local head = S("(mob name) (min light) (max light) (amount)"
21 .. " (player distance) (Y offset)")
22
2123 -- text entry formspec
2224 meta:set_string("formspec",
23 "field[text;" .. S("Mob MinLight MaxLight Amount PlayerDist") .. ";${command}]")
25 "size[10,3.5]"
26 .. "label[0.15,0.5;" .. minetest.formspec_escape(head) .. "]"
27 .. "field[1,2.5;8.5,0.8;text;" .. S("Command:")
28 .. ";${command}]")
29
2430 meta:set_string("infotext", S("Spawner Not Active (enter settings)"))
2531 meta:set_string("command", spawner_default)
2632 end,
5460 local pla = tonumber(comm[5]) -- player distance (0 to disable)
5561 local yof = tonumber(comm[6]) or 0 -- Y offset to spawn mob
5662
57 if mob and mob ~= "" and mobs.spawning_mobs[mob] == true
63 if mob and mob ~= "" and mobs.spawning_mobs[mob]
5864 and num and num >= 0 and num <= 10
5965 and mlig and mlig >= 0 and mlig <= 15
6066 and xlig and xlig >= 0 and xlig <= 15
61 and pla and pla >=0 and pla <= 20
67 and pla and pla >= 0 and pla <= 20
6268 and yof and yof > -10 and yof < 10 then
6369
6470 meta:set_string("command", fields.text)
6773 else
6874 minetest.chat_send_player(name, S("Mob Spawner settings failed!"))
6975 minetest.chat_send_player(name,
70 S("Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10]”"))
76 S("Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] player_distance[1-20] y_offset[-10 to 10]”"))
7177 end
72 end,
78 end
7379 })
7480
7581
116122 -- check objects inside 9x9 area around spawner
117123 local objs = minetest.get_objects_inside_radius(pos, 9)
118124 local count = 0
119 local ent = nil
125 local ent
120126
121127 -- count mob objects of same type in area
122 for k, obj in ipairs(objs) do
128 for _, obj in ipairs(objs) do
123129
124130 ent = obj:get_luaentity()
125131
137143 if pla > 0 then
138144
139145 local in_range = 0
140 local objs = minetest.get_objects_inside_radius(pos, pla)
146 local objsp = minetest.get_objects_inside_radius(pos, pla)
141147
142 for _,oir in pairs(objs) do
148 for _, oir in pairs(objsp) do
143149
144150 if oir:is_player() then
145151
155161 end
156162 end
157163
164 -- set medium mob usually spawns in (defaults to air)
165 local reg = minetest.registered_entities[mob].fly_in
166
167 if not reg or type(reg) == "string" then
168 reg = {(reg or "air")}
169 end
170
158171 -- find air blocks within 5 nodes of spawner
159172 local air = minetest.find_nodes_in_area(
160173 {x = pos.x - 5, y = pos.y + yof, z = pos.z - 5},
161 {x = pos.x + 5, y = pos.y + yof, z = pos.z + 5},
162 {"air"})
174 {x = pos.x + 5, y = pos.y + yof, z = pos.z + 5}, reg)
163175
164176 -- spawn in random air block
165177 if air and #air > 0 then
175187 minetest.add_entity(pos2, mob)
176188 end
177189 end
178
179190 end
180191 })
Binary diff not shown