Codebase list minetest-mod-mobs-redo / 3605ce34-1458-4cf3-9588-7b66c872cb1e/upstream
Import upstream version 20181016+git20210601.1.f131806 Debian Janitor 2 years ago
32 changed file(s) with 3337 addition(s) and 5512 deletion(s). Raw diff Collapse all Expand all
+2046
-1159
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 = "20210601",
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
3330 --error("atan bassed NaN")
3633 return atann(x)
3734 end
3835 end
39
36 local table_copy = table.copy
37 local table_remove = table.remove
38 local vadd = vector.add
39 local vdirection = vector.direction
40 local vmultiply = vector.multiply
41 local vsubtract = vector.subtract
42 local settings = minetest.settings
43
44 -- creative check
45 local creative_cache = minetest.settings:get_bool("creative_mode")
46 function mobs.is_creative(name)
47 return creative_cache or minetest.check_player_privs(name,
48 {creative = true})
49 end
4050
4151 -- 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)
52 local damage_enabled = settings:get_bool("enable_damage")
53 local mobs_spawn = settings:get_bool("mobs_spawn") ~= false
54 local peaceful_only = settings:get_bool("only_peaceful_mobs")
55 local disable_blood = settings:get_bool("mobs_disable_blood")
56 local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false
57 local mobs_griefing = settings:get_bool("mobs_griefing") ~= false
58 local spawn_protected = settings:get_bool("mobs_spawn_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
84 local stuck_timeout = 3 -- how long before stuck mod starts searching
7085 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
7186
7287 -- default nodes
7792 local node_snow = "default:snow"
7893 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
7994
95 local mob_class = {
96 stepheight = 1.1,
97 fly_in = "air",
98 owner = "",
99 order = "",
100 jump_height = 4,
101 lifetimer = 180, -- 3 minutes
102 physical = true,
103 collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
104 visual_size = {x = 1, y = 1},
105 texture_mods = "",
106 makes_footstep_sound = false,
107 view_range = 5,
108 walk_velocity = 1,
109 run_velocity = 2,
110 light_damage = 0,
111 light_damage_min = 14,
112 light_damage_max = 15,
113 water_damage = 0,
114 lava_damage = 4,
115 fire_damage = 4,
116 air_damage = 0,
117 suffocation = 2,
118 fall_damage = 1,
119 fall_speed = -10, -- must be lower than -2 (default: -10)
120 drops = {},
121 armor = 100,
122 sounds = {},
123 jump = true,
124 knock_back = true,
125 walk_chance = 50,
126 stand_chance = 30,
127 attack_chance = 5,
128 passive = false,
129 blood_amount = 5,
130 blood_texture = "mobs_blood.png",
131 shoot_offset = 0,
132 floats = 1, -- floats in water by default
133 replace_offset = 0,
134 timer = 0,
135 env_damage_timer = 0, -- only used when state = "attack"
136 tamed = false,
137 pause_timer = 0,
138 horny = false,
139 hornytimer = 0,
140 child = false,
141 gotten = false,
142 health = 0,
143 reach = 3,
144 htimer = 0,
145 docile_by_day = false,
146 time_of_day = 0.5,
147 fear_height = 0,
148 runaway_timer = 0,
149 immune_to = {},
150 explosion_timer = 3,
151 allow_fuse_reset = true,
152 stop_to_explode = true,
153 dogshoot_count = 0,
154 dogshoot_count_max = 5,
155 dogshoot_count2_max = 5,
156 group_attack = false,
157 attack_monsters = false,
158 attack_animals = false,
159 attack_players = true,
160 attack_npcs = true,
161 facing_fence = false,
162 _cmi_is_mob = true
163 }
164
165 local mob_class_meta = {__index = mob_class}
166
80167
81168 -- play sound
82 local mob_sound = function(self, sound)
169 function mob_class:mob_sound(sound)
170
171 local pitch = 1.0
172
173 -- higher pitch for a child
174 if self.child then pitch = pitch * 1.5 end
175
176 -- a little random pitch to be different
177 pitch = pitch + random(-10, 10) * 0.005
83178
84179 if sound then
85180 minetest.sound_play(sound, {
86181 object = self.object,
87182 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
183 max_hear_distance = self.sounds.distance,
184 pitch = pitch
185 }, true)
90186 end
91187 end
92188
93189
94190 -- attack player/mob
95 local do_attack = function(self, player)
191 function mob_class:do_attack(player)
96192
97193 if self.state == "attack" then
98194 return
102198 self.state = "attack"
103199
104200 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
201 self:mob_sound(self.sounds.war_cry)
106202 end
107203 end
108204
110206 -- calculate distance
111207 local get_distance = function(a, b)
112208
209 if not a or not b then return 50 end -- nil check
210
113211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114212
115213 return square(x * x + y * y + z * z)
117215
118216
119217 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
218 function mob_class:collision()
121219
122220 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124221 local x, z = 0, 0
125222 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126223
127224 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128225
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
226 if object:is_player() then
227 -- or (object:get_luaentity()
228 -- and object:get_luaentity()._cmi_is_mob == true
229 -- and object ~= self.object) then
131230
132231 local pos2 = object:get_pos()
133232 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
141240 end
142241
143242
243 -- check if string exists in another string or table
244 local check_for = function(look_for, look_inside)
245
246 if type(look_inside) == "string" and look_inside == look_for then
247
248 return true
249
250 elseif type(look_inside) == "table" then
251
252 for _, str in pairs(look_inside) do
253
254 if str == look_for then
255 return true
256 end
257
258 if str:find("group:") then
259
260 local group = str:split(":")[2]
261
262 if minetest.get_item_group(look_for, group) ~= 0 then
263 return true
264 end
265 end
266 end
267 end
268
269 return false
270 end
271
272
144273 -- move mob in facing direction
145 local set_velocity = function(self, v)
274 function mob_class:set_velocity(v)
275
276 -- halt mob if it has been ordered to stay
277 if self.order == "stand" then
278
279 self.object:set_velocity({x = 0, y = 0, 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
251408 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
409 function mobs:set_animation(entity, anim)
410 entity.set_animation(entity, anim)
411 end
412
413
414 -- check line of sight (BrunoMine)
415 local line_of_sight = function(self, pos1, pos2, stepsize)
416
417 stepsize = stepsize or 1
418
419 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
420
421 -- normal walking and flying mobs can see you through air
422 if s == true then
423 return true
424 end
425
426 -- New pos1 to be analyzed
427 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
428
429 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
430
431 -- Checks the return
432 if r == true then return true end
433
434 -- Nodename found
435 local nn = minetest.get_node(pos).name
436
437 -- Target Distance (td) to travel
438 local td = get_distance(pos1, pos2)
439
440 -- Actual Distance (ad) traveled
441 local ad = 0
442
443 -- It continues to advance in the line of sight in search of a real
444 -- obstruction which counts as 'walkable' nodebox.
445 while minetest.registered_nodes[nn]
446 and (minetest.registered_nodes[nn].walkable == false) do
447
448 -- Check if you can still move forward
449 if td < ad + stepsize then
450 return true -- Reached the target
451 end
452
453 -- Moves the analyzed pos
454 local d = get_distance(pos1, pos2)
455
456 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
457 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
458 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
459
460 -- NaN checks
461 if d == 0
462 or npos1.x ~= npos1.x
463 or npos1.y ~= npos1.y
464 or npos1.z ~= npos1.z then
465 return false
466 end
467
468 ad = ad + stepsize
469
470 -- scan again
471 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
472
473 if r == true then return true end
474
475 -- New Nodename found
476 nn = minetest.get_node(pos).name
477 end
478
479 return false
254480 end
255481
256482
257483 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
484 local new_line_of_sight = function(self, pos1, pos2, stepsize)
259485
260486 if not pos1 or not pos2 then return end
261487
262488 stepsize = stepsize or 1
263489
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
490 local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
265491
266492 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267493
280506 local nn = minetest.get_node(pos).name
281507
282508 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
509 -- obstruction which counts as 'walkable' nodebox.
284510 while minetest.registered_nodes[nn]
285511 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
512
513 npos1 = vadd(npos1, stepv)
289514
290515 if get_distance(npos1, pos2) < stepsize then return true end
291516
301526 return false
302527 end
303528
529 -- check line of sight using raycasting (thanks Astrobe)
530 local ray_line_of_sight = function(self, pos1, pos2)
531
532 local ray = minetest.raycast(pos1, pos2, true, false)
533 local thing = ray:next()
534
535 while thing do -- thing.type, thing.ref
536
537 if thing.type == "node" then
538
539 local name = minetest.get_node(thing.under).name
540
541 if minetest.registered_items[name]
542 and minetest.registered_items[name].walkable then
543 return false
544 end
545 end
546
547 thing = ray:next()
548 end
549
550 return true
551 end
552
553
554 function mob_class:line_of_sight(pos1, pos2, stepsize)
555
556 if minetest.raycast then -- only use if minetest 5.0 is detected
557 return ray_line_of_sight(self, pos1, pos2)
558 end
559
560 return line_of_sight(self, pos1, pos2, stepsize)
561 end
562
304563 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
564 function mobs:line_of_sight(entity, pos1, pos2, stepsize)
565 return entity:line_of_sight(pos1, pos2, stepsize)
566 end
567
568
569 function mob_class:attempt_flight_correction(override)
570
571 if self:flight_check() and override ~= true then return true end
572
573 -- We are not flying in what we are supposed to.
574 -- See if we can find intended flight medium and return to it
575 local pos = self.object:get_pos() ; if not pos then return true end
576 local searchnodes = self.fly_in
577
578 if type(searchnodes) == "string" then
579 searchnodes = {self.fly_in}
580 end
581
582 local flyable_nodes = minetest.find_nodes_in_area(
583 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
584 {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes)
585 -- pos.y + 0 hopefully fixes floating swimmers
586
587 if #flyable_nodes < 1 then
588 return false
589 end
590
591 local escape_target = flyable_nodes[random(#flyable_nodes)]
592 local escape_direction = vdirection(pos, escape_target)
593
594 self.object:set_velocity(
595 vmultiply(escape_direction, 1)) --self.run_velocity))
596
597 return true
308598 end
309599
310600
311601 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
602 function mob_class:flight_check()
313603
314604 local def = minetest.registered_nodes[self.standing_in]
315605
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
606 if not def then return false end
607
608 -- are we standing inside what we should be to fly/swim ?
609 if check_for(self.standing_in, self.fly_in) then
321610 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
332611 end
333612
334613 -- stops mobs getting stuck inside stairs and plantlike nodes
342621 end
343622
344623
624 -- turn mob to face position
625 local yaw_to_pos = function(self, target, rot)
626
627 rot = rot or 0
628
629 local pos = self.object:get_pos()
630 local vec = {x = target.x - pos.x, z = target.z - pos.z}
631 local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
632
633 if target.x > pos.x then
634 yaw = yaw + pi
635 end
636
637 yaw = self:set_yaw(yaw, rot)
638
639 return yaw
640 end
641
642 function mobs:yaw_to_pos(self, target, rot)
643 return yaw_to_pos(self, target, rot)
644 end
645
646
647 -- if stay near set then check periodically for nodes and turn towards them
648 function mob_class:do_stay_near()
649
650 if not self.stay_near then return false end
651
652 local pos = self.object:get_pos()
653 local searchnodes = self.stay_near[1]
654 local chance = self.stay_near[2] or 10
655
656 if not pos or random(chance) > 1 then
657 return false
658 end
659
660 if type(searchnodes) == "string" then
661 searchnodes = {self.stay_near[1]}
662 end
663
664 local r = self.view_range
665 local nearby_nodes = minetest.find_nodes_in_area(
666 {x = pos.x - r, y = pos.y - 1, z = pos.z - r},
667 {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
668
669 if #nearby_nodes < 1 then
670 return false
671 end
672
673 yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)])
674
675 self:set_animation("walk")
676
677 self:set_velocity(self.walk_velocity)
678
679 return true
680 end
681
682
345683 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
684 local effect = function(pos, amount, texture, min_size, max_size,
685 radius, gravity, glow, fall)
347686
348687 radius = radius or 2
349688 min_size = min_size or 0.5
351690 gravity = gravity or -10
352691 glow = glow or 0
353692
693 if fall == true then
694 fall = 0
695 elseif fall == false then
696 fall = radius
697 else
698 fall = -radius
699 end
700
354701 minetest.add_particlespawner({
355702 amount = amount,
356703 time = 0.25,
357704 minpos = pos,
358705 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
706 minvel = {x = -radius, y = fall, z = -radius},
360707 maxvel = {x = radius, y = radius, z = radius},
361708 minacc = {x = 0, y = gravity, z = 0},
362709 maxacc = {x = 0, y = gravity, z = 0},
365712 minsize = min_size,
366713 maxsize = max_size,
367714 texture = texture,
368 glow = glow,
715 glow = glow
369716 })
370717 end
371718
719 function mobs:effect(pos, amount, texture, min_size, max_size,
720 radius, gravity, glow, fall)
721
722 effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
723 end
724
372725
373726 -- update nametag colour
374 local update_tag = function(self)
727 function mob_class:update_tag()
375728
376729 local col = "#00FF00"
377730 local qua = self.hp_max / 4
392745 nametag = self.nametag,
393746 nametag_color = col
394747 })
395
396748 end
397749
398750
399751 -- drop items
400 local item_drop = function(self)
752 function mob_class:item_drop()
753
754 -- no drops if disabled by setting or mob is child
755 if not mobs_drop_items or self.child then return end
756
757 local pos = self.object:get_pos()
758
759 -- check for drops function
760 self.drops = type(self.drops) == "function"
761 and self.drops(pos) or self.drops
401762
402763 -- check for nil or no drops
403764 if not self.drops or #self.drops == 0 then
404765 return
405766 end
406767
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
413768 -- 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
769 local death_by_player = self.cause_of_death
770 and self.cause_of_death.puncher
771 and self.cause_of_death.puncher:is_player()
416772
417773 local obj, item, num
418 local pos = self.object:get_pos()
419774
420775 for n = 1, #self.drops do
421776
422 if random(1, self.drops[n].chance) == 1 then
777 if random(self.drops[n].chance) == 1 then
423778
424779 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425780 item = self.drops[n].name
435790 end
436791 end
437792
438 -- only drop rare items (drops.min=0) if killed by player
793 -- only drop rare items (drops.min = 0) if killed by player
439794 if death_by_player then
440795 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
441796
448803 obj:set_velocity({
449804 x = random(-10, 10) / 9,
450805 y = 6,
451 z = random(-10, 10) / 9,
806 z = random(-10, 10) / 9
452807 })
453808
454809 elseif obj then
461816 end
462817
463818
819 -- remove mob and descrease counter
820 local remove_mob = function(self, decrease)
821
822 self.object:remove()
823
824 if decrease and active_limit > 0 then
825
826 active_mobs = active_mobs - 1
827
828 if active_mobs < 0 then
829 active_mobs = 0
830 end
831 end
832 --print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
833 end
834
835 -- global function for removing mobs
836 function mobs:remove(self, decrease)
837 remove_mob(self, decrease)
838 end
839
840
464841 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
842 function mob_class:check_for_death(cmi_cause)
843
844 -- We dead already
845 if self.state == "die" then
846 return true
847 end
466848
467849 -- has health actually changed?
468850 if self.health == self.old_health and self.health > 0 then
469 return
470 end
851 return false
852 end
853
854 local damaged = self.health < self.old_health
471855
472856 self.old_health = self.health
473857
474858 -- still got some health? play hurt sound
475859 if self.health > 0 then
476860
477 mob_sound(self, self.sounds.damage)
861 -- only play hurt sound if damaged
862 if damaged then
863 self:mob_sound(self.sounds.damage)
864 end
478865
479866 -- make sure health isn't higher than max
480867 if self.health > self.hp_max then
492879 self.htimer = 2
493880 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
494881
495 update_tag(self)
882 self:update_tag()
496883 end
497884
498885 return false
501888 self.cause_of_death = cmi_cause
502889
503890 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
891 self:item_drop()
892
893 self:mob_sound(self.sounds.death)
507894
508895 local pos = self.object:get_pos()
509896
510897 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
898 if pos and self.on_die then
899
900 self:on_die(pos)
514901
515902 if use_cmi then
516903 cmi.notify_die(self.object, cmi_cause)
517904 end
518905
519 self.object:remove()
906 remove_mob(self, true)
520907
521908 return true
522909 end
523910
524 -- default death function and die animation (if defined)
911 -- check for custom death function and die animation
525912 if self.animation
526913 and self.animation.die_start
527914 and self.animation.die_end then
528915
529916 local frames = self.animation.die_end - self.animation.die_start
530917 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
918 local length = max((frames / speed), 0)
919 local rot = self.animation.die_rotate and 5
532920
533921 self.attack = nil
922 self.following = nil
534923 self.v_start = false
535924 self.timer = 0
536925 self.blinktimer = 0
537926 self.passive = true
538927 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
928 self.object:set_properties({
929 pointable = false, collide_with_objects = false,
930 automatic_rotate = rot, static_save = false
931 })
932 self:set_velocity(0)
933 self:set_animation("die")
541934
542935 minetest.after(length, function(self)
543936
544 if use_cmi and self.object:get_luaentity() then
545 cmi.notify_die(self.object, cmi_cause)
546 end
547
548 self.object:remove()
937 if self.object:get_luaentity() then
938
939 if use_cmi then
940 cmi.notify_die(self.object, cmi_cause)
941 end
942
943 remove_mob(self, true)
944 end
549945 end, self)
550 else
946
947 return true
948
949 elseif pos then -- otherwise remove mod and show particle effect
551950
552951 if use_cmi then
553952 cmi.notify_die(self.object, cmi_cause)
554953 end
555954
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
955 remove_mob(self, true)
956
957 effect(pos, 20, "tnt_smoke.png")
958 end
560959
561960 return true
562961 end
563962
564963
964 -- get node but use fallback for nil or unknown
965 local node_ok = function(pos, fallback)
966
967 fallback = fallback or mobs.fallback_node
968
969 local node = minetest.get_node_or_nil(pos)
970
971 if node and minetest.registered_nodes[node.name] then
972 return node
973 end
974
975 return minetest.registered_nodes[fallback]
976 end
977
978
979 -- Returns true is node can deal damage to self
980 local is_node_dangerous = function(self, nodename)
981
982 if self.water_damage > 0
983 and minetest.get_item_group(nodename, "water") ~= 0 then
984 return true
985 end
986
987 if self.lava_damage > 0
988 and minetest.get_item_group(nodename, "lava") ~= 0 then
989 return true
990 end
991
992 if self.fire_damage > 0
993 and minetest.get_item_group(nodename, "fire") ~= 0 then
994 return true
995 end
996
997 if minetest.registered_nodes[nodename].damage_per_second > 0 then
998 return true
999 end
1000
1001 return false
1002 end
1003
1004
5651005 -- is mob facing a cliff
566 local is_at_cliff = function(self)
1006 function mob_class:is_at_cliff()
5671007
5681008 if self.fear_height == 0 then -- 0 for no falling protection!
5691009 return false
5701010 end
5711011
1012 -- get yaw but if nil returned object no longer exists
5721013 local yaw = self.object:get_yaw()
1014
1015 if not yaw then return false end
1016
5731017 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
5741018 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
5751019 local pos = self.object:get_pos()
5761020 local ypos = pos.y + self.collisionbox[2] -- just above floor
5771021
578 if minetest.line_of_sight(
1022 local free_fall, blocker = minetest.line_of_sight(
5791023 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
580 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
581 , 1) then
582
1024 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
1025
1026 -- check for straight drop
1027 if free_fall then
5831028 return true
5841029 end
5851030
586 return false
587 end
588
589
590 -- get node but use fallback for nil or unknown
591 local node_ok = function(pos, fallback)
592
593 fallback = fallback or mobs.fallback_node
594
595 local node = minetest.get_node_or_nil(pos)
596
597 if node and minetest.registered_nodes[node.name] then
598 return node
599 end
600
601 return minetest.registered_nodes[fallback]
1031 local bnode = node_ok(blocker)
1032
1033 -- will we drop onto dangerous node?
1034 if is_node_dangerous(self, bnode.name) then
1035 return true
1036 end
1037
1038 local def = minetest.registered_nodes[bnode.name]
1039
1040 return (not def and def.walkable)
6021041 end
6031042
6041043
6051044 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
1045 function mob_class:do_env_damage()
6071046
6081047 -- feed/tame text timer (so mob 'full' messages dont spam chat)
6091048 if self.htimer > 0 then
6161055 self.nametag = self.nametag2
6171056 self.nametag2 = nil
6181057
619 update_tag(self)
620 end
621
622 local pos = self.object:get_pos()
1058 self:update_tag()
1059 end
1060
1061 local pos = self.object:get_pos() ; if not pos then return end
6231062
6241063 self.time_of_day = minetest.get_timeofday()
6251064
626 -- remove mob if standing inside ignore node
1065 -- halt mob if standing inside ignore node
6271066 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
1067
1068 self.object:set_velocity({x = 0, y = 0, z = 0})
1069
1070 return true
1071 end
1072
1073 -- particle appears at random mob height
1074 local py = {
1075 x = pos.x,
1076 y = pos.y + random(self.collisionbox[2], self.collisionbox[5]),
1077 z = pos.z
1078 }
1079
1080 local nodef = minetest.registered_nodes[self.standing_in]
1081
1082 -- water
1083 if self.water_damage ~= 0
1084 and nodef.groups.water then
1085
1086 self.health = self.health - self.water_damage
1087
1088 effect(py, 5, "bubble.png", nil, nil, 1, nil)
1089
1090 if self:check_for_death({type = "environment",
1091 pos = pos, node = self.standing_in}) then
1092 return true
1093 end
1094
1095 -- lava damage
1096 elseif self.lava_damage ~= 0
1097 and nodef.groups.lava then
1098
1099 self.health = self.health - self.lava_damage
1100
1101 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1102
1103 if self:check_for_death({type = "environment", pos = pos,
1104 node = self.standing_in, hot = true}) then
1105 return true
1106 end
1107
1108 -- fire damage
1109 elseif self.fire_damage ~= 0
1110 and nodef.groups.fire then
1111
1112 self.health = self.health - self.fire_damage
1113
1114 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1115
1116 if self:check_for_death({type = "environment", pos = pos,
1117 node = self.standing_in, hot = true}) then
1118 return true
1119 end
1120
1121 -- damage_per_second node check (not fire and lava)
1122 elseif nodef.damage_per_second ~= 0
1123 and nodef.groups.lava == nil and nodef.groups.fire == nil then
1124
1125 self.health = self.health - nodef.damage_per_second
1126
1127 effect(py, 5, "tnt_smoke.png")
1128
1129 if self:check_for_death({type = "environment",
1130 pos = pos, node = self.standing_in}) then
1131 return true
1132 end
1133 end
1134
1135 -- air damage
1136 if self.air_damage ~= 0 and self.standing_in == "air" then
1137
1138 self.health = self.health - self.air_damage
1139
1140 effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
1141
1142 if self:check_for_death({type = "environment",
1143 pos = pos, node = self.standing_in}) then
1144 return true
1145 end
6301146 end
6311147
6321148 -- is mob light sensative, or scared of the dark :P
6391155
6401156 self.health = self.health - self.light_damage
6411157
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 --[[
1158 effect(py, 5, "tnt_smoke.png")
1159
1160 if self:check_for_death({type = "light"}) then
1161 return true
1162 end
1163 end
1164 end
1165
6941166 --- 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"})
1167 if (self.suffocation and self.suffocation ~= 0)
1168 and (nodef.walkable == nil or nodef.walkable == true)
1169 and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
1170 and (nodef.node_box == nil or nodef.node_box.type == "regular")
1171 and (nodef.groups.disable_suffocation ~= 1) then
1172
1173 local damage
1174
1175 if self.suffocation == true then
1176 damage = 2
1177 else
1178 damage = (self.suffocation or 2)
1179 end
1180
1181 self.health = self.health - damage
1182
1183 if self:check_for_death({type = "suffocation",
1184 pos = pos, node = self.standing_in}) then
1185 return true
1186 end
1187 end
1188
1189 return self:check_for_death({type = "unknown"})
7071190 end
7081191
7091192
7101193 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
1194 function mob_class:do_jump()
7121195
7131196 if not self.jump
7141197 or self.jump_height == 0
7221205
7231206 -- something stopping us while moving?
7241207 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
1208 and self:get_velocity() > 0.5
7261209 and self.object:get_velocity().y ~= 0 then
7271210 return false
7281211 end
7301213 local pos = self.object:get_pos()
7311214 local yaw = self.object:get_yaw()
7321215
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
1216 -- sanity check
1217 if not yaw then return false end
1218
1219 -- we can only jump if standing on solid node
1220 if minetest.registered_nodes[self.standing_on].walkable == false then
7411221 return false
7421222 end
7431223
7451225 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
7461226 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
7471227
1228 -- set y_pos to base of mob
1229 pos.y = pos.y + self.collisionbox[2]
1230
7481231 -- what is in front of mob?
7491232 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
1233 x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
7531234 })
7541235
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
1236 -- what is above and in front?
1237 local nodt = node_ok({
1238 x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
1239 })
1240
1241 local blocked = minetest.registered_nodes[nodt.name].walkable
1242
1243 --print("standing on:", self.standing_on, pos.y - 0.25)
1244 --print("in front:", nod.name, pos.y + 0.5)
1245 --print("in front above:", nodt.name, pos.y + 1.5)
1246
1247 -- jump if standing on solid node (not snow) and not blocked above
1248 if (self.walk_chance == 0
1249 or minetest.registered_items[nod.name].walkable)
1250 and not blocked
1251 and nod.name ~= node_snow then
7641252
7651253 if not nod.name:find("fence")
766 and not nod.name:find("gate") then
1254 and not nod.name:find("gate")
1255 and not nod.name:find("wall") then
7671256
7681257 local v = self.object:get_velocity()
7691258
7701259 v.y = self.jump_height
7711260
772 set_animation(self, "jump") -- only when defined
1261 self:set_animation("jump") -- only when defined
7731262
7741263 self.object:set_velocity(v)
7751264
7791268 if self.object:get_luaentity() then
7801269
7811270 self.object:set_acceleration({
782 x = v.x * 2,--1.5,
1271 x = v.x * 2,
7831272 y = 0,
784 z = v.z * 2,--1.5
1273 z = v.z * 2
7851274 })
7861275 end
7871276 end, self, v)
7881277
789 if get_velocity(self) > 0 then
790 mob_sound(self, self.sounds.jump)
791 end
1278 if self:get_velocity() > 0 then
1279 self:mob_sound(self.sounds.jump)
1280 end
1281
1282 return true
7921283 else
7931284 self.facing_fence = true
7941285 end
795
796 return true
1286 end
1287
1288 -- if blocked against a block/wall for 5 counts then turn
1289 if not self.following
1290 and (self.facing_fence or blocked) then
1291
1292 self.jump_count = (self.jump_count or 0) + 1
1293
1294 if self.jump_count > 4 then
1295
1296 local yaw = self.object:get_yaw() or 0
1297 local turn = random(0, 2) + 1.35
1298
1299 yaw = self:set_yaw(yaw + turn, 12)
1300
1301 self.jump_count = 0
1302 end
7971303 end
7981304
7991305 return false
8131319 obj_pos = objs[n]:get_pos()
8141320
8151321 dist = get_distance(pos, obj_pos)
1322
8161323 if dist < 1 then dist = 1 end
8171324
8181325 local damage = floor((4 / dist) * radius)
8271334 end
8281335
8291336
1337 -- can mob see player
1338 local is_invisible = function(self, player_name)
1339
1340 if mobs.invis[player_name] and not self.ignore_invisibility then
1341 return true
1342 end
1343 end
1344
1345
8301346 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
1347 function mob_class:follow_holding(clicker)
1348
1349 if is_invisible(self, clicker:get_player_name()) then
8341350 return false
8351351 end
8361352
8371353 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
1354
1355 -- are we holding an item mob can follow ?
1356 if check_for(item:get_name(), self.follow) then
8431357 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
8541358 end
8551359
8561360 return false
8571361 end
8581362
1363 -- Thanks Wuzzy for the following editable settings
1364 local HORNY_TIME = 30
1365 local HORNY_AGAIN_TIME = 300
1366 local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
8591367
8601368 -- 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
1369 function mob_class:breed()
1370
1371 -- child takes a long time before growing into adult
8641372 if self.child == true then
8651373
8661374 self.hornytimer = self.hornytimer + 1
8671375
868 if self.hornytimer > 240 then
1376 if self.hornytimer > CHILD_GROW_TIME then
8691377
8701378 self.child = false
8711379 self.hornytimer = 0
8751383 mesh = self.base_mesh,
8761384 visual_size = self.base_size,
8771385 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
1386 selectionbox = self.base_selbox
8791387 })
8801388
8811389 -- custom function when child grows up
8831391 self.on_grown(self)
8841392 else
8851393 -- 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 })
1394 -- self.object:set_velocity({
1395 -- x = 0,
1396 -- y = self.jump_height,
1397 -- z = 0
1398 -- })
1399 local pos = self.object:get_pos() ; if not pos then return end
1400 local ent = self.object:get_luaentity()
1401 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
1402 self.object:set_pos(pos)
8911403 end
8921404 end
8931405
8941406 return
8951407 end
8961408
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
1409 -- horny animal can mate for HORNY_TIME seconds,
1410 -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
8991411 if self.horny == true
900 and self.hornytimer < 240 then
1412 and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
9011413
9021414 self.hornytimer = self.hornytimer + 1
9031415
904 if self.hornytimer >= 240 then
1416 if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
9051417 self.hornytimer = 0
9061418 self.horny = false
9071419 end
9091421
9101422 -- find another same animal who is also horny and mate if nearby
9111423 if self.horny == true
912 and self.hornytimer <= 40 then
1424 and self.hornytimer <= HORNY_TIME then
9131425
9141426 local pos = self.object:get_pos()
9151427
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
1428 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
1429 "heart.png", 3, 4, 1, 0.1)
9171430
9181431 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
1432 local ent
9211433
9221434 for n = 1, #objs do
9231435
9311443 if ent.name == self.name then
9321444 canmate = true
9331445 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
1446 local entname = ent.name:split(":")
1447 local selfname = self.name:split(":")
9361448
9371449 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
1450 entname = entname[2]:split("_")
1451 selfname = selfname[2]:split("_")
9401452
9411453 if entname[1] == selfname[1] then
9421454 canmate = true
9451457 end
9461458 end
9471459
948 if ent
1460 -- found another similar horny animal that isn't self?
1461 if ent and ent.object ~= self.object
9491462 and canmate == true
9501463 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
1464 and ent.hornytimer <= HORNY_TIME then
1465
1466 local pos2 = ent.object:get_pos()
1467
1468 -- Have mobs face one another
1469 yaw_to_pos(self, pos2)
1470 yaw_to_pos(ent, self.object:get_pos())
1471
1472 self.hornytimer = HORNY_TIME + 1
1473 ent.hornytimer = HORNY_TIME + 1
1474
1475 -- have we reached active mob limit
1476 if active_limit > 0 and active_mobs >= active_limit then
1477 minetest.chat_send_player(self.owner,
1478 S("Active Mob Limit Reached!")
1479 .. " (" .. active_mobs
1480 .. " / " .. active_limit .. ")")
1481 return
1482 end
9601483
9611484 -- spawn baby
9621485 minetest.after(5, function(self, ent)
9691492 if self.on_breed then
9701493
9711494 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
1495 if self:on_breed(ent) == false then
9731496 return
9741497 end
9751498 else
9761499 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
9771500 end
9781501
1502 pos.y = pos.y + 0.5 -- spawn child a little higher
1503
9791504 local mob = minetest.add_entity(pos, self.name)
9801505 local ent2 = mob:get_luaentity()
9811506 local textures = self.base_texture
9901515 textures = textures,
9911516 visual_size = {
9921517 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
1518 y = self.base_size.y * .5
9941519 },
9951520 collisionbox = {
9961521 self.base_colbox[1] * .5,
9981523 self.base_colbox[3] * .5,
9991524 self.base_colbox[4] * .5,
10001525 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1526 self.base_colbox[6] * .5
10021527 },
10031528 selectionbox = {
10041529 self.base_selbox[1] * .5,
10061531 self.base_selbox[3] * .5,
10071532 self.base_selbox[4] * .5,
10081533 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1534 self.base_selbox[6] * .5
10101535 },
10111536 })
10121537 -- tamed and owned by parents' owner
10151540 ent2.owner = self.owner
10161541 end, self, ent)
10171542
1018 num = 0
1019
10201543 break
10211544 end
10221545 end
10251548
10261549
10271550 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1551 function mob_class:replace(pos)
1552
1553 local vel = self.object:get_velocity()
1554 if not vel then return end
10291555
10301556 if not mobs_griefing
10311557 or not self.replace_rate
10321558 or not self.replace_what
10331559 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1560 or vel.y ~= 0
1561 or random(self.replace_rate) > 1 then
10361562 return
10371563 end
10381564
10551581
10561582 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
10571583
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
1584 -- print("replace node = ".. minetest.get_node(pos).name, pos.y)
10631585
10641586 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
1587
1588 local oldnode = what or ""
1589 local newnode = with
1590
1591 -- pass actual node name when using table or groups
1592 if type(oldnode) == "table"
1593 or oldnode:find("group:") then
1594 oldnode = minetest.get_node(pos).name
1595 end
1596
1597 if self:on_replace(pos, oldnode, newnode) == false then
1598 return
1599 end
1600 end
1601
1602 minetest.set_node(pos, {name = with})
10781603 end
10791604 end
10801605
10811606
10821607 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1608 function mob_class:day_docile()
10841609
10851610 if self.docile_by_day == false then
10861611
10981623 local los_switcher = false
10991624 local height_switcher = false
11001625
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)
1626 -- path finding and smart mob routine by rnd,
1627 -- line_of_sight and other edits by Elkien3
1628 function mob_class:smart_mobs(s, p, dist, dtime)
11031629
11041630 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1631 local target_pos = p
1632
11071633
11081634 -- is it becoming stuck?
11091635 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
11761702 end, self)
11771703 end
11781704
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1705 if abs(vsubtract(s,target_pos).y) > self.stepheight then
11801706
11811707 if height_switcher then
11821708 use_pathfind = true
11891715 end
11901716 end
11911717
1718 -- lets try find a path, first take care of positions
1719 -- since pathfinder is very sensitive
11921720 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]
11961721
11971722 -- round position to center of node to avoid stuck in walls
11981723 -- also adjust height for player models!
11991724 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
12011725 s.z = floor(s.z + 0.5)
12021726
12031727 local ssight, sground = minetest.line_of_sight(s, {
12151739 p1.z = floor(p1.z + 0.5)
12161740
12171741 local dropheight = 6
1742
12181743 if self.fear_height ~= 0 then dropheight = self.fear_height end
12191744
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1745 local jumpheight = 0
1746
1747 if self.jump and self.jump_height >= 4 then
1748 jumpheight = min(ceil(self.jump_height / 4), 4)
1749
1750 elseif self.stepheight > 0.5 then
1751 jumpheight = 1
1752 end
1753
1754 self.path.way = minetest.find_path(s, p1, 16, jumpheight,
1755 dropheight, "Dijkstra")
12211756
12221757 --[[
12231758 -- show path using particles
12241759 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1760 print("-- path length:" .. tonumber(#self.path.way))
12261761 for _,pos in pairs(self.path.way) do
12271762 minetest.add_particle({
12281763 pos = pos,
12391774 ]]
12401775
12411776 self.state = ""
1242 do_attack(self, self.attack)
1777
1778 if self.attack then
1779 self:do_attack(self.attack)
1780 end
12431781
12441782 -- no path found, try something else
12451783 if not self.path.way then
12631801 end
12641802 end
12651803
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1804 local sheight = ceil(self.collisionbox[5]) + 1
12671805
12681806 -- assume mob is 2 blocks high so it digs above its head
12691807 s.y = s.y + sheight
13291867 minetest.add_item(p1, ItemStack(node1))
13301868 minetest.set_node(p1, {name = "air"})
13311869 end
1332
13331870 end
13341871 end
13351872 end
13371874 -- will try again in 2 second
13381875 self.path.stuck_timer = stuck_timeout - 2
13391876
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1877 elseif s.y < p1.y and (not self.fly) then
1878 self:do_jump() --add jump to pathfinding
1879 self.path.following = true
13421880 else
13431881 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1882 if self.attack then
1883 self:mob_sound(self.sounds.war_cry)
1884 else
1885 self:mob_sound(self.sounds.random)
1886 end
1887
1888 self:set_velocity(self.walk_velocity)
13461889
13471890 -- follow path now that it has it
13481891 self.path.following = true
13511894 end
13521895
13531896
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
1897 -- peaceful player privilege support
1898 local function is_peaceful_player(player)
1899
1900 if peaceful_player_enabled then
1901
1902 local player_name = player:get_player_name()
1903
1904 if player_name
1905 and minetest.check_player_privs(player_name, "peaceful_player") then
13661906 return true
13671907 end
13681908 end
13711911 end
13721912
13731913
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1914 -- general attack function for all mobs
1915 function mob_class:general_attack()
13761916
13771917 -- return if already attacking, passive or docile during day
13781918 if self.passive
1919 or self.state == "runaway"
13791920 or self.state == "attack"
1380 or day_docile(self) then
1921 or self:day_docile() then
13811922 return
13821923 end
13831924
1384 local s = self.object:get_pos()
1925 local s = self.object:get_pos() ; if not s then return end
13851926 local objs = minetest.get_objects_inside_radius(s, self.view_range)
13861927
13871928 -- remove entities we aren't interested in
13921933 -- are we a player?
13931934 if objs[n]:is_player() then
13941935
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1936 -- if player invisible or mob cannot attack then remove from list
1937 if not damage_enabled
1938 or self.attack_players == false
13971939 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
1940 or is_invisible(self, objs[n]:get_player_name())
1941 or (self.specific_attack
1942 and not check_for("player", self.specific_attack)) then
14001943 objs[n] = nil
14011944 --print("- pla", n)
14021945 end
14091952 or (not self.attack_animals and ent.type == "animal")
14101953 or (not self.attack_monsters and ent.type == "monster")
14111954 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1955 or (self.specific_attack
1956 and not check_for(ent.name, self.specific_attack)) then
14131957 objs[n] = nil
14141958 --print("- mob", n, self.name, ent.name)
14151959 end
14391983 -- choose closest player to attack that isnt self
14401984 if dist ~= 0
14411985 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
1986 and self:line_of_sight(sp, p, 2) == true
1987 and not is_peaceful_player(player) then
14431988 min_dist = dist
14441989 min_player = player
14451990 end
14461991 end
14471992
14481993 -- 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
1994 if min_player and random(100) > self.attack_chance then
1995 self:do_attack(min_player)
1996 end
14721997 end
14731998
14741999
14752000 -- find someone to runaway from
1476 local runaway_from = function(self)
2001 function mob_class:do_runaway_from()
14772002
14782003 if not self.runaway_from then
14792004 return
14802005 end
14812006
1482 local s = self.object:get_pos()
2007 local s = self.object:get_pos() ; if not s then return end
14832008 local p, sp, dist, pname
14842009 local player, obj, min_player, name
14852010 local min_dist = self.view_range + 1
14912016
14922017 pname = objs[n]:get_player_name()
14932018
1494 if mobs.invis[pname]
2019 if is_invisible(self, pname)
14952020 or self.owner == pname then
14962021
14972022 name = ""
15102035
15112036 -- find specific mob to runaway from
15122037 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
2038 and (self.runaway_from and check_for(name, self.runaway_from)) then
2039
15162040 sp = s
2041 p = player and player:get_pos() or s
15172042
15182043 -- aim higher to make looking up hills more realistic
15192044 p.y = p.y + 1
15232048
15242049 -- choose closest player/mob to runaway from
15252050 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
2051 and self:line_of_sight(sp, p, 2) == true then
15272052 min_dist = dist
15282053 min_player = player
15292054 end
15322057
15332058 if min_player then
15342059
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)
2060 yaw_to_pos(self, min_player:get_pos(), 3)
2061
15492062 self.state = "runaway"
15502063 self.runaway_timer = 3
15512064 self.following = nil
15542067
15552068
15562069 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
2070 function mob_class:follow_flop()
15582071
15592072 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
2073 if (self.follow ~= "" or self.order == "follow")
15622074 and not self.following
15632075 and self.state ~= "attack"
15642076 and self.state ~= "runaway" then
15652077
1566 local s = self.object:get_pos()
2078 local s = self.object:get_pos() ; if not s then return end
15672079 local players = minetest.get_connected_players()
15682080
15692081 for n = 1, #players do
15702082
15712083 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
2084 and not is_invisible(self, players[n]:get_player_name()) then
15732085
15742086 self.following = players[n]
15752087
15902102 self.following = nil
15912103 end
15922104 else
1593 -- stop following player if not holding specific item
2105 -- stop following player if not holding specific item or mob is horny
15942106 if self.following
15952107 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
2108 and (self:follow_holding(self.following) == false
2109 or self.horny) then
15972110 self.following = nil
15982111 end
15992112
16222135 if dist > self.view_range then
16232136 self.following = nil
16242137 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)
2138 yaw_to_pos(self, p)
16352139
16362140 -- anyone but standing npc's can move along
16372141 if dist > self.reach
16382142 and self.order ~= "stand" then
16392143
1640 set_velocity(self, self.walk_velocity)
2144 self:set_velocity(self.walk_velocity)
16412145
16422146 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
2147 self:set_animation("walk")
16442148 end
16452149 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
2150 self:set_velocity(0)
2151 self:set_animation("stand")
16482152 end
16492153
16502154 return
16542158
16552159 -- swimmers flop when out of their element, and swim again when back in
16562160 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
2161
2162 if not self:attempt_flight_correction() then
16592163
16602164 self.state = "flop"
2165
2166 -- do we have a custom on_flop function?
2167 if self.on_flop then
2168
2169 if self:on_flop(self) then
2170 return
2171 end
2172 end
2173
16612174 self.object:set_velocity({x = 0, y = -5, z = 0})
16622175
1663 set_animation(self, "stand")
2176 self:set_animation("stand")
16642177
16652178 return
2179
16662180 elseif self.state == "flop" then
16672181 self.state = "stand"
16682182 end
16712185
16722186
16732187 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
2188 function mob_class:dogswitch(dtime)
16752189
16762190 -- switch mode not activated
16772191 if not self.dogshoot_switch
17002214
17012215
17022216 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
2217 function mob_class:do_states(dtime)
2218
2219 local yaw = self.object:get_yaw() ; if not yaw then return end
17062220
17072221 if self.state == "stand" then
17082222
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
2223 if self.randomly_turn and random(4) == 1 then
2224
2225 local lp
17122226 local s = self.object:get_pos()
17132227 local objs = minetest.get_objects_inside_radius(s, 3)
17142228
17222236
17232237 -- look at any players nearby, otherwise turn randomly
17242238 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
2239 yaw = yaw_to_pos(self, lp)
17342240 else
17352241 yaw = yaw + random(-0.5, 0.5)
17362242 end
17372243
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
2244 yaw = self:set_yaw(yaw, 8)
2245 end
2246
2247 self:set_velocity(0)
2248 self:set_animation("stand")
17432249
17442250 -- mobs ordered to stand stay standing
17452251 if self.order ~= "stand"
17462252 and self.walk_chance ~= 0
17472253 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)
2254 and random(100) <= self.walk_chance
2255 and self.at_cliff == false then
2256
2257 self:set_velocity(self.walk_velocity)
17522258 self.state = "walk"
1753 set_animation(self, "walk")
2259 self:set_animation("walk")
17542260 end
17552261
17562262 elseif self.state == "walk" then
17572263
17582264 local s = self.object:get_pos()
1759 local lp = nil
2265 local lp
17602266
17612267 -- is there something I need to avoid?
17622268 if self.water_damage > 0
17632269 and self.lava_damage > 0 then
17642270
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
2271 lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"})
17662272
17672273 elseif self.water_damage > 0 then
17682274
17702276
17712277 elseif self.lava_damage > 0 then
17722278
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
2279 lp = minetest.find_node_near(s, 1, {"group:igniter"})
17742280 end
17752281
17762282 if lp then
17772283
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})
2284 -- if mob in dangerous node then look for land
2285 if not is_node_dangerous(self, self.standing_in) then
2286
2287 lp = minetest.find_nodes_in_area_under_air(
2288 {s.x - 5, s.y - 1, s.z - 5},
2289 {s.x + 5, s.y + 2, s.z + 5},
2290 {"group:soil", "group:stone", "group:sand",
2291 node_ice, node_snowblock})
2292
2293 -- select position of random block to climb onto
2294 lp = #lp > 0 and lp[random(#lp)]
17862295
17872296 -- did we find land?
17882297 if lp then
17892298
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)
2299 yaw = yaw_to_pos(self, lp)
2300
2301 self:do_jump()
2302 self:set_velocity(self.walk_velocity)
18032303 else
18042304 yaw = yaw + random(-0.5, 0.5)
18052305 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)
2306 end
2307
2308 yaw = self:set_yaw(yaw, 8)
18202309
18212310 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
2311 elseif self.randomly_turn and random(100) <= 30 then
18232312
18242313 yaw = yaw + random(-0.5, 0.5)
18252314
1826 yaw = set_yaw(self, yaw, 8)
2315 yaw = self:set_yaw(yaw, 8)
2316
2317 -- for flying/swimming mobs randomly move up and down also
2318 if self.fly_in
2319 and not self.following then
2320 self:attempt_flight_correction(true)
2321 end
18272322 end
18282323
18292324 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
18322325 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")
2326 or self.at_cliff
2327 or random(100) <= self.stand_chance then
2328
2329 -- don't stand if mob flies and keep_flying set
2330 if (self.fly and not self.keep_flying)
2331 or not self.fly then
2332
2333 self:set_velocity(0)
2334 self.state = "stand"
2335 self:set_animation("stand", true)
2336 end
18392337 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
2338 self:set_velocity(self.walk_velocity)
2339
2340 if self:flight_check()
18432341 and self.animation
18442342 and self.animation.fly_start
18452343 and self.animation.fly_end then
1846 set_animation(self, "fly")
2344 self:set_animation("fly")
18472345 else
1848 set_animation(self, "walk")
2346 self:set_animation("walk")
18492347 end
18502348 end
18512349
18562354
18572355 -- stop after 5 seconds or when at cliff
18582356 if self.runaway_timer > 5
1859 or is_at_cliff(self)
2357 or self.at_cliff
18602358 or self.order == "stand" then
18612359 self.runaway_timer = 0
1862 set_velocity(self, 0)
2360 self:set_velocity(0)
18632361 self.state = "stand"
1864 set_animation(self, "stand")
2362 self:set_animation("stand")
18652363 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
2364 self:set_velocity(self.run_velocity)
2365 self:set_animation("walk")
18682366 end
18692367
18702368 -- attack routines (explode, dogfight, shoot, dogshoot)
18712369 elseif self.state == "attack" then
18722370
1873 -- calculate distance from mob and enemy
2371 -- get mob and enemy positions and distance between
18742372 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
2373 local p = self.attack and self.attack:get_pos()
2374 local dist = p and get_distance(p, s) or 500
2375
2376 -- stop attacking if player out of range or invisible
18792377 if dist > self.view_range
18802378 or not self.attack
18812379 or not self.attack:get_pos()
18822380 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)
2381 or (self.attack:is_player()
2382 and is_invisible(self, self.attack:get_player_name())) then
2383
2384 --print(" ** stop attacking **", dist, self.view_range)
2385
18862386 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
2387 self:set_velocity(0)
2388 self:set_animation("stand")
18892389 self.attack = nil
18902390 self.v_start = false
18912391 self.timer = 0
18972397
18982398 if self.attack_type == "explode" then
18992399
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)
2400 yaw = yaw_to_pos(self, p)
19102401
19112402 local node_break_radius = self.explosion_radius or 1
19122403 local entity_damage_radius = self.explosion_damage_radius
19132404 or (node_break_radius * 2)
19142405
2406 -- look a little higher to fix raycast
2407 s.y = s.y + 0.5 ; p.y = p.y + 0.5
2408
19152409 -- start timer when in reach and line of sight
19162410 if not self.v_start
19172411 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
2412 and self:line_of_sight(s, p, 2) then
19192413
19202414 self.v_start = true
19212415 self.timer = 0
19222416 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
2417 self:mob_sound(self.sounds.fuse)
2418
2419 --print("=== explosion timer started", self.explosion_timer)
19252420
19262421 -- stop timer if out of reach or direct line of sight
19272422 elseif self.allow_fuse_reset
19282423 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
2424 and (dist > self.reach or not self:line_of_sight(s, p, 2)) then
2425
2426 --print("=== explosion timer stopped")
2427
19312428 self.v_start = false
19322429 self.timer = 0
19332430 self.blinktimer = 0
19342431 self.blinkstatus = false
1935 self.object:settexturemod("")
2432 self.object:set_texture_mod("")
19362433 end
19372434
19382435 -- walk right up to player unless the timer is active
19392436 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
2437 self:set_velocity(0)
19412438 else
1942 set_velocity(self, self.run_velocity)
2439 self:set_velocity(self.run_velocity)
19432440 end
19442441
19452442 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
2443 self:set_animation("run")
19472444 else
1948 set_animation(self, "walk")
2445 self:set_animation("walk")
19492446 end
19502447
19512448 if self.v_start then
19582455 self.blinktimer = 0
19592456
19602457 if self.blinkstatus then
1961 self.object:settexturemod("")
2458
2459 self.object:set_texture_mod(self.texture_mods)
19622460 else
1963 self.object:settexturemod("^[brighten")
2461
2462 self.object:set_texture_mod(self.texture_mods
2463 .. "^[brighten")
19642464 end
19652465
19662466 self.blinkstatus = not self.blinkstatus
19672467 end
19682468
1969 -- print ("=== explosion timer", self.timer)
2469 --print("=== explosion timer", self.timer)
19702470
19712471 if self.timer > self.explosion_timer then
19722472
19792479 node_break_radius = 1
19802480 end
19812481
1982 self.object:remove()
2482 remove_mob(self, true)
19832483
19842484 if minetest.get_modpath("tnt") and tnt and tnt.boom
19852485 and not minetest.is_protected(pos, "") then
19872487 tnt.boom(pos, {
19882488 radius = node_break_radius,
19892489 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
2490 sound = self.sounds.explode
19912491 })
19922492 else
19932493
19982498 })
19992499
20002500 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2501
2502 effect(pos, 32, "tnt_smoke.png", nil, nil,
2503 node_break_radius, 1, 0)
20022504 end
20032505
2004 return
2506 return true
20052507 end
20062508 end
20072509
20082510 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
2511 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
2512 or (self.attack_type == "dogshoot" and dist <= self.reach
2513 and self:dogswitch() == 0) then
20112514
20122515 if self.fly
20132516 and dist > self.reach then
20182521 local p_y = floor(p2.y + 1)
20192522 local v = self.object:get_velocity()
20202523
2021 if flight_check(self, s) then
2524 if self:flight_check() then
20222525
20232526 if me_y < p_y then
20242527
20542557 })
20552558 end
20562559 end
2057
20582560 end
20592561
20602562 -- rnd: new movement direction
20762578 return
20772579 end
20782580
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2581 if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
20802582 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2583 table_remove(self.path.way, 1)
20822584 end
20832585
20842586 -- set new temporary target
20852587 p = {x = p1.x, y = p1.y, z = p1.z}
20862588 end
20872589
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)
2590 yaw = yaw_to_pos(self, p)
20982591
20992592 -- move towards enemy if beyond mob reach
21002593 if dist > self.reach then
21032596 if self.pathfinding -- only if mob has pathfinding enabled
21042597 and enable_pathfinding then
21052598
2106 smart_mobs(self, s, p, dist, dtime)
2599 self:smart_mobs(s, p, dist, dtime)
21072600 end
21082601
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2602 if self.at_cliff then
2603
2604 self:set_velocity(0)
2605 self:set_animation("stand")
21132606 else
21142607
21152608 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2609 self:set_velocity(self.walk_velocity)
21172610 else
2118 set_velocity(self, self.run_velocity)
2611 self:set_velocity(self.run_velocity)
21192612 end
21202613
21212614 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2615 self:set_animation("run")
21232616 else
2124 set_animation(self, "walk")
2617 self:set_animation("walk")
21252618 end
21262619 end
21272620
21312624 self.path.stuck_timer = 0
21322625 self.path.following = false -- not stuck anymore
21332626
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2627 self:set_velocity(0)
2628
2629 if self.timer > 1 then
2630
2631 -- no custom attack or custom attack returns true to continue
2632 if not self.custom_attack
2633 or self:custom_attack(self, p) == true then
21392634
21402635 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
2636 self:set_animation("punch")
21482637
21492638 local p2 = p
21502639 local s2 = s
21522641 p2.y = p2.y + .5
21532642 s2.y = s2.y + .5
21542643
2155 if line_of_sight(self, p2, s2) == true then
2644 if self:line_of_sight(p2, s2) == true then
21562645
21572646 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2647 self:mob_sound(self.sounds.attack)
21592648
21602649 -- punch player (or what player is attached to)
21612650 local attached = self.attack:get_attach()
2651
21622652 if attached then
21632653 self.attack = attached
21642654 end
2655
2656 local dgroup = self.damage_group or "fleshy"
2657
21652658 self.attack:punch(self.object, 1.0, {
21662659 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2660 damage_groups = {[dgroup] = self.damage}
21682661 }, nil)
21692662 end
21702663 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
21792664 end
21802665 end
21812666
21822667 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
2668 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
2669 or (self.attack_type == "dogshoot" and dist > self.reach and
2670 self:dogswitch() == 0) then
21852671
21862672 p.y = p.y - .5
21872673 s.y = s.y + .5
21882674
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)
2675 local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
2676
2677 yaw = yaw_to_pos(self, p)
2678
2679 self:set_velocity(0)
22032680
22042681 if self.shoot_interval
22052682 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2683 and random(100) <= 60 then
22072684
22082685 self.timer = 0
2209 set_animation(self, "shoot")
2686 self:set_animation("shoot")
22102687
22112688 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2689 self:mob_sound(self.sounds.shoot_attack)
22132690
22142691 local p = self.object:get_pos()
22152692
22402717
22412718
22422719 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2720 function mob_class:falling(pos)
2721
2722 if self.fly or self.disable_falling then
22462723 return
22472724 end
22482725
22492726 -- floating in water (or falling)
22502727 local v = self.object:get_velocity()
22512728
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
2729 -- sanity check
2730 if not v then return end
2731
2732 local fall_speed = self.fall_speed
2733
2734 -- in water then use liquid viscosity for float/sink speed
2735 if self.floats == 1 and self.standing_in
2736 and minetest.registered_nodes[self.standing_in].groups.liquid then
2737
2738 local visc = min(
2739 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2740
2741 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2742 fall_speed = -1.2 / visc
22862743 else
22872744
22882745 -- fall damage onto solid ground
22972754
22982755 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992756
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2757 if self:check_for_death({type = "fall"}) then
2758 return true
23022759 end
23032760 end
23042761
23052762 self.old_y = self.object:get_pos().y
23062763 end
23072764 end
2765
2766 -- fall at set speed
2767 self.object:set_acceleration({
2768 x = 0,
2769 y = fall_speed,
2770 z = 0
2771 })
23082772 end
23092773
23102774
23122776 local tr = minetest.get_modpath("toolranks")
23132777
23142778 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2779 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162780
23172781 -- mob health check
23182782 if self.health <= 0 then
2319 return
2783 return true
23202784 end
23212785
23222786 -- custom punch function
23232787 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2788 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2789 return true
23262790 end
23272791
23282792 -- error checking when mod profiling is enabled
23292793 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
2794 minetest.log("warning",
2795 "[mobs] Mod profiling enabled, damage not enabled")
2796 return true
2797 end
2798
2799 -- is mob protected
2800 if self.protected then
2801
2802 -- did player hit mob and if so is it in protected area
2803 if hitter:is_player() then
2804
2805 local player_name = hitter:get_player_name()
2806
2807 if player_name ~= self.owner
2808 and minetest.is_protected(self.object:get_pos(), player_name) then
2809
2810 minetest.chat_send_player(hitter:get_player_name(),
2811 S("Mob has been protected!"))
2812
2813 return true
2814 end
2815
2816 -- if protection is on level 2 then dont let arrows harm mobs
2817 elseif self.protected == 2 then
2818
2819 local ent = hitter and hitter:get_luaentity()
2820
2821 if ent and ent._is_arrow then
2822
2823 return true -- arrow entity
2824
2825 elseif not ent then
2826
2827 return true -- non entity
2828 end
2829 end
23392830 end
23402831
23412832 local weapon = hitter:get_wielded_item()
23672858 end
23682859
23692860 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2861 * tmp * ((armor[group] or 0) / 100.0)
23712862 end
23722863 end
23732864
23792870 damage = self.immune_to[n][2] or 0
23802871 break
23812872
2382 -- if "all" then no tool does damage unless it's specified in list
2873 -- if "all" then no tools deal damage unless it's specified in list
23832874 elseif self.immune_to[n][1] == "all" then
23842875 damage = self.immune_to[n][2] or 0
23852876 end
23862877 end
2878
2879 --print("Mob Damage is", damage)
23872880
23882881 -- healing
23892882 if damage <= -1 then
23902883 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2884 return true
2885 end
23952886
23962887 if use_cmi
2397 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2888 and cmi.notify_punch(
2889 self.object, hitter, tflp, tool_capabilities, dir, damage) then
2890 return true
23992891 end
24002892
24012893 -- add weapon wear
24152907
24162908 if tr then
24172909 if weapon_def.original_description then
2418 weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
2910 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24192911 end
24202912 else
24212913 weapon:add_wear(wear)
24262918 -- only play hit sound and show blood effects if damage is 1 or over
24272919 if damage >= 1 then
24282920
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
2921 -- select tool use sound if found, or fallback to default
2922 local snd = weapon_def.sound and weapon_def.sound.use
2923 or "default_punch"
2924
2925 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442926
24452927 -- blood_particles
24462928 if not disable_blood and self.blood_amount > 0 then
24472929
24482930 local pos = self.object:get_pos()
2931 local blood = self.blood_texture
2932 local amount = self.blood_amount
24492933
24502934 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2935
2936 -- lots of damage = more blood :)
2937 if damage > 10 then
2938 amount = self.blood_amount * 2
2939 end
24512940
24522941 -- do we have a single blood texture or multiple?
24532942 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
2943 blood = self.blood_texture[random(#self.blood_texture)]
2944 end
2945
2946 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612947 end
24622948
24632949 -- do damage
24672953 local hot = tool_capabilities and tool_capabilities.damage_groups
24682954 and tool_capabilities.damage_groups.fire
24692955
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)
2956 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2957 return true
2958 end
2959
2960 --[[ add healthy afterglow when hit (causes lag with large textures)
24762961 minetest.after(0.1, function()
24772962
24782963 if not self.object:get_luaentity() then return end
24792964
2480 self.object:settexturemod("^[colorize:#c9900070")
2481
2482 core.after(0.3, function()
2483 self.object:settexturemod("")
2965 self.object:set_texture_mod("^[colorize:#c9900070")
2966
2967 minetest.after(0.3, function()
2968 if not self.object:get_luaentity() then return end
2969 self.object:set_texture_mod(self.texture_mods)
24842970 end)
24852971 end) ]]
24862972
24872973 end -- END if damage
24882974
24892975 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2976 if self.knock_back and tflp >= punch_interval then
24922977
24932978 local v = self.object:get_velocity()
2979
2980 -- sanity check
2981 if not v then return true end
2982
24942983 local kb = damage or 1
24952984 local up = 2
24962985
25042993 dir = dir or {x = 0, y = 0, z = 0}
25052994
25062995 -- use tool knockback value or default
2507 kb = tool_capabilities.damage_groups["knockback"] or (kb * 1.5)
2996 kb = tool_capabilities.damage_groups["knockback"] or kb
25082997
25092998 self.object:set_velocity({
25102999 x = dir.x * kb,
25203009 and self.order ~= "stand" then
25213010
25223011 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)
3012 local yaw = yaw_to_pos(self, lp, 3)
3013
25373014 self.state = "runaway"
25383015 self.runaway_timer = 0
25393016 self.following = nil
25473024 and self.child == false
25483025 and self.attack_players == true
25493026 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3027 and not is_invisible(self, name)
3028 and self.object ~= hitter then
25513029
25523030 -- attack whoever punched mob
25533031 self.state = ""
2554 do_attack(self, hitter)
3032 self:do_attack(hitter)
25553033
25563034 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3035 local objs = minetest.get_objects_inside_radius(
3036 hitter:get_pos(), self.view_range)
3037 local obj
25593038
25603039 for n = 1, #objs do
25613040
25633042
25643043 if obj and obj._cmi_is_mob then
25653044
2566 -- only alert members of same mob
3045 -- only alert members of same mob and assigned helper
25673046 if obj.group_attack == true
25683047 and obj.state ~= "attack"
25693048 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3049 and (obj.name == self.name
3050 or obj.name == self.group_helper) then
3051
3052 obj:do_attack(hitter)
25723053 end
25733054
25743055 -- have owned mobs attack player threat
25753056 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3057 obj:do_attack(self.object)
25773058 end
25783059 end
25793060 end
25823063
25833064
25843065 -- get entity staticdata
2585 local mob_staticdata = function(self)
3066 function mob_class:mob_staticdata()
3067
3068 -- this handles mob count for mobs activated, unloaded, reloaded
3069 if active_limit > 0 and self.active_toggle then
3070 active_mobs = active_mobs + self.active_toggle
3071 self.active_toggle = -self.active_toggle
3072 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3073 end
25863074
25873075 -- remove mob when out of range unless tamed
25883076 if remove_far
25923080 and not self.tamed
25933081 and self.lifetimer < 20000 then
25943082
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3083 --print("REMOVED " .. self.name)
3084
3085 remove_mob(self, true)
3086
3087 return minetest.serialize({remove_ok = true, static_save = true})
26003088 end
26013089
26023090 self.remove_ok = true
26053093 self.state = "stand"
26063094
26073095 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3096 if self.drawtype and self.drawtype == "side" then
3097 self.rotate = rad(90)
26113098 end
26123099
26133100 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3101 self.serialized_cmi_components = cmi.serialize_components(
3102 self._cmi_components)
3103 end
3104
3105 local tmp, t = {}
26183106
26193107 for _,stat in pairs(self) do
26203108
2621 local t = type(stat)
3109 t = type(stat)
26223110
26233111 if t ~= "function"
26243112 and t ~= "nil"
26253113 and t ~= "userdata"
3114 and _ ~= "object"
26263115 and _ ~= "_cmi_components" then
26273116 tmp[_] = self[_]
26283117 end
26293118 end
26303119
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3120 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3121
26323122 return minetest.serialize(tmp)
26333123 end
26343124
26353125
26363126 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
3127 function mob_class:mob_activate(staticdata, def, dtime)
3128
3129 -- if dtime == 0 then entity has just been created
3130 -- anything higher means it is respawning (thanks SorceryKid)
3131 if dtime == 0 and active_limit > 0 then
3132 self.active_toggle = 1
3133 end
3134
3135 -- remove mob if not tamed and mob total reached
3136 if active_limit > 0 and active_mobs >= active_limit and not self.tamed then
3137
3138 remove_mob(self)
3139 --print("-- mob limit reached, removing " .. self.name)
3140 return
3141 end
26383142
26393143 -- remove monsters in peaceful mode
2640 if self.type == "monster"
2641 and peaceful_only then
2642
2643 self.object:remove()
3144 if self.type == "monster" and peaceful_only then
3145
3146 remove_mob(self, true)
26443147
26453148 return
26463149 end
26493152 local tmp = minetest.deserialize(staticdata)
26503153
26513154 if tmp then
3155
3156 local t
3157
26523158 for _,stat in pairs(tmp) do
2653 self[_] = stat
2654 end
2655 end
3159
3160 t = type(stat)
3161
3162 if t ~= "function"
3163 and t ~= "nil"
3164 and t ~= "userdata" then
3165 self[_] = stat
3166 end
3167 end
3168 end
3169
3170 -- force current model into mob
3171 self.mesh = def.mesh
3172 self.base_mesh = def.mesh
3173 self.collisionbox = def.collisionbox
3174 self.selectionbox = def.selectionbox
26563175
26573176 -- select random texture, set model and size
26583177 if not self.base_texture then
26623181 def.textures = {def.textures}
26633182 end
26643183
2665 self.base_texture = def.textures and def.textures[random(1, #def.textures)]
3184 self.base_texture = def.textures and def.textures[random(#def.textures)]
26663185 self.base_mesh = def.mesh
26673186 self.base_size = self.visual_size
26683187 self.base_colbox = self.collisionbox
26823201 local selbox = self.base_selbox
26833202
26843203 -- specific texture if gotten
2685 if self.gotten == true
2686 and def.gotten_texture then
3204 if self.gotten == true and def.gotten_texture then
26873205 textures = def.gotten_texture
26883206 end
26893207
26903208 -- specific mesh if gotten
2691 if self.gotten == true
2692 and def.gotten_mesh then
3209 if self.gotten == true and def.gotten_mesh then
26933210 mesh = def.gotten_mesh
26943211 end
26953212
26963213 -- set child objects to half size
26973214 if self.child == true then
26983215
2699 vis_size = {
2700 x = self.base_size.x * .5,
2701 y = self.base_size.y * .5,
2702 }
3216 vis_size = {x = self.base_size.x * .5, y = self.base_size.y * .5}
27033217
27043218 if def.child_texture then
27053219 textures = def.child_texture[1]
27063220 end
27073221
27083222 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 }
3223 self.base_colbox[1] * .5, self.base_colbox[2] * .5,
3224 self.base_colbox[3] * .5, self.base_colbox[4] * .5,
3225 self.base_colbox[5] * .5, self.base_colbox[6] * .5}
3226
27163227 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 }
3228 self.base_selbox[1] * .5, self.base_selbox[2] * .5,
3229 self.base_selbox[3] * .5, self.base_selbox[4] * .5,
3230 self.base_selbox[5] * .5, self.base_selbox[6] * .5}
27243231 end
27253232
27263233 if self.health == 0 then
2727 self.health = random (self.hp_min, self.hp_max)
3234 self.health = random(self.hp_min, self.hp_max)
27283235 end
27293236
27303237 -- pathfinding init
27353242 self.path.following = false -- currently following path?
27363243 self.path.stuck_timer = 0 -- if stuck for too long search for path
27373244
3245 -- Armor groups (immortal = 1 for custom damage handling)
3246 local armor
3247 if type(self.armor) == "table" then
3248 armor = table_copy(self.armor)
3249 -- armor.immortal = 1
3250 else
3251 -- armor = {immortal = 1, fleshy = self.armor}
3252 armor = {fleshy = self.armor}
3253 end
3254 self.object:set_armor_groups(armor)
3255
27383256 -- mob defaults
2739 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
27403257 self.old_y = self.object:get_pos().y
27413258 self.old_health = self.health
27423259 self.sounds.distance = self.sounds.distance or 10
27463263 self.selectionbox = selbox
27473264 self.visual_size = vis_size
27483265 self.standing_in = "air"
3266 self.standing_on = "air"
27493267
27503268 -- check existing nametag
27513269 if not self.nametag then
27543272
27553273 -- set anything changed above
27563274 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")
3275 self:set_yaw((random(0, 360) - 180) / 180 * pi, 6)
3276 self:update_tag()
3277 self:set_animation("stand")
3278
3279 -- apply any texture mods
3280 self.object:set_texture_mod(self.texture_mods)
3281
3282 -- set 5.x flag to remove monsters when map area unloaded
3283 if remove_far and self.type == "monster" then
3284 self.static_save = false
3285 end
27603286
27613287 -- run on_spawn function if found
27623288 if self.on_spawn and not self.on_spawn_run then
27633289 if self.on_spawn(self) then
2764 self.on_spawn_run = true -- if true, set flag to run once only
3290 self.on_spawn_run = true -- if true, set flag to run once only
27653291 end
27663292 end
27673293
27713297 end
27723298
27733299 if use_cmi then
2774 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
3300 self._cmi_components = cmi.activate_components(
3301 self.serialized_cmi_components)
27753302 cmi.notify_activate(self.object, dtime)
27763303 end
27773304 end
27783305
27793306
27803307 -- handle mob lifetimer and expiration
2781 local mob_expire = function(self, pos, dtime)
3308 function mob_class:mob_expire(pos, dtime)
27823309
27833310 -- when lifetimer expires remove mob (except npc and tamed)
27843311 if self.type ~= "npc"
28093336
28103337 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
28113338
2812 self.object:remove()
3339 remove_mob(self, true)
28133340
28143341 return
28153342 end
28183345
28193346
28203347 -- main mob function
2821 local mob_step = function(self, dtime)
3348 function mob_class:on_step(dtime, moveresult)
3349
3350 --[[ moveresult contains this for physical mobs
3351 {
3352 touching_ground = boolean,
3353 collides = boolean,
3354 standing_on_object = boolean,
3355 collisions = {
3356 {
3357 type = string, -- "node" or "object",
3358 axis = string, -- "x", "y" or "z"
3359 node_pos = vector, -- if type is "node"
3360 object = ObjectRef, -- if type is "object"
3361 old_velocity = vector,
3362 new_velocity = vector,
3363 }}
3364 }]]
3365
3366 if self.state == "die" then return end ----------------
28223367
28233368 if use_cmi then
28243369 cmi.notify_step(self.object, dtime)
28253370 end
28263371
28273372 local pos = self.object:get_pos()
2828 local yaw = 0
3373 local yaw = self.object:get_yaw()
3374
3375 -- early warning check, if no yaw then no entity, skip rest of function
3376 if not yaw then return end
28293377
28303378 -- get node at foot level every quarter second
28313379 self.node_timer = (self.node_timer or 0) + dtime
28433391 -- what is mob standing in?
28443392 self.standing_in = node_ok({
28453393 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)
3394
3395 self.standing_on = node_ok({
3396 x = pos.x, y = pos.y + y_level - 0.25, z = pos.z}, "air").name
3397
3398 --print("standing in " .. self.standing_in)
3399
3400 -- if standing inside solid block then jump to escape
3401 if minetest.registered_nodes[self.standing_in].walkable
3402 and minetest.registered_nodes[self.standing_in].drawtype
3403 == "normal" then
3404
3405 self.object:set_velocity({
3406 x = 0,
3407 y = self.jump_height,
3408 z = 0
3409 })
3410 end
3411
3412 -- check and stop if standing at cliff and fear of heights
3413 self.at_cliff = self:is_at_cliff()
3414
3415 if self.at_cliff then
3416 self:set_velocity(0)
3417 end
3418
3419 -- has mob expired (0.25 instead of dtime since were in a timer)
3420 self:mob_expire(pos, 0.25)
3421 end
3422
3423 -- check if falling, flying, floating and return if player died
3424 if self:falling(pos) then
3425 return
3426 end
28543427
28553428 -- smooth rotation by ThomasMonroe314
2856
28573429 if self.delay and self.delay > 0 then
2858
2859 local yaw = self.object:get_yaw()
28603430
28613431 if self.delay == 1 then
28623432 yaw = self.target_yaw
28903460 self.object:set_yaw(yaw)
28913461 end
28923462
2893 -- end rotation
2894
28953463 -- knockback timer
28963464 if self.pause_timer > 0 then
28973465
29043472 if self.do_custom then
29053473
29063474 -- when false skip going any further
2907 if self.do_custom(self, dtime) == false then
3475 if self:do_custom(dtime) == false then
29083476 return
29093477 end
29103478 end
29273495 end
29283496
29293497 -- mob plays random sound at times
2930 if random(1, 100) == 1 then
2931 mob_sound(self, self.sounds.random)
3498 if random(100) == 1 then
3499 self:mob_sound(self.sounds.random)
29323500 end
29333501
29343502 -- environmental damage timer (every 1 second)
29403508 self.env_damage_timer = 0
29413509
29423510 -- check for environmental damage (water, fire, lava etc.)
2943 do_env_damage(self)
3511 if self:do_env_damage() then return end
29443512
29453513 -- 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
3514 self:replace(pos)
3515 end
3516
3517 self:general_attack()
3518
3519 self:breed()
3520
3521 self:follow_flop()
3522
3523 if self:do_states(dtime) then return end
3524
3525 self:do_jump()
3526
3527 self:do_runaway_from(self)
3528
3529 self:do_stay_near()
29613530 end
29623531
29633532
29643533 -- 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, {
3534 function mob_class:on_blast(damage)
3535
3536 --print("-- blast damage", damage)
3537
3538 self.object:punch(self.object, 1.0, {
29703539 full_punch_interval = 1.0,
29713540 damage_groups = {fleshy = damage},
29723541 }, nil)
29733542
2974 return false, true, {}
3543 -- return no damage, no knockback, no item drops, mob api handles all
3544 return false, false, {}
29753545 end
29763546
29773547
29803550 -- register mob entity
29813551 function mobs:register_mob(name, def)
29823552
2983 mobs.spawning_mobs[name] = true
2984
2985 minetest.register_entity(name, {
2986
2987 stepheight = def.stepheight or 1.1, -- was 0.6
3553 mobs.spawning_mobs[name] = {}
3554
3555 minetest.register_entity(name, setmetatable({
3556
3557 stepheight = def.stepheight,
29883558 name = name,
29893559 type = def.type,
29903560 attack_type = def.attack_type,
29913561 fly = def.fly,
2992 fly_in = def.fly_in or "air",
2993 owner = def.owner or "",
2994 order = def.order or "",
3562 fly_in = def.fly_in,
3563 keep_flying = def.keep_flying,
3564 owner = def.owner,
3565 order = def.order,
29953566 on_die = def.on_die,
3567 on_flop = def.on_flop,
29963568 do_custom = def.do_custom,
2997 jump_height = def.jump_height or 4, -- was 6
3569 jump_height = def.jump_height,
29983570 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
3571 rotate = rad(def.rotate or 0), -- 0=front 90=side 180=back 270=side2
3572 glow = def.glow,
3573 lifetimer = def.lifetimer,
30013574 hp_min = max(1, (def.hp_min or 5) * difficulty),
30023575 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},
3576 collisionbox = def.collisionbox,
30053577 selectionbox = def.selectionbox or def.collisionbox,
30063578 visual = def.visual,
3007 visual_size = def.visual_size or {x = 1, y = 1},
3579 visual_size = def.visual_size,
30083580 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,
3581 makes_footstep_sound = def.makes_footstep_sound,
3582 view_range = def.view_range,
3583 walk_velocity = def.walk_velocity,
3584 run_velocity = def.run_velocity,
30133585 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,
3586 damage_group = def.damage_group,
3587 damage_texture_modifier = def.damage_texture_modifier,
3588 light_damage = def.light_damage,
3589 light_damage_min = def.light_damage_min,
3590 light_damage_max = def.light_damage_max,
3591 water_damage = def.water_damage,
3592 lava_damage = def.lava_damage,
3593 fire_damage = def.fire_damage,
3594 air_damage = def.air_damage,
3595 suffocation = def.suffocation,
3596 fall_damage = def.fall_damage,
3597 fall_speed = def.fall_speed,
3598 drops = def.drops,
3599 armor = def.armor,
30243600 on_rightclick = def.on_rightclick,
30253601 arrow = def.arrow,
30263602 shoot_interval = def.shoot_interval,
3027 sounds = def.sounds or {},
3603 sounds = def.sounds,
30283604 animation = def.animation,
30293605 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
3606 jump = def.jump,
3607 walk_chance = def.walk_chance,
3608 stand_chance = def.stand_chance,
3609 attack_chance = def.attack_chance,
3610 passive = def.passive,
3611 knock_back = def.knock_back,
3612 blood_amount = def.blood_amount,
3613 blood_texture = def.blood_texture,
3614 shoot_offset = def.shoot_offset,
3615 floats = def.floats,
30393616 replace_rate = def.replace_rate,
30403617 replace_what = def.replace_what,
30413618 replace_with = def.replace_with,
3042 replace_offset = def.replace_offset or 0,
3619 replace_offset = def.replace_offset,
30433620 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,
3621 reach = def.reach,
30553622 texture_list = def.textures,
3623 texture_mods = def.texture_mods or "",
30563624 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,
3625 docile_by_day = def.docile_by_day,
3626 fear_height = def.fear_height,
30603627 runaway = def.runaway,
3061 runaway_timer = 0,
30623628 pathfinding = def.pathfinding,
3063 immune_to = def.immune_to or {},
3629 immune_to = def.immune_to,
30643630 explosion_radius = def.explosion_radius,
30653631 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,
3632 explosion_timer = def.explosion_timer,
3633 allow_fuse_reset = def.allow_fuse_reset,
3634 stop_to_explode = def.stop_to_explode,
30693635 custom_attack = def.custom_attack,
30703636 double_melee_attack = def.double_melee_attack,
30713637 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,
3638 dogshoot_count_max = def.dogshoot_count_max,
3639 dogshoot_count2_max = def.dogshoot_count2_max or def.dogshoot_count_max,
3640 group_attack = def.group_attack,
3641 group_helper = def.group_helper,
3642 attack_monsters = def.attacks_monsters or def.attack_monsters,
3643 attack_animals = def.attack_animals,
3644 attack_players = def.attack_players,
3645 attack_npcs = def.attack_npcs,
30803646 specific_attack = def.specific_attack,
30813647 runaway_from = def.runaway_from,
30823648 owner_loyal = def.owner_loyal,
3083 facing_fence = false,
30843649 pushable = def.pushable,
3085 _cmi_is_mob = true,
3650 stay_near = def.stay_near,
3651 randomly_turn = def.randomly_turn ~= false,
3652 ignore_invisibility = def.ignore_invisibility,
30863653
30873654 on_spawn = def.on_spawn,
30883655
3089 on_blast = def.on_blast or do_tnt,
3090
3091 on_step = mob_step,
3656 on_blast = def.on_blast, -- class redifinition
30923657
30933658 do_punch = def.do_punch,
30943659
3095 on_punch = mob_punch,
3096
30973660 on_breed = def.on_breed,
30983661
30993662 on_grown = def.on_grown,
31003663
31013664 on_activate = function(self, staticdata, dtime)
3102 return mob_activate(self, staticdata, def, dtime)
3665 return self:mob_activate(staticdata, def, dtime)
31033666 end,
31043667
31053668 get_staticdata = function(self)
3106 return mob_staticdata(self)
3669 return self:mob_staticdata(self)
31073670 end,
31083671
3109 })
3672 }, mob_class_meta))
31103673
31113674 end -- END mobs:register_mob function
31123675
31133676
31143677 -- count how many mobs of one type are inside an area
3678 -- will also return true for second value if player is inside area
31153679 local count_mobs = function(pos, type)
31163680
31173681 local total = 0
31183682 local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2)
31193683 local ent
3684 local players
31203685
31213686 for n = 1, #objs do
31223687
31283693 if ent and ent.name and ent.name == type then
31293694 total = total + 1
31303695 end
3131 end
3132 end
3133
3134 return total
3696 else
3697 players = true
3698 end
3699 end
3700
3701 return total, players
3702 end
3703
3704
3705 -- do we have enough space to spawn mob? (thanks wuzzy)
3706 local can_spawn = function(pos, name)
3707
3708 local ent = minetest.registered_entities[name]
3709 local width_x = max(1, ceil(ent.collisionbox[4] - ent.collisionbox[1]))
3710 local min_x, max_x
3711
3712 if width_x % 2 == 0 then
3713 max_x = floor(width_x / 2)
3714 min_x = -(max_x - 1)
3715 else
3716 max_x = floor(width_x / 2)
3717 min_x = -max_x
3718 end
3719
3720 local width_z = max(1, ceil(ent.collisionbox[6] - ent.collisionbox[3]))
3721 local min_z, max_z
3722
3723 if width_z % 2 == 0 then
3724 max_z = floor(width_z / 2)
3725 min_z = -(max_z - 1)
3726 else
3727 max_z = floor(width_z / 2)
3728 min_z = -max_z
3729 end
3730
3731 local max_y = max(0, ceil(ent.collisionbox[5] - ent.collisionbox[2]) - 1)
3732 local pos2
3733
3734 for y = 0, max_y do
3735 for x = min_x, max_x do
3736 for z = min_z, max_z do
3737
3738 pos2 = {x = pos.x + x, y = pos.y + y, z = pos.z + z}
3739
3740 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3741 return nil
3742 end
3743 end
3744 end
3745 end
3746
3747 -- tweak X/Z spawn pos
3748 if width_x % 2 == 0 then
3749 pos.x = pos.x + 0.5
3750 end
3751
3752 if width_z % 2 == 0 then
3753 pos.z = pos.z + 0.5
3754 end
3755
3756 return pos
3757 end
3758
3759 function mobs:can_spawn(pos, name)
3760 return can_spawn(pos, name)
31353761 end
31363762
31373763
31383764 -- global functions
3765
3766 function mobs:add_mob(pos, def)
3767
3768 -- is mob actually registered?
3769 if not mobs.spawning_mobs[def.name]
3770 or not minetest.registered_entities[def.name] then
3771 --print("--- mob doesn't exist", def.name)
3772 return
3773 end
3774
3775 -- are we over active mob limit
3776 if active_limit > 0 and active_mobs >= active_limit then
3777 --print("--- active mob limit reached", active_mobs, active_limit)
3778 return
3779 end
3780
3781 -- get total number of this mob in area
3782 local num_mob, is_pla = count_mobs(pos, def.name)
3783
3784 if not is_pla then
3785 --print("--- no players within active area, will not spawn " .. def.name)
3786 return
3787 end
3788
3789 local aoc = mobs.spawning_mobs[def.name]
3790 and mobs.spawning_mobs[def.name].aoc or 1
3791
3792 if def.ignore_count ~= true and num_mob >= aoc then
3793 --print("--- too many " .. def.name .. " in area", num_mob .. "/" .. aoc)
3794 return
3795 end
3796
3797 local mob = minetest.add_entity(pos, def.name)
3798
3799 --print("[mobs] Spawned " .. def.name .. " at " .. minetest.pos_to_string(pos))
3800
3801 local ent = mob:get_luaentity()
3802
3803 if not ent then
3804 --print("[mobs] entity not found " .. def.name)
3805 return false
3806 end
3807
3808 if def.child then
3809
3810 local textures = ent.base_texture
3811
3812 -- using specific child texture (if found)
3813 if ent.child_texture then
3814 textures = ent.child_texture[1]
3815 end
3816
3817 -- and resize to half height
3818 mob:set_properties({
3819 textures = textures,
3820 visual_size = {
3821 x = ent.base_size.x * .5,
3822 y = ent.base_size.y * .5
3823 },
3824 collisionbox = {
3825 ent.base_colbox[1] * .5,
3826 ent.base_colbox[2] * .5,
3827 ent.base_colbox[3] * .5,
3828 ent.base_colbox[4] * .5,
3829 ent.base_colbox[5] * .5,
3830 ent.base_colbox[6] * .5
3831 },
3832 selectionbox = {
3833 ent.base_selbox[1] * .5,
3834 ent.base_selbox[2] * .5,
3835 ent.base_selbox[3] * .5,
3836 ent.base_selbox[4] * .5,
3837 ent.base_selbox[5] * .5,
3838 ent.base_selbox[6] * .5
3839 },
3840 })
3841
3842 ent.child = true
3843 end
3844
3845 if def.owner then
3846 ent.tamed = true
3847 ent.owner = def.owner
3848 end
3849
3850 if def.nametag then
3851
3852 -- limit name entered to 64 characters long
3853 if def.nametag:len() > 64 then
3854 def.nametag = def.nametag:sub(1, 64)
3855 end
3856
3857 ent.nametag = def.nametag
3858
3859 ent:update_tag()
3860 end
3861
3862 return ent
3863 end
3864
31393865
31403866 function mobs:spawn_abm_check(pos, node, name)
31413867 -- global function to add additional spawn checks
31433869 end
31443870
31453871
3146 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3147 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3872 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light, interval,
3873 chance, aoc, min_height, max_height, day_toggle, on_spawn, map_load)
31483874
31493875 -- Do mobs spawn at all?
3150 if not mobs_spawn then
3876 if not mobs_spawn or not mobs.spawning_mobs[name] then
3877 --print ("--- spawning not registered for " .. name)
31513878 return
31523879 end
31533880
31543881 -- chance/spawn number override in minetest.conf for registered mob
3155 local numbers = minetest.settings:get(name)
3882 local numbers = settings:get(name)
31563883
31573884 if numbers then
31583885 numbers = numbers:split(",")
31603887 aoc = tonumber(numbers[2]) or aoc
31613888
31623889 if chance == 0 then
3163 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3890 minetest.log("warning",
3891 string.format("[mobs] %s has spawning disabled", name))
31643892 return
31653893 end
31663894
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
3895 minetest.log("action", string.format(
3896 "[mobs] Chance setting for %s changed to %s (total: %s)",
3897 name, chance, aoc))
3898 end
3899
3900 mobs.spawning_mobs[name].aoc = aoc
3901
3902 local spawn_action = function(pos, node, active_object_count,
3903 active_object_count_wider)
3904
3905 -- use instead of abm's chance setting when using lbm
3906 if map_load and random(max(1, (chance * mob_chance_multiplier))) > 1 then
3907 return
3908 end
3909
3910 -- use instead of abm's neighbor setting when using lbm
3911 if map_load and not minetest.find_node_near(pos, 1, neighbors) then
3912 --print("--- lbm neighbors not found")
3913 return
3914 end
3915
3916 -- is mob actually registered?
3917 if not mobs.spawning_mobs[name]
3918 or not minetest.registered_entities[name] then
3919 --print("--- mob doesn't exist", name)
3920 return
3921 end
3922
3923 -- are we over active mob limit
3924 if active_limit > 0 and active_mobs >= active_limit then
3925 --print("--- active mob limit reached", active_mobs, active_limit)
3926 return
3927 end
3928
3929 -- additional custom checks for spawning mob
3930 if mobs:spawn_abm_check(pos, node, name) == true then
3931 return
3932 end
3933
3934 -- do not spawn if too many entities in area
3935 if active_object_count_wider
3936 and active_object_count_wider >= max_per_block then
31973937 --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
3938 return
3939 end
3940
3941 -- get total number of this mob in area
3942 local num_mob, is_pla = count_mobs(pos, name)
3943
3944 if not is_pla then
3945 --print("--- no players within active area, will not spawn " .. name)
3946 return
3947 end
3948
3949 if num_mob >= aoc then
3950 --print("--- too many " .. name .. " in area", num_mob .. "/" .. aoc)
3951 return
3952 end
32083953
32093954 -- 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)
3955 if day_toggle ~= nil then
3956
3957 local tod = (minetest.get_timeofday() or 0) * 24000
3958
3959 if tod > 4500 and tod < 19500 then
3960 -- daylight, but mob wants night
3961 if day_toggle == false then
3962 --print("--- mob needs night", name)
32553963 return
32563964 end
3257 end
3965 else
3966 -- night time but mob wants day
3967 if day_toggle == true then
3968 --print("--- mob needs day", name)
3969 return
3970 end
3971 end
3972 end
3973
3974 -- spawn above node
3975 pos.y = pos.y + 1
3976
3977 -- are we spawning within height limits?
3978 if pos.y > max_height
3979 or pos.y < min_height then
3980 --print("--- height limits not met", name, pos.y)
3981 return
3982 end
3983
3984 -- are light levels ok?
3985 local light = minetest.get_node_light(pos)
3986 if not light
3987 or light > max_light
3988 or light < min_light then
3989 --print("--- light limits not met", name, light)
3990 return
3991 end
3992
3993 -- mobs cannot spawn in protected areas when enabled
3994 if not spawn_protected
3995 and minetest.is_protected(pos, "") then
3996 --print("--- inside protected area", name)
3997 return
3998 end
3999
4000 -- only spawn a set distance away from player
4001 local objs = minetest.get_objects_inside_radius(pos, mob_nospawn_range)
4002
4003 for n = 1, #objs do
4004
4005 if objs[n]:is_player() then
4006 --print("--- player too close", name)
4007 return
4008 end
4009 end
4010
4011 local ent = minetest.registered_entities[name]
4012
4013 -- should we check mob area for obstructions ?
4014 if mob_area_spawn ~= true then
32584015
32594016 -- 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
4017 local height = max(0, ent.collisionbox[5] - ent.collisionbox[2])
4018
4019 for n = 0, floor(height) do
32664020
32674021 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
32684022
32714025 return
32724026 end
32734027 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
4028 else
4029 -- returns position if we have enough space to spawn mob
4030 pos = can_spawn(pos, name)
4031 end
4032
4033 if pos then
4034
4035 -- adjust for mob collision box
4036 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
32844037
32854038 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 ]]
4039
4040 -- print("[mobs] Spawned " .. name .. " at "
4041 -- .. minetest.pos_to_string(pos) .. " on "
4042 -- .. node.name .. " near " .. neighbors[1])
4043
32914044 if on_spawn then
3292
3293 local ent = mob:get_luaentity()
3294
3295 on_spawn(ent, pos)
3296 end
3297 end
3298 })
4045 on_spawn(mob:get_luaentity(), pos)
4046 end
4047 else
4048 --print("--- not enough space to spawn", name)
4049 end
4050 end
4051
4052
4053 -- are we registering an abm or lbm?
4054 if map_load == true then
4055
4056 minetest.register_lbm({
4057 name = name .. "_spawning",
4058 label = name .. " spawning",
4059 nodenames = nodes,
4060 run_at_every_load = false,
4061
4062 action = function(pos, node)
4063 spawn_action(pos, node)
4064 end
4065 })
4066
4067 else
4068
4069 minetest.register_abm({
4070 label = name .. " spawning",
4071 nodenames = nodes,
4072 neighbors = neighbors,
4073 interval = interval,
4074 chance = max(1, (chance * mob_chance_multiplier)),
4075 catch_up = false,
4076
4077 action = function(pos, node, active_object_count, active_object_count_wider)
4078 spawn_action(pos, node, active_object_count, active_object_count_wider)
4079 end
4080 })
4081 end
32994082 end
33004083
33014084
33084091 end
33094092
33104093
3311 -- MarkBu's spawn function
4094 -- MarkBu's spawn function (USE this one please)
33124095 function mobs:spawn(def)
33134096
33144097 mobs:spawn_specific(
33234106 def.min_height or -31000,
33244107 def.max_height or 31000,
33254108 def.day_toggle,
3326 def.on_spawn
3327 )
4109 def.on_spawn,
4110 def.on_map_load)
33284111 end
33294112
33304113
33354118
33364119 minetest.register_entity(name, {
33374120
3338 physical = false,
4121 physical = def.physical or false,
4122 collide_with_objects = def.collide_with_objects or false,
4123 static_save = false,
4124
33394125 visual = def.visual,
33404126 visual_size = def.visual_size,
33414127 textures = def.textures,
33434129 hit_player = def.hit_player,
33444130 hit_node = def.hit_node,
33454131 hit_mob = def.hit_mob,
4132 hit_object = def.hit_object,
33464133 drop = def.drop or false, -- drops arrow as registered item when true
3347 collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
4134 collisionbox = def.collisionbox or {-.1, -.1, -.1, .1, .1, .1},
33484135 timer = 0,
4136 lifetime = def.lifetime or 4.5,
33494137 switch = 0,
33504138 owner_id = def.owner_id,
33514139 rotate = def.rotate,
33544142
33554143 on_activate = def.on_activate,
33564144
3357 on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir)
4145 on_punch = def.on_punch or function(
4146 self, hitter, tflp, tool_capabilities, dir)
33584147 end,
33594148
33604149 on_step = def.on_step or function(self, dtime)
33614150
3362 self.timer = self.timer + 1
4151 self.timer = self.timer + dtime
33634152
33644153 local pos = self.object:get_pos()
33654154
3366 if self.switch == 0
3367 or self.timer > 150 then
3368
3369 self.object:remove() ; -- print ("removed arrow")
4155 if self.switch == 0 or self.timer > self.lifetime then
4156
4157 self.object:remove() ; -- print("removed arrow")
33704158
33714159 return
33724160 end
33734161
33744162 -- does arrow have a tail (fireball)
3375 if def.tail
3376 and def.tail == 1
3377 and def.tail_texture then
4163 if def.tail and def.tail == 1 and def.tail_texture then
33784164
33794165 minetest.add_particle({
33804166 pos = pos,
33844170 collisiondetection = false,
33854171 texture = def.tail_texture,
33864172 size = def.tail_size or 5,
3387 glow = def.glow or 0,
4173 glow = def.glow or 0
33884174 })
33894175 end
33904176
33944180
33954181 if minetest.registered_nodes[node].walkable then
33964182
3397 self.hit_node(self, pos, node)
4183 self:hit_node(pos, node)
33984184
33994185 if self.drop == true then
34004186
34024188
34034189 self.lastpos = (self.lastpos or pos)
34044190
3405 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
4191 minetest.add_item(self.lastpos,
4192 self.object:get_luaentity().name)
34064193 end
34074194
3408 self.object:remove() ; -- print ("hit node")
4195 self.object:remove() ; -- print("hit node")
34094196
34104197 return
34114198 end
34124199 end
34134200
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")
4201 if self.hit_player or self.hit_mob or self.hit_object then
4202
4203 for _,player in pairs(
4204 minetest.get_objects_inside_radius(pos, 1.0)) do
4205
4206 if self.hit_player and player:is_player() then
4207
4208 self:hit_player(player)
4209
4210 self.object:remove() ; -- print("hit player")
4211
34234212 return
34244213 end
34254214
34314220 and tostring(player) ~= self.owner_id
34324221 and entity.name ~= self.object:get_luaentity().name then
34334222
3434 self.hit_mob(self, player)
3435
3436 self.object:remove() ; --print ("hit mob")
4223 self:hit_mob(player)
4224
4225 self.object:remove() ; --print("hit mob")
4226
4227 return
4228 end
4229
4230 if entity
4231 and self.hit_object
4232 and (not entity._cmi_is_mob)
4233 and tostring(player) ~= self.owner_id
4234 and entity.name ~= self.object:get_luaentity().name then
4235
4236 self:hit_object(player)
4237
4238 self.object:remove() ; -- print("hit object")
34374239
34384240 return
34394241 end
34484250
34494251 -- compatibility function
34504252 function mobs:explosion(pos, radius)
3451
3452 local self = {sounds = {explode = "tnt_explode"}}
3453
3454 mobs:boom(self, pos, radius)
4253 mobs:boom({sounds = {explode = "tnt_explode"}}, pos, radius)
34554254 end
34564255
34574256
34624261 pos = pos,
34634262 gain = 1.0,
34644263 max_hear_distance = self.sounds and self.sounds.distance or 32
3465 })
4264 }, true)
34664265
34674266 entity_physics(pos, radius)
34684267
34814280 radius = radius,
34824281 damage_radius = radius,
34834282 sound = self.sounds and self.sounds.explode,
3484 explode_center = true,
4283 explode_center = true
34854284 })
34864285 else
34874286 mobs:safe_boom(self, pos, radius)
34994298 local grp = {spawn_egg = 1}
35004299
35014300 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3502 if creative and no_creative == true then
4301 if no_creative == true then
35034302 grp.not_in_creative_inventory = 1
35044303 end
35054304
35224321
35234322 local pos = pointed_thing.above
35244323
3525 -- am I clicking on something with existing on_rightclick function?
4324 -- does existing on_rightclick function exist?
35264325 local under = minetest.get_node(pointed_thing.under)
35274326 local def = minetest.registered_nodes[under.name]
4327
35284328 if def and def.on_rightclick then
3529 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4329
4330 return def.on_rightclick(
4331 pointed_thing.under, under, placer, itemstack)
35304332 end
35314333
35324334 if pos
35394341 pos.y = pos.y + 1
35404342
35414343 local data = itemstack:get_metadata()
3542 local mob = minetest.add_entity(pos, mob, data)
3543 local ent = mob:get_luaentity()
4344 local smob = minetest.add_entity(pos, mob, data)
4345 local ent = smob and smob:get_luaentity()
4346
4347 if not ent then return end -- sanity check
35444348
35454349 -- set owner if not a monster
35464350 if ent.type ~= "monster" then
35684372
35694373 local pos = pointed_thing.above
35704374
3571 -- am I clicking on something with existing on_rightclick function?
4375 -- does existing on_rightclick function exist?
35724376 local under = minetest.get_node(pointed_thing.under)
35734377 local def = minetest.registered_nodes[under.name]
4378
35744379 if def and def.on_rightclick then
3575 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
4380
4381 return def.on_rightclick(
4382 pointed_thing.under, under, placer, itemstack)
35764383 end
35774384
35784385 if pos
35824389 return
35834390 end
35844391
4392 -- have we reached active mob limit
4393 if active_limit > 0 and active_mobs >= active_limit then
4394 minetest.chat_send_player(placer:get_player_name(),
4395 S("Active Mob Limit Reached!")
4396 .. " (" .. active_mobs
4397 .. " / " .. active_limit .. ")")
4398 return
4399 end
4400
35854401 pos.y = pos.y + 1
35864402
3587 local mob = minetest.add_entity(pos, mob)
3588 local ent = mob:get_luaentity()
4403 local smob = minetest.add_entity(pos, mob)
4404 local ent = smob and smob:get_luaentity()
4405
4406 if not ent then return end -- sanity check
35894407
35904408 -- don't set owner if monster or sneak pressed
35914409 if ent.type ~= "monster"
36034421 return itemstack
36044422 end,
36054423 })
3606
4424 end
4425
4426
4427 -- force capture a mob if space available in inventory, or drop as spawn egg
4428 function mobs:force_capture(self, clicker)
4429
4430 -- add special mob egg with all mob information
4431 local new_stack = ItemStack(self.name .. "_set")
4432
4433 local tmp, t = {}
4434
4435 for _,stat in pairs(self) do
4436
4437 t = type(stat)
4438
4439 if t ~= "function"
4440 and t ~= "nil"
4441 and t ~= "userdata" then
4442 tmp[_] = self[_]
4443 end
4444 end
4445
4446 local data_str = minetest.serialize(tmp)
4447
4448 new_stack:set_metadata(data_str)
4449
4450 local inv = clicker:get_inventory()
4451
4452 if inv:room_for_item("main", new_stack) then
4453 inv:add_item("main", new_stack)
4454 else
4455 minetest.add_item(clicker:get_pos(), new_stack)
4456 end
4457
4458 self:mob_sound("default_place_node_hard")
4459
4460 remove_mob(self, true)
36074461 end
36084462
36094463
36104464 -- capture critter (thanks to blert2112 for idea)
3611 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
3612 force_take, replacewith)
4465 function mobs:capture_mob(self, clicker, chance_hand, chance_net,
4466 chance_lasso, force_take, replacewith)
36134467
36144468 if self.child
36154469 or not clicker:is_player()
36364490 end
36374491
36384492 -- is mob tamed?
3639 if self.tamed == false
3640 and force_take == false then
4493 if self.tamed == false and force_take == false then
36414494
36424495 minetest.chat_send_player(name, S("Not tamed!"))
36434496
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
4497 return false
4498 end
4499
4500 -- cannot pick up if not owner (unless player has protection_bypass priv)
4501 if not minetest.check_player_privs(name, "protection_bypass")
4502 and self.owner ~= name and force_take == false then
36504503
36514504 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
36524505
3653 return true -- false
4506 return false
36544507 end
36554508
36564509 if clicker:get_inventory():room_for_item("main", mobname) then
36804533 end
36814534
36824535 -- calculate chance.. add to inventory if successful?
3683 if chance > 0 and random(1, 100) <= chance then
4536 if chance and chance > 0 and random(100) <= chance then
36844537
36854538 -- default mob egg
36864539 local new_stack = ItemStack(mobname)
36914544
36924545 new_stack = ItemStack(mobname .. "_set")
36934546
3694 local tmp = {}
4547 local tmp, t = {}
36954548
36964549 for _,stat in pairs(self) do
3697 local t = type(stat)
4550
4551 t = type(stat)
4552
36984553 if t ~= "function"
36994554 and t ~= "nil"
37004555 and t ~= "userdata" then
37154570 minetest.add_item(clicker:get_pos(), new_stack)
37164571 end
37174572
3718 self.object:remove()
3719
3720 mob_sound(self, "default_place_node_hard")
3721
3722 elseif chance ~= 0 then
4573 self:mob_sound("default_place_node_hard")
4574
4575 remove_mob(self, true)
4576
4577 return new_stack
4578
4579 -- when chance above fails or set to 0, miss!
4580 elseif chance and chance ~= 0 then
4581
37234582 minetest.chat_send_player(name, S("Missed!"))
37244583
3725 mob_sound(self, "mobs_swing")
4584 self:mob_sound("mobs_swing")
4585
4586 return false
4587
4588 -- when chance is nil always return a miss (used for npc walk/follow)
4589 elseif not chance then
4590 return false
37264591 end
37274592 end
37284593
37354600
37364601 local name = clicker:get_player_name()
37374602 local tool = clicker:get_wielded_item()
3738
3739 if tool:get_name() ~= "mobs:protector" then
4603 local tool_name = tool:get_name()
4604
4605 if tool_name ~= "mobs:protector"
4606 and tool_name ~= "mobs:protector2" then
37404607 return false
37414608 end
37424609
3743 if self.tamed == false then
4610 if not self.tamed then
37444611 minetest.chat_send_player(name, S("Not tamed!"))
3745 return true -- false
3746 end
3747
3748 if self.protected == true then
4612 return true
4613 end
4614
4615 if (self.protected and tool_name == "mobs:protector")
4616 or (self.protected == 2 and tool_name == "mobs:protector2") then
37494617 minetest.chat_send_player(name, S("Already protected!"))
3750 return true -- false
4618 return true
37514619 end
37524620
37534621 if not mobs.is_creative(clicker:get_player_name()) then
37554623 clicker:set_wielded_item(tool)
37564624 end
37574625
3758 self.protected = true
4626 -- set protection level
4627 if tool_name == "mobs:protector" then
4628 self.protected = true
4629 else
4630 self.protected = 2 ; self.fire_damage = 0
4631 end
37594632
37604633 local pos = self.object:get_pos()
4634
37614635 pos.y = pos.y + self.collisionbox[2] + 0.5
37624636
3763 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3764
3765 mob_sound(self, "mobs_spell")
4637 effect(self.object:get_pos(), 25, "mobs_protect_particle.png",
4638 0.5, 4, 2, 15)
4639
4640 self:mob_sound("mobs_spell")
37664641
37674642 return true
37684643 end
37744649 -- feeding, taming and breeding (thanks blert2112)
37754650 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
37764651
3777 if not self.follow then
3778 return false
3779 end
3780
37814652 -- can eat/tame with item in hand
3782 if follow_holding(self, clicker) then
4653 if self.follow
4654 and self:follow_holding(clicker) then
37834655
37844656 -- if not in creative then take item
37854657 if not mobs.is_creative(clicker:get_player_name()) then
38104682
38114683 self.object:set_hp(self.health)
38124684
3813 update_tag(self)
4685 self:update_tag()
38144686
38154687 -- make children grow quicker
38164688 if self.child == true then
38174689
3818 self.hornytimer = self.hornytimer + 20
3819
4690 -- self.hornytimer = self.hornytimer + 20
4691 -- deduct 10% of the time to adulthood
4692 self.hornytimer = self.hornytimer + (
4693 (CHILD_GROW_TIME - self.hornytimer) * 0.1)
4694 --print ("====", self.hornytimer)
38204695 return true
38214696 end
38224697
38234698 -- feed and tame
38244699 self.food = (self.food or 0) + 1
4700
38254701 if self.food >= feed_count then
38264702
38274703 self.food = 0
38294705 if breed and self.hornytimer == 0 then
38304706 self.horny = true
38314707 end
3832
3833 self.gotten = false
38344708
38354709 if tame then
38364710
38484722 end
38494723
38504724 -- make sound when fed so many times
3851 mob_sound(self, self.sounds.random)
4725 self:mob_sound(self.sounds.random)
38524726 end
38534727
38544728 return true
38554729 end
38564730
38574731 local item = clicker:get_wielded_item()
4732 local name = clicker:get_player_name()
38584733
38594734 -- if mob has been tamed you can name it with a nametag
38604735 if item:get_name() == "mobs:nametag"
3861 and clicker:get_player_name() == self.owner then
3862
3863 local name = clicker:get_player_name()
4736 and (name == self.owner
4737 or minetest.check_player_privs(name, "protection_bypass")) then
38644738
38654739 -- store mob and nametag stack in external variables
38664740 mob_obj[name] = self
38674741 mob_sta[name] = item
38684742
38694743 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")) .. "]")
4744 local esc = minetest.formspec_escape
4745
4746 minetest.show_formspec(name, "mobs_nametag",
4747 "size[8,4]" ..
4748 "field[0.5,1;7.5,0;name;" ..
4749 esc(S("Enter name:")) ..
4750 ";" .. tag .. "]" ..
4751 "button_exit[2.5,3.5;3,1;mob_rename;" ..
4752 esc(S("Rename")) .. "]")
4753
4754 return true
38784755 end
38794756
38804757 return false
39044781 end
39054782
39064783 -- limit name entered to 64 characters long
3907 if string.len(fields.name) > 64 then
3908 fields.name = string.sub(fields.name, 1, 64)
4784 if fields.name:len() > 64 then
4785 fields.name = fields.name:sub(1, 64)
39094786 end
39104787
39114788 -- update nametag
39124789 mob_obj[name].nametag = fields.name
39134790
3914 update_tag(mob_obj[name])
4791 mob_obj[name]:update_tag()
39154792
39164793 -- if not in creative then take item
39174794 if not mobs.is_creative(name) then
39314808 -- compatibility function for old entities to new modpack entities
39324809 function mobs:alias_mob(old_name, new_name)
39334810
4811 -- check old_name entity doesnt already exist
4812 if minetest.registered_entities[old_name] then
4813 return
4814 end
4815
39344816 -- spawn egg
39354817 minetest.register_alias(old_name, new_name)
39364818
39374819 -- entity
39384820 minetest.register_entity(":" .. old_name, {
39394821
3940 physical = false,
3941
3942 on_activate = function(self)
4822 physical = false, static_save = false,
4823
4824 on_activate = function(self, staticdata)
39434825
39444826 if minetest.registered_entities[new_name] then
3945 minetest.add_entity(self.object:get_pos(), new_name)
3946 end
3947
3948 self.object:remove()
4827
4828 minetest.add_entity(self.object:get_pos(), new_name, staticdata)
4829 end
4830
4831 remove_mob(self)
4832 end,
4833
4834 get_staticdata = function(self)
4835 return self
39494836 end
39504837 })
39514838 end
+0
-3985
api.lua_testspawn less more
0
1 -- Mobs Api
2
3 mobs = {}
4 mobs.mod = "redo"
5 mobs.version = "20180623"
6
7
8 -- Intllib
9 local MP = minetest.get_modpath(minetest.get_current_modname())
10 local S, NS = dofile(MP .. "/intllib.lua")
11 mobs.intllib = S
12
13
14 -- CMI support check
15 local use_cmi = minetest.global_exists("cmi")
16
17
18 -- Invisibility mod check
19 mobs.invis = {}
20 if minetest.global_exists("invisibility") then
21 mobs.invis = invisibility
22 end
23
24
25 -- creative check
26 local creative_mode_cache = minetest.settings:get_bool("creative_mode")
27 function mobs.is_creative(name)
28 return creative_mode_cache or minetest.check_player_privs(name, {creative = true})
29 end
30
31
32 -- localize math functions
33 local pi = math.pi
34 local square = math.sqrt
35 local sin = math.sin
36 local cos = math.cos
37 local abs = math.abs
38 local min = math.min
39 local max = math.max
40 local atann = math.atan
41 local random = math.random
42 local floor = math.floor
43 local atan = function(x)
44 if not x or x ~= x then
45 --error("atan bassed NaN")
46 return 0
47 else
48 return atann(x)
49 end
50 end
51
52
53 -- Load settings
54 local damage_enabled = minetest.settings:get_bool("enable_damage")
55 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
56 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
57 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
58 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
59 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
60 local creative = minetest.settings:get_bool("creative_mode")
61 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
62 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
63 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
64 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
65 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
66 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
67
68 -- Peaceful mode message so players will know there are no monsters
69 if peaceful_only then
70 minetest.register_on_joinplayer(function(player)
71 minetest.chat_send_player(player:get_player_name(),
72 S("** Peaceful Mode Active - No Monsters Will Spawn"))
73 end)
74 end
75
76 -- calculate aoc range for mob count
77 local aosrb = tonumber(minetest.settings:get("active_object_send_range_blocks"))
78 local abr = tonumber(minetest.settings:get("active_block_range"))
79 local aoc_range = max(aosrb, abr) * 16
80
81 -- pathfinding settings
82 local enable_pathfinding = true
83 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
84 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
85
86 -- default nodes
87 local node_fire = "fire:basic_flame"
88 local node_permanent_flame = "fire:permanent_flame"
89 local node_ice = "default:ice"
90 local node_snowblock = "default:snowblock"
91 local node_snow = "default:snow"
92 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
93
94
95 -- play sound
96 local mob_sound = function(self, sound)
97
98 if sound then
99 minetest.sound_play(sound, {
100 object = self.object,
101 gain = 1.0,
102 max_hear_distance = self.sounds.distance
103 })
104 end
105 end
106
107
108 -- attack player/mob
109 local do_attack = function(self, player)
110
111 if self.state == "attack" then
112 return
113 end
114
115 self.attack = player
116 self.state = "attack"
117
118 if random(0, 100) < 90 then
119 mob_sound(self, self.sounds.war_cry)
120 end
121 end
122
123
124 -- move mob in facing direction
125 local set_velocity = function(self, v)
126
127 -- do not move if mob has been ordered to stay
128 if self.order == "stand" then
129 self.object:setvelocity({x = 0, y = 0, z = 0})
130 return
131 end
132
133 local yaw = (self.object:get_yaw() or 0) + self.rotate
134
135 self.object:setvelocity({
136 x = sin(yaw) * -v,
137 y = self.object:getvelocity().y,
138 z = cos(yaw) * v
139 })
140 end
141
142
143 -- calculate mob velocity
144 local get_velocity = function(self)
145
146 local v = self.object:getvelocity()
147
148 return (v.x * v.x + v.z * v.z) ^ 0.5
149 end
150
151
152 -- set and return valid yaw
153 local set_yaw = function(self, yaw, delay)
154
155 if not yaw or yaw ~= yaw then
156 yaw = 0
157 end
158
159 delay = delay or 0
160
161 if delay == 0 then
162 self.object:set_yaw(yaw)
163 return yaw
164 end
165
166 self.target_yaw = yaw
167 self.delay = delay
168
169 return self.target_yaw
170 end
171
172 -- global function to set mob yaw
173 function mobs:yaw(self, yaw, delay)
174 set_yaw(self, yaw, delay)
175 end
176
177
178 -- set defined animation
179 local set_animation = function(self, anim)
180
181 if not self.animation
182 or not anim then return end
183
184 self.animation.current = self.animation.current or ""
185
186 if anim == self.animation.current
187 or not self.animation[anim .. "_start"]
188 or not self.animation[anim .. "_end"] then
189 return
190 end
191
192 self.animation.current = anim
193
194 self.object:set_animation({
195 x = self.animation[anim .. "_start"],
196 y = self.animation[anim .. "_end"]},
197 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
198 0, self.animation[anim .. "_loop"] ~= false)
199 end
200
201
202 -- above function exported for mount.lua
203 function mobs:set_animation(self, anim)
204 set_animation(self, anim)
205 end
206
207
208 -- calculate distance
209 local get_distance = function(a, b)
210
211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
212
213 return square(x * x + y * y + z * z)
214 end
215
216
217 -- check line of sight (BrunoMine)
218 local line_of_sight = function(self, pos1, pos2, stepsize)
219
220 stepsize = stepsize or 1
221
222 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
223
224 -- normal walking and flying mobs can see you through air
225 if s == true then
226 return true
227 end
228
229 -- New pos1 to be analyzed
230 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
231
232 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
233
234 -- Checks the return
235 if r == true then return true end
236
237 -- Nodename found
238 local nn = minetest.get_node(pos).name
239
240 -- Target Distance (td) to travel
241 local td = get_distance(pos1, pos2)
242
243 -- Actual Distance (ad) traveled
244 local ad = 0
245
246 -- It continues to advance in the line of sight in search of a real
247 -- obstruction which counts as 'normal' nodebox.
248 while minetest.registered_nodes[nn]
249 and (minetest.registered_nodes[nn].walkable == false
250 or minetest.registered_nodes[nn].drawtype == "nodebox") do
251
252 -- Check if you can still move forward
253 if td < ad + stepsize then
254 return true -- Reached the target
255 end
256
257 -- Moves the analyzed pos
258 local d = get_distance(pos1, pos2)
259
260 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
261 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
262 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
263
264 -- NaN checks
265 if d == 0
266 or npos1.x ~= npos1.x
267 or npos1.y ~= npos1.y
268 or npos1.z ~= npos1.z then
269 return false
270 end
271
272 ad = ad + stepsize
273
274 -- scan again
275 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
276
277 if r == true then return true end
278
279 -- New Nodename found
280 nn = minetest.get_node(pos).name
281
282 end
283
284 return false
285 end
286
287
288 -- are we flying in what we are suppose to? (taikedz)
289 local flight_check = function(self, pos_w)
290
291 local def = minetest.registered_nodes[self.standing_in]
292
293 if not def then return false end -- nil check
294
295 if type(self.fly_in) == "string"
296 and self.standing_in == self.fly_in then
297
298 return true
299
300 elseif type(self.fly_in) == "table" then
301
302 for _,fly_in in pairs(self.fly_in) do
303
304 if self.standing_in == fly_in then
305
306 return true
307 end
308 end
309 end
310
311 -- stops mobs getting stuck inside stairs and plantlike nodes
312 if def.drawtype ~= "airlike"
313 and def.drawtype ~= "liquid"
314 and def.drawtype ~= "flowingliquid" then
315 return true
316 end
317
318 return false
319 end
320
321
322 -- custom particle effects
323 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
324
325 radius = radius or 2
326 min_size = min_size or 0.5
327 max_size = max_size or 1
328 gravity = gravity or -10
329 glow = glow or 0
330
331 minetest.add_particlespawner({
332 amount = amount,
333 time = 0.25,
334 minpos = pos,
335 maxpos = pos,
336 minvel = {x = -radius, y = -radius, z = -radius},
337 maxvel = {x = radius, y = radius, z = radius},
338 minacc = {x = 0, y = gravity, z = 0},
339 maxacc = {x = 0, y = gravity, z = 0},
340 minexptime = 0.1,
341 maxexptime = 1,
342 minsize = min_size,
343 maxsize = max_size,
344 texture = texture,
345 glow = glow,
346 })
347 end
348
349
350 -- update nametag colour
351 local update_tag = function(self)
352
353 local col = "#00FF00"
354 local qua = self.hp_max / 4
355
356 if self.health <= floor(qua * 3) then
357 col = "#FFFF00"
358 end
359
360 if self.health <= floor(qua * 2) then
361 col = "#FF6600"
362 end
363
364 if self.health <= floor(qua) then
365 col = "#FF0000"
366 end
367
368 self.object:set_properties({
369 nametag = self.nametag,
370 nametag_color = col
371 })
372
373 end
374
375
376 -- drop items
377 local item_drop = function(self, cooked)
378
379 -- no drops if disabled by setting
380 if not mobs_drop_items then return end
381
382 -- no drops for child mobs
383 if self.child then return end
384
385 local obj, item, num
386 local pos = self.object:get_pos()
387
388 self.drops = self.drops or {} -- nil check
389
390 for n = 1, #self.drops do
391
392 if random(1, self.drops[n].chance) == 1 then
393
394 num = random(self.drops[n].min or 1, self.drops[n].max or 1)
395 item = self.drops[n].name
396
397 -- cook items when true
398 if cooked then
399
400 local output = minetest.get_craft_result({
401 method = "cooking", width = 1, items = {item}})
402
403 if output and output.item and not output.item:is_empty() then
404 item = output.item:get_name()
405 end
406 end
407
408 -- add item if it exists
409 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
410
411 if obj and obj:get_luaentity() then
412
413 obj:setvelocity({
414 x = random(-10, 10) / 9,
415 y = 6,
416 z = random(-10, 10) / 9,
417 })
418 elseif obj then
419 obj:remove() -- item does not exist
420 end
421 end
422 end
423
424 self.drops = {}
425 end
426
427
428 -- check if mob is dead or only hurt
429 local check_for_death = function(self, cause, cmi_cause)
430
431 -- has health actually changed?
432 if self.health == self.old_health and self.health > 0 then
433 return
434 end
435
436 self.old_health = self.health
437
438 -- still got some health? play hurt sound
439 if self.health > 0 then
440
441 mob_sound(self, self.sounds.damage)
442
443 -- make sure health isn't higher than max
444 if self.health > self.hp_max then
445 self.health = self.hp_max
446 end
447
448 -- backup nametag so we can show health stats
449 if not self.nametag2 then
450 self.nametag2 = self.nametag or ""
451 end
452
453 if show_health
454 and (cmi_cause and cmi_cause.type == "punch") then
455
456 self.htimer = 2
457 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
458
459 update_tag(self)
460 end
461
462 return false
463 end
464
465 -- dropped cooked item if mob died in lava
466 if cause == "lava" then
467 item_drop(self, true)
468 else
469 item_drop(self, nil)
470 end
471
472 mob_sound(self, self.sounds.death)
473
474 local pos = self.object:get_pos()
475
476 -- execute custom death function
477 if self.on_die then
478
479 self.on_die(self, pos)
480
481 if use_cmi then
482 cmi.notify_die(self.object, cmi_cause)
483 end
484
485 self.object:remove()
486
487 return true
488 end
489
490 -- default death function and die animation (if defined)
491 if self.animation
492 and self.animation.die_start
493 and self.animation.die_end then
494
495 local frames = self.animation.die_end - self.animation.die_start
496 local speed = self.animation.die_speed or 15
497 local length = max(frames / speed, 0)
498
499 self.attack = nil
500 self.v_start = false
501 self.timer = 0
502 self.blinktimer = 0
503 self.passive = true
504 self.state = "die"
505 set_velocity(self, 0)
506 set_animation(self, "die")
507
508 minetest.after(length, function(self)
509
510 if use_cmi and self.object:get_luaentity() then
511 cmi.notify_die(self.object, cmi_cause)
512 end
513
514 self.object:remove()
515 end, self)
516 else
517
518 if use_cmi then
519 cmi.notify_die(self.object, cmi_cause)
520 end
521
522 self.object:remove()
523 end
524
525 effect(pos, 20, "tnt_smoke.png")
526
527 return true
528 end
529
530
531 -- check if within physical map limits (-30911 to 30927)
532 local within_limits = function(pos, radius)
533
534 if (pos.x - radius) > -30913
535 and (pos.x + radius) < 30928
536 and (pos.y - radius) > -30913
537 and (pos.y + radius) < 30928
538 and (pos.z - radius) > -30913
539 and (pos.z + radius) < 30928 then
540 return true -- within limits
541 end
542
543 return false -- beyond limits
544 end
545
546
547 -- is mob facing a cliff
548 local is_at_cliff = function(self)
549
550 if self.fear_height == 0 then -- 0 for no falling protection!
551 return false
552 end
553
554 local yaw = self.object:get_yaw()
555 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
556 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
557 local pos = self.object:get_pos()
558 local ypos = pos.y + self.collisionbox[2] -- just above floor
559
560 if minetest.line_of_sight(
561 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
562 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
563 , 1) then
564
565 return true
566 end
567
568 return false
569 end
570
571
572 -- get node but use fallback for nil or unknown
573 local node_ok = function(pos, fallback)
574
575 fallback = fallback or mobs.fallback_node
576
577 local node = minetest.get_node_or_nil(pos)
578
579 if node and minetest.registered_nodes[node.name] then
580 return node
581 end
582
583 return minetest.registered_nodes[fallback]
584 end
585
586
587 -- environmental damage (water, lava, fire, light etc.)
588 local do_env_damage = function(self)
589
590 -- feed/tame text timer (so mob 'full' messages dont spam chat)
591 if self.htimer > 0 then
592 self.htimer = self.htimer - 1
593 end
594
595 -- reset nametag after showing health stats
596 if self.htimer < 1 and self.nametag2 then
597
598 self.nametag = self.nametag2
599 self.nametag2 = nil
600
601 update_tag(self)
602 end
603
604 local pos = self.object:get_pos()
605
606 self.time_of_day = minetest.get_timeofday()
607
608 -- remove mob if beyond map limits
609 if not within_limits(pos, 0) then
610 self.object:remove()
611 return
612 end
613
614 -- bright light harms mob
615 if self.light_damage ~= 0
616 -- and pos.y > 0
617 -- and self.time_of_day > 0.2
618 -- and self.time_of_day < 0.8
619 and (minetest.get_node_light(pos) or 0) > 12 then
620
621 self.health = self.health - self.light_damage
622
623 effect(pos, 5, "tnt_smoke.png")
624
625 if check_for_death(self, "light", {type = "light"}) then return end
626 end
627 --[[
628 local y_level = self.collisionbox[2]
629
630 if self.child then
631 y_level = self.collisionbox[2] * 0.5
632 end
633
634 -- what is mob standing in?
635 pos.y = pos.y + y_level + 0.25 -- foot level
636 self.standing_in = node_ok(pos, "air").name
637 -- print ("standing in " .. self.standing_in)
638 ]]
639 -- don't fall when on ignore, just stand still
640 if self.standing_in == "ignore" then
641 self.object:setvelocity({x = 0, y = 0, z = 0})
642 end
643
644 local nodef = minetest.registered_nodes[self.standing_in]
645
646 pos.y = pos.y + 1 -- for particle effect position
647
648 -- water
649 if self.water_damage
650 and nodef.groups.water then
651
652 if self.water_damage ~= 0 then
653
654 self.health = self.health - self.water_damage
655
656 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
657
658 if check_for_death(self, "water", {type = "environment",
659 pos = pos, node = self.standing_in}) then return end
660 end
661
662 -- lava or fire
663 elseif self.lava_damage
664 and (nodef.groups.lava
665 or self.standing_in == node_fire
666 or self.standing_in == node_permanent_flame) then
667
668 if self.lava_damage ~= 0 then
669
670 self.health = self.health - self.lava_damage
671
672 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
673
674 if check_for_death(self, "lava", {type = "environment",
675 pos = pos, node = self.standing_in}) then return end
676 end
677
678 -- damage_per_second node check
679 elseif nodef.damage_per_second ~= 0 then
680
681 self.health = self.health - nodef.damage_per_second
682
683 effect(pos, 5, "tnt_smoke.png")
684
685 if check_for_death(self, "dps", {type = "environment",
686 pos = pos, node = self.standing_in}) then return end
687 end
688 --[[
689 --- suffocation inside solid node
690 if self.suffocation ~= 0
691 and nodef.walkable == true
692 and nodef.groups.disable_suffocation ~= 1
693 and nodef.drawtype == "normal" then
694
695 self.health = self.health - self.suffocation
696
697 if check_for_death(self, "suffocation", {type = "environment",
698 pos = pos, node = self.standing_in}) then return end
699 end
700 ]]
701 check_for_death(self, "", {type = "unknown"})
702 end
703
704
705 -- jump if facing a solid node (not fences or gates)
706 local do_jump = function(self)
707
708 if not self.jump
709 or self.jump_height == 0
710 or self.fly
711 or self.child
712 or self.order == "stand" then
713 return false
714 end
715
716 self.facing_fence = false
717
718 -- something stopping us while moving?
719 if self.state ~= "stand"
720 and get_velocity(self) > 0.5
721 and self.object:getvelocity().y ~= 0 then
722 return false
723 end
724
725 local pos = self.object:get_pos()
726 local yaw = self.object:get_yaw()
727
728 -- what is mob standing on?
729 pos.y = pos.y + self.collisionbox[2] - 0.2
730
731 local nod = node_ok(pos)
732
733 --print ("standing on:", nod.name, pos.y)
734
735 if minetest.registered_nodes[nod.name].walkable == false then
736 return false
737 end
738
739 -- where is front
740 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
741 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
742
743 -- what is in front of mob?
744 local nod = node_ok({
745 x = pos.x + dir_x,
746 y = pos.y + 0.5,
747 z = pos.z + dir_z
748 })
749
750 -- thin blocks that do not need to be jumped
751 if nod.name == node_snow then
752 return false
753 end
754
755 --print ("in front:", nod.name, pos.y + 0.5)
756
757 if self.walk_chance == 0
758 or minetest.registered_items[nod.name].walkable then
759
760 if not nod.name:find("fence")
761 and not nod.name:find("gate") then
762
763 local v = self.object:getvelocity()
764
765 v.y = self.jump_height
766
767 set_animation(self, "jump") -- only when defined
768
769 self.object:setvelocity(v)
770
771 -- when in air move forward
772 minetest.after(0.3, function(self, v)
773
774 if self.object:get_luaentity() then
775
776 self.object:set_acceleration({
777 x = v.x * 2,--1.5,
778 y = 0,
779 z = v.z * 2,--1.5
780 })
781 end
782 end, self, v)
783
784 if get_velocity(self) > 0 then
785 mob_sound(self, self.sounds.jump)
786 end
787 else
788 self.facing_fence = true
789 end
790
791 return true
792 end
793
794 return false
795 end
796
797
798 -- blast damage to entities nearby (modified from TNT mod)
799 local entity_physics = function(pos, radius)
800
801 radius = radius * 2
802
803 local objs = minetest.get_objects_inside_radius(pos, radius)
804 local obj_pos, dist
805
806 for n = 1, #objs do
807
808 obj_pos = objs[n]:get_pos()
809
810 dist = get_distance(pos, obj_pos)
811 if dist < 1 then dist = 1 end
812
813 local damage = floor((4 / dist) * radius)
814 local ent = objs[n]:get_luaentity()
815
816 -- punches work on entities AND players
817 objs[n]:punch(objs[n], 1.0, {
818 full_punch_interval = 1.0,
819 damage_groups = {fleshy = damage},
820 }, pos)
821 end
822 end
823
824
825 -- should mob follow what I'm holding ?
826 local follow_holding = function(self, clicker)
827
828 if mobs.invis[clicker:get_player_name()] then
829 return false
830 end
831
832 local item = clicker:get_wielded_item()
833 local t = type(self.follow)
834
835 -- single item
836 if t == "string"
837 and item:get_name() == self.follow then
838 return true
839
840 -- multiple items
841 elseif t == "table" then
842
843 for no = 1, #self.follow do
844
845 if self.follow[no] == item:get_name() then
846 return true
847 end
848 end
849 end
850
851 return false
852 end
853
854
855 -- find two animals of same type and breed if nearby and horny
856 local breed = function(self)
857
858 -- child takes 240 seconds before growing into adult
859 if self.child == true then
860
861 self.hornytimer = self.hornytimer + 1
862
863 if self.hornytimer > 240 then
864
865 self.child = false
866 self.hornytimer = 0
867
868 self.object:set_properties({
869 textures = self.base_texture,
870 mesh = self.base_mesh,
871 visual_size = self.base_size,
872 collisionbox = self.base_colbox,
873 selectionbox = self.base_selbox,
874 })
875
876 -- custom function when child grows up
877 if self.on_grown then
878 self.on_grown(self)
879 else
880 -- jump when fully grown so as not to fall into ground
881 self.object:setvelocity({
882 x = 0,
883 y = self.jump_height,
884 z = 0
885 })
886 end
887 end
888
889 return
890 end
891
892 -- horny animal can mate for 40 seconds,
893 -- afterwards horny animal cannot mate again for 200 seconds
894 if self.horny == true
895 and self.hornytimer < 240 then
896
897 self.hornytimer = self.hornytimer + 1
898
899 if self.hornytimer >= 240 then
900 self.hornytimer = 0
901 self.horny = false
902 end
903 end
904
905 -- find another same animal who is also horny and mate if nearby
906 if self.horny == true
907 and self.hornytimer <= 40 then
908
909 local pos = self.object:get_pos()
910
911 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
912
913 local objs = minetest.get_objects_inside_radius(pos, 3)
914 local num = 0
915 local ent = nil
916
917 for n = 1, #objs do
918
919 ent = objs[n]:get_luaentity()
920
921 -- check for same animal with different colour
922 local canmate = false
923
924 if ent then
925
926 if ent.name == self.name then
927 canmate = true
928 else
929 local entname = string.split(ent.name,":")
930 local selfname = string.split(self.name,":")
931
932 if entname[1] == selfname[1] then
933 entname = string.split(entname[2],"_")
934 selfname = string.split(selfname[2],"_")
935
936 if entname[1] == selfname[1] then
937 canmate = true
938 end
939 end
940 end
941 end
942
943 if ent
944 and canmate == true
945 and ent.horny == true
946 and ent.hornytimer <= 40 then
947 num = num + 1
948 end
949
950 -- found your mate? then have a baby
951 if num > 1 then
952
953 self.hornytimer = 41
954 ent.hornytimer = 41
955
956 -- spawn baby
957 minetest.after(5, function(self, ent)
958
959 if not self.object:get_luaentity() then
960 return
961 end
962
963 -- custom breed function
964 if self.on_breed then
965
966 -- when false skip going any further
967 if self.on_breed(self, ent) == false then
968 return
969 end
970 else
971 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
972 end
973
974 local mob = minetest.add_entity(pos, self.name)
975 local ent2 = mob:get_luaentity()
976 local textures = self.base_texture
977
978 -- using specific child texture (if found)
979 if self.child_texture then
980 textures = self.child_texture[1]
981 end
982
983 -- and resize to half height
984 mob:set_properties({
985 textures = textures,
986 visual_size = {
987 x = self.base_size.x * .5,
988 y = self.base_size.y * .5,
989 },
990 collisionbox = {
991 self.base_colbox[1] * .5,
992 self.base_colbox[2] * .5,
993 self.base_colbox[3] * .5,
994 self.base_colbox[4] * .5,
995 self.base_colbox[5] * .5,
996 self.base_colbox[6] * .5,
997 },
998 selectionbox = {
999 self.base_selbox[1] * .5,
1000 self.base_selbox[2] * .5,
1001 self.base_selbox[3] * .5,
1002 self.base_selbox[4] * .5,
1003 self.base_selbox[5] * .5,
1004 self.base_selbox[6] * .5,
1005 },
1006 })
1007 -- tamed and owned by parents' owner
1008 ent2.child = true
1009 ent2.tamed = true
1010 ent2.owner = self.owner
1011 end, self, ent)
1012
1013 num = 0
1014
1015 break
1016 end
1017 end
1018 end
1019 end
1020
1021
1022 -- find and replace what mob is looking for (grass, wheat etc.)
1023 local replace = function(self, pos)
1024
1025 if not mobs_griefing
1026 or not self.replace_rate
1027 or not self.replace_what
1028 or self.child == true
1029 or self.object:getvelocity().y ~= 0
1030 or random(1, self.replace_rate) > 1 then
1031 return
1032 end
1033
1034 local what, with, y_offset
1035
1036 if type(self.replace_what[1]) == "table" then
1037
1038 local num = random(#self.replace_what)
1039
1040 what = self.replace_what[num][1] or ""
1041 with = self.replace_what[num][2] or ""
1042 y_offset = self.replace_what[num][3] or 0
1043 else
1044 what = self.replace_what
1045 with = self.replace_with or ""
1046 y_offset = self.replace_offset or 0
1047 end
1048
1049 pos.y = pos.y + y_offset
1050
1051 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
1052
1053 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1054
1055 local oldnode = {name = what}
1056 local newnode = {name = with}
1057 local on_replace_return
1058
1059 if self.on_replace then
1060 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1061 end
1062
1063 if on_replace_return ~= false then
1064
1065 minetest.set_node(pos, {name = with})
1066
1067 -- when cow/sheep eats grass, replace wool and milk
1068 if self.gotten == true then
1069 self.gotten = false
1070 self.object:set_properties(self)
1071 end
1072 end
1073 end
1074 end
1075
1076
1077 -- check if daytime and also if mob is docile during daylight hours
1078 local day_docile = function(self)
1079
1080 if self.docile_by_day == false then
1081
1082 return false
1083
1084 elseif self.docile_by_day == true
1085 and self.time_of_day > 0.2
1086 and self.time_of_day < 0.8 then
1087
1088 return true
1089 end
1090 end
1091
1092
1093 local los_switcher = false
1094 local height_switcher = false
1095
1096 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1097 local smart_mobs = function(self, s, p, dist, dtime)
1098
1099 local s1 = self.path.lastpos
1100
1101 local target_pos = self.attack:get_pos()
1102
1103 -- is it becoming stuck?
1104 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
1105 self.path.stuck_timer = self.path.stuck_timer + dtime
1106 else
1107 self.path.stuck_timer = 0
1108 end
1109
1110 self.path.lastpos = {x = s.x, y = s.y, z = s.z}
1111
1112 local use_pathfind = false
1113 local has_lineofsight = minetest.line_of_sight(
1114 {x = s.x, y = (s.y) + .5, z = s.z},
1115 {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
1116
1117 -- im stuck, search for path
1118 if not has_lineofsight then
1119
1120 if los_switcher == true then
1121 use_pathfind = true
1122 los_switcher = false
1123 end -- cannot see target!
1124 else
1125 if los_switcher == false then
1126
1127 los_switcher = true
1128 use_pathfind = false
1129
1130 minetest.after(1, function(self)
1131
1132 if self.object:get_luaentity() then
1133
1134 if has_lineofsight then
1135 self.path.following = false
1136 end
1137 end
1138 end, self)
1139 end -- can see target!
1140 end
1141
1142 if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
1143
1144 use_pathfind = true
1145 self.path.stuck_timer = 0
1146
1147 minetest.after(1, function(self)
1148
1149 if self.object:get_luaentity() then
1150
1151 if has_lineofsight then
1152 self.path.following = false
1153 end
1154 end
1155 end, self)
1156 end
1157
1158 if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
1159
1160 use_pathfind = true
1161 self.path.stuck_timer = 0
1162
1163 minetest.after(1, function(self)
1164
1165 if self.object:get_luaentity() then
1166
1167 if has_lineofsight then
1168 self.path.following = false
1169 end
1170 end
1171 end, self)
1172 end
1173
1174 if math.abs(vector.subtract(s,target_pos).y) > self.stepheight then
1175
1176 if height_switcher then
1177 use_pathfind = true
1178 height_switcher = false
1179 end
1180 else
1181 if not height_switcher then
1182 use_pathfind = false
1183 height_switcher = true
1184 end
1185 end
1186
1187 if use_pathfind then
1188 -- lets try find a path, first take care of positions
1189 -- since pathfinder is very sensitive
1190 local sheight = self.collisionbox[5] - self.collisionbox[2]
1191
1192 -- round position to center of node to avoid stuck in walls
1193 -- also adjust height for player models!
1194 s.x = floor(s.x + 0.5)
1195 -- s.y = floor(s.y + 0.5) - sheight
1196 s.z = floor(s.z + 0.5)
1197
1198 local ssight, sground = minetest.line_of_sight(s, {
1199 x = s.x, y = s.y - 4, z = s.z}, 1)
1200
1201 -- determine node above ground
1202 if not ssight then
1203 s.y = sground.y + 1
1204 end
1205
1206 local p1 = self.attack:get_pos()
1207
1208 p1.x = floor(p1.x + 0.5)
1209 p1.y = floor(p1.y + 0.5)
1210 p1.z = floor(p1.z + 0.5)
1211
1212 local dropheight = 6
1213 if self.fear_height ~= 0 then dropheight = self.fear_height end
1214
1215 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1216 --[[
1217 -- show path using particles
1218 if self.path.way and #self.path.way > 0 then
1219 print ("-- path length:" .. tonumber(#self.path.way))
1220 for _,pos in pairs(self.path.way) do
1221 minetest.add_particle({
1222 pos = pos,
1223 velocity = {x=0, y=0, z=0},
1224 acceleration = {x=0, y=0, z=0},
1225 expirationtime = 1,
1226 size = 4,
1227 collisiondetection = false,
1228 vertical = false,
1229 texture = "heart.png",
1230 })
1231 end
1232 end
1233 ]]
1234
1235 self.state = ""
1236 do_attack(self, self.attack)
1237
1238 -- no path found, try something else
1239 if not self.path.way then
1240
1241 self.path.following = false
1242
1243 -- lets make way by digging/building if not accessible
1244 if self.pathfinding == 2 and mobs_griefing then
1245
1246 -- is player higher than mob?
1247 if s.y < p1.y then
1248
1249 -- build upwards
1250 if not minetest.is_protected(s, "") then
1251
1252 local ndef1 = minetest.registered_nodes[self.standing_in]
1253
1254 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1255
1256 minetest.set_node(s, {name = mobs.fallback_node})
1257 end
1258 end
1259
1260 local sheight = math.ceil(self.collisionbox[5]) + 1
1261
1262 -- assume mob is 2 blocks high so it digs above its head
1263 s.y = s.y + sheight
1264
1265 -- remove one block above to make room to jump
1266 if not minetest.is_protected(s, "") then
1267
1268 local node1 = node_ok(s, "air").name
1269 local ndef1 = minetest.registered_nodes[node1]
1270
1271 if node1 ~= "air"
1272 and node1 ~= "ignore"
1273 and ndef1
1274 and not ndef1.groups.level
1275 and not ndef1.groups.unbreakable
1276 and not ndef1.groups.liquid then
1277
1278 minetest.set_node(s, {name = "air"})
1279 minetest.add_item(s, ItemStack(node1))
1280
1281 end
1282 end
1283
1284 s.y = s.y - sheight
1285 self.object:setpos({x = s.x, y = s.y + 2, z = s.z})
1286
1287 else -- dig 2 blocks to make door toward player direction
1288
1289 local yaw1 = self.object:get_yaw() + pi / 2
1290 local p1 = {
1291 x = s.x + cos(yaw1),
1292 y = s.y,
1293 z = s.z + sin(yaw1)
1294 }
1295
1296 if not minetest.is_protected(p1, "") then
1297
1298 local node1 = node_ok(p1, "air").name
1299 local ndef1 = minetest.registered_nodes[node1]
1300
1301 if node1 ~= "air"
1302 and node1 ~= "ignore"
1303 and ndef1
1304 and not ndef1.groups.level
1305 and not ndef1.groups.unbreakable
1306 and not ndef1.groups.liquid then
1307
1308 minetest.add_item(p1, ItemStack(node1))
1309 minetest.set_node(p1, {name = "air"})
1310 end
1311
1312 p1.y = p1.y + 1
1313 node1 = node_ok(p1, "air").name
1314 ndef1 = minetest.registered_nodes[node1]
1315
1316 if node1 ~= "air"
1317 and node1 ~= "ignore"
1318 and ndef1
1319 and not ndef1.groups.level
1320 and not ndef1.groups.unbreakable
1321 and not ndef1.groups.liquid then
1322
1323 minetest.add_item(p1, ItemStack(node1))
1324 minetest.set_node(p1, {name = "air"})
1325 end
1326
1327 end
1328 end
1329 end
1330
1331 -- will try again in 2 second
1332 self.path.stuck_timer = stuck_timeout - 2
1333
1334 -- frustration! cant find the damn path :(
1335 mob_sound(self, self.sounds.random)
1336 else
1337 -- yay i found path
1338 mob_sound(self, self.sounds.war_cry)
1339 set_velocity(self, self.walk_velocity)
1340
1341 -- follow path now that it has it
1342 self.path.following = true
1343 end
1344 end
1345 end
1346
1347
1348 -- specific attacks
1349 local specific_attack = function(list, what)
1350
1351 -- no list so attack default (player, animals etc.)
1352 if list == nil then
1353 return true
1354 end
1355
1356 -- found entity on list to attack?
1357 for no = 1, #list do
1358
1359 if list[no] == what then
1360 return true
1361 end
1362 end
1363
1364 return false
1365 end
1366
1367
1368 -- monster find someone to attack
1369 local monster_attack = function(self)
1370
1371 if self.type ~= "monster"
1372 or not damage_enabled
1373 or creative
1374 or self.state == "attack"
1375 or day_docile(self) then
1376 return
1377 end
1378
1379 local s = self.object:get_pos()
1380 local p, sp, dist
1381 local player, obj, min_player
1382 local type, name = "", ""
1383 local min_dist = self.view_range + 1
1384 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1385
1386 for n = 1, #objs do
1387
1388 if objs[n]:is_player() then
1389
1390 if mobs.invis[ objs[n]:get_player_name() ] then
1391
1392 type = ""
1393 else
1394 player = objs[n]
1395 type = "player"
1396 name = "player"
1397 end
1398 else
1399 obj = objs[n]:get_luaentity()
1400
1401 if obj then
1402 player = obj.object
1403 type = obj.type
1404 name = obj.name or ""
1405 end
1406 end
1407
1408 -- find specific mob to attack, failing that attack player/npc/animal
1409 if specific_attack(self.specific_attack, name)
1410 and (type == "player" or type == "npc"
1411 or (type == "animal" and self.attack_animals == true)) then
1412
1413 p = player:get_pos()
1414 sp = s
1415
1416 dist = get_distance(p, s)
1417
1418 -- aim higher to make looking up hills more realistic
1419 p.y = p.y + 1
1420 sp.y = sp.y + 1
1421
1422
1423 -- choose closest player to attack
1424 if dist < min_dist
1425 and line_of_sight(self, sp, p, 2) == true then
1426 min_dist = dist
1427 min_player = player
1428 end
1429 end
1430 end
1431
1432 -- attack player
1433 if min_player then
1434 do_attack(self, min_player)
1435 end
1436 end
1437
1438
1439 -- npc, find closest monster to attack
1440 local npc_attack = function(self)
1441
1442 if self.type ~= "npc"
1443 or not self.attacks_monsters
1444 or self.state == "attack" then
1445 return
1446 end
1447
1448 local p, sp, obj, min_player, dist
1449 local s = self.object:get_pos()
1450 local min_dist = self.view_range + 1
1451 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1452
1453 for n = 1, #objs do
1454
1455 obj = objs[n]:get_luaentity()
1456
1457 if obj and obj.type == "monster" then
1458
1459 p = obj.object:get_pos()
1460 sp = s
1461
1462 dist = get_distance(p, s)
1463
1464 -- aim higher to make looking up hills more realistic
1465 p.y = p.y + 1
1466 sp.y = sp.y + 1
1467
1468 if dist < min_dist
1469 and line_of_sight(self, sp, p, 2) == true then
1470 min_dist = dist
1471 min_player = obj.object
1472 end
1473 end
1474 end
1475
1476 if min_player then
1477 do_attack(self, min_player)
1478 end
1479 end
1480
1481
1482 -- specific runaway
1483 local specific_runaway = function(list, what)
1484
1485 -- no list so do not run
1486 if list == nil then
1487 return false
1488 end
1489
1490 -- found entity on list to attack?
1491 for no = 1, #list do
1492
1493 if list[no] == what then
1494 return true
1495 end
1496 end
1497
1498 return false
1499 end
1500
1501
1502 -- find someone to runaway from
1503 local runaway_from = function(self)
1504
1505 if not self.runaway_from then
1506 return
1507 end
1508
1509 local s = self.object:get_pos()
1510 local p, sp, dist
1511 local player, obj, min_player
1512 local type, name = "", ""
1513 local min_dist = self.view_range + 1
1514 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1515
1516 for n = 1, #objs do
1517
1518 if objs[n]:is_player() then
1519
1520 if mobs.invis[ objs[n]:get_player_name() ]
1521 or self.owner == objs[n]:get_player_name() then
1522
1523 type = ""
1524 else
1525 player = objs[n]
1526 type = "player"
1527 name = "player"
1528 end
1529 else
1530 obj = objs[n]:get_luaentity()
1531
1532 if obj then
1533 player = obj.object
1534 type = obj.type
1535 name = obj.name or ""
1536 end
1537 end
1538
1539 -- find specific mob to runaway from
1540 if name ~= "" and name ~= self.name
1541 and specific_runaway(self.runaway_from, name) then
1542
1543 p = player:get_pos()
1544 sp = s
1545
1546 -- aim higher to make looking up hills more realistic
1547 p.y = p.y + 1
1548 sp.y = sp.y + 1
1549
1550 dist = get_distance(p, s)
1551
1552
1553 -- choose closest player/mpb to runaway from
1554 if dist < min_dist
1555 and line_of_sight(self, sp, p, 2) == true then
1556 min_dist = dist
1557 min_player = player
1558 end
1559 end
1560 end
1561
1562 if min_player then
1563
1564 local lp = player:get_pos()
1565 local vec = {
1566 x = lp.x - s.x,
1567 y = lp.y - s.y,
1568 z = lp.z - s.z
1569 }
1570
1571 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1572
1573 if lp.x > s.x then
1574 yaw = yaw + pi
1575 end
1576
1577 yaw = set_yaw(self, yaw, 4)
1578 self.state = "runaway"
1579 self.runaway_timer = 3
1580 self.following = nil
1581 end
1582 end
1583
1584
1585 -- follow player if owner or holding item, if fish outta water then flop
1586 local follow_flop = function(self)
1587
1588 -- find player to follow
1589 if (self.follow ~= ""
1590 or self.order == "follow")
1591 and not self.following
1592 and self.state ~= "attack"
1593 and self.state ~= "runaway" then
1594
1595 local s = self.object:get_pos()
1596 local players = minetest.get_connected_players()
1597
1598 for n = 1, #players do
1599
1600 if get_distance(players[n]:get_pos(), s) < self.view_range
1601 and not mobs.invis[ players[n]:get_player_name() ] then
1602
1603 self.following = players[n]
1604
1605 break
1606 end
1607 end
1608 end
1609
1610 if self.type == "npc"
1611 and self.order == "follow"
1612 and self.state ~= "attack"
1613 and self.owner ~= "" then
1614
1615 -- npc stop following player if not owner
1616 if self.following
1617 and self.owner
1618 and self.owner ~= self.following:get_player_name() then
1619 self.following = nil
1620 end
1621 else
1622 -- stop following player if not holding specific item
1623 if self.following
1624 and self.following:is_player()
1625 and follow_holding(self, self.following) == false then
1626 self.following = nil
1627 end
1628
1629 end
1630
1631 -- follow that thing
1632 if self.following then
1633
1634 local s = self.object:get_pos()
1635 local p
1636
1637 if self.following:is_player() then
1638
1639 p = self.following:get_pos()
1640
1641 elseif self.following.object then
1642
1643 p = self.following.object:get_pos()
1644 end
1645
1646 if p then
1647
1648 local dist = get_distance(p, s)
1649
1650 -- dont follow if out of range
1651 if dist > self.view_range then
1652 self.following = nil
1653 else
1654 local vec = {
1655 x = p.x - s.x,
1656 z = p.z - s.z
1657 }
1658
1659 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1660
1661 if p.x > s.x then yaw = yaw + pi end
1662
1663 yaw = set_yaw(self, yaw, 6)
1664
1665 -- anyone but standing npc's can move along
1666 if dist > self.reach
1667 and self.order ~= "stand" then
1668
1669 set_velocity(self, self.walk_velocity)
1670
1671 if self.walk_chance ~= 0 then
1672 set_animation(self, "walk")
1673 end
1674 else
1675 set_velocity(self, 0)
1676 set_animation(self, "stand")
1677 end
1678
1679 return
1680 end
1681 end
1682 end
1683
1684 -- swimmers flop when out of their element, and swim again when back in
1685 if self.fly then
1686 local s = self.object:get_pos()
1687 if not flight_check(self, s) then
1688
1689 self.state = "flop"
1690 self.object:setvelocity({x = 0, y = -5, z = 0})
1691
1692 set_animation(self, "stand")
1693
1694 return
1695 elseif self.state == "flop" then
1696 self.state = "stand"
1697 end
1698 end
1699 end
1700
1701
1702 -- dogshoot attack switch and counter function
1703 local dogswitch = function(self, dtime)
1704
1705 -- switch mode not activated
1706 if not self.dogshoot_switch
1707 or not dtime then
1708 return 0
1709 end
1710
1711 self.dogshoot_count = self.dogshoot_count + dtime
1712
1713 if (self.dogshoot_switch == 1
1714 and self.dogshoot_count > self.dogshoot_count_max)
1715 or (self.dogshoot_switch == 2
1716 and self.dogshoot_count > self.dogshoot_count2_max) then
1717
1718 self.dogshoot_count = 0
1719
1720 if self.dogshoot_switch == 1 then
1721 self.dogshoot_switch = 2
1722 else
1723 self.dogshoot_switch = 1
1724 end
1725 end
1726
1727 return self.dogshoot_switch
1728 end
1729
1730
1731 -- execute current state (stand, walk, run, attacks)
1732 local do_states = function(self, dtime)
1733
1734 local yaw = self.object:get_yaw() or 0
1735
1736 if self.state == "stand" then
1737
1738 if random(1, 4) == 1 then
1739
1740 local lp = nil
1741 local s = self.object:get_pos()
1742 local objs = minetest.get_objects_inside_radius(s, 3)
1743
1744 for n = 1, #objs do
1745
1746 if objs[n]:is_player() then
1747 lp = objs[n]:get_pos()
1748 break
1749 end
1750 end
1751
1752 -- look at any players nearby, otherwise turn randomly
1753 if lp then
1754
1755 local vec = {
1756 x = lp.x - s.x,
1757 z = lp.z - s.z
1758 }
1759
1760 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1761
1762 if lp.x > s.x then yaw = yaw + pi end
1763 else
1764 yaw = yaw + random(-0.5, 0.5)
1765 end
1766
1767 yaw = set_yaw(self, yaw, 8)
1768 end
1769
1770 set_velocity(self, 0)
1771 set_animation(self, "stand")
1772
1773 -- npc's ordered to stand stay standing
1774 if self.type ~= "npc"
1775 or self.order ~= "stand" then
1776
1777 if self.walk_chance ~= 0
1778 and self.facing_fence ~= true
1779 and random(1, 100) <= self.walk_chance
1780 and is_at_cliff(self) == false then
1781
1782 set_velocity(self, self.walk_velocity)
1783 self.state = "walk"
1784 set_animation(self, "walk")
1785
1786 --[[ fly up/down randomly for flying mobs
1787 if self.fly and random(1, 100) <= self.walk_chance then
1788
1789 local v = self.object:getvelocity()
1790 local ud = random(-1, 2) / 9
1791
1792 self.object:setvelocity({x = v.x, y = ud, z = v.z})
1793 end--]]
1794 end
1795 end
1796
1797 elseif self.state == "walk" then
1798
1799 local s = self.object:get_pos()
1800 local lp = nil
1801
1802 -- is there something I need to avoid?
1803 if self.water_damage > 0
1804 and self.lava_damage > 0 then
1805
1806 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
1807
1808 elseif self.water_damage > 0 then
1809
1810 lp = minetest.find_node_near(s, 1, {"group:water"})
1811
1812 elseif self.lava_damage > 0 then
1813
1814 lp = minetest.find_node_near(s, 1, {"group:lava"})
1815 end
1816
1817 if lp then
1818
1819 -- if mob in water or lava then look for land
1820 if (self.lava_damage
1821 and minetest.registered_nodes[self.standing_in].groups.lava)
1822 or (self.water_damage
1823 and minetest.registered_nodes[self.standing_in].groups.water) then
1824
1825 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1826 "group:sand", node_ice, node_snowblock})
1827
1828 -- did we find land?
1829 if lp then
1830
1831 local vec = {
1832 x = lp.x - s.x,
1833 z = lp.z - s.z
1834 }
1835
1836 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1837
1838 if lp.x > s.x then yaw = yaw + pi end
1839
1840 -- look towards land and jump/move in that direction
1841 yaw = set_yaw(self, yaw, 6)
1842 do_jump(self)
1843 set_velocity(self, self.walk_velocity)
1844 else
1845 yaw = yaw + random(-0.5, 0.5)
1846 end
1847
1848 else
1849
1850 local vec = {
1851 x = lp.x - s.x,
1852 z = lp.z - s.z
1853 }
1854
1855 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1856
1857 if lp.x > s.x then yaw = yaw + pi end
1858 end
1859
1860 yaw = set_yaw(self, yaw, 8)
1861
1862 -- otherwise randomly turn
1863 elseif random(1, 100) <= 30 then
1864
1865 yaw = yaw + random(-0.5, 0.5)
1866
1867 yaw = set_yaw(self, yaw, 8)
1868 end
1869
1870 -- stand for great fall in front
1871 local temp_is_cliff = is_at_cliff(self)
1872
1873 if self.facing_fence == true
1874 or temp_is_cliff
1875 or random(1, 100) <= 30 then
1876
1877 set_velocity(self, 0)
1878 self.state = "stand"
1879 set_animation(self, "stand")
1880 else
1881 set_velocity(self, self.walk_velocity)
1882
1883 if flight_check(self)
1884 and self.animation
1885 and self.animation.fly_start
1886 and self.animation.fly_end then
1887 set_animation(self, "fly")
1888 else
1889 set_animation(self, "walk")
1890 end
1891 end
1892
1893 -- runaway when punched
1894 elseif self.state == "runaway" then
1895
1896 self.runaway_timer = self.runaway_timer + 1
1897
1898 -- stop after 5 seconds or when at cliff
1899 if self.runaway_timer > 5
1900 or is_at_cliff(self) then
1901 self.runaway_timer = 0
1902 set_velocity(self, 0)
1903 self.state = "stand"
1904 set_animation(self, "stand")
1905 else
1906 set_velocity(self, self.run_velocity)
1907 set_animation(self, "walk")
1908 end
1909
1910 -- attack routines (explode, dogfight, shoot, dogshoot)
1911 elseif self.state == "attack" then
1912
1913 -- calculate distance from mob and enemy
1914 local s = self.object:get_pos()
1915 local p = self.attack:get_pos() or s
1916 local dist = get_distance(p, s)
1917
1918 -- stop attacking if player invisible or out of range
1919 if dist > self.view_range
1920 or not self.attack
1921 or not self.attack:get_pos()
1922 or self.attack:get_hp() <= 0
1923 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1924
1925 -- print(" ** stop attacking **", dist, self.view_range)
1926 self.state = "stand"
1927 set_velocity(self, 0)
1928 set_animation(self, "stand")
1929 self.attack = nil
1930 self.v_start = false
1931 self.timer = 0
1932 self.blinktimer = 0
1933 self.path.way = nil
1934
1935 return
1936 end
1937
1938 if self.attack_type == "explode" then
1939
1940 local vec = {
1941 x = p.x - s.x,
1942 z = p.z - s.z
1943 }
1944
1945 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1946
1947 if p.x > s.x then yaw = yaw + pi end
1948
1949 yaw = set_yaw(self, yaw)
1950
1951 local node_break_radius = self.explosion_radius or 1
1952 local entity_damage_radius = self.explosion_damage_radius
1953 or (node_break_radius * 2)
1954
1955 -- start timer when in reach and line of sight
1956 if not self.v_start
1957 and dist <= self.reach
1958 and line_of_sight(self, s, p, 2) then
1959
1960 self.v_start = true
1961 self.timer = 0
1962 self.blinktimer = 0
1963 mob_sound(self, self.sounds.fuse)
1964 -- print ("=== explosion timer started", self.explosion_timer)
1965
1966 -- stop timer if out of reach or direct line of sight
1967 elseif self.allow_fuse_reset
1968 and self.v_start
1969 and (dist > self.reach
1970 or not line_of_sight(self, s, p, 2)) then
1971 self.v_start = false
1972 self.timer = 0
1973 self.blinktimer = 0
1974 self.blinkstatus = false
1975 self.object:settexturemod("")
1976 end
1977
1978 -- walk right up to player unless the timer is active
1979 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1980 set_velocity(self, 0)
1981 else
1982 set_velocity(self, self.run_velocity)
1983 end
1984
1985 if self.animation and self.animation.run_start then
1986 set_animation(self, "run")
1987 else
1988 set_animation(self, "walk")
1989 end
1990
1991 if self.v_start then
1992
1993 self.timer = self.timer + dtime
1994 self.blinktimer = (self.blinktimer or 0) + dtime
1995
1996 if self.blinktimer > 0.2 then
1997
1998 self.blinktimer = 0
1999
2000 if self.blinkstatus then
2001 self.object:settexturemod("")
2002 else
2003 self.object:settexturemod("^[brighten")
2004 end
2005
2006 self.blinkstatus = not self.blinkstatus
2007 end
2008
2009 -- print ("=== explosion timer", self.timer)
2010
2011 if self.timer > self.explosion_timer then
2012
2013 local pos = self.object:get_pos()
2014
2015 -- dont damage anything if area protected or next to water
2016 if minetest.find_node_near(pos, 1, {"group:water"})
2017 or minetest.is_protected(pos, "") then
2018
2019 node_break_radius = 1
2020 end
2021
2022 self.object:remove()
2023
2024 if minetest.get_modpath("tnt") and tnt and tnt.boom
2025 and not minetest.is_protected(pos, "") then
2026
2027 tnt.boom(pos, {
2028 radius = node_break_radius,
2029 damage_radius = entity_damage_radius,
2030 sound = self.sounds.explode,
2031 })
2032 else
2033
2034 minetest.sound_play(self.sounds.explode, {
2035 pos = pos,
2036 gain = 1.0,
2037 max_hear_distance = self.sounds.distance or 32
2038 })
2039
2040 entity_physics(pos, entity_damage_radius)
2041 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2042 end
2043
2044 return
2045 end
2046 end
2047
2048 elseif self.attack_type == "dogfight"
2049 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2050 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2051
2052 if self.fly
2053 and dist > self.reach then
2054
2055 local p1 = s
2056 local me_y = floor(p1.y)
2057 local p2 = p
2058 local p_y = floor(p2.y + 1)
2059 local v = self.object:getvelocity()
2060
2061 if flight_check(self, s) then
2062
2063 if me_y < p_y then
2064
2065 self.object:setvelocity({
2066 x = v.x,
2067 y = 1 * self.walk_velocity,
2068 z = v.z
2069 })
2070
2071 elseif me_y > p_y then
2072
2073 self.object:setvelocity({
2074 x = v.x,
2075 y = -1 * self.walk_velocity,
2076 z = v.z
2077 })
2078 end
2079 else
2080 if me_y < p_y then
2081
2082 self.object:setvelocity({
2083 x = v.x,
2084 y = 0.01,
2085 z = v.z
2086 })
2087
2088 elseif me_y > p_y then
2089
2090 self.object:setvelocity({
2091 x = v.x,
2092 y = -0.01,
2093 z = v.z
2094 })
2095 end
2096 end
2097
2098 end
2099
2100 -- rnd: new movement direction
2101 if self.path.following
2102 and self.path.way
2103 and self.attack_type ~= "dogshoot" then
2104
2105 -- no paths longer than 50
2106 if #self.path.way > 50
2107 or dist < self.reach then
2108 self.path.following = false
2109 return
2110 end
2111
2112 local p1 = self.path.way[1]
2113
2114 if not p1 then
2115 self.path.following = false
2116 return
2117 end
2118
2119 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2120 -- reached waypoint, remove it from queue
2121 table.remove(self.path.way, 1)
2122 end
2123
2124 -- set new temporary target
2125 p = {x = p1.x, y = p1.y, z = p1.z}
2126 end
2127
2128 local vec = {
2129 x = p.x - s.x,
2130 z = p.z - s.z
2131 }
2132
2133 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2134
2135 if p.x > s.x then yaw = yaw + pi end
2136
2137 yaw = set_yaw(self, yaw)
2138
2139 -- move towards enemy if beyond mob reach
2140 if dist > self.reach then
2141
2142 -- path finding by rnd
2143 if self.pathfinding -- only if mob has pathfinding enabled
2144 and enable_pathfinding then
2145
2146 smart_mobs(self, s, p, dist, dtime)
2147 end
2148
2149 if is_at_cliff(self) then
2150
2151 set_velocity(self, 0)
2152 set_animation(self, "stand")
2153 else
2154
2155 if self.path.stuck then
2156 set_velocity(self, self.walk_velocity)
2157 else
2158 set_velocity(self, self.run_velocity)
2159 end
2160
2161 if self.animation and self.animation.run_start then
2162 set_animation(self, "run")
2163 else
2164 set_animation(self, "walk")
2165 end
2166 end
2167
2168 else -- rnd: if inside reach range
2169
2170 self.path.stuck = false
2171 self.path.stuck_timer = 0
2172 self.path.following = false -- not stuck anymore
2173
2174 set_velocity(self, 0)
2175
2176 if not self.custom_attack then
2177
2178 if self.timer > 1 then
2179
2180 self.timer = 0
2181
2182 if self.double_melee_attack
2183 and random(1, 2) == 1 then
2184 set_animation(self, "punch2")
2185 else
2186 set_animation(self, "punch")
2187 end
2188
2189 local p2 = p
2190 local s2 = s
2191
2192 p2.y = p2.y + .5
2193 s2.y = s2.y + .5
2194
2195 if line_of_sight(self, p2, s2) == true then
2196
2197 -- play attack sound
2198 mob_sound(self, self.sounds.attack)
2199
2200 -- punch player (or what player is attached to)
2201 local attached = self.attack:get_attach()
2202 if attached then
2203 self.attack = attached
2204 end
2205 self.attack:punch(self.object, 1.0, {
2206 full_punch_interval = 1.0,
2207 damage_groups = {fleshy = self.damage}
2208 }, nil)
2209 end
2210 end
2211 else -- call custom attack every second
2212 if self.custom_attack
2213 and self.timer > 1 then
2214
2215 self.timer = 0
2216
2217 self.custom_attack(self, p)
2218 end
2219 end
2220 end
2221
2222 elseif self.attack_type == "shoot"
2223 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2224 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2225
2226 p.y = p.y - .5
2227 s.y = s.y + .5
2228
2229 local dist = get_distance(p, s)
2230 local vec = {
2231 x = p.x - s.x,
2232 y = p.y - s.y,
2233 z = p.z - s.z
2234 }
2235
2236 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2237
2238 if p.x > s.x then yaw = yaw + pi end
2239
2240 yaw = set_yaw(self, yaw)
2241
2242 set_velocity(self, 0)
2243
2244 if self.shoot_interval
2245 and self.timer > self.shoot_interval
2246 and random(1, 100) <= 60 then
2247
2248 self.timer = 0
2249 set_animation(self, "shoot")
2250
2251 -- play shoot attack sound
2252 mob_sound(self, self.sounds.shoot_attack)
2253
2254 local p = self.object:get_pos()
2255
2256 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
2257
2258 if minetest.registered_entities[self.arrow] then
2259
2260 local obj = minetest.add_entity(p, self.arrow)
2261 local ent = obj:get_luaentity()
2262 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2263 local v = ent.velocity or 1 -- or set to default
2264
2265 ent.switch = 1
2266 ent.owner_id = tostring(self.object) -- add unique owner id to arrow
2267
2268 -- offset makes shoot aim accurate
2269 vec.y = vec.y + self.shoot_offset
2270 vec.x = vec.x * (v / amount)
2271 vec.y = vec.y * (v / amount)
2272 vec.z = vec.z * (v / amount)
2273
2274 obj:setvelocity(vec)
2275 end
2276 end
2277 end
2278 end
2279 end
2280
2281
2282 -- falling and fall damage
2283 local falling = function(self, pos)
2284
2285 if self.fly then
2286 return
2287 end
2288
2289 -- floating in water (or falling)
2290 local v = self.object:getvelocity()
2291
2292 if v.y > 0 then
2293
2294 -- apply gravity when moving up
2295 self.object:setacceleration({
2296 x = 0,
2297 y = -10,
2298 z = 0
2299 })
2300
2301 elseif v.y <= 0 and v.y > self.fall_speed then
2302
2303 -- fall downwards at set speed
2304 self.object:setacceleration({
2305 x = 0,
2306 y = self.fall_speed,
2307 z = 0
2308 })
2309 else
2310 -- stop accelerating once max fall speed hit
2311 self.object:setacceleration({x = 0, y = 0, z = 0})
2312 end
2313
2314 -- in water then float up
2315 if minetest.registered_nodes[self.standing_in].groups.water then
2316
2317 if self.floats == 1 then
2318
2319 self.object:setacceleration({
2320 x = 0,
2321 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2322 z = 0
2323 })
2324 end
2325 else
2326
2327 -- fall damage onto solid ground
2328 if self.fall_damage == 1
2329 and self.object:getvelocity().y == 0 then
2330
2331 local d = (self.old_y or 0) - self.object:get_pos().y
2332
2333 if d > 5 then
2334
2335 self.health = self.health - floor(d - 5)
2336
2337 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
2338
2339 if check_for_death(self, "fall", {type = "fall"}) then
2340 return
2341 end
2342 end
2343
2344 self.old_y = self.object:get_pos().y
2345 end
2346 end
2347 end
2348
2349
2350 -- deal damage and effects when mob punched
2351 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2352
2353 -- custom punch function
2354 if self.do_punch then
2355
2356 -- when false skip going any further
2357 if self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2358 return
2359 end
2360 end
2361
2362 -- mob health check
2363 -- if self.health <= 0 then
2364 -- return
2365 -- end
2366
2367 -- error checking when mod profiling is enabled
2368 if not tool_capabilities then
2369 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2370 return
2371 end
2372
2373 -- is mob protected?
2374 if self.protected and hitter:is_player()
2375 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2376 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2377 return
2378 end
2379
2380
2381 -- weapon wear
2382 local weapon = hitter:get_wielded_item()
2383 local punch_interval = 1.4
2384
2385 -- calculate mob damage
2386 local damage = 0
2387 local armor = self.object:get_armor_groups() or {}
2388 local tmp
2389
2390 -- quick error check incase it ends up 0 (serialize.h check test)
2391 if tflp == 0 then
2392 tflp = 0.2
2393 end
2394
2395 if use_cmi then
2396 damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
2397 else
2398
2399 for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
2400
2401 tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
2402
2403 if tmp < 0 then
2404 tmp = 0.0
2405 elseif tmp > 1 then
2406 tmp = 1.0
2407 end
2408
2409 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2410 * tmp * ((armor[group] or 0) / 100.0)
2411 end
2412 end
2413
2414 -- check for tool immunity or special damage
2415 for n = 1, #self.immune_to do
2416
2417 if self.immune_to[n][1] == weapon:get_name() then
2418
2419 damage = self.immune_to[n][2] or 0
2420 break
2421
2422 -- if "all" then no tool does damage unless it's specified in list
2423 elseif self.immune_to[n][1] == "all" then
2424 damage = self.immune_to[n][2] or 0
2425 end
2426 end
2427
2428 -- healing
2429 if damage <= -1 then
2430 self.health = self.health - floor(damage)
2431 return
2432 end
2433
2434 -- print ("Mob Damage is", damage)
2435
2436 if use_cmi then
2437
2438 local cancel = cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage)
2439
2440 if cancel then return end
2441 end
2442
2443 -- add weapon wear
2444 if tool_capabilities then
2445 punch_interval = tool_capabilities.full_punch_interval or 1.4
2446 end
2447
2448 if weapon:get_definition()
2449 and weapon:get_definition().tool_capabilities then
2450
2451 weapon:add_wear(floor((punch_interval / 75) * 9000))
2452 hitter:set_wielded_item(weapon)
2453 end
2454
2455 -- only play hit sound and show blood effects if damage is 1 or over
2456 if damage >= 1 then
2457
2458 -- weapon sounds
2459 if weapon:get_definition().sounds ~= nil then
2460
2461 local s = random(0, #weapon:get_definition().sounds)
2462
2463 minetest.sound_play(weapon:get_definition().sounds[s], {
2464 object = self.object, --hitter,
2465 max_hear_distance = 8
2466 })
2467 else
2468 minetest.sound_play("default_punch", {
2469 object = self.object, --hitter,
2470 max_hear_distance = 5
2471 })
2472 end
2473
2474 -- blood_particles
2475 if self.blood_amount > 0
2476 and not disable_blood then
2477
2478 local pos = self.object:get_pos()
2479
2480 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2481
2482 -- do we have a single blood texture or multiple?
2483 if type(self.blood_texture) == "table" then
2484
2485 local blood = self.blood_texture[random(1, #self.blood_texture)]
2486
2487 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2488 else
2489 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2490 end
2491 end
2492
2493 -- do damage
2494 self.health = self.health - floor(damage)
2495
2496 -- exit here if dead, special item check
2497 if weapon:get_name() == "mobs:pick_lava" then
2498 if check_for_death(self, "lava", {type = "punch",
2499 puncher = hitter}) then
2500 return
2501 end
2502 else
2503 if check_for_death(self, "hit", {type = "punch",
2504 puncher = hitter}) then
2505 return
2506 end
2507 end
2508
2509 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2510 minetest.after(0.1, function()
2511
2512 if not self.object:get_luaentity() then return end
2513
2514 self.object:settexturemod("^[colorize:#c9900070")
2515
2516 core.after(0.3, function()
2517 self.object:settexturemod("")
2518 end)
2519 end) ]]
2520
2521 -- knock back effect (only on full punch)
2522 if self.knock_back
2523 and tflp >= punch_interval then
2524
2525 local v = self.object:getvelocity()
2526 local r = 1.4 - min(punch_interval, 1.4)
2527 local kb = r * 5
2528 local up = 2
2529
2530 -- if already in air then dont go up anymore when hit
2531 if v.y > 0
2532 or self.fly then
2533 up = 0
2534 end
2535
2536 -- direction error check
2537 dir = dir or {x = 0, y = 0, z = 0}
2538
2539 -- check if tool already has specific knockback value
2540 if tool_capabilities.damage_groups["knockback"] then
2541 kb = tool_capabilities.damage_groups["knockback"]
2542 else
2543 kb = kb * 1.5
2544 end
2545
2546 self.object:setvelocity({
2547 x = dir.x * kb,
2548 y = up,
2549 z = dir.z * kb
2550 })
2551
2552 self.pause_timer = 0.25
2553 end
2554 end -- END if damage
2555
2556 -- if skittish then run away
2557 if self.runaway == true then
2558
2559 local lp = hitter:get_pos()
2560 local s = self.object:get_pos()
2561 local vec = {
2562 x = lp.x - s.x,
2563 y = lp.y - s.y,
2564 z = lp.z - s.z
2565 }
2566
2567 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2568
2569 if lp.x > s.x then
2570 yaw = yaw + pi
2571 end
2572
2573 yaw = set_yaw(self, yaw, 6)
2574 self.state = "runaway"
2575 self.runaway_timer = 0
2576 self.following = nil
2577 end
2578
2579 local name = hitter:get_player_name() or ""
2580
2581 -- attack puncher and call other mobs for help
2582 if self.passive == false
2583 and self.state ~= "flop"
2584 and self.child == false
2585 and hitter:get_player_name() ~= self.owner
2586 and not mobs.invis[ name ] then
2587
2588 -- attack whoever punched mob
2589 self.state = ""
2590 do_attack(self, hitter)
2591
2592 -- alert others to the attack
2593 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2594 local obj = nil
2595
2596 for n = 1, #objs do
2597
2598 obj = objs[n]:get_luaentity()
2599
2600 if obj then
2601
2602 -- only alert members of same mob
2603 if obj.group_attack == true
2604 and obj.state ~= "attack"
2605 and obj.owner ~= name
2606 and obj.name == self.name then
2607 do_attack(obj, hitter)
2608 end
2609
2610 -- have owned mobs attack player threat
2611 if obj.owner == name and obj.owner_loyal then
2612 do_attack(obj, self.object)
2613 end
2614 end
2615 end
2616 end
2617 end
2618
2619
2620 -- get entity staticdata
2621 local mob_staticdata = function(self)
2622
2623 -- remove mob when out of range unless tamed
2624 if remove_far
2625 and self.remove_ok
2626 and self.type ~= "npc"
2627 and self.state ~= "attack"
2628 and not self.tamed
2629 and self.lifetimer < 20000 then
2630
2631 --print ("REMOVED " .. self.name)
2632
2633 self.object:remove()
2634
2635 return ""-- nil
2636 end
2637
2638 self.remove_ok = true
2639 self.attack = nil
2640 self.following = nil
2641 self.state = "stand"
2642
2643 -- used to rotate older mobs
2644 if self.drawtype
2645 and self.drawtype == "side" then
2646 self.rotate = math.rad(90)
2647 end
2648
2649 if use_cmi then
2650 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2651 end
2652
2653 local tmp = {}
2654
2655 for _,stat in pairs(self) do
2656
2657 local t = type(stat)
2658
2659 if t ~= "function"
2660 and t ~= "nil"
2661 and t ~= "userdata"
2662 and _ ~= "_cmi_components" then
2663 tmp[_] = self[_]
2664 end
2665 end
2666
2667 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
2668 return minetest.serialize(tmp)
2669 end
2670
2671
2672 -- activate mob and reload settings
2673 local mob_activate = function(self, staticdata, def, dtime)
2674
2675 -- remove monsters in peaceful mode
2676 if self.type == "monster"
2677 and peaceful_only then
2678
2679 self.object:remove()
2680
2681 return
2682 end
2683
2684 -- load entity variables
2685 local tmp = minetest.deserialize(staticdata)
2686
2687 if tmp then
2688 for _,stat in pairs(tmp) do
2689 self[_] = stat
2690 end
2691 end
2692
2693 -- select random texture, set model and size
2694 if not self.base_texture then
2695
2696 -- compatiblity with old simple mobs textures
2697 if type(def.textures[1]) == "string" then
2698 def.textures = {def.textures}
2699 end
2700
2701 self.base_texture = def.textures[random(1, #def.textures)]
2702 self.base_mesh = def.mesh
2703 self.base_size = self.visual_size
2704 self.base_colbox = self.collisionbox
2705 self.base_selbox = self.selectionbox
2706 end
2707
2708 -- for current mobs that dont have this set
2709 if not self.base_selbox then
2710 self.base_selbox = self.selectionbox or self.base_colbox
2711 end
2712
2713 -- set texture, model and size
2714 local textures = self.base_texture
2715 local mesh = self.base_mesh
2716 local vis_size = self.base_size
2717 local colbox = self.base_colbox
2718 local selbox = self.base_selbox
2719
2720 -- specific texture if gotten
2721 if self.gotten == true
2722 and def.gotten_texture then
2723 textures = def.gotten_texture
2724 end
2725
2726 -- specific mesh if gotten
2727 if self.gotten == true
2728 and def.gotten_mesh then
2729 mesh = def.gotten_mesh
2730 end
2731
2732 -- set child objects to half size
2733 if self.child == true then
2734
2735 vis_size = {
2736 x = self.base_size.x * .5,
2737 y = self.base_size.y * .5,
2738 }
2739
2740 if def.child_texture then
2741 textures = def.child_texture[1]
2742 end
2743
2744 colbox = {
2745 self.base_colbox[1] * .5,
2746 self.base_colbox[2] * .5,
2747 self.base_colbox[3] * .5,
2748 self.base_colbox[4] * .5,
2749 self.base_colbox[5] * .5,
2750 self.base_colbox[6] * .5
2751 }
2752 selbox = {
2753 self.base_selbox[1] * .5,
2754 self.base_selbox[2] * .5,
2755 self.base_selbox[3] * .5,
2756 self.base_selbox[4] * .5,
2757 self.base_selbox[5] * .5,
2758 self.base_selbox[6] * .5
2759 }
2760 end
2761
2762 if self.health == 0 then
2763 self.health = random (self.hp_min, self.hp_max)
2764 end
2765
2766 -- pathfinding init
2767 self.path = {}
2768 self.path.way = {} -- path to follow, table of positions
2769 self.path.lastpos = {x = 0, y = 0, z = 0}
2770 self.path.stuck = false
2771 self.path.following = false -- currently following path?
2772 self.path.stuck_timer = 0 -- if stuck for too long search for path
2773
2774 -- mob defaults
2775 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
2776 self.old_y = self.object:get_pos().y
2777 self.old_health = self.health
2778 self.sounds.distance = self.sounds.distance or 10
2779 self.textures = textures
2780 self.mesh = mesh
2781 self.collisionbox = colbox
2782 self.selectionbox = selbox
2783 self.visual_size = vis_size
2784 self.standing_in = "air"
2785
2786 -- check existing nametag
2787 if not self.nametag then
2788 self.nametag = def.nametag
2789 end
2790
2791 -- set anything changed above
2792 self.object:set_properties(self)
2793 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2794 update_tag(self)
2795 set_animation(self, "stand")
2796
2797 -- run on_spawn function if found
2798 if self.on_spawn and not self.on_spawn_run then
2799 if self.on_spawn(self) then
2800 self.on_spawn_run = true -- if true, set flag to run once only
2801 end
2802 end
2803
2804 -- run after_activate
2805 if def.after_activate then
2806 def.after_activate(self, staticdata, def, dtime)
2807 end
2808
2809 if use_cmi then
2810 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
2811 cmi.notify_activate(self.object, dtime)
2812 end
2813 end
2814
2815
2816 -- main mob function
2817 local mob_step = function(self, dtime)
2818
2819 if use_cmi then
2820 cmi.notify_step(self.object, dtime)
2821 end
2822
2823 local pos = self.object:get_pos()
2824 local yaw = 0
2825
2826 -- when lifetimer expires remove mob (except npc and tamed)
2827 if self.type ~= "npc"
2828 and not self.tamed
2829 and self.state ~= "attack"
2830 and remove_far ~= true
2831 and self.lifetimer < 20000 then
2832
2833 self.lifetimer = self.lifetimer - dtime
2834
2835 if self.lifetimer <= 0 then
2836
2837 -- only despawn away from player
2838 local objs = minetest.get_objects_inside_radius(pos, 15)
2839
2840 for n = 1, #objs do
2841
2842 if objs[n]:is_player() then
2843
2844 self.lifetimer = 20
2845
2846 return
2847 end
2848 end
2849
2850 -- minetest.log("action",
2851 -- S("lifetimer expired, removed @1", self.name))
2852
2853 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
2854
2855 self.object:remove()
2856
2857 return
2858 end
2859 end
2860
2861 -- get node at foot level every quarter second
2862 self.node_timer = (self.node_timer or 0) + dtime
2863
2864 if self.node_timer > 0.25 then
2865
2866 self.node_timer = 0
2867
2868 local y_level = self.collisionbox[2]
2869
2870 if self.child then
2871 y_level = self.collisionbox[2] * 0.5
2872 end
2873
2874 -- what is mob standing in?
2875 self.standing_in = node_ok({
2876 x = pos.x, y = pos.y + y_level + 0.25, z = pos.z}, "air").name
2877 -- print ("standing in " .. self.standing_in)
2878 end
2879
2880 -- check if falling, flying, floating
2881 falling(self, pos)
2882
2883 -- smooth rotation by ThomasMonroe314
2884
2885 if self.delay and self.delay > 0 then
2886
2887 local yaw = self.object:get_yaw()
2888
2889 if self.delay == 1 then
2890 yaw = self.target_yaw
2891 else
2892 local dif = abs(yaw - self.target_yaw)
2893
2894 if yaw > self.target_yaw then
2895
2896 if dif > pi then
2897 dif = 2 * pi - dif -- need to add
2898 yaw = yaw + dif / self.delay
2899 else
2900 yaw = yaw - dif / self.delay -- need to subtract
2901 end
2902
2903 elseif yaw < self.target_yaw then
2904
2905 if dif > pi then
2906 dif = 2 * pi - dif
2907 yaw = yaw - dif / self.delay -- need to subtract
2908 else
2909 yaw = yaw + dif / self.delay -- need to add
2910 end
2911 end
2912
2913 if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
2914 if yaw < 0 then yaw = yaw + (pi * 2) end
2915 end
2916
2917 self.delay = self.delay - 1
2918 self.object:set_yaw(yaw)
2919 end
2920
2921 -- end rotation
2922
2923 -- knockback timer
2924 if self.pause_timer > 0 then
2925
2926 self.pause_timer = self.pause_timer - dtime
2927
2928 return
2929 end
2930
2931 -- run custom function (defined in mob lua file)
2932 if self.do_custom then
2933
2934 -- when false skip going any further
2935 if self.do_custom(self, dtime) == false then
2936 return
2937 end
2938 end
2939
2940 -- attack timer
2941 self.timer = self.timer + dtime
2942
2943 if self.state ~= "attack" then
2944
2945 if self.timer < 1 then
2946 return
2947 end
2948
2949 self.timer = 0
2950 end
2951
2952 -- never go over 100
2953 if self.timer > 100 then
2954 self.timer = 1
2955 end
2956
2957 -- mob plays random sound at times
2958 if random(1, 100) == 1 then
2959 mob_sound(self, self.sounds.random)
2960 end
2961
2962 -- environmental damage timer (every 1 second)
2963 self.env_damage_timer = self.env_damage_timer + dtime
2964
2965 if (self.state == "attack" and self.env_damage_timer > 1)
2966 or self.state ~= "attack" then
2967
2968 self.env_damage_timer = 0
2969
2970 -- check for environmental damage (water, fire, lava etc.)
2971 do_env_damage(self)
2972
2973 -- node replace check (cow eats grass etc.)
2974 replace(self, pos)
2975 end
2976
2977 monster_attack(self)
2978
2979 npc_attack(self)
2980
2981 breed(self)
2982
2983 follow_flop(self)
2984
2985 do_states(self, dtime)
2986
2987 do_jump(self)
2988
2989 runaway_from(self)
2990
2991 end
2992
2993
2994 -- default function when mobs are blown up with TNT
2995 local do_tnt = function(obj, damage)
2996
2997 --print ("----- Damage", damage)
2998
2999 obj.object:punch(obj.object, 1.0, {
3000 full_punch_interval = 1.0,
3001 damage_groups = {fleshy = damage},
3002 }, nil)
3003
3004 return false, true, {}
3005 end
3006
3007
3008 mobs.spawning_mobs = {}
3009
3010 -- register mob entity
3011 function mobs:register_mob(name, def)
3012
3013 mobs.spawning_mobs[name] = true
3014
3015 minetest.register_entity(name, {
3016
3017 stepheight = def.stepheight or 1.1, -- was 0.6
3018 name = name,
3019 type = def.type,
3020 attack_type = def.attack_type,
3021 fly = def.fly,
3022 fly_in = def.fly_in or "air",
3023 owner = def.owner or "",
3024 order = def.order or "",
3025 on_die = def.on_die,
3026 do_custom = def.do_custom,
3027 jump_height = def.jump_height or 4, -- was 6
3028 drawtype = def.drawtype, -- DEPRECATED, use rotate instead
3029 rotate = math.rad(def.rotate or 0), -- 0=front, 90=side, 180=back, 270=side2
3030 lifetimer = def.lifetimer or 180, -- 3 minutes
3031 hp_min = max(1, (def.hp_min or 5) * difficulty),
3032 hp_max = max(1, (def.hp_max or 10) * difficulty),
3033 physical = true,
3034 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3035 selectionbox = def.selectionbox or def.collisionbox,
3036 visual = def.visual,
3037 visual_size = def.visual_size or {x = 1, y = 1},
3038 mesh = def.mesh,
3039 makes_footstep_sound = def.makes_footstep_sound or false,
3040 view_range = def.view_range or 5,
3041 walk_velocity = def.walk_velocity or 1,
3042 run_velocity = def.run_velocity or 2,
3043 damage = max(0, (def.damage or 0) * difficulty),
3044 light_damage = def.light_damage or 0,
3045 water_damage = def.water_damage or 0,
3046 lava_damage = def.lava_damage or 0,
3047 suffocation = def.suffocation or 2,
3048 fall_damage = def.fall_damage or 1,
3049 fall_speed = def.fall_speed or -10, -- must be lower than -2 (default: -10)
3050 drops = def.drops or {},
3051 armor = def.armor or 100,
3052 on_rightclick = def.on_rightclick,
3053 arrow = def.arrow,
3054 shoot_interval = def.shoot_interval,
3055 sounds = def.sounds or {},
3056 animation = def.animation,
3057 follow = def.follow,
3058 jump = def.jump ~= false,
3059 walk_chance = def.walk_chance or 50,
3060 attacks_monsters = def.attacks_monsters or false,
3061 group_attack = def.group_attack or false,
3062 passive = def.passive or false,
3063 knock_back = def.knock_back ~= false,
3064 blood_amount = def.blood_amount or 5,
3065 blood_texture = def.blood_texture or "mobs_blood.png",
3066 shoot_offset = def.shoot_offset or 0,
3067 floats = def.floats or 1, -- floats in water by default
3068 replace_rate = def.replace_rate,
3069 replace_what = def.replace_what,
3070 replace_with = def.replace_with,
3071 replace_offset = def.replace_offset or 0,
3072 on_replace = def.on_replace,
3073 timer = 0,
3074 env_damage_timer = 0, -- only used when state = "attack"
3075 tamed = false,
3076 pause_timer = 0,
3077 horny = false,
3078 hornytimer = 0,
3079 child = false,
3080 gotten = false,
3081 health = 0,
3082 reach = def.reach or 3,
3083 htimer = 0,
3084 texture_list = def.textures,
3085 child_texture = def.child_texture,
3086 docile_by_day = def.docile_by_day or false,
3087 time_of_day = 0.5,
3088 fear_height = def.fear_height or 0,
3089 runaway = def.runaway,
3090 runaway_timer = 0,
3091 pathfinding = def.pathfinding,
3092 immune_to = def.immune_to or {},
3093 explosion_radius = def.explosion_radius,
3094 explosion_damage_radius = def.explosion_damage_radius,
3095 explosion_timer = def.explosion_timer or 3,
3096 allow_fuse_reset = def.allow_fuse_reset ~= false,
3097 stop_to_explode = def.stop_to_explode ~= false,
3098 custom_attack = def.custom_attack,
3099 double_melee_attack = def.double_melee_attack,
3100 dogshoot_switch = def.dogshoot_switch,
3101 dogshoot_count = 0,
3102 dogshoot_count_max = def.dogshoot_count_max or 5,
3103 dogshoot_count2_max = def.dogshoot_count2_max or (def.dogshoot_count_max or 5),
3104 attack_animals = def.attack_animals or false,
3105 specific_attack = def.specific_attack,
3106 runaway_from = def.runaway_from,
3107 owner_loyal = def.owner_loyal,
3108 facing_fence = false,
3109 _cmi_is_mob = true,
3110
3111 on_spawn = def.on_spawn,
3112
3113 on_blast = def.on_blast or do_tnt,
3114
3115 on_step = mob_step,
3116
3117 do_punch = def.do_punch,
3118
3119 on_punch = mob_punch,
3120
3121 on_breed = def.on_breed,
3122
3123 on_grown = def.on_grown,
3124
3125 on_activate = function(self, staticdata, dtime)
3126 return mob_activate(self, staticdata, def, dtime)
3127 end,
3128
3129 get_staticdata = function(self)
3130 return mob_staticdata(self)
3131 end,
3132
3133 })
3134
3135 end -- END mobs:register_mob function
3136
3137
3138 -- count how many mobs of one type are inside an area
3139 local count_mobs = function(pos, type)
3140
3141 local num_type = 0
3142 local num_total = 0
3143 local objs = minetest.get_objects_inside_radius(pos, aoc_range)
3144
3145 for n = 1, #objs do
3146
3147 if not objs[n]:is_player() then
3148
3149 local obj = objs[n]:get_luaentity()
3150
3151 -- count mob type and add to total also
3152 if obj and obj.name and obj.name == type then
3153
3154 num_type = num_type + 1
3155 num_total = num_total + 1
3156
3157 -- add to total mobs
3158 elseif obj and obj.name and obj.health ~= nil then
3159
3160 num_total = num_total + 1
3161 end
3162 end
3163 end
3164
3165 return num_type, num_total
3166 end
3167
3168
3169 -- global functions
3170
3171 function mobs:spawn_abm_check(pos, node, name)
3172 -- global function to add additional spawn checks
3173 -- return true to stop spawning mob
3174 end
3175
3176
3177 local function player_near(pos, radius)
3178
3179 local objs = minetest.get_objects_inside_radius(pos, radius)
3180
3181 for n = 1, #objs do
3182
3183 if objs[n]:is_player() then
3184 return true
3185 end
3186 end
3187
3188 return false
3189 end
3190
3191
3192 local function daycheck(day_toggle)
3193
3194 if day_toggle ~= nil then
3195
3196 local tod = (minetest.get_timeofday() or 0) * 24000
3197
3198 if tod > 4500 and tod < 19500 then
3199
3200 if day_toggle == false then
3201 return false -- mob requires night
3202 end
3203 else
3204 if day_toggle == true then
3205 return false -- mob requires day
3206 end
3207 end
3208 end
3209
3210 return true -- mob doesn't care
3211 end
3212
3213
3214 local function is_protected(pos)
3215
3216 if not spawn_protected
3217 and minetest.is_protected(pos, "") then
3218 return true -- protected area
3219 end
3220
3221 return false -- mobs can spawn
3222 end
3223
3224
3225 local interval = 30
3226 local timer = 0
3227 local spawning_mobs = {}
3228
3229 minetest.register_globalstep(function(dtime)
3230
3231 if not mobs_spawn then
3232 return
3233 end
3234
3235 timer = timer + dtime
3236 if timer < interval then
3237 return
3238 end
3239 timer = 0
3240
3241 for _,player in ipairs(minetest.get_connected_players()) do
3242
3243 if player:get_hp() > 0 then
3244
3245 local pos = player:getpos()
3246 local area, pos2, light, obj, base
3247
3248 for _,mob in ipairs(spawning_mobs) do
3249
3250 area = nil
3251
3252 if minetest.registered_entities[mob.name]
3253 and random(1, mob.chance) == 1 then
3254
3255 area = minetest.find_nodes_in_area_under_air(
3256 {x = pos.x - 20, y = pos.y - 20, z = pos.z - 20},
3257 {x = pos.x + 20, y = pos.y + 20, z = pos.z + 20},
3258 mob.nodes)
3259 end
3260
3261 if area and #area > 0 then
3262
3263 pos2 = area[math.random(1, #area)]
3264 base = minetest.registered_entities[mob.name].collisionbox[5]
3265 pos2.y = pos2.y + 1 + base
3266
3267 light = minetest.get_node_light(pos2) or -1
3268
3269 if pos2.y >= mob.min_height
3270 and pos2.y <= mob.max_height
3271 and light >= mob.min_light
3272 and light <= mob.max_light
3273 and daycheck(mob.day_toggle)
3274 and minetest.find_node_near(pos2, 1, mob.neighbors)
3275 and count_mobs(pos2, mob.name) < mob.total
3276 and not player_near(pos2, 10)
3277 and not is_protected(pos2) then
3278
3279 print ("--- Spawned ", mob.name, minetest.pos_to_string(pos2), mob.chance)
3280
3281 obj = minetest.add_entity(pos2, mob.name)
3282
3283 if mob.on_spawn then
3284 mob.on_spawn(obj:get_luaentity(), pos2)
3285 end
3286 else
3287 print ("--- Cannot spawn ", mob.name)
3288 end
3289 end
3290 end
3291 end
3292 end
3293 end)
3294
3295
3296 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3297 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3298
3299 -- chance/spawn number override in minetest.conf for registered mob
3300 local numbers = minetest.settings:get(name)
3301
3302 if numbers then
3303 numbers = numbers:split(",")
3304 chance = tonumber(numbers[1]) or chance
3305 aoc = tonumber(numbers[2]) or aoc
3306
3307 if chance == 0 then
3308 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3309 return
3310 end
3311
3312 minetest.log("action",
3313 string.format("[mobs] Chance setting for %s changed to %s (total: %s)", name, chance, aoc))
3314 end
3315
3316 -- change old chance values to be more useable by new spawn routine
3317 if chance > 999 then
3318 chance = max(1, chance / 1000)
3319 end
3320
3321 -- adjust for mob chance multiplier
3322 chance = max(1, chance * mob_chance_multiplier)
3323
3324 -- add mob to table for spawning with routine above
3325 table.insert(spawning_mobs, {
3326 name = name,
3327 nodes = nodes,
3328 neighbors = neighbors,
3329 chance = chance,
3330 min_height = min_height,
3331 max_height = max_height,
3332 min_light = min_light,
3333 max_light = max_light,
3334 total = aoc,
3335 day_toggle = day_toggle,
3336 on_spawn = on_spawn,
3337 })
3338 end
3339
3340
3341 -- compatibility with older mob registration
3342 function mobs:register_spawn(name, nodes, max_light, min_light, chance, active_object_count, max_height, day_toggle)
3343
3344 mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
3345 chance, active_object_count, -31000, max_height, day_toggle)
3346 end
3347
3348
3349 -- MarkBu's spawn function
3350 function mobs:spawn(def)
3351
3352 mobs:spawn_specific(
3353 def.name,
3354 def.nodes or {"group:soil", "group:stone"},
3355 def.neighbors or {"air"},
3356 def.min_light or 0,
3357 def.max_light or 15,
3358 def.interval or 30,
3359 def.chance or 5000,
3360 def.active_object_count or 1,
3361 def.min_height or -31000,
3362 def.max_height or 31000,
3363 def.day_toggle,
3364 def.on_spawn
3365 )
3366 end
3367
3368
3369 -- register arrow for shoot attack
3370 function mobs:register_arrow(name, def)
3371
3372 if not name or not def then return end -- errorcheck
3373
3374 minetest.register_entity(name, {
3375
3376 physical = false,
3377 visual = def.visual,
3378 visual_size = def.visual_size,
3379 textures = def.textures,
3380 velocity = def.velocity,
3381 hit_player = def.hit_player,
3382 hit_node = def.hit_node,
3383 hit_mob = def.hit_mob,
3384 drop = def.drop or false, -- drops arrow as registered item when true
3385 collisionbox = {0, 0, 0, 0, 0, 0}, -- remove box around arrows
3386 timer = 0,
3387 switch = 0,
3388 owner_id = def.owner_id,
3389 rotate = def.rotate,
3390 automatic_face_movement_dir = def.rotate
3391 and (def.rotate - (pi / 180)) or false,
3392
3393 on_activate = def.on_activate,
3394
3395 on_step = def.on_step or function(self, dtime)
3396
3397 self.timer = self.timer + 1
3398
3399 local pos = self.object:get_pos()
3400
3401 if self.switch == 0
3402 or self.timer > 150
3403 or not within_limits(pos, 0) then
3404
3405 self.object:remove() ; -- print ("removed arrow")
3406
3407 return
3408 end
3409
3410 -- does arrow have a tail (fireball)
3411 if def.tail
3412 and def.tail == 1
3413 and def.tail_texture then
3414
3415 minetest.add_particle({
3416 pos = pos,
3417 velocity = {x = 0, y = 0, z = 0},
3418 acceleration = {x = 0, y = 0, z = 0},
3419 expirationtime = def.expire or 0.25,
3420 collisiondetection = false,
3421 texture = def.tail_texture,
3422 size = def.tail_size or 5,
3423 glow = def.glow or 0,
3424 })
3425 end
3426
3427 if self.hit_node then
3428
3429 local node = node_ok(pos).name
3430
3431 if minetest.registered_nodes[node].walkable then
3432
3433 self.hit_node(self, pos, node)
3434
3435 if self.drop == true then
3436
3437 pos.y = pos.y + 1
3438
3439 self.lastpos = (self.lastpos or pos)
3440
3441 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
3442 end
3443
3444 self.object:remove() ; -- print ("hit node")
3445
3446 return
3447 end
3448 end
3449
3450 if self.hit_player or self.hit_mob then
3451
3452 for _,player in pairs(minetest.get_objects_inside_radius(pos, 1.0)) do
3453
3454 if self.hit_player
3455 and player:is_player() then
3456
3457 self.hit_player(self, player)
3458 self.object:remove() ; -- print ("hit player")
3459 return
3460 end
3461
3462 local entity = player:get_luaentity()
3463
3464 if entity
3465 and self.hit_mob
3466 and entity._cmi_is_mob == true
3467 and tostring(player) ~= self.owner_id
3468 and entity.name ~= self.object:get_luaentity().name then
3469
3470 self.hit_mob(self, player)
3471
3472 self.object:remove() ; --print ("hit mob")
3473
3474 return
3475 end
3476 end
3477 end
3478
3479 self.lastpos = pos
3480 end
3481 })
3482 end
3483
3484
3485 -- compatibility function
3486 function mobs:explosion(pos, radius)
3487 local self = {sounds = {}}
3488 self.sounds.explode = "tnt_explode"
3489 mobs:boom(self, pos, radius)
3490 end
3491
3492
3493 -- no damage to nodes explosion
3494 function mobs:safe_boom(self, pos, radius)
3495
3496 minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
3497 pos = pos,
3498 gain = 1.0,
3499 max_hear_distance = self.sounds and self.sounds.distance or 32
3500 })
3501
3502 entity_physics(pos, radius)
3503 effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
3504 end
3505
3506
3507 -- make explosion with protection and tnt mod check
3508 function mobs:boom(self, pos, radius)
3509
3510 if mobs_griefing
3511 and minetest.get_modpath("tnt") and tnt and tnt.boom
3512 and not minetest.is_protected(pos, "") then
3513
3514 tnt.boom(pos, {
3515 radius = radius,
3516 damage_radius = radius,
3517 sound = self.sounds and self.sounds.explode,
3518 explode_center = true,
3519 })
3520 else
3521 mobs:safe_boom(self, pos, radius)
3522 end
3523 end
3524
3525
3526 -- Register spawn eggs
3527
3528 -- Note: This also introduces the “spawn_egg” group:
3529 -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
3530 -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
3531 function mobs:register_egg(mob, desc, background, addegg, no_creative)
3532
3533 local grp = {spawn_egg = 1}
3534
3535 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3536 if creative and no_creative == true then
3537 grp.not_in_creative_inventory = 1
3538 end
3539
3540 local invimg = background
3541
3542 if addegg == 1 then
3543 invimg = "mobs_chicken_egg.png^(" .. invimg ..
3544 "^[mask:mobs_chicken_egg_overlay.png)"
3545 end
3546
3547 -- register new spawn egg containing mob information
3548 minetest.register_craftitem(mob .. "_set", {
3549
3550 description = S("@1 (Tamed)", desc),
3551 inventory_image = invimg,
3552 groups = {spawn_egg = 2, not_in_creative_inventory = 1},
3553 stack_max = 1,
3554
3555 on_place = function(itemstack, placer, pointed_thing)
3556
3557 local pos = pointed_thing.above
3558
3559 -- am I clicking on something with existing on_rightclick function?
3560 local under = minetest.get_node(pointed_thing.under)
3561 local def = minetest.registered_nodes[under.name]
3562 if def and def.on_rightclick then
3563 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3564 end
3565
3566 if pos
3567 and within_limits(pos, 0)
3568 and not minetest.is_protected(pos, placer:get_player_name()) then
3569
3570 if not minetest.registered_entities[mob] then
3571 return
3572 end
3573
3574 pos.y = pos.y + 1
3575
3576 local data = itemstack:get_metadata()
3577 local mob = minetest.add_entity(pos, mob, data)
3578 local ent = mob:get_luaentity()
3579
3580 -- set owner if not a monster
3581 if ent.type ~= "monster" then
3582 ent.owner = placer:get_player_name()
3583 ent.tamed = true
3584 end
3585
3586 -- since mob is unique we remove egg once spawned
3587 itemstack:take_item()
3588 end
3589
3590 return itemstack
3591 end,
3592 })
3593
3594
3595 -- register old stackable mob egg
3596 minetest.register_craftitem(mob, {
3597
3598 description = desc,
3599 inventory_image = invimg,
3600 groups = grp,
3601
3602 on_place = function(itemstack, placer, pointed_thing)
3603
3604 local pos = pointed_thing.above
3605
3606 -- am I clicking on something with existing on_rightclick function?
3607 local under = minetest.get_node(pointed_thing.under)
3608 local def = minetest.registered_nodes[under.name]
3609 if def and def.on_rightclick then
3610 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3611 end
3612
3613 if pos
3614 and within_limits(pos, 0)
3615 and not minetest.is_protected(pos, placer:get_player_name()) then
3616
3617 if not minetest.registered_entities[mob] then
3618 return
3619 end
3620
3621 pos.y = pos.y + 1
3622
3623 local mob = minetest.add_entity(pos, mob)
3624 local ent = mob:get_luaentity()
3625
3626 -- don't set owner if monster or sneak pressed
3627 if ent.type ~= "monster"
3628 and not placer:get_player_control().sneak then
3629 ent.owner = placer:get_player_name()
3630 ent.tamed = true
3631 end
3632
3633 -- if not in creative then take item
3634 if not mobs.is_creative(placer:get_player_name()) then
3635 itemstack:take_item()
3636 end
3637 end
3638
3639 return itemstack
3640 end,
3641 })
3642
3643 end
3644
3645
3646 -- capture critter (thanks to blert2112 for idea)
3647 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso, force_take, replacewith)
3648
3649 if self.child
3650 or not clicker:is_player()
3651 or not clicker:get_inventory() then
3652 return false
3653 end
3654
3655 -- get name of clicked mob
3656 local mobname = self.name
3657
3658 -- if not nil change what will be added to inventory
3659 if replacewith then
3660 mobname = replacewith
3661 end
3662
3663 local name = clicker:get_player_name()
3664 local tool = clicker:get_wielded_item()
3665
3666 -- are we using hand, net or lasso to pick up mob?
3667 if tool:get_name() ~= ""
3668 and tool:get_name() ~= "mobs:net"
3669 and tool:get_name() ~= "mobs:lasso" then
3670 return false
3671 end
3672
3673 -- is mob tamed?
3674 if self.tamed == false
3675 and force_take == false then
3676
3677 minetest.chat_send_player(name, S("Not tamed!"))
3678
3679 return true -- false
3680 end
3681
3682 -- cannot pick up if not owner
3683 if self.owner ~= name
3684 and force_take == false then
3685
3686 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
3687
3688 return true -- false
3689 end
3690
3691 if clicker:get_inventory():room_for_item("main", mobname) then
3692
3693 -- was mob clicked with hand, net, or lasso?
3694 local chance = 0
3695
3696 if tool:get_name() == "" then
3697 chance = chance_hand
3698
3699 elseif tool:get_name() == "mobs:net" then
3700
3701 chance = chance_net
3702
3703 tool:add_wear(4000) -- 17 uses
3704
3705 clicker:set_wielded_item(tool)
3706
3707 elseif tool:get_name() == "mobs:lasso" then
3708
3709 chance = chance_lasso
3710
3711 tool:add_wear(650) -- 100 uses
3712
3713 clicker:set_wielded_item(tool)
3714
3715 end
3716
3717 -- calculate chance.. add to inventory if successful?
3718 if chance > 0 and random(1, 100) <= chance then
3719
3720 -- default mob egg
3721 local new_stack = ItemStack(mobname)
3722
3723 -- add special mob egg with all mob information
3724 -- unless 'replacewith' contains new item to use
3725 if not replacewith then
3726
3727 new_stack = ItemStack(mobname .. "_set")
3728
3729 local tmp = {}
3730
3731 for _,stat in pairs(self) do
3732 local t = type(stat)
3733 if t ~= "function"
3734 and t ~= "nil"
3735 and t ~= "userdata" then
3736 tmp[_] = self[_]
3737 end
3738 end
3739
3740 local data_str = minetest.serialize(tmp)
3741
3742 new_stack:set_metadata(data_str)
3743 end
3744
3745 local inv = clicker:get_inventory()
3746
3747 if inv:room_for_item("main", new_stack) then
3748 inv:add_item("main", new_stack)
3749 else
3750 minetest.add_item(clicker:get_pos(), new_stack)
3751 end
3752
3753 self.object:remove()
3754
3755 mob_sound(self, "default_place_node_hard")
3756
3757 elseif chance ~= 0 then
3758 minetest.chat_send_player(name, S("Missed!"))
3759
3760 mob_sound(self, "mobs_swing")
3761 end
3762 end
3763
3764 return true
3765 end
3766
3767
3768 -- protect tamed mob with rune item
3769 function mobs:protect(self, clicker)
3770
3771 local name = clicker:get_player_name()
3772 local tool = clicker:get_wielded_item()
3773
3774 if tool:get_name() ~= "mobs:protector" then
3775 return false
3776 end
3777
3778 if self.tamed == false then
3779 minetest.chat_send_player(name, S("Not tamed!"))
3780 return true -- false
3781 end
3782
3783 if self.protected == true then
3784 minetest.chat_send_player(name, S("Already protected!"))
3785 return true -- false
3786 end
3787
3788 if not mobs.is_creative(clicker:get_player_name()) then
3789 tool:take_item() -- take 1 protection rune
3790 clicker:set_wielded_item(tool)
3791 end
3792
3793 self.protected = true
3794
3795 local pos = self.object:get_pos()
3796 pos.y = pos.y + self.collisionbox[2] + 0.5
3797
3798 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3799
3800 mob_sound(self, "mobs_spell")
3801
3802 return true
3803 end
3804
3805
3806 local mob_obj = {}
3807 local mob_sta = {}
3808
3809 -- feeding, taming and breeding (thanks blert2112)
3810 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
3811
3812 if not self.follow then
3813 return false
3814 end
3815
3816 -- can eat/tame with item in hand
3817 if follow_holding(self, clicker) then
3818
3819 -- if not in creative then take item
3820 if not mobs.is_creative(clicker:get_player_name()) then
3821
3822 local item = clicker:get_wielded_item()
3823
3824 item:take_item()
3825
3826 clicker:set_wielded_item(item)
3827 end
3828
3829 -- increase health
3830 self.health = self.health + 4
3831
3832 if self.health >= self.hp_max then
3833
3834 self.health = self.hp_max
3835
3836 if self.htimer < 1 then
3837
3838 minetest.chat_send_player(clicker:get_player_name(),
3839 S("@1 at full health (@2)",
3840 self.name:split(":")[2], tostring(self.health)))
3841
3842 self.htimer = 5
3843 end
3844 end
3845
3846 self.object:set_hp(self.health)
3847
3848 update_tag(self)
3849
3850 -- make children grow quicker
3851 if self.child == true then
3852
3853 self.hornytimer = self.hornytimer + 20
3854
3855 return true
3856 end
3857
3858 -- feed and tame
3859 self.food = (self.food or 0) + 1
3860 if self.food >= feed_count then
3861
3862 self.food = 0
3863
3864 if breed and self.hornytimer == 0 then
3865 self.horny = true
3866 end
3867
3868 self.gotten = false
3869
3870 if tame then
3871
3872 if self.tamed == false then
3873 minetest.chat_send_player(clicker:get_player_name(),
3874 S("@1 has been tamed!",
3875 self.name:split(":")[2]))
3876 end
3877
3878 self.tamed = true
3879
3880 if not self.owner or self.owner == "" then
3881 self.owner = clicker:get_player_name()
3882 end
3883 end
3884
3885 -- make sound when fed so many times
3886 mob_sound(self, self.sounds.random)
3887 end
3888
3889 return true
3890 end
3891
3892 local item = clicker:get_wielded_item()
3893
3894 -- if mob has been tamed you can name it with a nametag
3895 if item:get_name() == "mobs:nametag"
3896 and clicker:get_player_name() == self.owner then
3897
3898 local name = clicker:get_player_name()
3899
3900 -- store mob and nametag stack in external variables
3901 mob_obj[name] = self
3902 mob_sta[name] = item
3903
3904 local tag = self.nametag or ""
3905
3906 minetest.show_formspec(name, "mobs_nametag", "size[8,4]"
3907 .. default.gui_bg
3908 .. default.gui_bg_img
3909 .. "field[0.5,1;7.5,0;name;" .. minetest.formspec_escape(S("Enter name:")) .. ";" .. tag .. "]"
3910 .. "button_exit[2.5,3.5;3,1;mob_rename;" .. minetest.formspec_escape(S("Rename")) .. "]")
3911 end
3912
3913 return false
3914 end
3915
3916
3917 -- inspired by blockmen's nametag mod
3918 minetest.register_on_player_receive_fields(function(player, formname, fields)
3919
3920 -- right-clicked with nametag and name entered?
3921 if formname == "mobs_nametag"
3922 and fields.name
3923 and fields.name ~= "" then
3924
3925 local name = player:get_player_name()
3926
3927 if not mob_obj[name]
3928 or not mob_obj[name].object then
3929 return
3930 end
3931
3932 -- make sure nametag is being used to name mob
3933 local item = player:get_wielded_item()
3934
3935 if item:get_name() ~= "mobs:nametag" then
3936 return
3937 end
3938
3939 -- limit name entered to 64 characters long
3940 if string.len(fields.name) > 64 then
3941 fields.name = string.sub(fields.name, 1, 64)
3942 end
3943
3944 -- update nametag
3945 mob_obj[name].nametag = fields.name
3946
3947 update_tag(mob_obj[name])
3948
3949 -- if not in creative then take item
3950 if not mobs.is_creative(name) then
3951
3952 mob_sta[name]:take_item()
3953
3954 player:set_wielded_item(mob_sta[name])
3955 end
3956
3957 -- reset external variables
3958 mob_obj[name] = nil
3959 mob_sta[name] = nil
3960 end
3961 end)
3962
3963
3964 -- compatibility function for old entities to new modpack entities
3965 function mobs:alias_mob(old_name, new_name)
3966
3967 -- spawn egg
3968 minetest.register_alias(old_name, new_name)
3969
3970 -- entity
3971 minetest.register_entity(":" .. old_name, {
3972
3973 physical = false,
3974
3975 on_step = function(self)
3976
3977 if minetest.registered_entities[new_name] then
3978 minetest.add_entity(self.object:get_pos(), new_name)
3979 end
3980
3981 self.object:remove()
3982 end
3983 })
3984 end
3030 'hp_max' has the maximum health value the mob can spawn with.
3131 'armor' holds strength of mob, 100 is normal, lower is more powerful
3232 and needs more hits and better weapons to kill.
33 'passive' when true allows animals to defend themselves when hit,
33 'passive' when false allows animals to defend themselves when hit,
3434 otherwise they amble onwards.
3535 'walk_velocity' is the speed that your mob can walk around.
3636 'run_velocity' is the speed your mob can run with, usually when attacking.
37 'stand_chance' has a 0-100 chance value your mob will stand from walking.
3738 'walk_chance' has a 0-100 chance value your mob will walk from standing,
3839 set to 0 for jumping mobs only.
40 'randomly_turn' if set to false then mob will not turn to face player or
41 randomly turn while walking or standing.
3942 'jump' when true allows your mob to jump updwards.
4043 'jump_height' holds the height your mob can jump, 0 to disable jumping.
4144 'stepheight' height of a block that your mob can easily walk up onto,
4346 'fly' when true allows your mob to fly around instead of walking.
4447 'fly_in' holds the node name that the mob flies (or swims) around
4548 in e.g. "air" or "default:water_source".
49 'keep_flying' when true mobs like birds no longer stop and stand.
50 'stay_near' when set allows mobs the chance to stay around certain nodes.
51 'nodes' string or table of nodes to stay nearby e.g. "farming:straw"
52 'chance' chance of searching for above node(s), default is 10.
4653 'runaway' if true causes animals to turn and run away when hit.
4754 'pushable' when true mobs can be pushed by player or other mobs.
4855 'view_range' how many nodes in distance the mob can see a player.
4956 'damage' how many health points the mob does to a player or another
5057 mob when melee attacking.
58 'damage_group' group in which damage is dealt, dedaults to "fleshy".
59 'damage_texture_modifier' applies texture modifier on hit e.g "^[brighten"
5160 'knock_back' when true has mobs falling backwards when hit, the greater
5261 the damage the more they move back.
5362 'fear_height' is how high a cliff or edge has to be before the mob stops
5665 'fall_damage' when true causes falling to inflict damage.
5766 'water_damage' holds the damage per second infliced to mobs when standing in
5867 water.
68 'air_damage' holds damage per second inflicted to mob when standing in air.
5969 'lava_damage' holds the damage per second inflicted to mobs when standing
60 in lava or fire or an ignition source.
70 in lava.
71 'fire_damage' holds the damage per second inflicted to mobs when standing
72 in fire.
73
6174 'light_damage' holds the damage per second inflicted to mobs when light
6275 level is between the min and max values below
6376 'light_damage_min' minimum light value when mob is affected (default: 14)
6477 'light_damage_max' maximum light value when mob is affected (default: 15)
65 'suffocation' when true causes mobs to suffocate inside solid blocks.
78 'suffocation' when > 0 mobs will suffocate inside solid blocks and will be
79 hurt by the value given every second (0 to disable).
6680 'floats' when set to 1 mob will float in water, 0 has them sink.
6781 'follow' mobs follow player when holding any of the items which appear
6882 on this table, the same items can be fed to a mob to tame or
69 breed e.g. {"farming:wheat", "default:apple"}
83 breed e.g. {"farming:wheat", "default:apple", "group:fish"}
7084
7185 'reach' is how far the mob can attack player when standing
7286 nearby, default is 3 nodes.
8296 punches when nearby.
8397 'group_attack' when true has same mob type grouping together to attack
8498 offender.
99 'group_helper' string containing mob name that attacks alongside
100 current mob when group attacking.
101 mob is attacking in groups.
85102 'attack_type' tells the api what a mob does when attacking the player
86103 or another mob:
87104 'dogfight' is a melee attack when player is within mob reach.
118135 e.g. {"player", "mobs_animal:chicken"}.
119136 'runaway_from' contains a table with mob names to run away from, add
120137 "player" to list to runaway from player also.
138 'ignore_invisibility' When true mob will still be able to see and attack
139 player even if invisible (invisibility mod only).
121140 'blood_amount' contains the number of blood droplets to appear when
122141 mob is hit.
123142 'blood_texture' has the texture name to use for droplets e.g.
125144 'pathfinding' set to 1 for mobs to use pathfinder feature to locate
126145 player, set to 2 so they can build/break also (only
127146 works with dogfight attack and when 'mobs_griefing'
128 in minetest.conf is not false).
147 in minetest.conf is not false). Adding {unbreakable=1}
148 to node groups stops them being broken by mobs.
129149 'immune_to' is a table that holds specific damage when being hit by
130150 certain items e.g.
131151 {"default:sword_wood", 0} -- causes no damage.
152172 'min' minimum number of items dropped, set to 0 for rare drops.
153173 'max' maximum number of items dropped.
154174 Note: If weapon has {fire=1} damage group set then cooked items will drop.
175 Note2: A function can now be passed which can also return drops table, e.g.
176 drops = function(pos)
177 -- do something
178 return { {name = "farming:bread"}, {name = "default:dirt", chance = 2} }
179 end
155180
156181 'visual' holds the look of the mob you wish to create:
157182 'cube' looks like a normal node
169194 'child_texture' holds the texture table for when baby mobs are used.
170195 'gotten_texture' holds the texture table for when self.gotten value is
171196 true, used for milking cows or shearing sheep.
197 'texture_mods' holds a string which overlays a texture on top of the
198 mob texture e.g. "^saddle.png"
172199 'mesh' holds the name of the external object used for mob model
173200 e.g. "mobs_cow.b3d"
174201 'gotten_mesh" holds the name of the external object used for when
175202 self.gotten is true for mobs.
176203 'rotate' custom model rotation, 0 = front, 90 = side, 180 = back,
177204 270 = other side.
205 'glow' has mob glow without light source, 0 to 15 or nil to disable
178206 'double_melee_attack' when true has the api choose between 'punch' and
179207 'punch2' animations. [DEPRECATED]
180208
181 'animation' holds a table containing animation names and settings for use with mesh models:
209 'animation' holds a table containing animation names and settings for use with
210 mesh models:
182211 'stand_start' start frame for when mob stands still.
183212 'stand_end' end frame of stand animation.
184213 'stand_speed' speed of animation in frames per second.
204233 'die_end'
205234 'die_speed'
206235 'die_loop' when set to false stops the animation looping.
236 'die_rotate' if true mob spins during death animation.
207237
208238 Using '_loop = false' setting will stop any of the above animations from
209239 looping.
243273 If false is returned, the mob will not replace the node.
244274
245275 By default, replacing sets self.gotten to true and resets the object
246 properties.
276 properties. (DEPRECATED, use on_replace to make changes).
247277
248278
249279 Custom Definition Functions
272302 time_from_last_punch, tool_capabilities, direction), return
273303 false to stop punch damage and knockback from taking place.
274304 'custom_attack' when set this function is called instead of the normal mob
275 melee attack, parameters are (self, to_attack).
276 'on_die' a function that is called when mob is killed (self, pos)
305 melee attack, parameters are (self, to_attack) and if true
306 is returned normal attack function continued.
307 'on_die' a function that is called when mob is killed (self, pos), also
308 has access to self.cause_of_death table.
309 'on_flop' function called when flying or swimmimng mob is no longer in
310 air/water, (self) paramater and return true to skip the built
311 in api flop feature.
277312 'do_custom' a custom function that is called every tick while mob is
278313 active and which has access to all of the self.* variables
279314 e.g. (self.health for health or self.standing_in for node
305340 'self.order' set to "follow" or "stand" so that npc will follow owner
306341 or stand it's ground
307342 'self.nametag' contains the name of the mob which it can show above
343 'self.state' Current mob state.
344 "stand": no movement (except turning around)
345 "walk": walk or move around aimlessly
346 "attack": chase and attack enemy
347 "runaway": flee from target
348 "flop": bounce around aimlessly
349 (for swimming mobs that have stranded)
350 "die": during death
351
352
353 Adding Mobs in World
354 --------------------
355
356 mobs:add_mob(pos, {
357 name = "mobs_animal:chicken",
358 child = true,
359 owner = "singleplayer",
360 nametag = "Bessy",
361 ignore_count = true -- ignores mob count per map area
362 })
363
364 Returns false if mob could not be added, returns mob object if spawned ok.
365
366
367 Removing Mob from World
368 -----------------------
369
370 mobs:remove(self, decrease)
371
372 Removes mob 'self' from the world and if 'decrease' is true then the mob counter
373 will also be decreased by one.
308374
309375
310376 Spawning Mobs in World
311377 ----------------------
312378
313 mobs:register_spawn(name, nodes, max_light, min_light, chance,
314 active_object_count, max_height, day_toggle)
315
316 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
317 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
318
319 These functions register a spawn algorithm for the mob. Without this function
320 the call the mobs won't spawn.
379 mobs:spawn({
380 name = "mobs_monster:tree_monster",
381 nodes = {"group:leaves"},
382 max_light = 7,
383 })
384
385 Spawn functions require the following settings, some of which already have a
386 default setting and can be omitted:
321387
322388 'name' is the name of the animal/monster
323389 'nodes' is a list of nodenames on that the animal/monster can
324 spawn on top of
390 spawn on top of (defaults to {"group:dirt", "group:stone"}
325391 'neighbors' is a list of nodenames on that the animal/monster will
326 spawn beside (default is {"air"} for
327 mobs:register_spawn)
328 'max_light' is the maximum of light
329 'min_light' is the minimum of light
330 'interval' is same as in register_abm() (default is 30 for
331 mobs:register_spawn)
332 'chance' is same as in register_abm()
392 spawn beside (default is {"air"})
393 'interval' is same as in register_abm() (default is 30)
394 'chance' is same as in register_abm() (default is 5000)
395 'min_light' is the minimum light level (default is 0)
396 'max_light' is the maximum light (default is 15)
397 'min_height' is the minimum height a mob can spawn (default: -31000)
398 'max_height' is the maximum height a mob can spawn (default is 31000)
333399 'active_object_count' number of this type of mob to spawn at one time inside
334 map area
335 'min_height' is the minimum height the mob can spawn
336 'max_height' is the maximum height the mob can spawn
400 map area (default is 1)
337401 'day_toggle' true for day spawning, false for night or nil for
338402 anytime
339403 'on_spawn' is a custom function which runs after mob has spawned
340404 and gives self and pos values.
405 'on_map_load' when true mobs will have a chance of spawning only
406 when new areas of map are loaded, interval will not be
407 used.
408
409 The older spawn functions are still active and working but have no defaults like
410 the mobs:spawn, so it is recommended to use the above instead.
411
412 mobs:register_spawn(name, nodes, max_light, min_light, chance,
413 active_object_count, max_height, day_toggle)
414
415 mobs:spawn_specfic(name, nodes, neighbors, min_light, max_light, interval,
416 chance, active_object_count, min_height, max_height, day_toggle, on_spawn)
341417
342418 A simpler way to handle mob spawns has been added with the mobs:spawn(def)
343419 command which uses above names to make settings clearer:
344
345 mobs:spawn({name = "mobs_monster:tree_monster",
346 nodes = {"group:leaves"},
347 max_light = 7,
348 })
349420
350421
351422 For each mob that spawns with this function is a field in mobs.spawning_mobs.
363434 'pos' holds the position of the spawning mob
364435 'node' contains the node the mob is spawning on top of
365436 'name' is the name of the animal/monster
437
438
439 Particle Effects
440 ----------------
441
442 mobs:effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
443
444 This function provides a quick way to spawn particles as an effect.
445
446 'pos' center position of particle effect.
447 'amount' how many particles.
448 'texture' texture filename to use for effect.
449 'min_size' smallest particle size.
450 'max_size' largest particle size.
451 'radius' how far particles spread outward from center.
452 'gravity' gravity applied to particles once they spawn.
453 'glow' number between 1 and 15 for glowing particles.
454 'fall' when true particles fall, false has them rising, nil has them scatter.
366455
367456
368457 Making Arrows
377466 'visual' same is in minetest.register_entity()
378467 'visual_size' same is in minetest.register_entity()
379468 'textures' same is in minetest.register_entity()
469 'physical' same is in minetest.register_entity() [default: false]
470 'collide_with_objects' same as above
380471 'velocity' the velocity of the arrow
381472 'drop' if set to true any arrows hitting a node will drop as item
382473 'hit_player' a function that is called when the arrow hits a player;
385476 'hit_mob' a function that is called when the arrow hits a mob;
386477 this function should hurt the mob, the parameters are
387478 (self, player)
479 'hit_object' a function that is called when the arrow hits an object;
480 this function parameters are (self, player)
388481 'hit_node' a function that is called when the arrow hits a node, the
389482 parameters are (self, pos, node)
390483 'tail' when set to 1 adds a trail or tail to mob arrows
398491 'on_step' is a custom function when arrow is active, nil for
399492 default.
400493 'on_punch' is a custom function when arrow is punched, nil by default
401 'collisionbox' is hitbox table for arrow, {0,0,0,0,0,0} by default.
494 'collisionbox' is hitbox table for arrow, {-.1,-.1,-.1,.1,.1,.1} by default.
495 'lifetime' contains float value for how many seconds arrow exists in
496 world before being removed (default is 4.5 seconds).
402497
403498
404499 Spawn Eggs
406501
407502 mobs:register_egg(name, description, background, addegg, no_creative)
408503
409 This function registers a spawn egg which can be used by admin to properly spawn in a mob.
504 This function registers a spawn egg which can be used to properly spawn in a mob.
505 Animals are spawned as tamed unless sneak/shift is held while spawning.
410506
411507 'name' this is the name of your new mob to spawn e.g. "mob:sheep"
412508 'description' the name of the new egg you are creating e.g. "Spawn Sheep"
452548 'force_take' take mob by force, even if tamed (true or false)
453549 'replacewith' once captured replace mob with this item instead (overrides
454550 new mob eggs with saved information)
551
552 mobs:force_capture(self, clicker)
553
554 Same as above but does no checks, it simply captures any and all mobs and places
555 inside a spawn egg containing all of the mob information.
455556
456557
457558 Feeding and Taming/Breeding
565666 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1}
566667
567668
568 mobs:line_of_sight(self, pos1, pos2, stepsize)
669 mobs:line_of_sight(self, pos1, pos2, stepsize) [DEPRECATED]
569670
570671 This function is for use within the mobs definition for special use cases and
571672 returns true if a mob can see the player or victim.
574675 'pos1' position of mob
575676 'pos2' position of vistim or player
576677 'stepsize' usually set to 1
678
679 Use this instead:
680
681 mob_class:line_of_sight(pos1, pos2, stepsize)
682
683
684 mobs:can_spawn(pos, name)
685
686 This function checks the surrounding area at [pos] to see if there is enough empty
687 space to spawn mob [name], if so then a new position is returned for use,
688 otherwise nil is returned.
577689
578690
579691 External Settings for "minetest.conf"
604716 'mobs_griefing' when false mobs cannot break blocks when using either
605717 pathfinding level 2, replace functions or mobs:boom
606718 function.
719 'mob_nospawn_range' Minimum range a mob can spawn near player (def: 12)
720 'mob_active_limit' Number of active mobs in game, 0 for unlimited
721 'mob_area_spawn' When true will check surrounding area the size of the
722 mob for obstructions before spawning, otherwise it
723 defaults to checking the height of the mob only.
724 'mob_smooth_rotate' Enables smooth rotation when mobs turn by default.
607725
608726 Players can override the spawn chance for each mob registered by adding a line
609727 to their minetest.conf file with a new value, the lower the value the more each
610728 mob will spawn e.g.
611729
612 mobs_animal:sheep_chance 11000
613 mobs_monster:sand_monster_chance 100
730 mobs_animal:sheep 11000
731 mobs_monster:sand_monster 100
732
733 ...you can also change how many of a certain mob appear in an active mapblock by
734 adding a comma and then a new value e.g.
735
736 mobs_animal:cow = 8000,4 <-- 4 cows per mapblock at 8000 spawn chance
737 mobs_monster:dirt_monster = ,20 <-- 20 dirt monsters per mapblock
614738
615739
616740 Rideable Horse Example Mob
622746 visual_size = {x = 1.20, y = 1.20},
623747 mesh = "mobs_horse.x",
624748 collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
625 animation = {
749 animation = {
626750 speed_normal = 15,
627751 speed_run = 30,
628752 stand_start = 25,
721845 if inv:room_for_item("main", "mobs:saddle") then
722846 inv:add_item("main", "mobs:saddle")
723847 else
724 minetest.add_item(clicker.getpos(), "mobs:saddle")
848 minetest.add_item(clicker.get_pos(), "mobs:saddle")
725849 end
726850
727851 -- 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",
12 -- type = "shapeless",
1313 output = "mobs:nametag",
14 recipe = {"default:paper", "dye:black", "farming:string"},
14 recipe = {{"default:paper", "dye:black", "farming:string"}}
1515 })
1616 end
1717
1919 minetest.register_craftitem("mobs:leather", {
2020 description = S("Leather"),
2121 inventory_image = "mobs_leather.png",
22 groups = {flammable = 2},
22 groups = {flammable = 2, leather = 1}
2323 })
2424
2525 -- raw meat
2727 description = S("Raw Meat"),
2828 inventory_image = "mobs_meat_raw.png",
2929 on_use = minetest.item_eat(3),
30 groups = {food_meat_raw = 1, flammable = 2},
30 groups = {food_meat_raw = 1, flammable = 2}
3131 })
3232
3333 -- cooked meat
3535 description = S("Meat"),
3636 inventory_image = "mobs_meat.png",
3737 on_use = minetest.item_eat(8),
38 groups = {food_meat = 1, flammable = 2},
38 groups = {food_meat = 1, flammable = 2}
3939 })
4040
4141 minetest.register_craft({
4242 type = "cooking",
4343 output = "mobs:meat",
4444 recipe = "mobs:meat_raw",
45 cooktime = 5,
45 cooktime = 5
4646 })
4747
4848 -- lasso
4949 minetest.register_tool("mobs:lasso", {
5050 description = S("Lasso (right-click animal to put in inventory)"),
5151 inventory_image = "mobs_magic_lasso.png",
52 groups = {flammable = 2},
52 groups = {flammable = 2}
5353 })
5454
5555 if minetest.get_modpath("farming") then
5858 recipe = {
5959 {"farming:string", "", "farming:string"},
6060 {"", "default:diamond", ""},
61 {"farming:string", "", "farming:string"},
61 {"farming:string", "", "farming:string"}
6262 }
6363 })
6464 end
6969 minetest.register_tool("mobs:net", {
7070 description = S("Net (right-click animal to put in inventory)"),
7171 inventory_image = "mobs_net.png",
72 groups = {flammable = 2},
72 groups = {flammable = 2}
7373 })
7474
7575 if minetest.get_modpath("farming") then
7878 recipe = {
7979 {"group:stick", "", "group:stick"},
8080 {"group:stick", "", "group:stick"},
81 {"farming:string", "group:stick", "farming:string"},
81 {"farming:string", "group:stick", "farming:string"}
8282 }
8383 })
8484 end
8787 minetest.register_tool("mobs:shears", {
8888 description = S("Steel Shears (right-click to shear)"),
8989 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'},
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"}
9898 }
9999 })
100100
102102 minetest.register_craftitem("mobs:protector", {
103103 description = S("Mob Protection Rune"),
104104 inventory_image = "mobs_protector.png",
105 groups = {flammable = 2},
105 groups = {flammable = 2}
106106 })
107107
108108 minetest.register_craft({
110110 recipe = {
111111 {"default:stone", "default:stone", "default:stone"},
112112 {"default:stone", "default:goldblock", "default:stone"},
113 {"default:stone", "default:stone", "default:stone"},
113 {"default:stone", "default:stone", "default:stone"}
114 }
115 })
116
117 -- level 2 protection rune
118 minetest.register_craftitem("mobs:protector2", {
119 description = S("Mob Protection Rune (Level 2)"),
120 inventory_image = "mobs_protector2.png",
121 groups = {flammable = 2}
122 })
123
124 minetest.register_craft({
125 output = "mobs:protector2",
126 recipe = {
127 {"mobs:protector", "default:mese_crystal", "mobs:protector"},
128 {"default:mese_crystal", "default:diamondblock", "default:mese_crystal"},
129 {"mobs:protector", "default:mese_crystal", "mobs:protector"}
114130 }
115131 })
116132
118134 minetest.register_craftitem("mobs:saddle", {
119135 description = S("Saddle"),
120136 inventory_image = "mobs_saddle.png",
121 groups = {flammable = 2},
137 groups = {flammable = 2, saddle = 1}
122138 })
123139
124140 minetest.register_craft({
126142 recipe = {
127143 {"mobs:leather", "mobs:leather", "mobs:leather"},
128144 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
129 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
130 }
131 })
145 {"mobs:leather", "default:steel_ingot", "mobs:leather"}
146 }
147 })
148
149
150 -- make sure we can register fences
151 if default.register_fence then
132152
133153 -- mob fence (looks like normal fence but collision is 2 high)
134154 default.register_fence("mobs:fence_wood", {
141161 type = "fixed",
142162 fixed = {
143163 {-0.5, -0.5, -0.5, 0.5, 1.9, 0.5},
144 },
164 }
165 }
166 })
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
199 end
200
178201
179202 -- items that can be used as fuel
180203 minetest.register_craft({
181204 type = "fuel",
182205 recipe = "mobs:nametag",
183 burntime = 3,
206 burntime = 3
184207 })
185208
186209 minetest.register_craft({
187210 type = "fuel",
188211 recipe = "mobs:lasso",
189 burntime = 7,
212 burntime = 7
190213 })
191214
192215 minetest.register_craft({
193216 type = "fuel",
194217 recipe = "mobs:net",
195 burntime = 8,
218 burntime = 8
196219 })
197220
198221 minetest.register_craft({
199222 type = "fuel",
200223 recipe = "mobs:leather",
201 burntime = 4,
224 burntime = 4
202225 })
203226
204227 minetest.register_craft({
205228 type = "fuel",
206229 recipe = "mobs:saddle",
207 burntime = 7,
230 burntime = 7
208231 })
209232
210233 minetest.register_craft({
211234 type = "fuel",
212235 recipe = "mobs:fence_wood",
213 burntime = 7,
236 burntime = 7
214237 })
215238
216239 minetest.register_craft({
217240 type = "fuel",
218241 recipe = "mobs:fence_top",
219 burntime = 2,
220 })
242 burntime = 2
243 })
244
245
246 -- this tool spawns same mob and adds owner, protected, nametag info
247 -- then removes original entity, this is used for fixing any issues.
248
249 local tex_obj
250
251 minetest.register_tool(":mobs:mob_reset_stick", {
252 description = S("Mob Reset Stick"),
253 inventory_image = "default_stick.png^[colorize:#ff000050",
254 stack_max = 1,
255 groups = {not_in_creative_inventory = 1},
256
257 on_use = function(itemstack, user, pointed_thing)
258
259 if pointed_thing.type ~= "object" then
260 return
261 end
262
263 local obj = pointed_thing.ref
264
265 local control = user:get_player_control()
266 local sneak = control and control.sneak
267
268 -- spawn same mob with saved stats, with random texture
269 if obj and not sneak then
270
271 local self = obj:get_luaentity()
272 local obj2 = minetest.add_entity(obj:get_pos(), self.name)
273
274 if obj2 then
275
276 local ent2 = obj2:get_luaentity()
277
278 ent2.protected = self.protected
279 ent2.owner = self.owner
280 ent2.nametag = self.nametag
281 ent2.gotten = self.gotten
282 ent2.tamed = self.tamed
283 ent2.health = self.health
284 ent2.order = self.order
285
286 if self.child then
287 obj2:set_velocity({x = 0, y = self.jump_height, z = 0})
288 end
289
290 obj2:set_properties({nametag = self.nametag})
291
292 obj:remove()
293 end
294 end
295
296 -- display form to enter texture name ending in .png
297 if obj and sneak then
298
299 tex_obj = obj
300
301 -- get base texture
302 local bt = tex_obj:get_luaentity().base_texture[1]
303
304 if type(bt) ~= "string" then
305 bt = ""
306 end
307
308 local name = user:get_player_name()
309
310 minetest.show_formspec(name, "mobs_texture", "size[8,4]"
311 .. "field[0.5,1;7.5,0;name;"
312 .. minetest.formspec_escape(S("Enter texture:")) .. ";" .. bt .. "]"
313 .. "button_exit[2.5,3.5;3,1;mob_texture_change;"
314 .. minetest.formspec_escape(S("Change")) .. "]")
315 end
316 end
317 })
318
319 minetest.register_on_player_receive_fields(function(player, formname, fields)
320
321 -- right-clicked with nametag and name entered?
322 if formname == "mobs_texture"
323 and fields.name
324 and fields.name ~= "" then
325
326 -- does mob still exist?
327 if not tex_obj
328 or not tex_obj:get_luaentity() then
329 return
330 end
331
332 -- make sure nametag is being used to name mob
333 local item = player:get_wielded_item()
334
335 if item:get_name() ~= "mobs:mob_reset_stick" then
336 return
337 end
338
339 -- limit name entered to 64 characters long
340 if fields.name:len() > 64 then
341 fields.name = fields.name:sub(1, 64)
342 end
343
344 -- update texture
345 local self = tex_obj:get_luaentity()
346
347 self.base_texture = {fields.name}
348
349 tex_obj:set_properties({textures = {fields.name}})
350
351 -- reset external variable
352 tex_obj = nil
353 end
354 end)
355
356
357 -- Meat Block (thanks to painterlypack.net for allowing me to use these textures)
358 minetest.register_node("mobs:meatblock", {
359 description = S("Meat Block"),
360 tiles = {"mobs_meat_top.png", "mobs_meat_bottom.png", "mobs_meat_side.png"},
361 paramtype2 = "facedir",
362 groups = {choppy = 1, oddly_breakable_by_hand = 1, flammable = 2},
363 sounds = default.node_sound_leaves_defaults(),
364 on_place = minetest.rotate_node,
365 on_use = minetest.item_eat(20),
366 })
367
368 minetest.register_craft({
369 output = "mobs:meatblock",
370 -- type = "shapeless",
371 recipe = {
372 {"group:food_meat", "group:food_meat", "group:food_meat"},
373 {"group:food_meat", "group:food_meat", "group:food_meat"},
374 {"group:food_meat", "group:food_meat", "group:food_meat"}
375 }
376 })
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 = default
2 optional_depends = tnt, dye, farming, invisibility, intllib, lucky_block, cmi, toolranks
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)
2421
2522
2623 local function node_is(pos)
27
2824 local node = node_ok(pos)
2925
3026 if node.name == "air" then
4844
4945
5046 local function get_sign(i)
51
5247 i = i or 0
5348
5449 if i == 0 then
5550 return 0
5651 else
57 return i / math.abs(i)
52 return i / abs(i)
5853 end
5954 end
6055
6156
6257 local function get_velocity(v, yaw, y)
63
64 local x = -math.sin(yaw) * v
65 local z = math.cos(yaw) * v
58 local x = -sin(yaw) * v
59 local z = cos(yaw) * v
6660
6761 return {x = x, y = y, z = z}
6862 end
6963
7064
7165 local function get_v(v)
72 return math.sqrt(v.x * v.x + v.z * v.z)
66 return sqrt(v.x * v.x + v.z * v.z)
7367 end
7468
7569
7670 local function force_detach(player)
77
7871 local attached_to = player:get_attach()
7972
8073 if not attached_to then
8376
8477 local entity = attached_to:get_luaentity()
8578
86 if entity.driver
79 if entity and entity.driver
8780 and entity.driver == player then
88
8981 entity.driver = nil
9082 end
9183
9284 player:set_detach()
93 default.player_attached[player:get_player_name()] = false
85 player_api.player_attached[player:get_player_name()] = false
9486 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} })
87 player_api.set_animation(player, "stand", 30)
88 player:set_properties({visual_size = {x = 1, y = 1}})
9789
9890 end
9991
10092 -------------------------------------------------------------------------------
101
10293
10394 minetest.register_on_leaveplayer(function(player)
10495 force_detach(player)
118109
119110 -------------------------------------------------------------------------------
120111
112 -- Just for correct detaching
113 local function find_free_pos(pos)
114 local check = {
115 {x = 1, y = 0, z = 0},
116 {x = 1, y = 1, z = 0},
117 {x = -1, y = 0, z = 0},
118 {x = -1, y = 1, z = 0},
119 {x = 0, y = 0, z = 1},
120 {x = 0, y = 1, z = 1},
121 {x = 0, y = 0, z = -1},
122 {x = 0, y = 1, z = -1}
123 }
124
125 for _, c in pairs(check) do
126 local npos = {x = pos.x + c.x, y = pos.y + c.y, z = pos.z + c.z}
127 local node = minetest.get_node_or_nil(npos)
128 if node and node.name then
129 local def = minetest.registered_nodes[node.name]
130 if def and not def.walkable and
131 def.liquidtype == "none" then
132 return npos
133 end
134 end
135 end
136
137 return pos
138 end
139
140 -------------------------------------------------------------------------------
141
121142 function mobs.attach(entity, player)
122
123 local attach_at, eye_offset = {}, {}
124
125143 entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
126144 entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
127145 entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0}
130148 local rot_view = 0
131149
132150 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
151 rot_view = pi / 2
152 end
153
154 local attach_at = entity.driver_attach_at
155 local eye_offset = entity.driver_eye_offset
138156 entity.driver = player
139157
140158 force_detach(player)
141159
142160 player:set_attach(entity.object, "", attach_at, entity.player_rotation)
143 default.player_attached[player:get_player_name()] = true
161 player_api.player_attached[player:get_player_name()] = true
144162 player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0})
145163
146164 player:set_properties({
151169 })
152170
153171 minetest.after(0.2, function()
154 default.player_set_animation(player, "sit" , 30)
172 if player and player:is_player() then
173 player_api.set_animation(player, "sit", 30)
174 end
155175 end)
156176
157 --player:set_look_yaw(entity.object:get_yaw() - rot_view)
158177 player:set_look_horizontal(entity.object:get_yaw() - rot_view)
159178 end
160179
161180
162 function mobs.detach(player, offset)
163
181 function mobs.detach(player)
164182 force_detach(player)
165183
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
172184 minetest.after(0.1, function()
173 player:set_pos(pos)
185 if player and player:is_player() then
186 local pos = find_free_pos(player:get_pos())
187 pos.y = pos.y + 0.5
188 player:set_pos(pos)
189 end
174190 end)
175191 end
176192
177193
178194 function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
179
180 local rot_steer, rot_view = math.pi/2, 0
195 local yaw = entity.object:get_yaw() or 0
196
197 local rot_view = 0
181198
182199 if entity.player_rotation.y == 90 then
183 rot_steer, rot_view = 0, math.pi/2
200 rot_view = pi / 2
184201 end
185202
186203 local acce_y = 0
190207
191208 -- process controls
192209 if entity.driver then
193
194 --print ("---velo", get_v(velo))
195
196210 local ctrl = entity.driver:get_player_control()
197211
198212 -- move forwards
199213 if ctrl.up then
200
201214 entity.v = entity.v + entity.accel / 10
202215
203216 -- move backwards
204217 elseif ctrl.down then
205
206218 if entity.max_speed_reverse == 0 and entity.v == 0 then
207219 return
208220 end
210222 entity.v = entity.v - entity.accel / 10
211223 end
212224
213 -- fix mob rotation
214 entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
225 -- mob rotation
226 local horz
227 if entity.alt_turn == true then
228 horz = yaw
229
230 if ctrl.left then
231 horz = horz + 0.05
232
233 elseif ctrl.right then
234 horz = horz - 0.05
235 end
236 else
237 horz = entity.driver:get_look_horizontal() or 0
238 end
239
240 entity.object:set_yaw(horz - entity.rotate)
215241
216242 if can_fly then
217
218243 -- fly up
219244 if ctrl.jump then
220245 velo.y = velo.y + 1
234259 velo.y = velo.y + 0.1
235260 if velo.y > 0 then velo.y = 0 end
236261 end
237
238262 else
239
240263 -- jump
241264 if ctrl.jump then
242265
245268 acce_y = acce_y + (acce_y * 3) + 1
246269 end
247270 end
248
249271 end
250272 end
251273
252274 -- if not moving then set animation and return
253275 if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
254
255276 if stand_anim then
256277 mobs:set_animation(entity, stand_anim)
257278 end
258279
259280 return
260281 end
261
282
262283 -- set moving animation
263284 if moving_anim then
264285 mobs:set_animation(entity, moving_anim)
270291 entity.v = entity.v - 0.02 * s
271292
272293 if s ~= get_sign(entity.v) then
273
274294 entity.object:set_velocity({x = 0, y = 0, z = 0})
275295 entity.v = 0
276296 return
283303 max_spd = entity.max_speed_forward
284304 end
285305
286 if math.abs(entity.v) > max_spd then
306 if abs(entity.v) > max_spd then
287307 entity.v = entity.v - get_sign(entity.v)
288308 end
289309
290310 -- Set position, velocity and acceleration
291311 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}
312 if not p then return end
313
314 local new_acce = {x = 0, y = -9.81, z = 0}
294315
295316 p.y = p.y - 0.5
296317
298319 local v = entity.v
299320
300321 if ni == "air" then
301
302322 if can_fly == true then
303323 new_acce.y = 0
304324 end
305
306325 elseif ni == "liquid" or ni == "lava" then
307
308326 if ni == "lava" and entity.lava_damage ~= 0 then
309
310327 entity.lava_counter = (entity.lava_counter or 0) + dtime
311328
312329 if entity.lava_counter > 1 then
313
314330 minetest.sound_play("default_punch", {
315331 object = entity.object,
316332 max_hear_distance = 5
325341 end
326342 end
327343
328 if entity.terrain_type == 2
329 or entity.terrain_type == 3 then
330
344 local terrain_type = entity.terrain_type
345 if terrain_type == 2 or terrain_type == 3 then
331346 new_acce.y = 0
332347 p.y = p.y + 1
333348
334349 if node_is(p) == "liquid" then
335
336350 if velo.y >= 5 then
337351 velo.y = 5
338352 elseif velo.y < 0 then
341355 new_acce.y = 5
342356 end
343357 else
344 if math.abs(velo.y) < 1 then
358 if abs(velo.y) < 1 then
345359 local pos = entity.object:get_pos()
346 pos.y = math.floor(pos.y) + 0.5
360 if not pos then return end
361
362 pos.y = floor(pos.y) + 0.5
347363 entity.object:set_pos(pos)
348364 velo.y = 0
349365 end
353369 end
354370 end
355371
356 new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y)
372 local new_velo = get_velocity(v, yaw - rot_view, velo.y)
357373 new_acce.y = new_acce.y + acce_y
358374
359375 entity.object:set_velocity(new_velo)
360376 entity.object:set_acceleration(new_acce)
361377
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
379378 entity.v2 = v
380379 end
381380
382381
383382 -- directional flying routine by D00Med (edited by TenPlus1)
384
385 function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
386
383 function mobs.fly(entity, _, speed, shoots, arrow, moving_anim, stand_anim)
387384 local ctrl = entity.driver:get_player_control()
388385 local velo = entity.object:get_velocity()
389386 local dir = entity.driver:get_look_dir()
390387 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
388
389 if not ctrl or not velo then return end
396390
397391 if ctrl.up then
398392 entity.object:set_velocity({
404398 elseif ctrl.down then
405399 entity.object:set_velocity({
406400 x = -dir.x * speed,
407 y = dir.y * speed + 2,
401 y = dir.y * speed + 2,
408402 z = -dir.z * speed
409403 })
410404
412406 entity.object:set_velocity({x = 0, y = -2, z = 0})
413407 end
414408
415 entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate)
409 entity.object:set_yaw(yaw + pi + pi / 2 - entity.rotate)
416410
417411 -- firing arrows
418412 if ctrl.LMB and ctrl.sneak and shoots then
419
420413 local pos = entity.object:get_pos()
421414 local obj = minetest.add_entity({
422415 x = pos.x + 0 + dir.x * 2.5,
428421 ent.switch = 1 -- for mob specific arrows
429422 ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding
430423 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)
424 yaw = entity.driver:get_look_horizontal()
425 obj:set_yaw(yaw + pi / 2)
433426 obj:set_velocity(vec)
434427 else
435428 obj:remove()
438431
439432 -- change animation if stopped
440433 if velo.x == 0 and velo.y == 0 and velo.z == 0 then
441
442434 mobs:set_animation(entity, stand_anim)
443435 else
444436 -- moving animation
0
1 MOBS REDO for MINETEST
2
3 Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
4
5
6 This mod contains the API only for adding your own mobs into the world, so please use the additional modpacks to add animals, monsters etc.
7
8
9 https://forum.minetest.net/viewtopic.php?f=11&t=9917
10
11
12 Crafts:
13
14 - Nametag (paper, black dye, string) can be used right-click on a tamed mob to give them a name.
15 - Nets can be used to right-click tamed mobs to pick them up and place inside inventory as a spawn egg.
16 - Magic Lasso is similar to nets but with a better chance of picking up larger mobs.
17 - Shears are used to right-click sheep and return 1-3 wool.
18 - Protection Rune lets you protect tamed mobs from harm by other players
19 - Mob Fence and Fence Top (to stop mobs escaping/glitching through fences)
20
21 Lucky Blocks: 9
22
23
24 Changelog:
25 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
26 - 1.46- Mobs only drop rare items when killed by player (drops.min = 0 makes them rare), code tweak, pathfinding no longer sees through walkable nodes
27 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
28 - 1.44- Added ToolRanks support for swords when attacking mobs
29 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
30 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
31 - 1.41- Mob pathfinding has been updated thanks to Elkien3
32 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
33 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
34 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
35 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
36 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
37 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
38 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
39 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
40 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs
41 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
42 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
43 - 1.29- Split original Mobs Redo into a modpack to make it easier to disable mob sets (animal, monster, npc) or simply use the Api itself for your own mod
44 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
45 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
46 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
47 - 1.25- Mobs no longer spawn within 12 blocks of player or despawn within same range, spawners now have player detection, Code tidy and tweak.
48 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
49 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
50 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
51 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
52 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
53 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
54 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
55 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
56 - 1.16- Mobs follow multiple items now, Npc's can breed
57 - 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility.
58 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
59 - 1.13- Added capture function (thanks blert2112) chance of picking up mob with hand; net; magic lasso, replaced some .x models with newer .b3d one's
60 - 1.12- Added animal ownership so that players cannot steal your tamed animals
61 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
62 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
63 - 1.09- reworked breeding routine, added mob rotation value, added footstep feature, added jumping mobs with sounds feature, added magic lasso for picking up animals
64 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
65 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
66 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
67 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
68 - 1.05- Added ExeterDad's bunny's which can be picked up and tamed with 4 carrots from farming redo or farming_plus, also shears added to get wool from sheep and lastly Jordach/BSD's kitten
69 - 1.04- Added mating for sheep, cows and hogs... feed animals to make horny and hope for a baby which is half size, will grow up quick though :)
70 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
71 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
72 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
73 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
74 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
75 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
76 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
77 - 0.6 - Api now supports multi-textured mobs, e.g oerkki, dungeon master, rats and chickens have random skins when spawning (sheep fix TODO), also new Honey block
78 - 0.5 - Mobs now float in water, die from falling, and some code improvements
79 - 0.4 - Dungeon Masters and Mese Monsters have much better aim due to shoot_offset, also they can both shoot through nodes that aren't walkable (flowers, grass etc) plus new sheep sound :)
80 - 0.3 - Added LOTT's Spider mob, made Cobwebs, added KPavel's Bee with Honey and Beehives (made texture), Warthogs now have sound and can be tamed, taming of shaved sheep or milked cow with 8 wheat so it will not despawn, many bug fixes :)
81 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
82 - 0.1 - Initial Release
0
1 MOBS REDO for MINETEST
2
3 Built from PilzAdam's original Simple Mobs with additional mobs by KrupnoPavel, Zeg9, ExeterDad and AspireMint.
4
5
6 This mod contains the API only for adding your own mobs into the world, so please use the additional modpacks to add animals, monsters etc.
7
8
9 https://forum.minetest.net/viewtopic.php?f=11&t=9917
10
11
12 Crafts:
13
14 - Nametag (paper, black dye, string) can be used right-click on a tamed mob to give them a name.
15 - Nets can be used to right-click tamed mobs to pick them up and place inside inventory as a spawn egg.
16 - Magic Lasso is similar to nets but with a better chance of picking up larger mobs.
17 - Shears are used to right-click sheep and return 1-3 wool.
18 - Protection Rune lets you protect tamed mobs from harm by other players
19 - Mob Fence and Fence Top (to stop mobs escaping/glitching through fences)
20
21 Lucky Blocks: 9
22
23
24 Changelog:
25 - 1.55 - Add 'peaceful_player' privelage and setting so mobs don't attack specific players (thanks sfence)
26 - 1.54 - Simplified animal breeding function, added editable settings (thanks Wuzzy), Child mobs now take 20 mins to grow up, reverted to simple mob spawning with setting to use area checks, on_flop added, air_damage added.
27 - 1.53 - Added 'on_map_load' settings to mobs:spawn so that mobs will only spawn when new areas of map are loaded.
28 - 1.52 - Added 'mob_active_limit' in settings to set number of mobs in game,
29 (default is 0 for unlimited), removed {immortal} from mob armor, fluid viscocity slows mobs
30 - 1.51 - Added some node checks for dangerous nodes, jumping and falling tweaks, spawn area check (thx for idea wuzzy), re-enabled mob suffocation, add 'mob_nospawn_range' setting
31 - 1.50 - Added new line_of_sight function that uses raycasting if mt5.0 is found, (thanks Astrobe), dont spawn mobs if world anchor nearby (technic or simple_anchor mods), chinese local added
32 - 1.49- Added mobs:force_capture(self, player) function, api functions now use metatables thanks to bell07
33 - 1.48- Add mobs:set_velocity(self, velocity) global function
34 - 1.47- Mob damage changes, min and max light level for damage added, ignition sources checked for lava damage
35 - 1.46- Mobs only drop rare items when killed by player (drops.min = 0 makes them rare), code tweak, pathfinding no longer sees through walkable nodes
36 - 1.45- Added Fence Top to add on top of any fence to stop mobs escaping, new line_of_sight tweaked by Astrobe
37 - 1.44- Added ToolRanks support for swords when attacking mobs
38 - 1.43- Better 0.4.16 compatibility, added general attack function and settings
39 - 1.42- Added "all" option to immune_to table, tidied floating mobs to be less intensive
40 - 1.41- Mob pathfinding has been updated thanks to Elkien3
41 - 1.40- Updated to use newer functions, requires Minetest 0.4.16+ to work.
42 - 1.39- Added 'on_breed', 'on_grown' and 'do_punch' custom functions per mob
43 - 1.38- Better entity checking, nametag setting and on_spawn function added to mob registry, tweaked light damage
44 - 1.37- Added support for Raymoo's CMI (common mob interface) mod: https://forum.minetest.net/viewtopic.php?f=9&t=15448
45 - 1.36- Death check added, if mob dies in fire/lava/with lava pick then drops are cooked
46 - 1.35- Added owner_loyal flag for owned mobs to attack player enemies, also fixed group_attack
47 - 1.34- Added function to fly mob using directional movement (thanks D00Med for flying code)
48 - 1.33- Added functions to mount ride mobs (mobs.attach, mobs.detach, mobs.drive) many thanks to Blert2112
49 - 1.32- Added new spawn check to count specific mobs AND new minetest.conf setting to chance spawn chance and numbers, added ability to protect tamed mobs
50 - 1.31- Added 'attack_animals' and 'specific_attack' flags for custom monster attacks, also 'mob_difficulty' .conf setting to make mobs harder.
51 - 1.30- Added support for invisibility mod (mobs cant attack what they cant see), tweaked and tidied code
52 - 1.29- Split original Mobs Redo into a modpack to make it easier to disable mob sets (animal, monster, npc) or simply use the Api itself for your own mod
53 - 1.28- New damage system added with ability for mob to be immune to weapons or healed by them :)
54 - 1.27- Added new sheep, lava flan and spawn egg textures. New Lava Pick tool smelts what you dig. New atan checking function.
55 - 1.26- Pathfinding feature added thanks to rnd, when monsters attack they become scary smart in finding you :) also, beehive produces honey now :)
56 - 1.25- Mobs no longer spawn within 12 blocks of player or despawn within same range, spawners now have player detection, Code tidy and tweak.
57 - 1.24- Added feature where certain animals run away when punched (runaway = true in mob definition)
58 - 1.23- Added mob spawner block for admin to setup spawners in-game (place and right click to enter settings)
59 - 1.22- Added ability to name tamed animals and npc using nametags, also npc will attack anyone who punches them apart from owner
60 - 1.21- Added some more error checking to reduce serialize.h error and added height checks for falling off cliffs (thanks cmdskp)
61 - 1.20- Error checking added to remove bad mobs, out of map limit mobs and stop serialize.h error
62 - 1.19- Chickens now drop egg items instead of placing the egg, also throwing eggs result in 1/8 chance of spawning chick
63 - 1.18- Added docile_by_day flag so that monsters will not attack automatically during daylight hours unless hit first
64 - 1.17- Added 'dogshoot' attack type, shoots when out of reach, melee attack when in reach, also api tweaks and self.reach added
65 - 1.16- Mobs follow multiple items now, Npc's can breed
66 - 1.15- Added Feeding/Taming/Breeding function, right-click to pick up any sheep with X mark on them and replace with new one to fix compatibility.
67 - 1.14- All .self variables saved in staticdata, Fixed self.health bug
68 - 1.13- Added capture function (thanks blert2112) chance of picking up mob with hand; net; magic lasso, replaced some .x models with newer .b3d one's
69 - 1.12- Added animal ownership so that players cannot steal your tamed animals
70 - 1.11- Added flying mobs (and swimming), fly=true and fly_in="air" or "deafult:water_source" for fishy
71 - 1,10- Footstep removed (use replace), explosion routine added for exploding mobs.
72 - 1.09- reworked breeding routine, added mob rotation value, added footstep feature, added jumping mobs with sounds feature, added magic lasso for picking up animals
73 - 1.08- Mob throwing attack has been rehauled so that they can damage one another, also drops and on_die function added
74 - 1.07- Npc's can now be set to follow player or stand by using self.order and self.owner variables
75 - beta- Npc mob added, kills monsters, attacks player when punched, right click with food to heal or gold lump for drop
76 - 1.06- Changed recovery times after breeding, and time taken to grow up (can be sped up by feeding baby animal)
77 - 1.05- Added ExeterDad's bunny's which can be picked up and tamed with 4 carrots from farming redo or farming_plus, also shears added to get wool from sheep and lastly Jordach/BSD's kitten
78 - 1.04- Added mating for sheep, cows and hogs... feed animals to make horny and hope for a baby which is half size, will grow up quick though :)
79 - 1.03- Added mob drop/replace feature so that chickens can drop eggs, cow/sheep can eat grass/wheat etc.
80 - 1.02- Sheared sheep are remembered and spawn shaven, Warthogs will attack when threatened, Api additions
81 - 1.01- Mobs that suffer fall damage or die in water/lava/sunlight will now drop items
82 - 1.0 - more work on Api so that certain mobs can float in water while some sink like a brick :)
83 - 0.9 - Spawn eggs added for all mobs (admin only, cannot be placed in protected areas)... Api tweaked
84 - 0.8 - Added sounds to monster mobs (thanks Cyberpangolin for the sfx) and also chicken sound
85 - 0.7 - mobs.protected switch added to api.lua, when set to 1 mobs no longer spawn in protected areas, also bug fixes
86 - 0.6 - Api now supports multi-textured mobs, e.g oerkki, dungeon master, rats and chickens have random skins when spawning (sheep fix TODO), also new Honey block
87 - 0.5 - Mobs now float in water, die from falling, and some code improvements
88 - 0.4 - Dungeon Masters and Mese Monsters have much better aim due to shoot_offset, also they can both shoot through nodes that aren't walkable (flowers, grass etc) plus new sheep sound :)
89 - 0.3 - Added LOTT's Spider mob, made Cobwebs, added KPavel's Bee with Honey and Beehives (made texture), Warthogs now have sound and can be tamed, taming of shaved sheep or milked cow with 8 wheat so it will not despawn, many bug fixes :)
90 - 0.2 - Cooking bucket of milk into cheese now returns empty bucket
91 - 0.1 - Initial Release
2626
2727 # When false Mob no longer drop items when killed
2828 mobs_drop_items (Mob drops) bool true
29
30 # Sets minimum distance around player that mobs cannot spawn
31 mob_nospawn_range (Mob no-spawn range) float 12.0
32
33 # Sets maximum number of active mobs in game (0 for unlimited)
34 mob_active_limit (Mob Active Limit) float 0
35
36 # Enables area check when spawning mobs
37 mob_area_spawn (Mob Area Spawn) bool false
38
39 # Enable peaceful player attack prevention
40 enable_peaceful_player (Mobs do not attack peaceful player without reason) bool false
41
42 # Enable mobs smooth rotation
43 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