Codebase list minetest-mod-mobs-redo / upstream/latest
New upstream version 20181016 Julien Puydt 5 years ago
44 changed file(s) with 10936 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0
1 -- Intllib and CMI support check
2 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
4 local use_cmi = minetest.global_exists("cmi")
5
6 mobs = {
7 mod = "redo",
8 version = "20181005",
9 intllib = S,
10 invis = minetest.global_exists("invisibility") and invisibility or {},
11 }
12
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
21 local pi = math.pi
22 local square = math.sqrt
23 local sin = math.sin
24 local cos = math.cos
25 local abs = math.abs
26 local min = math.min
27 local max = math.max
28 local atann = math.atan
29 local random = math.random
30 local floor = math.floor
31 local atan = function(x)
32 if not x or x ~= x then
33 --error("atan bassed NaN")
34 return 0
35 else
36 return atann(x)
37 end
38 end
39
40
41 -- 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)
55
56 -- Peaceful mode message so players will know there are no monsters
57 if peaceful_only then
58 minetest.register_on_joinplayer(function(player)
59 minetest.chat_send_player(player:get_player_name(),
60 S("** Peaceful Mode Active - No Monsters Will Spawn"))
61 end)
62 end
63
64 -- calculate aoc range for mob count
65 local aoc_range = tonumber(minetest.settings:get("active_block_range")) * 16
66
67 -- pathfinding settings
68 local enable_pathfinding = true
69 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
70 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
71
72 -- default nodes
73 local node_fire = "fire:basic_flame"
74 local node_permanent_flame = "fire:permanent_flame"
75 local node_ice = "default:ice"
76 local node_snowblock = "default:snowblock"
77 local node_snow = "default:snow"
78 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
79
80
81 -- play sound
82 local mob_sound = function(self, sound)
83
84 if sound then
85 minetest.sound_play(sound, {
86 object = self.object,
87 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
90 end
91 end
92
93
94 -- attack player/mob
95 local do_attack = function(self, player)
96
97 if self.state == "attack" then
98 return
99 end
100
101 self.attack = player
102 self.state = "attack"
103
104 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
106 end
107 end
108
109
110 -- calculate distance
111 local get_distance = function(a, b)
112
113 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114
115 return square(x * x + y * y + z * z)
116 end
117
118
119 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
121
122 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124 local x, z = 0, 0
125 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126
127 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
131
132 local pos2 = object:get_pos()
133 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
134
135 x = x + vec.x
136 z = z + vec.z
137 end
138 end
139
140 return({x, z})
141 end
142
143
144 -- move mob in facing direction
145 local set_velocity = function(self, v)
146
147 local c_x, c_y = 0, 0
148
149 -- can mob be pushed, if so calculate direction
150 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({
163 x = (sin(yaw) * -v) + c_x,
164 y = self.object:get_velocity().y,
165 z = (cos(yaw) * v) + c_y,
166 })
167 end
168
169
170 -- calculate mob velocity
171 local get_velocity = function(self)
172
173 local v = self.object:get_velocity()
174
175 return (v.x * v.x + v.z * v.z) ^ 0.5
176 end
177
178
179 -- set and return valid yaw
180 local set_yaw = function(self, yaw, delay)
181
182 if not yaw or yaw ~= yaw then
183 yaw = 0
184 end
185
186 delay = delay or 0
187
188 if delay == 0 then
189 self.object:set_yaw(yaw)
190 return yaw
191 end
192
193 self.target_yaw = yaw
194 self.delay = delay
195
196 return self.target_yaw
197 end
198
199 -- global function to set mob yaw
200 function mobs:yaw(self, yaw, delay)
201 set_yaw(self, yaw, delay)
202 end
203
204
205 -- set defined animation
206 local set_animation = function(self, anim)
207
208 if not self.animation
209 or not anim then return end
210
211 self.animation.current = self.animation.current or ""
212
213 -- only set different animation for attacks when setting to same set
214 if anim ~= "punch" and anim ~= "shoot"
215 and string.find(self.animation.current, anim) then
216 return
217 end
218
219 -- check for more than one animation
220 local num = 0
221
222 for n = 1, 4 do
223
224 if self.animation[anim .. n .. "_start"]
225 and self.animation[anim .. n .. "_end"] then
226 num = n
227 end
228 end
229
230 -- choose random animation from set
231 if num > 0 then
232 num = random(0, num)
233 anim = anim .. (num ~= 0 and num or "")
234 end
235
236 if anim == self.animation.current
237 or not self.animation[anim .. "_start"]
238 or not self.animation[anim .. "_end"] then
239 return
240 end
241
242 self.animation.current = anim
243
244 self.object:set_animation({
245 x = self.animation[anim .. "_start"],
246 y = self.animation[anim .. "_end"]},
247 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
248 0, self.animation[anim .. "_loop"] ~= false)
249 end
250
251 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
254 end
255
256
257 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
259
260 if not pos1 or not pos2 then return end
261
262 stepsize = stepsize or 1
263
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
265
266 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267
268 -- normal walking and flying mobs can see you through air
269 if s == true then return true end
270
271 -- New pos1 to be analyzed
272 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
273
274 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
275
276 -- Checks the return
277 if r == true then return true end
278
279 -- Nodename found
280 local nn = minetest.get_node(pos).name
281
282 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
284 while minetest.registered_nodes[nn]
285 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
289
290 if get_distance(npos1, pos2) < stepsize then return true end
291
292 -- scan again
293 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
294
295 if r == true then return true end
296
297 -- New Nodename found
298 nn = minetest.get_node(pos).name
299 end
300
301 return false
302 end
303
304 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
308 end
309
310
311 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
313
314 local def = minetest.registered_nodes[self.standing_in]
315
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
321 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
332 end
333
334 -- stops mobs getting stuck inside stairs and plantlike nodes
335 if def.drawtype ~= "airlike"
336 and def.drawtype ~= "liquid"
337 and def.drawtype ~= "flowingliquid" then
338 return true
339 end
340
341 return false
342 end
343
344
345 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
347
348 radius = radius or 2
349 min_size = min_size or 0.5
350 max_size = max_size or 1
351 gravity = gravity or -10
352 glow = glow or 0
353
354 minetest.add_particlespawner({
355 amount = amount,
356 time = 0.25,
357 minpos = pos,
358 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
360 maxvel = {x = radius, y = radius, z = radius},
361 minacc = {x = 0, y = gravity, z = 0},
362 maxacc = {x = 0, y = gravity, z = 0},
363 minexptime = 0.1,
364 maxexptime = 1,
365 minsize = min_size,
366 maxsize = max_size,
367 texture = texture,
368 glow = glow,
369 })
370 end
371
372
373 -- update nametag colour
374 local update_tag = function(self)
375
376 local col = "#00FF00"
377 local qua = self.hp_max / 4
378
379 if self.health <= floor(qua * 3) then
380 col = "#FFFF00"
381 end
382
383 if self.health <= floor(qua * 2) then
384 col = "#FF6600"
385 end
386
387 if self.health <= floor(qua) then
388 col = "#FF0000"
389 end
390
391 self.object:set_properties({
392 nametag = self.nametag,
393 nametag_color = col
394 })
395
396 end
397
398
399 -- drop items
400 local item_drop = function(self)
401
402 -- check for nil or no drops
403 if not self.drops or #self.drops == 0 then
404 return
405 end
406
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
413 -- 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
416
417 local obj, item, num
418 local pos = self.object:get_pos()
419
420 for n = 1, #self.drops do
421
422 if random(1, self.drops[n].chance) == 1 then
423
424 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425 item = self.drops[n].name
426
427 -- cook items on a hot death
428 if self.cause_of_death.hot then
429
430 local output = minetest.get_craft_result({
431 method = "cooking", width = 1, items = {item}})
432
433 if output and output.item and not output.item:is_empty() then
434 item = output.item:get_name()
435 end
436 end
437
438 -- only drop rare items (drops.min=0) if killed by player
439 if death_by_player then
440 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
441
442 elseif self.drops[n].min ~= 0 then
443 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
444 end
445
446 if obj and obj:get_luaentity() then
447
448 obj:set_velocity({
449 x = random(-10, 10) / 9,
450 y = 6,
451 z = random(-10, 10) / 9,
452 })
453
454 elseif obj then
455 obj:remove() -- item does not exist
456 end
457 end
458 end
459
460 self.drops = {}
461 end
462
463
464 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
466
467 -- has health actually changed?
468 if self.health == self.old_health and self.health > 0 then
469 return
470 end
471
472 self.old_health = self.health
473
474 -- still got some health? play hurt sound
475 if self.health > 0 then
476
477 mob_sound(self, self.sounds.damage)
478
479 -- make sure health isn't higher than max
480 if self.health > self.hp_max then
481 self.health = self.hp_max
482 end
483
484 -- backup nametag so we can show health stats
485 if not self.nametag2 then
486 self.nametag2 = self.nametag or ""
487 end
488
489 if show_health
490 and (cmi_cause and cmi_cause.type == "punch") then
491
492 self.htimer = 2
493 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
494
495 update_tag(self)
496 end
497
498 return false
499 end
500
501 self.cause_of_death = cmi_cause
502
503 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
507
508 local pos = self.object:get_pos()
509
510 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
514
515 if use_cmi then
516 cmi.notify_die(self.object, cmi_cause)
517 end
518
519 self.object:remove()
520
521 return true
522 end
523
524 -- default death function and die animation (if defined)
525 if self.animation
526 and self.animation.die_start
527 and self.animation.die_end then
528
529 local frames = self.animation.die_end - self.animation.die_start
530 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
532
533 self.attack = nil
534 self.v_start = false
535 self.timer = 0
536 self.blinktimer = 0
537 self.passive = true
538 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
541
542 minetest.after(length, function(self)
543
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()
549 end, self)
550 else
551
552 if use_cmi then
553 cmi.notify_die(self.object, cmi_cause)
554 end
555
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
560
561 return true
562 end
563
564
565 -- is mob facing a cliff
566 local is_at_cliff = function(self)
567
568 if self.fear_height == 0 then -- 0 for no falling protection!
569 return false
570 end
571
572 local yaw = self.object:get_yaw()
573 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
574 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
575 local pos = self.object:get_pos()
576 local ypos = pos.y + self.collisionbox[2] -- just above floor
577
578 if minetest.line_of_sight(
579 {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
583 return true
584 end
585
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]
602 end
603
604
605 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
607
608 -- feed/tame text timer (so mob 'full' messages dont spam chat)
609 if self.htimer > 0 then
610 self.htimer = self.htimer - 1
611 end
612
613 -- reset nametag after showing health stats
614 if self.htimer < 1 and self.nametag2 then
615
616 self.nametag = self.nametag2
617 self.nametag2 = nil
618
619 update_tag(self)
620 end
621
622 local pos = self.object:get_pos()
623
624 self.time_of_day = minetest.get_timeofday()
625
626 -- remove mob if standing inside ignore node
627 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
630 end
631
632 -- is mob light sensative, or scared of the dark :P
633 if self.light_damage ~= 0 then
634
635 local light = minetest.get_node_light(pos) or 0
636
637 if light >= self.light_damage_min
638 and light <= self.light_damage_max then
639
640 self.health = self.health - self.light_damage
641
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 --[[
694 --- 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"})
707 end
708
709
710 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
712
713 if not self.jump
714 or self.jump_height == 0
715 or self.fly
716 or self.child
717 or self.order == "stand" then
718 return false
719 end
720
721 self.facing_fence = false
722
723 -- something stopping us while moving?
724 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
726 and self.object:get_velocity().y ~= 0 then
727 return false
728 end
729
730 local pos = self.object:get_pos()
731 local yaw = self.object:get_yaw()
732
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
741 return false
742 end
743
744 -- where is front
745 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
746 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
747
748 -- what is in front of mob?
749 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
753 })
754
755 -- thin blocks that do not need to be jumped
756 if nod.name == node_snow then
757 return false
758 end
759
760 --print ("in front:", nod.name, pos.y + 0.5)
761
762 if self.walk_chance == 0
763 or minetest.registered_items[nod.name].walkable then
764
765 if not nod.name:find("fence")
766 and not nod.name:find("gate") then
767
768 local v = self.object:get_velocity()
769
770 v.y = self.jump_height
771
772 set_animation(self, "jump") -- only when defined
773
774 self.object:set_velocity(v)
775
776 -- when in air move forward
777 minetest.after(0.3, function(self, v)
778
779 if self.object:get_luaentity() then
780
781 self.object:set_acceleration({
782 x = v.x * 2,--1.5,
783 y = 0,
784 z = v.z * 2,--1.5
785 })
786 end
787 end, self, v)
788
789 if get_velocity(self) > 0 then
790 mob_sound(self, self.sounds.jump)
791 end
792 else
793 self.facing_fence = true
794 end
795
796 return true
797 end
798
799 return false
800 end
801
802
803 -- blast damage to entities nearby (modified from TNT mod)
804 local entity_physics = function(pos, radius)
805
806 radius = radius * 2
807
808 local objs = minetest.get_objects_inside_radius(pos, radius)
809 local obj_pos, dist
810
811 for n = 1, #objs do
812
813 obj_pos = objs[n]:get_pos()
814
815 dist = get_distance(pos, obj_pos)
816 if dist < 1 then dist = 1 end
817
818 local damage = floor((4 / dist) * radius)
819 local ent = objs[n]:get_luaentity()
820
821 -- punches work on entities AND players
822 objs[n]:punch(objs[n], 1.0, {
823 full_punch_interval = 1.0,
824 damage_groups = {fleshy = damage},
825 }, pos)
826 end
827 end
828
829
830 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
834 return false
835 end
836
837 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
843 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
854 end
855
856 return false
857 end
858
859
860 -- 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
864 if self.child == true then
865
866 self.hornytimer = self.hornytimer + 1
867
868 if self.hornytimer > 240 then
869
870 self.child = false
871 self.hornytimer = 0
872
873 self.object:set_properties({
874 textures = self.base_texture,
875 mesh = self.base_mesh,
876 visual_size = self.base_size,
877 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
879 })
880
881 -- custom function when child grows up
882 if self.on_grown then
883 self.on_grown(self)
884 else
885 -- jump when fully grown so as not to fall into ground
886 self.object:set_velocity({
887 x = 0,
888 y = self.jump_height,
889 z = 0
890 })
891 end
892 end
893
894 return
895 end
896
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
899 if self.horny == true
900 and self.hornytimer < 240 then
901
902 self.hornytimer = self.hornytimer + 1
903
904 if self.hornytimer >= 240 then
905 self.hornytimer = 0
906 self.horny = false
907 end
908 end
909
910 -- find another same animal who is also horny and mate if nearby
911 if self.horny == true
912 and self.hornytimer <= 40 then
913
914 local pos = self.object:get_pos()
915
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
917
918 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
921
922 for n = 1, #objs do
923
924 ent = objs[n]:get_luaentity()
925
926 -- check for same animal with different colour
927 local canmate = false
928
929 if ent then
930
931 if ent.name == self.name then
932 canmate = true
933 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
936
937 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
940
941 if entname[1] == selfname[1] then
942 canmate = true
943 end
944 end
945 end
946 end
947
948 if ent
949 and canmate == true
950 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
960
961 -- spawn baby
962 minetest.after(5, function(self, ent)
963
964 if not self.object:get_luaentity() then
965 return
966 end
967
968 -- custom breed function
969 if self.on_breed then
970
971 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
973 return
974 end
975 else
976 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
977 end
978
979 local mob = minetest.add_entity(pos, self.name)
980 local ent2 = mob:get_luaentity()
981 local textures = self.base_texture
982
983 -- using specific child texture (if found)
984 if self.child_texture then
985 textures = self.child_texture[1]
986 end
987
988 -- and resize to half height
989 mob:set_properties({
990 textures = textures,
991 visual_size = {
992 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
994 },
995 collisionbox = {
996 self.base_colbox[1] * .5,
997 self.base_colbox[2] * .5,
998 self.base_colbox[3] * .5,
999 self.base_colbox[4] * .5,
1000 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1002 },
1003 selectionbox = {
1004 self.base_selbox[1] * .5,
1005 self.base_selbox[2] * .5,
1006 self.base_selbox[3] * .5,
1007 self.base_selbox[4] * .5,
1008 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1010 },
1011 })
1012 -- tamed and owned by parents' owner
1013 ent2.child = true
1014 ent2.tamed = true
1015 ent2.owner = self.owner
1016 end, self, ent)
1017
1018 num = 0
1019
1020 break
1021 end
1022 end
1023 end
1024 end
1025
1026
1027 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1029
1030 if not mobs_griefing
1031 or not self.replace_rate
1032 or not self.replace_what
1033 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1036 return
1037 end
1038
1039 local what, with, y_offset
1040
1041 if type(self.replace_what[1]) == "table" then
1042
1043 local num = random(#self.replace_what)
1044
1045 what = self.replace_what[num][1] or ""
1046 with = self.replace_what[num][2] or ""
1047 y_offset = self.replace_what[num][3] or 0
1048 else
1049 what = self.replace_what
1050 with = self.replace_with or ""
1051 y_offset = self.replace_offset or 0
1052 end
1053
1054 pos.y = pos.y + y_offset
1055
1056 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
1057
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
1063
1064 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
1078 end
1079 end
1080
1081
1082 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1084
1085 if self.docile_by_day == false then
1086
1087 return false
1088
1089 elseif self.docile_by_day == true
1090 and self.time_of_day > 0.2
1091 and self.time_of_day < 0.8 then
1092
1093 return true
1094 end
1095 end
1096
1097
1098 local los_switcher = false
1099 local height_switcher = false
1100
1101 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1102 local smart_mobs = function(self, s, p, dist, dtime)
1103
1104 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1107
1108 -- is it becoming stuck?
1109 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
1110 self.path.stuck_timer = self.path.stuck_timer + dtime
1111 else
1112 self.path.stuck_timer = 0
1113 end
1114
1115 self.path.lastpos = {x = s.x, y = s.y, z = s.z}
1116
1117 local use_pathfind = false
1118 local has_lineofsight = minetest.line_of_sight(
1119 {x = s.x, y = (s.y) + .5, z = s.z},
1120 {x = target_pos.x, y = (target_pos.y) + 1.5, z = target_pos.z}, .2)
1121
1122 -- im stuck, search for path
1123 if not has_lineofsight then
1124
1125 if los_switcher == true then
1126 use_pathfind = true
1127 los_switcher = false
1128 end -- cannot see target!
1129 else
1130 if los_switcher == false then
1131
1132 los_switcher = true
1133 use_pathfind = false
1134
1135 minetest.after(1, function(self)
1136
1137 if self.object:get_luaentity() then
1138
1139 if has_lineofsight then
1140 self.path.following = false
1141 end
1142 end
1143 end, self)
1144 end -- can see target!
1145 end
1146
1147 if (self.path.stuck_timer > stuck_timeout and not self.path.following) then
1148
1149 use_pathfind = true
1150 self.path.stuck_timer = 0
1151
1152 minetest.after(1, function(self)
1153
1154 if self.object:get_luaentity() then
1155
1156 if has_lineofsight then
1157 self.path.following = false
1158 end
1159 end
1160 end, self)
1161 end
1162
1163 if (self.path.stuck_timer > stuck_path_timeout and self.path.following) then
1164
1165 use_pathfind = true
1166 self.path.stuck_timer = 0
1167
1168 minetest.after(1, function(self)
1169
1170 if self.object:get_luaentity() then
1171
1172 if has_lineofsight then
1173 self.path.following = false
1174 end
1175 end
1176 end, self)
1177 end
1178
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1180
1181 if height_switcher then
1182 use_pathfind = true
1183 height_switcher = false
1184 end
1185 else
1186 if not height_switcher then
1187 use_pathfind = false
1188 height_switcher = true
1189 end
1190 end
1191
1192 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]
1196
1197 -- round position to center of node to avoid stuck in walls
1198 -- also adjust height for player models!
1199 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
1201 s.z = floor(s.z + 0.5)
1202
1203 local ssight, sground = minetest.line_of_sight(s, {
1204 x = s.x, y = s.y - 4, z = s.z}, 1)
1205
1206 -- determine node above ground
1207 if not ssight then
1208 s.y = sground.y + 1
1209 end
1210
1211 local p1 = self.attack:get_pos()
1212
1213 p1.x = floor(p1.x + 0.5)
1214 p1.y = floor(p1.y + 0.5)
1215 p1.z = floor(p1.z + 0.5)
1216
1217 local dropheight = 6
1218 if self.fear_height ~= 0 then dropheight = self.fear_height end
1219
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1221
1222 --[[
1223 -- show path using particles
1224 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1226 for _,pos in pairs(self.path.way) do
1227 minetest.add_particle({
1228 pos = pos,
1229 velocity = {x=0, y=0, z=0},
1230 acceleration = {x=0, y=0, z=0},
1231 expirationtime = 1,
1232 size = 4,
1233 collisiondetection = false,
1234 vertical = false,
1235 texture = "heart.png",
1236 })
1237 end
1238 end
1239 ]]
1240
1241 self.state = ""
1242 do_attack(self, self.attack)
1243
1244 -- no path found, try something else
1245 if not self.path.way then
1246
1247 self.path.following = false
1248
1249 -- lets make way by digging/building if not accessible
1250 if self.pathfinding == 2 and mobs_griefing then
1251
1252 -- is player higher than mob?
1253 if s.y < p1.y then
1254
1255 -- build upwards
1256 if not minetest.is_protected(s, "") then
1257
1258 local ndef1 = minetest.registered_nodes[self.standing_in]
1259
1260 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1261
1262 minetest.set_node(s, {name = mobs.fallback_node})
1263 end
1264 end
1265
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1267
1268 -- assume mob is 2 blocks high so it digs above its head
1269 s.y = s.y + sheight
1270
1271 -- remove one block above to make room to jump
1272 if not minetest.is_protected(s, "") then
1273
1274 local node1 = node_ok(s, "air").name
1275 local ndef1 = minetest.registered_nodes[node1]
1276
1277 if node1 ~= "air"
1278 and node1 ~= "ignore"
1279 and ndef1
1280 and not ndef1.groups.level
1281 and not ndef1.groups.unbreakable
1282 and not ndef1.groups.liquid then
1283
1284 minetest.set_node(s, {name = "air"})
1285 minetest.add_item(s, ItemStack(node1))
1286
1287 end
1288 end
1289
1290 s.y = s.y - sheight
1291 self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
1292
1293 else -- dig 2 blocks to make door toward player direction
1294
1295 local yaw1 = self.object:get_yaw() + pi / 2
1296 local p1 = {
1297 x = s.x + cos(yaw1),
1298 y = s.y,
1299 z = s.z + sin(yaw1)
1300 }
1301
1302 if not minetest.is_protected(p1, "") then
1303
1304 local node1 = node_ok(p1, "air").name
1305 local ndef1 = minetest.registered_nodes[node1]
1306
1307 if node1 ~= "air"
1308 and node1 ~= "ignore"
1309 and ndef1
1310 and not ndef1.groups.level
1311 and not ndef1.groups.unbreakable
1312 and not ndef1.groups.liquid then
1313
1314 minetest.add_item(p1, ItemStack(node1))
1315 minetest.set_node(p1, {name = "air"})
1316 end
1317
1318 p1.y = p1.y + 1
1319 node1 = node_ok(p1, "air").name
1320 ndef1 = minetest.registered_nodes[node1]
1321
1322 if node1 ~= "air"
1323 and node1 ~= "ignore"
1324 and ndef1
1325 and not ndef1.groups.level
1326 and not ndef1.groups.unbreakable
1327 and not ndef1.groups.liquid then
1328
1329 minetest.add_item(p1, ItemStack(node1))
1330 minetest.set_node(p1, {name = "air"})
1331 end
1332
1333 end
1334 end
1335 end
1336
1337 -- will try again in 2 second
1338 self.path.stuck_timer = stuck_timeout - 2
1339
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1342 else
1343 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1346
1347 -- follow path now that it has it
1348 self.path.following = true
1349 end
1350 end
1351 end
1352
1353
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
1366 return true
1367 end
1368 end
1369
1370 return false
1371 end
1372
1373
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1376
1377 -- return if already attacking, passive or docile during day
1378 if self.passive
1379 or self.state == "attack"
1380 or day_docile(self) then
1381 return
1382 end
1383
1384 local s = self.object:get_pos()
1385 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1386
1387 -- remove entities we aren't interested in
1388 for n = 1, #objs do
1389
1390 local ent = objs[n]:get_luaentity()
1391
1392 -- are we a player?
1393 if objs[n]:is_player() then
1394
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1397 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
1400 objs[n] = nil
1401 --print("- pla", n)
1402 end
1403
1404 -- or are we a mob?
1405 elseif ent and ent._cmi_is_mob then
1406
1407 -- remove mobs not to attack
1408 if self.name == ent.name
1409 or (not self.attack_animals and ent.type == "animal")
1410 or (not self.attack_monsters and ent.type == "monster")
1411 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1413 objs[n] = nil
1414 --print("- mob", n, self.name, ent.name)
1415 end
1416
1417 -- remove all other entities
1418 else
1419 --print(" -obj", n)
1420 objs[n] = nil
1421 end
1422 end
1423
1424 local p, sp, dist, min_player
1425 local min_dist = self.view_range + 1
1426
1427 -- go through remaining entities and select closest
1428 for _,player in pairs(objs) do
1429
1430 p = player:get_pos()
1431 sp = s
1432
1433 dist = get_distance(p, s)
1434
1435 -- aim higher to make looking up hills more realistic
1436 p.y = p.y + 1
1437 sp.y = sp.y + 1
1438
1439 -- choose closest player to attack that isnt self
1440 if dist ~= 0
1441 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
1443 min_dist = dist
1444 min_player = player
1445 end
1446 end
1447
1448 -- 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
1472 end
1473
1474
1475 -- find someone to runaway from
1476 local runaway_from = function(self)
1477
1478 if not self.runaway_from then
1479 return
1480 end
1481
1482 local s = self.object:get_pos()
1483 local p, sp, dist, pname
1484 local player, obj, min_player, name
1485 local min_dist = self.view_range + 1
1486 local objs = minetest.get_objects_inside_radius(s, self.view_range)
1487
1488 for n = 1, #objs do
1489
1490 if objs[n]:is_player() then
1491
1492 pname = objs[n]:get_player_name()
1493
1494 if mobs.invis[pname]
1495 or self.owner == pname then
1496
1497 name = ""
1498 else
1499 player = objs[n]
1500 name = "player"
1501 end
1502 else
1503 obj = objs[n]:get_luaentity()
1504
1505 if obj then
1506 player = obj.object
1507 name = obj.name or ""
1508 end
1509 end
1510
1511 -- find specific mob to runaway from
1512 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
1516 sp = s
1517
1518 -- aim higher to make looking up hills more realistic
1519 p.y = p.y + 1
1520 sp.y = sp.y + 1
1521
1522 dist = get_distance(p, s)
1523
1524 -- choose closest player/mob to runaway from
1525 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
1527 min_dist = dist
1528 min_player = player
1529 end
1530 end
1531 end
1532
1533 if min_player then
1534
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)
1549 self.state = "runaway"
1550 self.runaway_timer = 3
1551 self.following = nil
1552 end
1553 end
1554
1555
1556 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
1558
1559 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
1562 and not self.following
1563 and self.state ~= "attack"
1564 and self.state ~= "runaway" then
1565
1566 local s = self.object:get_pos()
1567 local players = minetest.get_connected_players()
1568
1569 for n = 1, #players do
1570
1571 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
1573
1574 self.following = players[n]
1575
1576 break
1577 end
1578 end
1579 end
1580
1581 if self.type == "npc"
1582 and self.order == "follow"
1583 and self.state ~= "attack"
1584 and self.owner ~= "" then
1585
1586 -- npc stop following player if not owner
1587 if self.following
1588 and self.owner
1589 and self.owner ~= self.following:get_player_name() then
1590 self.following = nil
1591 end
1592 else
1593 -- stop following player if not holding specific item
1594 if self.following
1595 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
1597 self.following = nil
1598 end
1599
1600 end
1601
1602 -- follow that thing
1603 if self.following then
1604
1605 local s = self.object:get_pos()
1606 local p
1607
1608 if self.following:is_player() then
1609
1610 p = self.following:get_pos()
1611
1612 elseif self.following.object then
1613
1614 p = self.following.object:get_pos()
1615 end
1616
1617 if p then
1618
1619 local dist = get_distance(p, s)
1620
1621 -- dont follow if out of range
1622 if dist > self.view_range then
1623 self.following = nil
1624 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)
1635
1636 -- anyone but standing npc's can move along
1637 if dist > self.reach
1638 and self.order ~= "stand" then
1639
1640 set_velocity(self, self.walk_velocity)
1641
1642 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
1644 end
1645 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
1648 end
1649
1650 return
1651 end
1652 end
1653 end
1654
1655 -- swimmers flop when out of their element, and swim again when back in
1656 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
1659
1660 self.state = "flop"
1661 self.object:set_velocity({x = 0, y = -5, z = 0})
1662
1663 set_animation(self, "stand")
1664
1665 return
1666 elseif self.state == "flop" then
1667 self.state = "stand"
1668 end
1669 end
1670 end
1671
1672
1673 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
1675
1676 -- switch mode not activated
1677 if not self.dogshoot_switch
1678 or not dtime then
1679 return 0
1680 end
1681
1682 self.dogshoot_count = self.dogshoot_count + dtime
1683
1684 if (self.dogshoot_switch == 1
1685 and self.dogshoot_count > self.dogshoot_count_max)
1686 or (self.dogshoot_switch == 2
1687 and self.dogshoot_count > self.dogshoot_count2_max) then
1688
1689 self.dogshoot_count = 0
1690
1691 if self.dogshoot_switch == 1 then
1692 self.dogshoot_switch = 2
1693 else
1694 self.dogshoot_switch = 1
1695 end
1696 end
1697
1698 return self.dogshoot_switch
1699 end
1700
1701
1702 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
1706
1707 if self.state == "stand" then
1708
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
1712 local s = self.object:get_pos()
1713 local objs = minetest.get_objects_inside_radius(s, 3)
1714
1715 for n = 1, #objs do
1716
1717 if objs[n]:is_player() then
1718 lp = objs[n]:get_pos()
1719 break
1720 end
1721 end
1722
1723 -- look at any players nearby, otherwise turn randomly
1724 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
1734 else
1735 yaw = yaw + random(-0.5, 0.5)
1736 end
1737
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
1743
1744 -- mobs ordered to stand stay standing
1745 if self.order ~= "stand"
1746 and self.walk_chance ~= 0
1747 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)
1752 self.state = "walk"
1753 set_animation(self, "walk")
1754 end
1755
1756 elseif self.state == "walk" then
1757
1758 local s = self.object:get_pos()
1759 local lp = nil
1760
1761 -- is there something I need to avoid?
1762 if self.water_damage > 0
1763 and self.lava_damage > 0 then
1764
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
1766
1767 elseif self.water_damage > 0 then
1768
1769 lp = minetest.find_node_near(s, 1, {"group:water"})
1770
1771 elseif self.lava_damage > 0 then
1772
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
1774 end
1775
1776 if lp then
1777
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})
1786
1787 -- did we find land?
1788 if lp then
1789
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)
1803 else
1804 yaw = yaw + random(-0.5, 0.5)
1805 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)
1820
1821 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
1823
1824 yaw = yaw + random(-0.5, 0.5)
1825
1826 yaw = set_yaw(self, yaw, 8)
1827 end
1828
1829 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
1832 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")
1839 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
1843 and self.animation
1844 and self.animation.fly_start
1845 and self.animation.fly_end then
1846 set_animation(self, "fly")
1847 else
1848 set_animation(self, "walk")
1849 end
1850 end
1851
1852 -- runaway when punched
1853 elseif self.state == "runaway" then
1854
1855 self.runaway_timer = self.runaway_timer + 1
1856
1857 -- stop after 5 seconds or when at cliff
1858 if self.runaway_timer > 5
1859 or is_at_cliff(self)
1860 or self.order == "stand" then
1861 self.runaway_timer = 0
1862 set_velocity(self, 0)
1863 self.state = "stand"
1864 set_animation(self, "stand")
1865 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
1868 end
1869
1870 -- attack routines (explode, dogfight, shoot, dogshoot)
1871 elseif self.state == "attack" then
1872
1873 -- calculate distance from mob and enemy
1874 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
1879 if dist > self.view_range
1880 or not self.attack
1881 or not self.attack:get_pos()
1882 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)
1886 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
1889 self.attack = nil
1890 self.v_start = false
1891 self.timer = 0
1892 self.blinktimer = 0
1893 self.path.way = nil
1894
1895 return
1896 end
1897
1898 if self.attack_type == "explode" then
1899
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)
1910
1911 local node_break_radius = self.explosion_radius or 1
1912 local entity_damage_radius = self.explosion_damage_radius
1913 or (node_break_radius * 2)
1914
1915 -- start timer when in reach and line of sight
1916 if not self.v_start
1917 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
1919
1920 self.v_start = true
1921 self.timer = 0
1922 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
1925
1926 -- stop timer if out of reach or direct line of sight
1927 elseif self.allow_fuse_reset
1928 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
1931 self.v_start = false
1932 self.timer = 0
1933 self.blinktimer = 0
1934 self.blinkstatus = false
1935 self.object:settexturemod("")
1936 end
1937
1938 -- walk right up to player unless the timer is active
1939 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
1941 else
1942 set_velocity(self, self.run_velocity)
1943 end
1944
1945 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
1947 else
1948 set_animation(self, "walk")
1949 end
1950
1951 if self.v_start then
1952
1953 self.timer = self.timer + dtime
1954 self.blinktimer = (self.blinktimer or 0) + dtime
1955
1956 if self.blinktimer > 0.2 then
1957
1958 self.blinktimer = 0
1959
1960 if self.blinkstatus then
1961 self.object:settexturemod("")
1962 else
1963 self.object:settexturemod("^[brighten")
1964 end
1965
1966 self.blinkstatus = not self.blinkstatus
1967 end
1968
1969 -- print ("=== explosion timer", self.timer)
1970
1971 if self.timer > self.explosion_timer then
1972
1973 local pos = self.object:get_pos()
1974
1975 -- dont damage anything if area protected or next to water
1976 if minetest.find_node_near(pos, 1, {"group:water"})
1977 or minetest.is_protected(pos, "") then
1978
1979 node_break_radius = 1
1980 end
1981
1982 self.object:remove()
1983
1984 if minetest.get_modpath("tnt") and tnt and tnt.boom
1985 and not minetest.is_protected(pos, "") then
1986
1987 tnt.boom(pos, {
1988 radius = node_break_radius,
1989 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
1991 })
1992 else
1993
1994 minetest.sound_play(self.sounds.explode, {
1995 pos = pos,
1996 gain = 1.0,
1997 max_hear_distance = self.sounds.distance or 32
1998 })
1999
2000 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2002 end
2003
2004 return
2005 end
2006 end
2007
2008 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
2011
2012 if self.fly
2013 and dist > self.reach then
2014
2015 local p1 = s
2016 local me_y = floor(p1.y)
2017 local p2 = p
2018 local p_y = floor(p2.y + 1)
2019 local v = self.object:get_velocity()
2020
2021 if flight_check(self, s) then
2022
2023 if me_y < p_y then
2024
2025 self.object:set_velocity({
2026 x = v.x,
2027 y = 1 * self.walk_velocity,
2028 z = v.z
2029 })
2030
2031 elseif me_y > p_y then
2032
2033 self.object:set_velocity({
2034 x = v.x,
2035 y = -1 * self.walk_velocity,
2036 z = v.z
2037 })
2038 end
2039 else
2040 if me_y < p_y then
2041
2042 self.object:set_velocity({
2043 x = v.x,
2044 y = 0.01,
2045 z = v.z
2046 })
2047
2048 elseif me_y > p_y then
2049
2050 self.object:set_velocity({
2051 x = v.x,
2052 y = -0.01,
2053 z = v.z
2054 })
2055 end
2056 end
2057
2058 end
2059
2060 -- rnd: new movement direction
2061 if self.path.following
2062 and self.path.way
2063 and self.attack_type ~= "dogshoot" then
2064
2065 -- no paths longer than 50
2066 if #self.path.way > 50
2067 or dist < self.reach then
2068 self.path.following = false
2069 return
2070 end
2071
2072 local p1 = self.path.way[1]
2073
2074 if not p1 then
2075 self.path.following = false
2076 return
2077 end
2078
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2080 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2082 end
2083
2084 -- set new temporary target
2085 p = {x = p1.x, y = p1.y, z = p1.z}
2086 end
2087
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)
2098
2099 -- move towards enemy if beyond mob reach
2100 if dist > self.reach then
2101
2102 -- path finding by rnd
2103 if self.pathfinding -- only if mob has pathfinding enabled
2104 and enable_pathfinding then
2105
2106 smart_mobs(self, s, p, dist, dtime)
2107 end
2108
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2113 else
2114
2115 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2117 else
2118 set_velocity(self, self.run_velocity)
2119 end
2120
2121 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2123 else
2124 set_animation(self, "walk")
2125 end
2126 end
2127
2128 else -- rnd: if inside reach range
2129
2130 self.path.stuck = false
2131 self.path.stuck_timer = 0
2132 self.path.following = false -- not stuck anymore
2133
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2139
2140 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
2148
2149 local p2 = p
2150 local s2 = s
2151
2152 p2.y = p2.y + .5
2153 s2.y = s2.y + .5
2154
2155 if line_of_sight(self, p2, s2) == true then
2156
2157 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2159
2160 -- punch player (or what player is attached to)
2161 local attached = self.attack:get_attach()
2162 if attached then
2163 self.attack = attached
2164 end
2165 self.attack:punch(self.object, 1.0, {
2166 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2168 }, nil)
2169 end
2170 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
2179 end
2180 end
2181
2182 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
2185
2186 p.y = p.y - .5
2187 s.y = s.y + .5
2188
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)
2203
2204 if self.shoot_interval
2205 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2207
2208 self.timer = 0
2209 set_animation(self, "shoot")
2210
2211 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2213
2214 local p = self.object:get_pos()
2215
2216 p.y = p.y + (self.collisionbox[2] + self.collisionbox[5]) / 2
2217
2218 if minetest.registered_entities[self.arrow] then
2219
2220 local obj = minetest.add_entity(p, self.arrow)
2221 local ent = obj:get_luaentity()
2222 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2223 local v = ent.velocity or 1 -- or set to default
2224
2225 ent.switch = 1
2226 ent.owner_id = tostring(self.object) -- add unique owner id to arrow
2227
2228 -- offset makes shoot aim accurate
2229 vec.y = vec.y + self.shoot_offset
2230 vec.x = vec.x * (v / amount)
2231 vec.y = vec.y * (v / amount)
2232 vec.z = vec.z * (v / amount)
2233
2234 obj:set_velocity(vec)
2235 end
2236 end
2237 end
2238 end
2239 end
2240
2241
2242 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2246 return
2247 end
2248
2249 -- floating in water (or falling)
2250 local v = self.object:get_velocity()
2251
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
2286 else
2287
2288 -- fall damage onto solid ground
2289 if self.fall_damage == 1
2290 and self.object:get_velocity().y == 0 then
2291
2292 local d = (self.old_y or 0) - self.object:get_pos().y
2293
2294 if d > 5 then
2295
2296 self.health = self.health - floor(d - 5)
2297
2298 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
2299
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2302 end
2303 end
2304
2305 self.old_y = self.object:get_pos().y
2306 end
2307 end
2308 end
2309
2310
2311 -- is Took Ranks mod active?
2312 local tr = minetest.get_modpath("toolranks")
2313
2314 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2316
2317 -- mob health check
2318 if self.health <= 0 then
2319 return
2320 end
2321
2322 -- custom punch function
2323 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2326 end
2327
2328 -- error checking when mod profiling is enabled
2329 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
2339 end
2340
2341 local weapon = hitter:get_wielded_item()
2342 local weapon_def = weapon:get_definition() or {}
2343 local punch_interval = 1.4
2344
2345 -- calculate mob damage
2346 local damage = 0
2347 local armor = self.object:get_armor_groups() or {}
2348 local tmp
2349
2350 -- quick error check incase it ends up 0 (serialize.h check test)
2351 if tflp == 0 then
2352 tflp = 0.2
2353 end
2354
2355 if use_cmi then
2356 damage = cmi.calculate_damage(self.object, hitter, tflp, tool_capabilities, dir)
2357 else
2358
2359 for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do
2360
2361 tmp = tflp / (tool_capabilities.full_punch_interval or 1.4)
2362
2363 if tmp < 0 then
2364 tmp = 0.0
2365 elseif tmp > 1 then
2366 tmp = 1.0
2367 end
2368
2369 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2371 end
2372 end
2373
2374 -- check for tool immunity or special damage
2375 for n = 1, #self.immune_to do
2376
2377 if self.immune_to[n][1] == weapon_def.name then
2378
2379 damage = self.immune_to[n][2] or 0
2380 break
2381
2382 -- if "all" then no tool does damage unless it's specified in list
2383 elseif self.immune_to[n][1] == "all" then
2384 damage = self.immune_to[n][2] or 0
2385 end
2386 end
2387
2388 -- healing
2389 if damage <= -1 then
2390 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2395
2396 if use_cmi
2397 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2399 end
2400
2401 -- add weapon wear
2402 punch_interval = tool_capabilities.full_punch_interval or 1.4
2403
2404 -- toolrank support
2405 local wear = floor((punch_interval / 75) * 9000)
2406
2407 if mobs.is_creative(hitter:get_player_name()) then
2408
2409 if tr then
2410 wear = 1
2411 else
2412 wear = 0
2413 end
2414 end
2415
2416 if tr then
2417 if weapon_def.original_description then
2418 weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
2419 end
2420 else
2421 weapon:add_wear(wear)
2422 end
2423
2424 hitter:set_wielded_item(weapon)
2425
2426 -- only play hit sound and show blood effects if damage is 1 or over
2427 if damage >= 1 then
2428
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
2444
2445 -- blood_particles
2446 if not disable_blood and self.blood_amount > 0 then
2447
2448 local pos = self.object:get_pos()
2449
2450 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2451
2452 -- do we have a single blood texture or multiple?
2453 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
2461 end
2462
2463 -- do damage
2464 self.health = self.health - floor(damage)
2465
2466 -- exit here if dead, check for tools with fire damage
2467 local hot = tool_capabilities and tool_capabilities.damage_groups
2468 and tool_capabilities.damage_groups.fire
2469
2470 if check_for_death(self, {type = "punch",
2471 puncher = hitter, hot = hot}) then
2472 return
2473 end
2474
2475 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2476 minetest.after(0.1, function()
2477
2478 if not self.object:get_luaentity() then return end
2479
2480 self.object:settexturemod("^[colorize:#c9900070")
2481
2482 core.after(0.3, function()
2483 self.object:settexturemod("")
2484 end)
2485 end) ]]
2486
2487 end -- END if damage
2488
2489 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2492
2493 local v = self.object:get_velocity()
2494 local kb = damage or 1
2495 local up = 2
2496
2497 -- if already in air then dont go up anymore when hit
2498 if v.y > 0
2499 or self.fly then
2500 up = 0
2501 end
2502
2503 -- direction error check
2504 dir = dir or {x = 0, y = 0, z = 0}
2505
2506 -- use tool knockback value or default
2507 kb = tool_capabilities.damage_groups["knockback"] or (kb * 1.5)
2508
2509 self.object:set_velocity({
2510 x = dir.x * kb,
2511 y = up,
2512 z = dir.z * kb
2513 })
2514
2515 self.pause_timer = 0.25
2516 end
2517
2518 -- if skittish then run away
2519 if self.runaway == true
2520 and self.order ~= "stand" then
2521
2522 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)
2537 self.state = "runaway"
2538 self.runaway_timer = 0
2539 self.following = nil
2540 end
2541
2542 local name = hitter:get_player_name() or ""
2543
2544 -- attack puncher and call other mobs for help
2545 if self.passive == false
2546 and self.state ~= "flop"
2547 and self.child == false
2548 and self.attack_players == true
2549 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
2551
2552 -- attack whoever punched mob
2553 self.state = ""
2554 do_attack(self, hitter)
2555
2556 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
2559
2560 for n = 1, #objs do
2561
2562 obj = objs[n]:get_luaentity()
2563
2564 if obj and obj._cmi_is_mob then
2565
2566 -- only alert members of same mob
2567 if obj.group_attack == true
2568 and obj.state ~= "attack"
2569 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
2572 end
2573
2574 -- have owned mobs attack player threat
2575 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
2577 end
2578 end
2579 end
2580 end
2581 end
2582
2583
2584 -- get entity staticdata
2585 local mob_staticdata = function(self)
2586
2587 -- remove mob when out of range unless tamed
2588 if remove_far
2589 and self.remove_ok
2590 and self.type ~= "npc"
2591 and self.state ~= "attack"
2592 and not self.tamed
2593 and self.lifetimer < 20000 then
2594
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
2600 end
2601
2602 self.remove_ok = true
2603 self.attack = nil
2604 self.following = nil
2605 self.state = "stand"
2606
2607 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
2611 end
2612
2613 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
2618
2619 for _,stat in pairs(self) do
2620
2621 local t = type(stat)
2622
2623 if t ~= "function"
2624 and t ~= "nil"
2625 and t ~= "userdata"
2626 and _ ~= "_cmi_components" then
2627 tmp[_] = self[_]
2628 end
2629 end
2630
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
2632 return minetest.serialize(tmp)
2633 end
2634
2635
2636 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
2638
2639 -- remove monsters in peaceful mode
2640 if self.type == "monster"
2641 and peaceful_only then
2642
2643 self.object:remove()
2644
2645 return
2646 end
2647
2648 -- load entity variables
2649 local tmp = minetest.deserialize(staticdata)
2650
2651 if tmp then
2652 for _,stat in pairs(tmp) do
2653 self[_] = stat
2654 end
2655 end
2656
2657 -- select random texture, set model and size
2658 if not self.base_texture then
2659
2660 -- compatiblity with old simple mobs textures
2661 if def.textures and type(def.textures[1]) == "string" then
2662 def.textures = {def.textures}
2663 end
2664
2665 self.base_texture = def.textures and def.textures[random(1, #def.textures)]
2666 self.base_mesh = def.mesh
2667 self.base_size = self.visual_size
2668 self.base_colbox = self.collisionbox
2669 self.base_selbox = self.selectionbox
2670 end
2671
2672 -- for current mobs that dont have this set
2673 if not self.base_selbox then
2674 self.base_selbox = self.selectionbox or self.base_colbox
2675 end
2676
2677 -- set texture, model and size
2678 local textures = self.base_texture
2679 local mesh = self.base_mesh
2680 local vis_size = self.base_size
2681 local colbox = self.base_colbox
2682 local selbox = self.base_selbox
2683
2684 -- specific texture if gotten
2685 if self.gotten == true
2686 and def.gotten_texture then
2687 textures = def.gotten_texture
2688 end
2689
2690 -- specific mesh if gotten
2691 if self.gotten == true
2692 and def.gotten_mesh then
2693 mesh = def.gotten_mesh
2694 end
2695
2696 -- set child objects to half size
2697 if self.child == true then
2698
2699 vis_size = {
2700 x = self.base_size.x * .5,
2701 y = self.base_size.y * .5,
2702 }
2703
2704 if def.child_texture then
2705 textures = def.child_texture[1]
2706 end
2707
2708 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 }
2716 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 }
2724 end
2725
2726 if self.health == 0 then
2727 self.health = random (self.hp_min, self.hp_max)
2728 end
2729
2730 -- pathfinding init
2731 self.path = {}
2732 self.path.way = {} -- path to follow, table of positions
2733 self.path.lastpos = {x = 0, y = 0, z = 0}
2734 self.path.stuck = false
2735 self.path.following = false -- currently following path?
2736 self.path.stuck_timer = 0 -- if stuck for too long search for path
2737
2738 -- mob defaults
2739 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
2740 self.old_y = self.object:get_pos().y
2741 self.old_health = self.health
2742 self.sounds.distance = self.sounds.distance or 10
2743 self.textures = textures
2744 self.mesh = mesh
2745 self.collisionbox = colbox
2746 self.selectionbox = selbox
2747 self.visual_size = vis_size
2748 self.standing_in = "air"
2749
2750 -- check existing nametag
2751 if not self.nametag then
2752 self.nametag = def.nametag
2753 end
2754
2755 -- set anything changed above
2756 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")
2760
2761 -- run on_spawn function if found
2762 if self.on_spawn and not self.on_spawn_run then
2763 if self.on_spawn(self) then
2764 self.on_spawn_run = true -- if true, set flag to run once only
2765 end
2766 end
2767
2768 -- run after_activate
2769 if def.after_activate then
2770 def.after_activate(self, staticdata, def, dtime)
2771 end
2772
2773 if use_cmi then
2774 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
2775 cmi.notify_activate(self.object, dtime)
2776 end
2777 end
2778
2779
2780 -- handle mob lifetimer and expiration
2781 local mob_expire = function(self, pos, dtime)
2782
2783 -- when lifetimer expires remove mob (except npc and tamed)
2784 if self.type ~= "npc"
2785 and not self.tamed
2786 and self.state ~= "attack"
2787 and remove_far ~= true
2788 and self.lifetimer < 20000 then
2789
2790 self.lifetimer = self.lifetimer - dtime
2791
2792 if self.lifetimer <= 0 then
2793
2794 -- only despawn away from player
2795 local objs = minetest.get_objects_inside_radius(pos, 15)
2796
2797 for n = 1, #objs do
2798
2799 if objs[n]:is_player() then
2800
2801 self.lifetimer = 20
2802
2803 return
2804 end
2805 end
2806
2807 -- minetest.log("action",
2808 -- S("lifetimer expired, removed @1", self.name))
2809
2810 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
2811
2812 self.object:remove()
2813
2814 return
2815 end
2816 end
2817 end
2818
2819
2820 -- main mob function
2821 local mob_step = function(self, dtime)
2822
2823 if use_cmi then
2824 cmi.notify_step(self.object, dtime)
2825 end
2826
2827 local pos = self.object:get_pos()
2828 local yaw = 0
2829
2830 -- get node at foot level every quarter second
2831 self.node_timer = (self.node_timer or 0) + dtime
2832
2833 if self.node_timer > 0.25 then
2834
2835 self.node_timer = 0
2836
2837 local y_level = self.collisionbox[2]
2838
2839 if self.child then
2840 y_level = self.collisionbox[2] * 0.5
2841 end
2842
2843 -- what is mob standing in?
2844 self.standing_in = node_ok({
2845 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)
2854
2855 -- smooth rotation by ThomasMonroe314
2856
2857 if self.delay and self.delay > 0 then
2858
2859 local yaw = self.object:get_yaw()
2860
2861 if self.delay == 1 then
2862 yaw = self.target_yaw
2863 else
2864 local dif = abs(yaw - self.target_yaw)
2865
2866 if yaw > self.target_yaw then
2867
2868 if dif > pi then
2869 dif = 2 * pi - dif -- need to add
2870 yaw = yaw + dif / self.delay
2871 else
2872 yaw = yaw - dif / self.delay -- need to subtract
2873 end
2874
2875 elseif yaw < self.target_yaw then
2876
2877 if dif > pi then
2878 dif = 2 * pi - dif
2879 yaw = yaw - dif / self.delay -- need to subtract
2880 else
2881 yaw = yaw + dif / self.delay -- need to add
2882 end
2883 end
2884
2885 if yaw > (pi * 2) then yaw = yaw - (pi * 2) end
2886 if yaw < 0 then yaw = yaw + (pi * 2) end
2887 end
2888
2889 self.delay = self.delay - 1
2890 self.object:set_yaw(yaw)
2891 end
2892
2893 -- end rotation
2894
2895 -- knockback timer
2896 if self.pause_timer > 0 then
2897
2898 self.pause_timer = self.pause_timer - dtime
2899
2900 return
2901 end
2902
2903 -- run custom function (defined in mob lua file)
2904 if self.do_custom then
2905
2906 -- when false skip going any further
2907 if self.do_custom(self, dtime) == false then
2908 return
2909 end
2910 end
2911
2912 -- attack timer
2913 self.timer = self.timer + dtime
2914
2915 if self.state ~= "attack" then
2916
2917 if self.timer < 1 then
2918 return
2919 end
2920
2921 self.timer = 0
2922 end
2923
2924 -- never go over 100
2925 if self.timer > 100 then
2926 self.timer = 1
2927 end
2928
2929 -- mob plays random sound at times
2930 if random(1, 100) == 1 then
2931 mob_sound(self, self.sounds.random)
2932 end
2933
2934 -- environmental damage timer (every 1 second)
2935 self.env_damage_timer = self.env_damage_timer + dtime
2936
2937 if (self.state == "attack" and self.env_damage_timer > 1)
2938 or self.state ~= "attack" then
2939
2940 self.env_damage_timer = 0
2941
2942 -- check for environmental damage (water, fire, lava etc.)
2943 do_env_damage(self)
2944
2945 -- 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
2961 end
2962
2963
2964 -- 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, {
2970 full_punch_interval = 1.0,
2971 damage_groups = {fleshy = damage},
2972 }, nil)
2973
2974 return false, true, {}
2975 end
2976
2977
2978 mobs.spawning_mobs = {}
2979
2980 -- register mob entity
2981 function mobs:register_mob(name, def)
2982
2983 mobs.spawning_mobs[name] = true
2984
2985 minetest.register_entity(name, {
2986
2987 stepheight = def.stepheight or 1.1, -- was 0.6
2988 name = name,
2989 type = def.type,
2990 attack_type = def.attack_type,
2991 fly = def.fly,
2992 fly_in = def.fly_in or "air",
2993 owner = def.owner or "",
2994 order = def.order or "",
2995 on_die = def.on_die,
2996 do_custom = def.do_custom,
2997 jump_height = def.jump_height or 4, -- was 6
2998 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
3001 hp_min = max(1, (def.hp_min or 5) * difficulty),
3002 hp_max = max(1, (def.hp_max or 10) * difficulty),
3003 physical = true,
3004 collisionbox = def.collisionbox or {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
3005 selectionbox = def.selectionbox or def.collisionbox,
3006 visual = def.visual,
3007 visual_size = def.visual_size or {x = 1, y = 1},
3008 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,
3013 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,
3024 on_rightclick = def.on_rightclick,
3025 arrow = def.arrow,
3026 shoot_interval = def.shoot_interval,
3027 sounds = def.sounds or {},
3028 animation = def.animation,
3029 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
3039 replace_rate = def.replace_rate,
3040 replace_what = def.replace_what,
3041 replace_with = def.replace_with,
3042 replace_offset = def.replace_offset or 0,
3043 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,
3055 texture_list = def.textures,
3056 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,
3060 runaway = def.runaway,
3061 runaway_timer = 0,
3062 pathfinding = def.pathfinding,
3063 immune_to = def.immune_to or {},
3064 explosion_radius = def.explosion_radius,
3065 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,
3069 custom_attack = def.custom_attack,
3070 double_melee_attack = def.double_melee_attack,
3071 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,
3080 specific_attack = def.specific_attack,
3081 runaway_from = def.runaway_from,
3082 owner_loyal = def.owner_loyal,
3083 facing_fence = false,
3084 pushable = def.pushable,
3085 _cmi_is_mob = true,
3086
3087 on_spawn = def.on_spawn,
3088
3089 on_blast = def.on_blast or do_tnt,
3090
3091 on_step = mob_step,
3092
3093 do_punch = def.do_punch,
3094
3095 on_punch = mob_punch,
3096
3097 on_breed = def.on_breed,
3098
3099 on_grown = def.on_grown,
3100
3101 on_activate = function(self, staticdata, dtime)
3102 return mob_activate(self, staticdata, def, dtime)
3103 end,
3104
3105 get_staticdata = function(self)
3106 return mob_staticdata(self)
3107 end,
3108
3109 })
3110
3111 end -- END mobs:register_mob function
3112
3113
3114 -- count how many mobs of one type are inside an area
3115 local count_mobs = function(pos, type)
3116
3117 local total = 0
3118 local objs = minetest.get_objects_inside_radius(pos, aoc_range * 2)
3119 local ent
3120
3121 for n = 1, #objs do
3122
3123 if not objs[n]:is_player() then
3124
3125 ent = objs[n]:get_luaentity()
3126
3127 -- count mob type and add to total also
3128 if ent and ent.name and ent.name == type then
3129 total = total + 1
3130 end
3131 end
3132 end
3133
3134 return total
3135 end
3136
3137
3138 -- global functions
3139
3140 function mobs:spawn_abm_check(pos, node, name)
3141 -- global function to add additional spawn checks
3142 -- return true to stop spawning mob
3143 end
3144
3145
3146 function mobs:spawn_specific(name, nodes, neighbors, min_light, max_light,
3147 interval, chance, aoc, min_height, max_height, day_toggle, on_spawn)
3148
3149 -- Do mobs spawn at all?
3150 if not mobs_spawn then
3151 return
3152 end
3153
3154 -- chance/spawn number override in minetest.conf for registered mob
3155 local numbers = minetest.settings:get(name)
3156
3157 if numbers then
3158 numbers = numbers:split(",")
3159 chance = tonumber(numbers[1]) or chance
3160 aoc = tonumber(numbers[2]) or aoc
3161
3162 if chance == 0 then
3163 minetest.log("warning", string.format("[mobs] %s has spawning disabled", name))
3164 return
3165 end
3166
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
3197 --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
3208
3209 -- 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)
3255 return
3256 end
3257 end
3258
3259 -- 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
3266
3267 local pos2 = {x = pos.x, y = pos.y + n, z = pos.z}
3268
3269 if minetest.registered_nodes[node_ok(pos2).name].walkable == true then
3270 --print ("--- inside block", name, node_ok(pos2).name)
3271 return
3272 end
3273 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
3284
3285 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 ]]
3291 if on_spawn then
3292
3293 local ent = mob:get_luaentity()
3294
3295 on_spawn(ent, pos)
3296 end
3297 end
3298 })
3299 end
3300
3301
3302 -- compatibility with older mob registration
3303 function mobs:register_spawn(name, nodes, max_light, min_light, chance,
3304 active_object_count, max_height, day_toggle)
3305
3306 mobs:spawn_specific(name, nodes, {"air"}, min_light, max_light, 30,
3307 chance, active_object_count, -31000, max_height, day_toggle)
3308 end
3309
3310
3311 -- MarkBu's spawn function
3312 function mobs:spawn(def)
3313
3314 mobs:spawn_specific(
3315 def.name,
3316 def.nodes or {"group:soil", "group:stone"},
3317 def.neighbors or {"air"},
3318 def.min_light or 0,
3319 def.max_light or 15,
3320 def.interval or 30,
3321 def.chance or 5000,
3322 def.active_object_count or 1,
3323 def.min_height or -31000,
3324 def.max_height or 31000,
3325 def.day_toggle,
3326 def.on_spawn
3327 )
3328 end
3329
3330
3331 -- register arrow for shoot attack
3332 function mobs:register_arrow(name, def)
3333
3334 if not name or not def then return end -- errorcheck
3335
3336 minetest.register_entity(name, {
3337
3338 physical = false,
3339 visual = def.visual,
3340 visual_size = def.visual_size,
3341 textures = def.textures,
3342 velocity = def.velocity,
3343 hit_player = def.hit_player,
3344 hit_node = def.hit_node,
3345 hit_mob = def.hit_mob,
3346 drop = def.drop or false, -- drops arrow as registered item when true
3347 collisionbox = def.collisionbox or {0, 0, 0, 0, 0, 0},
3348 timer = 0,
3349 switch = 0,
3350 owner_id = def.owner_id,
3351 rotate = def.rotate,
3352 automatic_face_movement_dir = def.rotate
3353 and (def.rotate - (pi / 180)) or false,
3354
3355 on_activate = def.on_activate,
3356
3357 on_punch = def.on_punch or function(self, hitter, tflp, tool_capabilities, dir)
3358 end,
3359
3360 on_step = def.on_step or function(self, dtime)
3361
3362 self.timer = self.timer + 1
3363
3364 local pos = self.object:get_pos()
3365
3366 if self.switch == 0
3367 or self.timer > 150 then
3368
3369 self.object:remove() ; -- print ("removed arrow")
3370
3371 return
3372 end
3373
3374 -- does arrow have a tail (fireball)
3375 if def.tail
3376 and def.tail == 1
3377 and def.tail_texture then
3378
3379 minetest.add_particle({
3380 pos = pos,
3381 velocity = {x = 0, y = 0, z = 0},
3382 acceleration = {x = 0, y = 0, z = 0},
3383 expirationtime = def.expire or 0.25,
3384 collisiondetection = false,
3385 texture = def.tail_texture,
3386 size = def.tail_size or 5,
3387 glow = def.glow or 0,
3388 })
3389 end
3390
3391 if self.hit_node then
3392
3393 local node = node_ok(pos).name
3394
3395 if minetest.registered_nodes[node].walkable then
3396
3397 self.hit_node(self, pos, node)
3398
3399 if self.drop == true then
3400
3401 pos.y = pos.y + 1
3402
3403 self.lastpos = (self.lastpos or pos)
3404
3405 minetest.add_item(self.lastpos, self.object:get_luaentity().name)
3406 end
3407
3408 self.object:remove() ; -- print ("hit node")
3409
3410 return
3411 end
3412 end
3413
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")
3423 return
3424 end
3425
3426 local entity = player:get_luaentity()
3427
3428 if entity
3429 and self.hit_mob
3430 and entity._cmi_is_mob == true
3431 and tostring(player) ~= self.owner_id
3432 and entity.name ~= self.object:get_luaentity().name then
3433
3434 self.hit_mob(self, player)
3435
3436 self.object:remove() ; --print ("hit mob")
3437
3438 return
3439 end
3440 end
3441 end
3442
3443 self.lastpos = pos
3444 end
3445 })
3446 end
3447
3448
3449 -- compatibility function
3450 function mobs:explosion(pos, radius)
3451
3452 local self = {sounds = {explode = "tnt_explode"}}
3453
3454 mobs:boom(self, pos, radius)
3455 end
3456
3457
3458 -- no damage to nodes explosion
3459 function mobs:safe_boom(self, pos, radius)
3460
3461 minetest.sound_play(self.sounds and self.sounds.explode or "tnt_explode", {
3462 pos = pos,
3463 gain = 1.0,
3464 max_hear_distance = self.sounds and self.sounds.distance or 32
3465 })
3466
3467 entity_physics(pos, radius)
3468
3469 effect(pos, 32, "tnt_smoke.png", radius * 3, radius * 5, radius, 1, 0)
3470 end
3471
3472
3473 -- make explosion with protection and tnt mod check
3474 function mobs:boom(self, pos, radius)
3475
3476 if mobs_griefing
3477 and minetest.get_modpath("tnt") and tnt and tnt.boom
3478 and not minetest.is_protected(pos, "") then
3479
3480 tnt.boom(pos, {
3481 radius = radius,
3482 damage_radius = radius,
3483 sound = self.sounds and self.sounds.explode,
3484 explode_center = true,
3485 })
3486 else
3487 mobs:safe_boom(self, pos, radius)
3488 end
3489 end
3490
3491
3492 -- Register spawn eggs
3493
3494 -- Note: This also introduces the “spawn_egg” group:
3495 -- * spawn_egg=1: Spawn egg (generic mob, no metadata)
3496 -- * spawn_egg=2: Spawn egg (captured/tamed mob, metadata)
3497 function mobs:register_egg(mob, desc, background, addegg, no_creative)
3498
3499 local grp = {spawn_egg = 1}
3500
3501 -- do NOT add this egg to creative inventory (e.g. dungeon master)
3502 if creative and no_creative == true then
3503 grp.not_in_creative_inventory = 1
3504 end
3505
3506 local invimg = background
3507
3508 if addegg == 1 then
3509 invimg = "mobs_chicken_egg.png^(" .. invimg ..
3510 "^[mask:mobs_chicken_egg_overlay.png)"
3511 end
3512
3513 -- register new spawn egg containing mob information
3514 minetest.register_craftitem(mob .. "_set", {
3515
3516 description = S("@1 (Tamed)", desc),
3517 inventory_image = invimg,
3518 groups = {spawn_egg = 2, not_in_creative_inventory = 1},
3519 stack_max = 1,
3520
3521 on_place = function(itemstack, placer, pointed_thing)
3522
3523 local pos = pointed_thing.above
3524
3525 -- am I clicking on something with existing on_rightclick function?
3526 local under = minetest.get_node(pointed_thing.under)
3527 local def = minetest.registered_nodes[under.name]
3528 if def and def.on_rightclick then
3529 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3530 end
3531
3532 if pos
3533 and not minetest.is_protected(pos, placer:get_player_name()) then
3534
3535 if not minetest.registered_entities[mob] then
3536 return
3537 end
3538
3539 pos.y = pos.y + 1
3540
3541 local data = itemstack:get_metadata()
3542 local mob = minetest.add_entity(pos, mob, data)
3543 local ent = mob:get_luaentity()
3544
3545 -- set owner if not a monster
3546 if ent.type ~= "monster" then
3547 ent.owner = placer:get_player_name()
3548 ent.tamed = true
3549 end
3550
3551 -- since mob is unique we remove egg once spawned
3552 itemstack:take_item()
3553 end
3554
3555 return itemstack
3556 end,
3557 })
3558
3559
3560 -- register old stackable mob egg
3561 minetest.register_craftitem(mob, {
3562
3563 description = desc,
3564 inventory_image = invimg,
3565 groups = grp,
3566
3567 on_place = function(itemstack, placer, pointed_thing)
3568
3569 local pos = pointed_thing.above
3570
3571 -- am I clicking on something with existing on_rightclick function?
3572 local under = minetest.get_node(pointed_thing.under)
3573 local def = minetest.registered_nodes[under.name]
3574 if def and def.on_rightclick then
3575 return def.on_rightclick(pointed_thing.under, under, placer, itemstack)
3576 end
3577
3578 if pos
3579 and not minetest.is_protected(pos, placer:get_player_name()) then
3580
3581 if not minetest.registered_entities[mob] then
3582 return
3583 end
3584
3585 pos.y = pos.y + 1
3586
3587 local mob = minetest.add_entity(pos, mob)
3588 local ent = mob:get_luaentity()
3589
3590 -- don't set owner if monster or sneak pressed
3591 if ent.type ~= "monster"
3592 and not placer:get_player_control().sneak then
3593 ent.owner = placer:get_player_name()
3594 ent.tamed = true
3595 end
3596
3597 -- if not in creative then take item
3598 if not mobs.is_creative(placer:get_player_name()) then
3599 itemstack:take_item()
3600 end
3601 end
3602
3603 return itemstack
3604 end,
3605 })
3606
3607 end
3608
3609
3610 -- capture critter (thanks to blert2112 for idea)
3611 function mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
3612 force_take, replacewith)
3613
3614 if self.child
3615 or not clicker:is_player()
3616 or not clicker:get_inventory() then
3617 return false
3618 end
3619
3620 -- get name of clicked mob
3621 local mobname = self.name
3622
3623 -- if not nil change what will be added to inventory
3624 if replacewith then
3625 mobname = replacewith
3626 end
3627
3628 local name = clicker:get_player_name()
3629 local tool = clicker:get_wielded_item()
3630
3631 -- are we using hand, net or lasso to pick up mob?
3632 if tool:get_name() ~= ""
3633 and tool:get_name() ~= "mobs:net"
3634 and tool:get_name() ~= "mobs:lasso" then
3635 return false
3636 end
3637
3638 -- is mob tamed?
3639 if self.tamed == false
3640 and force_take == false then
3641
3642 minetest.chat_send_player(name, S("Not tamed!"))
3643
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
3650
3651 minetest.chat_send_player(name, S("@1 is owner!", self.owner))
3652
3653 return true -- false
3654 end
3655
3656 if clicker:get_inventory():room_for_item("main", mobname) then
3657
3658 -- was mob clicked with hand, net, or lasso?
3659 local chance = 0
3660
3661 if tool:get_name() == "" then
3662 chance = chance_hand
3663
3664 elseif tool:get_name() == "mobs:net" then
3665
3666 chance = chance_net
3667
3668 tool:add_wear(4000) -- 17 uses
3669
3670 clicker:set_wielded_item(tool)
3671
3672 elseif tool:get_name() == "mobs:lasso" then
3673
3674 chance = chance_lasso
3675
3676 tool:add_wear(650) -- 100 uses
3677
3678 clicker:set_wielded_item(tool)
3679
3680 end
3681
3682 -- calculate chance.. add to inventory if successful?
3683 if chance > 0 and random(1, 100) <= chance then
3684
3685 -- default mob egg
3686 local new_stack = ItemStack(mobname)
3687
3688 -- add special mob egg with all mob information
3689 -- unless 'replacewith' contains new item to use
3690 if not replacewith then
3691
3692 new_stack = ItemStack(mobname .. "_set")
3693
3694 local tmp = {}
3695
3696 for _,stat in pairs(self) do
3697 local t = type(stat)
3698 if t ~= "function"
3699 and t ~= "nil"
3700 and t ~= "userdata" then
3701 tmp[_] = self[_]
3702 end
3703 end
3704
3705 local data_str = minetest.serialize(tmp)
3706
3707 new_stack:set_metadata(data_str)
3708 end
3709
3710 local inv = clicker:get_inventory()
3711
3712 if inv:room_for_item("main", new_stack) then
3713 inv:add_item("main", new_stack)
3714 else
3715 minetest.add_item(clicker:get_pos(), new_stack)
3716 end
3717
3718 self.object:remove()
3719
3720 mob_sound(self, "default_place_node_hard")
3721
3722 elseif chance ~= 0 then
3723 minetest.chat_send_player(name, S("Missed!"))
3724
3725 mob_sound(self, "mobs_swing")
3726 end
3727 end
3728
3729 return true
3730 end
3731
3732
3733 -- protect tamed mob with rune item
3734 function mobs:protect(self, clicker)
3735
3736 local name = clicker:get_player_name()
3737 local tool = clicker:get_wielded_item()
3738
3739 if tool:get_name() ~= "mobs:protector" then
3740 return false
3741 end
3742
3743 if self.tamed == false then
3744 minetest.chat_send_player(name, S("Not tamed!"))
3745 return true -- false
3746 end
3747
3748 if self.protected == true then
3749 minetest.chat_send_player(name, S("Already protected!"))
3750 return true -- false
3751 end
3752
3753 if not mobs.is_creative(clicker:get_player_name()) then
3754 tool:take_item() -- take 1 protection rune
3755 clicker:set_wielded_item(tool)
3756 end
3757
3758 self.protected = true
3759
3760 local pos = self.object:get_pos()
3761 pos.y = pos.y + self.collisionbox[2] + 0.5
3762
3763 effect(self.object:get_pos(), 25, "mobs_protect_particle.png", 0.5, 4, 2, 15)
3764
3765 mob_sound(self, "mobs_spell")
3766
3767 return true
3768 end
3769
3770
3771 local mob_obj = {}
3772 local mob_sta = {}
3773
3774 -- feeding, taming and breeding (thanks blert2112)
3775 function mobs:feed_tame(self, clicker, feed_count, breed, tame)
3776
3777 if not self.follow then
3778 return false
3779 end
3780
3781 -- can eat/tame with item in hand
3782 if follow_holding(self, clicker) then
3783
3784 -- if not in creative then take item
3785 if not mobs.is_creative(clicker:get_player_name()) then
3786
3787 local item = clicker:get_wielded_item()
3788
3789 item:take_item()
3790
3791 clicker:set_wielded_item(item)
3792 end
3793
3794 -- increase health
3795 self.health = self.health + 4
3796
3797 if self.health >= self.hp_max then
3798
3799 self.health = self.hp_max
3800
3801 if self.htimer < 1 then
3802
3803 minetest.chat_send_player(clicker:get_player_name(),
3804 S("@1 at full health (@2)",
3805 self.name:split(":")[2], tostring(self.health)))
3806
3807 self.htimer = 5
3808 end
3809 end
3810
3811 self.object:set_hp(self.health)
3812
3813 update_tag(self)
3814
3815 -- make children grow quicker
3816 if self.child == true then
3817
3818 self.hornytimer = self.hornytimer + 20
3819
3820 return true
3821 end
3822
3823 -- feed and tame
3824 self.food = (self.food or 0) + 1
3825 if self.food >= feed_count then
3826
3827 self.food = 0
3828
3829 if breed and self.hornytimer == 0 then
3830 self.horny = true
3831 end
3832
3833 self.gotten = false
3834
3835 if tame then
3836
3837 if self.tamed == false then
3838 minetest.chat_send_player(clicker:get_player_name(),
3839 S("@1 has been tamed!",
3840 self.name:split(":")[2]))
3841 end
3842
3843 self.tamed = true
3844
3845 if not self.owner or self.owner == "" then
3846 self.owner = clicker:get_player_name()
3847 end
3848 end
3849
3850 -- make sound when fed so many times
3851 mob_sound(self, self.sounds.random)
3852 end
3853
3854 return true
3855 end
3856
3857 local item = clicker:get_wielded_item()
3858
3859 -- if mob has been tamed you can name it with a nametag
3860 if item:get_name() == "mobs:nametag"
3861 and clicker:get_player_name() == self.owner then
3862
3863 local name = clicker:get_player_name()
3864
3865 -- store mob and nametag stack in external variables
3866 mob_obj[name] = self
3867 mob_sta[name] = item
3868
3869 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")) .. "]")
3878 end
3879
3880 return false
3881 end
3882
3883
3884 -- inspired by blockmen's nametag mod
3885 minetest.register_on_player_receive_fields(function(player, formname, fields)
3886
3887 -- right-clicked with nametag and name entered?
3888 if formname == "mobs_nametag"
3889 and fields.name
3890 and fields.name ~= "" then
3891
3892 local name = player:get_player_name()
3893
3894 if not mob_obj[name]
3895 or not mob_obj[name].object then
3896 return
3897 end
3898
3899 -- make sure nametag is being used to name mob
3900 local item = player:get_wielded_item()
3901
3902 if item:get_name() ~= "mobs:nametag" then
3903 return
3904 end
3905
3906 -- limit name entered to 64 characters long
3907 if string.len(fields.name) > 64 then
3908 fields.name = string.sub(fields.name, 1, 64)
3909 end
3910
3911 -- update nametag
3912 mob_obj[name].nametag = fields.name
3913
3914 update_tag(mob_obj[name])
3915
3916 -- if not in creative then take item
3917 if not mobs.is_creative(name) then
3918
3919 mob_sta[name]:take_item()
3920
3921 player:set_wielded_item(mob_sta[name])
3922 end
3923
3924 -- reset external variables
3925 mob_obj[name] = nil
3926 mob_sta[name] = nil
3927 end
3928 end)
3929
3930
3931 -- compatibility function for old entities to new modpack entities
3932 function mobs:alias_mob(old_name, new_name)
3933
3934 -- spawn egg
3935 minetest.register_alias(old_name, new_name)
3936
3937 -- entity
3938 minetest.register_entity(":" .. old_name, {
3939
3940 physical = false,
3941
3942 on_activate = function(self)
3943
3944 if minetest.registered_entities[new_name] then
3945 minetest.add_entity(self.object:get_pos(), new_name)
3946 end
3947
3948 self.object:remove()
3949 end
3950 })
3951 end
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
0
1 Mobs Redo API
2 =============
3
4 Welcome to the world of mobs in minetest and hopefully an easy guide to defining
5 your own mobs and having them appear in your worlds.
6
7
8 Registering Mobs
9 ----------------
10
11 To register a mob and have it ready for use requires the following function:
12
13 mobs:register_mob(name, definition)
14
15 The 'name' of a mob usually starts with the mod name it's running from followed
16 by it's own name e.g.
17
18 "mobs_monster:sand_monster" or "mymod:totally_awesome_beast"
19
20 ... and the 'definition' is a table which holds all of the settings and
21 functions needed for the mob to work properly which contains the following:
22
23 'nametag' contains the name which is shown above mob.
24 'type' holds the type of mob that inhabits your world e.g.
25 "animal" usually docile and walking around.
26 "monster" attacks player or npc on sight.
27 "npc" walk around and will defend themselves if hit first, they
28 kill monsters.
29 'hp_min' has the minimum health value the mob can spawn with.
30 'hp_max' has the maximum health value the mob can spawn with.
31 'armor' holds strength of mob, 100 is normal, lower is more powerful
32 and needs more hits and better weapons to kill.
33 'passive' when true allows animals to defend themselves when hit,
34 otherwise they amble onwards.
35 'walk_velocity' is the speed that your mob can walk around.
36 'run_velocity' is the speed your mob can run with, usually when attacking.
37 'walk_chance' has a 0-100 chance value your mob will walk from standing,
38 set to 0 for jumping mobs only.
39 'jump' when true allows your mob to jump updwards.
40 'jump_height' holds the height your mob can jump, 0 to disable jumping.
41 'stepheight' height of a block that your mob can easily walk up onto,
42 defaults to 1.1.
43 'fly' when true allows your mob to fly around instead of walking.
44 'fly_in' holds the node name that the mob flies (or swims) around
45 in e.g. "air" or "default:water_source".
46 'runaway' if true causes animals to turn and run away when hit.
47 'pushable' when true mobs can be pushed by player or other mobs.
48 'view_range' how many nodes in distance the mob can see a player.
49 'damage' how many health points the mob does to a player or another
50 mob when melee attacking.
51 'knock_back' when true has mobs falling backwards when hit, the greater
52 the damage the more they move back.
53 'fear_height' is how high a cliff or edge has to be before the mob stops
54 walking, 0 to turn off height fear.
55 'fall_speed' has the maximum speed the mob can fall at, default is -10.
56 'fall_damage' when true causes falling to inflict damage.
57 'water_damage' holds the damage per second infliced to mobs when standing in
58 water.
59 'lava_damage' holds the damage per second inflicted to mobs when standing
60 in lava or fire or an ignition source.
61 'light_damage' holds the damage per second inflicted to mobs when light
62 level is between the min and max values below
63 'light_damage_min' minimum light value when mob is affected (default: 14)
64 'light_damage_max' maximum light value when mob is affected (default: 15)
65 'suffocation' when true causes mobs to suffocate inside solid blocks.
66 'floats' when set to 1 mob will float in water, 0 has them sink.
67 'follow' mobs follow player when holding any of the items which appear
68 on this table, the same items can be fed to a mob to tame or
69 breed e.g. {"farming:wheat", "default:apple"}
70
71 'reach' is how far the mob can attack player when standing
72 nearby, default is 3 nodes.
73 'docile_by_day' when true has mobs wandering around during daylight
74 hours and only attacking player at night or when
75 provoked.
76 'attack_chance' 0 to 100 chance the mob will attack (default is 5).
77 'attack_monsters' when true mob will attack monsters.
78 'attack_animals' when true mob will attack animals.
79 'attack_npcs' when true mob will attack npcs within range.
80 'attack_players' when true mob will attack players nearby.
81 'owner_loyal' when true non-docile tamed mobs attack anything player
82 punches when nearby.
83 'group_attack' when true has same mob type grouping together to attack
84 offender.
85 'attack_type' tells the api what a mob does when attacking the player
86 or another mob:
87 'dogfight' is a melee attack when player is within mob reach.
88 'shoot' has mob shoot pre-defined arrows at player when inside
89 view_range.
90 'dogshoot' has melee attack when inside reach and shoot attack
91 when inside view_range.
92 'explode' causes mob to stop and explode when inside reach.
93 'explosion_radius' the radius of explosion node destruction,
94 defaults to 1
95 'explosion_damage_radius' the radius of explosion entity & player damage,
96 defaults to explosion_radius * 2
97 'explosion_timer' number of seconds before mob explodes while its target
98 is still inside reach or explosion_damage_radius,
99 defaults to 3.
100 'allow_fuse_reset' Allow 'explode' attack_type to reset fuse and resume
101 chasing if target leaves the blast radius or line of
102 sight. Defaults to true.
103 'stop_to_explode' When set to true (default), mob must stop and wait for
104 explosion_timer in order to explode. If false, mob will
105 continue chasing.
106 'arrow' holds the pre-defined arrow object to shoot when
107 attacking.
108 'dogshoot_switch' allows switching between attack types by using timers
109 (1 for shoot, 2 for dogfight)
110 'dogshoot_count_max' contains how many seconds before switching from
111 dogfight to shoot.
112 'dogshoot_count2_max' contains how many seconds before switching from shoot
113 to dogfight.
114 'shoot_interval' has the number of seconds between shots.
115 'shoot_offset' holds the y position added as to where the
116 arrow/fireball appears on mob.
117 'specific_attack' has a table of entity names that mob can also attack
118 e.g. {"player", "mobs_animal:chicken"}.
119 'runaway_from' contains a table with mob names to run away from, add
120 "player" to list to runaway from player also.
121 'blood_amount' contains the number of blood droplets to appear when
122 mob is hit.
123 'blood_texture' has the texture name to use for droplets e.g.
124 "mobs_blood.png", or table {"blood1.png", "blood2.png"}
125 'pathfinding' set to 1 for mobs to use pathfinder feature to locate
126 player, set to 2 so they can build/break also (only
127 works with dogfight attack and when 'mobs_griefing'
128 in minetest.conf is not false).
129 'immune_to' is a table that holds specific damage when being hit by
130 certain items e.g.
131 {"default:sword_wood", 0} -- causes no damage.
132 {"default:gold_lump", -10} -- heals by 10 health points.
133 {"default:coal_block", 20} -- 20 damage when hit on head with coal blocks.
134 {"all"} -- stops all weapons causing damage apart from those on list.
135
136 'makes_footstep_sound' when true you can hear mobs walking.
137 'sounds' this is a table with sounds of the mob
138 'distance' maximum distance sounds can be heard, default is 10.
139 'random' random sound that plays during gameplay.
140 'war_cry' what you hear when mob starts to attack player.
141 'attack' what you hear when being attacked.
142 'shoot_attack' sound played when mob shoots.
143 'damage' sound heard when mob is hurt.
144 'death' played when mob is killed.
145 'jump' played when mob jumps.
146 'fuse' sound played when mob explode timer starts.
147 'explode' sound played when mob explodes.
148
149 'drops' table of items that are dropped when mob is killed, fields are:
150 'name' name of item to drop.
151 'chance' chance of drop, 1 for always, 2 for 1-in-2 chance etc.
152 'min' minimum number of items dropped, set to 0 for rare drops.
153 'max' maximum number of items dropped.
154 Note: If weapon has {fire=1} damage group set then cooked items will drop.
155
156 'visual' holds the look of the mob you wish to create:
157 'cube' looks like a normal node
158 'sprite' sprite which looks same from all angles.
159 'upright_sprite' flat model standing upright.
160 'wielditem' how it looks when player holds it in hand.
161 'mesh' uses separate object file to define mob.
162 'visual_size' has the size of the mob, defaults to {x = 1, y = 1}
163 'collisionbox' has the box in which mob can be interacted with the
164 world e.g. {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}
165 'selectionbox' has the box in which player can interact with mob
166 'textures' holds a table list of textures to be used for mob, or you
167 could use multiple lists inside another table for random
168 selection e.g. { {"texture1.png"}, {"texture2.png"} }
169 'child_texture' holds the texture table for when baby mobs are used.
170 'gotten_texture' holds the texture table for when self.gotten value is
171 true, used for milking cows or shearing sheep.
172 'mesh' holds the name of the external object used for mob model
173 e.g. "mobs_cow.b3d"
174 'gotten_mesh" holds the name of the external object used for when
175 self.gotten is true for mobs.
176 'rotate' custom model rotation, 0 = front, 90 = side, 180 = back,
177 270 = other side.
178 'double_melee_attack' when true has the api choose between 'punch' and
179 'punch2' animations. [DEPRECATED]
180
181 'animation' holds a table containing animation names and settings for use with mesh models:
182 'stand_start' start frame for when mob stands still.
183 'stand_end' end frame of stand animation.
184 'stand_speed' speed of animation in frames per second.
185 'walk_start' when mob is walking around.
186 'walk_end'
187 'walk_speed'
188 'run_start' when a mob runs or attacks.
189 'run_end'
190 'run_speed'
191 'fly_start' when a mob is flying.
192 'fly_end'
193 'fly_speed'
194 'punch_start' when a mob melee attacks.
195 'punch_end'
196 'punch_speed'
197 'punch2_start' alternative melee attack animation.
198 'punch2_end'
199 'punch2_speed'
200 'shoot_start' shooting animation.
201 'shoot_end'
202 'shoot_speed'
203 'die_start' death animation
204 'die_end'
205 'die_speed'
206 'die_loop' when set to false stops the animation looping.
207
208 Using '_loop = false' setting will stop any of the above animations from
209 looping.
210
211 'speed_normal' is used for animation speed for compatibility with some
212 older mobs.
213
214 Note: Up to 5 different animations can be used per action e.g.
215 stand_start, stand_end, stand1_start, stand1_end .. up to stand4_start
216
217
218 Node Replacement
219 ----------------
220
221 Mobs can look around for specific nodes as they walk and replace them to mimic
222 eating.
223
224 'replace_what' group of items to replace e.g.
225 {"farming:wheat_8", "farming:carrot_8"}
226 or you can use the specific options of what, with and
227 y offset by using this instead:
228 {
229 {"group:grass", "air", 0},
230 {"default:dirt_with_grass", "default:dirt", -1}
231 }
232 'replace_with' replace with what e.g. "air" or in chickens case "mobs:egg"
233 'replace_rate' how random should the replace rate be (typically 10)
234 'replace_offset' +/- value to check specific node to replace
235
236 'on_replace(self, pos, oldnode, newnode)' is called when mob is about to
237 replace a node.
238 'self' ObjectRef of mob
239 'pos' Position of node to replace
240 'oldnode' Current node
241 'newnode' What the node will become after replacing
242
243 If false is returned, the mob will not replace the node.
244
245 By default, replacing sets self.gotten to true and resets the object
246 properties.
247
248
249 Custom Definition Functions
250 ---------------------------
251
252 Along with the above mob registry settings we can also use custom functions to
253 enhance mob functionality and have them do many interesting things:
254
255 'on_die' a function that is called when the mob is killed the
256 parameters are (self, pos)
257 'on_rightclick' its same as in minetest.register_entity()
258 'on_blast' is called when an explosion happens near mob when using TNT
259 functions, parameters are (object, damage) and returns
260 (do_damage, do_knockback, drops)
261 'on_spawn' is a custom function that runs on mob spawn with 'self' as
262 variable, return true at end of function to run only once.
263 'after_activate' is a custom function that runs once mob has been activated
264 with these paramaters (self, staticdata, def, dtime)
265 'on_breed' called when two similar mobs breed, paramaters are
266 (parent1, parent2) objects, return false to stop child from
267 being resized and owner/tamed flags and child textures being
268 applied. Function itself must spawn new child mob.
269 'on_grown' is called when a child mob has grown up, only paramater is
270 (self).
271 'do_punch' called when mob is punched with paramaters (self, hitter,
272 time_from_last_punch, tool_capabilities, direction), return
273 false to stop punch damage and knockback from taking place.
274 '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)
277 'do_custom' a custom function that is called every tick while mob is
278 active and which has access to all of the self.* variables
279 e.g. (self.health for health or self.standing_in for node
280 status), return with 'false' to skip remainder of mob API.
281
282
283 Internal Variables
284 ------------------
285
286 The mob api also has some preset variables and functions that it will remember
287 for each mob.
288
289 'self.health' contains current health of mob (cannot exceed
290 self.hp_max)
291 'self.texture_list' contains list of all mob textures
292 'self.child_texture' contains mob child texture when growing up
293 'self.base_texture' contains current skin texture which was randomly
294 selected from textures list
295 'self.gotten' this is used for obtaining milk from cow and wool from
296 sheep
297 'self.horny' when animal fed enough it is set to true and animal can
298 breed with same animal
299 'self.hornytimer' background timer that controls breeding functions and
300 mob childhood timings
301 'self.child' used for when breeding animals have child, will use
302 child_texture and be half size
303 'self.owner' string used to set owner of npc mobs, typically used for
304 dogs
305 'self.order' set to "follow" or "stand" so that npc will follow owner
306 or stand it's ground
307 'self.nametag' contains the name of the mob which it can show above
308
309
310 Spawning Mobs in World
311 ----------------------
312
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.
321
322 'name' is the name of the animal/monster
323 'nodes' is a list of nodenames on that the animal/monster can
324 spawn on top of
325 '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()
333 '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
337 'day_toggle' true for day spawning, false for night or nil for
338 anytime
339 'on_spawn' is a custom function which runs after mob has spawned
340 and gives self and pos values.
341
342 A simpler way to handle mob spawns has been added with the mobs:spawn(def)
343 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 })
349
350
351 For each mob that spawns with this function is a field in mobs.spawning_mobs.
352 It tells if the mob should spawn or not. Default is true. So other mods can
353 only use the API of this mod by disabling the spawning of the default mobs in
354 this mod.
355
356
357 mobs:spawn_abm_check(pos, node, name)
358
359 This global function can be changed to contain additional checks for mobs to
360 spawn e.g. mobs that spawn only in specific areas and the like. By returning
361 true the mob will not spawn.
362
363 'pos' holds the position of the spawning mob
364 'node' contains the node the mob is spawning on top of
365 'name' is the name of the animal/monster
366
367
368 Making Arrows
369 -------------
370
371 mobs:register_arrow(name, definition)
372
373 This function registers a arrow for mobs with the attack type shoot.
374
375 'name' is the name of the arrow
376 'definition' is a table with the following values:
377 'visual' same is in minetest.register_entity()
378 'visual_size' same is in minetest.register_entity()
379 'textures' same is in minetest.register_entity()
380 'velocity' the velocity of the arrow
381 'drop' if set to true any arrows hitting a node will drop as item
382 'hit_player' a function that is called when the arrow hits a player;
383 this function should hurt the player, the parameters are
384 (self, player)
385 'hit_mob' a function that is called when the arrow hits a mob;
386 this function should hurt the mob, the parameters are
387 (self, player)
388 'hit_node' a function that is called when the arrow hits a node, the
389 parameters are (self, pos, node)
390 'tail' when set to 1 adds a trail or tail to mob arrows
391 'tail_texture' texture string used for above effect
392 'tail_size' has size for above texture (defaults to between 5 and 10)
393 'expire' contains float value for how long tail appears for
394 (defaults to 0.25)
395 'glow' has value for how brightly tail glows 1 to 10 (default is
396 0 for no glow)
397 'rotate' integer value in degrees to rotate arrow
398 'on_step' is a custom function when arrow is active, nil for
399 default.
400 '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.
402
403
404 Spawn Eggs
405 ----------
406
407 mobs:register_egg(name, description, background, addegg, no_creative)
408
409 This function registers a spawn egg which can be used by admin to properly spawn in a mob.
410
411 'name' this is the name of your new mob to spawn e.g. "mob:sheep"
412 'description' the name of the new egg you are creating e.g. "Spawn Sheep"
413 'background' the texture displayed for the egg in inventory
414 'addegg' would you like an egg image in front of your texture (1 = yes,
415 0 = no)
416 'no_creative' when set to true this stops spawn egg appearing in creative
417 mode for destructive mobs like Dungeon Masters.
418
419
420 Explosion Function
421 ------------------
422
423 mobs:explosion(pos, radius) -- DEPRECATED!!! use mobs:boom() instead
424
425 mobs:boom(self, pos, radius)
426 'self' mob entity
427 'pos' centre position of explosion
428 'radius' radius of explosion (typically set to 3)
429
430 This function generates an explosion which removes nodes in a specific radius
431 and damages any entity caught inside the blast radius. Protection will limit
432 node destruction but not entity damage.
433
434
435 Capturing Mobs
436 --------------
437
438 mobs:capture_mob(self, clicker, chance_hand, chance_net, chance_lasso,
439 force_take, replacewith)
440
441 This function is generally called inside the on_rightclick section of the mob
442 api code, it provides a chance of capturing the mob by hand, using the net or
443 lasso items, and can also have the player take the mob by force if tamed and
444 replace with another item entirely.
445
446 'self' mob information
447 'clicker' player information
448 'chance_hand' chance of capturing mob by hand (1 to 100) 0 to disable
449 'chance_net' chance of capturing mob using net (1 to 100) 0 to disable
450 'chance_lasso' chance of capturing mob using magic lasso (1 to 100) 0 to
451 disable
452 'force_take' take mob by force, even if tamed (true or false)
453 'replacewith' once captured replace mob with this item instead (overrides
454 new mob eggs with saved information)
455
456
457 Feeding and Taming/Breeding
458 ---------------------------
459
460 mobs:feed_tame(self, clicker, feed_count, breed, tame)
461
462 This function allows the mob to be fed the item inside self.follow be it apple,
463 wheat or whatever a set number of times and be tamed or bred as a result.
464 Will return true when mob is fed with item it likes.
465
466 'self' mob information
467 'clicker' player information
468 'feed_count' number of times mob must be fed to tame or breed
469 'breed' true or false stating if mob can be bred and a child created
470 afterwards
471 'tame' true or false stating if mob can be tamed so player can pick
472 them up
473
474
475 Protecting Mobs
476 ---------------
477
478 mobs:protect(self, clicker)
479
480 This function can be used to right-click any tamed mob with mobs:protector item,
481 this will protect the mob from harm inside of a protected area from other
482 players. Will return true when mob right-clicked with mobs:protector item.
483
484 'self' mob information
485 'clicker' player information
486
487
488 Riding Mobs
489 -----------
490
491 Mobs can now be ridden by players and the following shows its functions and
492 usage:
493
494
495 mobs:attach(self, player)
496
497 This function attaches a player to the mob so it can be ridden.
498
499 'self' mob information
500 'player' player information
501
502
503 mobs:detach(player, offset)
504
505 This function will detach the player currently riding a mob to an offset
506 position.
507
508 'player' player information
509 'offset' position table containing offset values
510
511
512 mobs:drive(self, move_animation, stand_animation, can_fly, dtime)
513
514 This function allows an attached player to move the mob around and animate it at
515 same time.
516
517 'self' mob information
518 'move_animation' string containing movement animation e.g. "walk"
519 'stand_animation' string containing standing animation e.g. "stand"
520 'can_fly' if true then jump and sneak controls will allow mob to fly
521 up and down
522 'dtime' tick time used inside drive function
523
524
525 mobs:fly(self, dtime, speed, can_shoot, arrow_entity, move_animation, stand_animation)
526
527 This function allows an attached player to fly the mob around using directional
528 controls.
529
530 'self' mob information
531 'dtime' tick time used inside fly function
532 'speed' speed of flight
533 'can_shoot' true if mob can fire arrow (sneak and left mouse button
534 fires)
535 'arrow_entity' name of arrow entity used for firing
536 'move_animation' string containing name of pre-defined animation e.g. "walk"
537 or "fly" etc.
538 'stand_animation' string containing name of pre-defined animation e.g.
539 "stand" or "blink" etc.
540
541 Note: animation names above are from the pre-defined animation lists inside mob
542 registry without extensions.
543
544
545 mobs:set_animation(self, name)
546
547 This function sets the current animation for mob, defaulting to "stand" if not
548 found.
549
550 'self' mob information
551 'name' name of animation
552
553
554 Certain variables need to be set before using the above functions:
555
556 'self.v2' toggle switch used to define below values for the
557 first time
558 'self.max_speed_forward' max speed mob can move forward
559 'self.max_speed_reverse' max speed mob can move backwards
560 'self.accel' acceleration speed
561 'self.terrain_type' integer containing terrain mob can walk on
562 (1 = water, 2 or 3 = land)
563 'self.driver_attach_at' position offset for attaching player to mob
564 'self.driver_eye_offset' position offset for attached player view
565 'self.driver_scale' sets driver scale for mobs larger than {x=1, y=1}
566
567
568 mobs:line_of_sight(self, pos1, pos2, stepsize)
569
570 This function is for use within the mobs definition for special use cases and
571 returns true if a mob can see the player or victim.
572
573 ...'self' mob information
574 'pos1' position of mob
575 'pos2' position of vistim or player
576 'stepsize' usually set to 1
577
578
579 External Settings for "minetest.conf"
580 ------------------------------------
581
582 'enable_damage' if true monsters will attack players (default is true)
583 'only_peaceful_mobs' if true only animals will spawn in game (default is
584 false)
585 'mobs_disable_blood' if false blood effects appear when mob is hit (default
586 is false)
587 'mobs_spawn_protected' if set to false then mobs will not spawn in protected
588 areas (default is true)
589 'remove_far_mobs' if true then untamed mobs that are outside players
590 visual range will be removed (default is true)
591 'mobname' can change specific mob chance rate (0 to disable) and
592 spawn number e.g. mobs_animal:cow = 1000,5
593 'mob_difficulty' sets difficulty level (health and hit damage
594 multiplied by this number), defaults to 1.0.
595 'mob_show_health' if false then punching mob will not show health status
596 (true by default)
597 'mob_chance_multiplier' multiplies chance of all mobs spawning and can be set
598 to 0.5 to have mobs spawn more or 2.0 to spawn less.
599 e.g. 1 in 7000 * 0.5 = 1 in 3500 so better odds of
600 spawning.
601 'mobs_spawn' if false then mobs no longer spawn without spawner or
602 spawn egg.
603 'mobs_drop_items' when false mobs no longer drop items when they die.
604 'mobs_griefing' when false mobs cannot break blocks when using either
605 pathfinding level 2, replace functions or mobs:boom
606 function.
607
608 Players can override the spawn chance for each mob registered by adding a line
609 to their minetest.conf file with a new value, the lower the value the more each
610 mob will spawn e.g.
611
612 mobs_animal:sheep_chance 11000
613 mobs_monster:sand_monster_chance 100
614
615
616 Rideable Horse Example Mob
617 --------------------------
618
619 mobs:register_mob("mob_horse:horse", {
620 type = "animal",
621 visual = "mesh",
622 visual_size = {x = 1.20, y = 1.20},
623 mesh = "mobs_horse.x",
624 collisionbox = {-0.4, -0.01, -0.4, 0.4, 1.25, 0.4},
625 animation = {
626 speed_normal = 15,
627 speed_run = 30,
628 stand_start = 25,
629 stand_end = 75,
630 walk_start = 75,
631 walk_end = 100,
632 run_start = 75,
633 run_end = 100,
634 },
635 textures = {
636 {"mobs_horse.png"},
637 {"mobs_horsepeg.png"},
638 {"mobs_horseara.png"}
639 },
640 fear_height = 3,
641 runaway = true,
642 fly = false,
643 walk_chance = 60,
644 view_range = 5,
645 follow = {"farming:wheat"},
646 passive = true,
647 hp_min = 12,
648 hp_max = 16,
649 armor = 200,
650 lava_damage = 5,
651 fall_damage = 5,
652 water_damage = 1,
653 makes_footstep_sound = true,
654 drops = {
655 {name = "mobs:meat_raw", chance = 1, min = 2, max = 3}
656 },
657 sounds = {
658 random = "horse_neigh.ogg",
659 damage = "horse_whinney.ogg",
660 },
661
662 do_custom = function(self, dtime)
663
664 -- set needed values if not already present
665 if not self.v2 then
666 self.v2 = 0
667 self.max_speed_forward = 6
668 self.max_speed_reverse = 2
669 self.accel = 6
670 self.terrain_type = 3
671 self.driver_attach_at = {x = 0, y = 20, z = -2}
672 self.driver_eye_offset = {x = 0, y = 3, z = 0}
673 self.driver_scale = {x = 1, y = 1}
674 end
675
676 -- if driver present allow control of horse
677 if self.driver then
678
679 mobs.drive(self, "walk", "stand", false, dtime)
680
681 return false -- skip rest of mob functions
682 end
683
684 return true
685 end,
686
687 on_die = function(self, pos)
688
689 -- drop saddle when horse is killed while riding
690 -- also detach from horse properly
691 if self.driver then
692 minetest.add_item(pos, "mobs:saddle")
693 mobs.detach(self.driver, {x = 1, y = 0, z = 1})
694 end
695
696 end,
697
698 on_rightclick = function(self, clicker)
699
700 -- make sure player is clicking
701 if not clicker or not clicker:is_player() then
702 return
703 end
704
705 -- feed, tame or heal horse
706 if mobs:feed_tame(self, clicker, 10, true, true) then
707 return
708 end
709
710 -- make sure tamed horse is being clicked by owner only
711 if self.tamed and self.owner == clicker:get_player_name() then
712
713 local inv = clicker:get_inventory()
714
715 -- detatch player already riding horse
716 if self.driver and clicker == self.driver then
717
718 mobs.detach(clicker, {x = 1, y = 0, z = 1})
719
720 -- add saddle back to inventory
721 if inv:room_for_item("main", "mobs:saddle") then
722 inv:add_item("main", "mobs:saddle")
723 else
724 minetest.add_item(clicker.getpos(), "mobs:saddle")
725 end
726
727 -- attach player to horse
728 elseif not self.driver
729 and clicker:get_wielded_item():get_name() == "mobs:saddle" then
730
731 self.object:set_properties({stepheight = 1.1})
732 mobs.attach(self, clicker)
733
734 -- take saddle from inventory
735 inv:remove_item("main", "mobs:saddle")
736 end
737 end
738
739 -- used to capture horse with magic lasso
740 mobs:capture_mob(self, clicker, 0, 0, 80, false, nil)
741 end
742 })
0
1 local S = mobs.intllib
2
3 -- name tag
4 minetest.register_craftitem("mobs:nametag", {
5 description = S("Name Tag"),
6 inventory_image = "mobs_nametag.png",
7 groups = {flammable = 2},
8 })
9
10 if minetest.get_modpath("dye") and minetest.get_modpath("farming") then
11 minetest.register_craft({
12 type = "shapeless",
13 output = "mobs:nametag",
14 recipe = {"default:paper", "dye:black", "farming:string"},
15 })
16 end
17
18 -- leather
19 minetest.register_craftitem("mobs:leather", {
20 description = S("Leather"),
21 inventory_image = "mobs_leather.png",
22 groups = {flammable = 2},
23 })
24
25 -- raw meat
26 minetest.register_craftitem("mobs:meat_raw", {
27 description = S("Raw Meat"),
28 inventory_image = "mobs_meat_raw.png",
29 on_use = minetest.item_eat(3),
30 groups = {food_meat_raw = 1, flammable = 2},
31 })
32
33 -- cooked meat
34 minetest.register_craftitem("mobs:meat", {
35 description = S("Meat"),
36 inventory_image = "mobs_meat.png",
37 on_use = minetest.item_eat(8),
38 groups = {food_meat = 1, flammable = 2},
39 })
40
41 minetest.register_craft({
42 type = "cooking",
43 output = "mobs:meat",
44 recipe = "mobs:meat_raw",
45 cooktime = 5,
46 })
47
48 -- lasso
49 minetest.register_tool("mobs:lasso", {
50 description = S("Lasso (right-click animal to put in inventory)"),
51 inventory_image = "mobs_magic_lasso.png",
52 groups = {flammable = 2},
53 })
54
55 if minetest.get_modpath("farming") then
56 minetest.register_craft({
57 output = "mobs:lasso",
58 recipe = {
59 {"farming:string", "", "farming:string"},
60 {"", "default:diamond", ""},
61 {"farming:string", "", "farming:string"},
62 }
63 })
64 end
65
66 minetest.register_alias("mobs:magic_lasso", "mobs:lasso")
67
68 -- net
69 minetest.register_tool("mobs:net", {
70 description = S("Net (right-click animal to put in inventory)"),
71 inventory_image = "mobs_net.png",
72 groups = {flammable = 2},
73 })
74
75 if minetest.get_modpath("farming") then
76 minetest.register_craft({
77 output = "mobs:net",
78 recipe = {
79 {"group:stick", "", "group:stick"},
80 {"group:stick", "", "group:stick"},
81 {"farming:string", "group:stick", "farming:string"},
82 }
83 })
84 end
85
86 -- shears (right click to shear animal)
87 minetest.register_tool("mobs:shears", {
88 description = S("Steel Shears (right-click to shear)"),
89 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'},
98 }
99 })
100
101 -- protection rune
102 minetest.register_craftitem("mobs:protector", {
103 description = S("Mob Protection Rune"),
104 inventory_image = "mobs_protector.png",
105 groups = {flammable = 2},
106 })
107
108 minetest.register_craft({
109 output = "mobs:protector",
110 recipe = {
111 {"default:stone", "default:stone", "default:stone"},
112 {"default:stone", "default:goldblock", "default:stone"},
113 {"default:stone", "default:stone", "default:stone"},
114 }
115 })
116
117 -- saddle
118 minetest.register_craftitem("mobs:saddle", {
119 description = S("Saddle"),
120 inventory_image = "mobs_saddle.png",
121 groups = {flammable = 2},
122 })
123
124 minetest.register_craft({
125 output = "mobs:saddle",
126 recipe = {
127 {"mobs:leather", "mobs:leather", "mobs:leather"},
128 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
129 {"mobs:leather", "default:steel_ingot", "mobs:leather"},
130 }
131 })
132
133 -- mob fence (looks like normal fence but collision is 2 high)
134 default.register_fence("mobs:fence_wood", {
135 description = S("Mob Fence"),
136 texture = "default_wood.png",
137 material = "default:fence_wood",
138 groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
139 sounds = default.node_sound_wood_defaults(),
140 collision_box = {
141 type = "fixed",
142 fixed = {
143 {-0.5, -0.5, -0.5, 0.5, 1.9, 0.5},
144 },
145 },
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 },
169 })
170
171 minetest.register_craft({
172 output = "mobs:fence_top 12",
173 recipe = {
174 {"group:wood", "group:wood", "group:wood"},
175 {"", "default:fence_wood", ""},
176 }
177 })
178
179 -- items that can be used as fuel
180 minetest.register_craft({
181 type = "fuel",
182 recipe = "mobs:nametag",
183 burntime = 3,
184 })
185
186 minetest.register_craft({
187 type = "fuel",
188 recipe = "mobs:lasso",
189 burntime = 7,
190 })
191
192 minetest.register_craft({
193 type = "fuel",
194 recipe = "mobs:net",
195 burntime = 8,
196 })
197
198 minetest.register_craft({
199 type = "fuel",
200 recipe = "mobs:leather",
201 burntime = 4,
202 })
203
204 minetest.register_craft({
205 type = "fuel",
206 recipe = "mobs:saddle",
207 burntime = 7,
208 })
209
210 minetest.register_craft({
211 type = "fuel",
212 recipe = "mobs:fence_wood",
213 burntime = 7,
214 })
215
216 minetest.register_craft({
217 type = "fuel",
218 recipe = "mobs:fence_top",
219 burntime = 2,
220 })
0 default
1 tnt?
2 dye?
3 farming?
4 invisibility?
5 intllib?
6 lucky_block?
7 cmi?
8 toolranks?
0 Adds a mob api for mods to add animals or monsters etc.
0
1 local path = minetest.get_modpath("mobs")
2
3 -- Mob API
4 dofile(path .. "/api.lua")
5
6 -- Rideable Mobs
7 dofile(path .. "/mount.lua")
8
9 -- Mob Items
10 dofile(path .. "/crafts.lua")
11
12 -- Mob Spawner
13 dofile(path .. "/spawner.lua")
14
15 -- Lucky Blocks
16 dofile(path .. "/lucky_block.lua")
17
18 minetest.log("action", "[MOD] Mobs Redo loaded")
0
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
0 The MIT License (MIT)
1
2 Copyright (c) 2016 TenPlus1
3
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
10
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
13
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
0 # Mobs Redo translation.
1 # Copyright (C) 2017 TenPlus1
2 # This file is distributed under the same license as the mobs package.
3 # Wuzzy <Wuzzy@mail.ru>, 2017
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: mobs\n"
8 "Report-Msgid-Bugs-To: \n"
9 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
10 "PO-Revision-Date: 2017-07-02 14:27+0200\n"
11 "Last-Translator: Wuzzy <almikes@aol.com>\n"
12 "Language-Team: \n"
13 "Language: de_DE\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17 "X-Generator: Poedit 2.0.2\n"
18 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr ""
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr "Kreatur wurde geschützt!"
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr "@1 (Gezähmt)"
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Nicht gezähmt!"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "@1 ist der Besitzer!"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Daneben!"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr "Bereits geschützt!"
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "@1 bei voller Gesundheit (@2)"
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 wurde gezähmt!"
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "Namen eingeben:"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Umbenennen"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "Namensschild"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Leder"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Rohes Fleisch"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Fleisch"
79
80 #: crafts.lua
81 msgid "Lasso (right-click animal to put in inventory)"
82 msgstr "Lasso (Rechtsklick auf Tier, um es zu nehmen)"
83
84 #: crafts.lua
85 msgid "Net (right-click animal to put in inventory)"
86 msgstr "Netz (Rechtsklick auf Tier, um es zu nehmen)"
87
88 #: crafts.lua
89 msgid "Steel Shears (right-click to shear)"
90 msgstr "Stahlschere (Rechtsklick zum Scheren)"
91
92 #: crafts.lua
93 msgid "Mob Protection Rune"
94 msgstr "Kreaturschutzrune"
95
96 #: crafts.lua
97 msgid "Saddle"
98 msgstr "Sattel"
99
100 #: crafts.lua
101 msgid "Mob Fence"
102 msgstr "Kreaturen Zaun"
103
104 #: spawner.lua
105 msgid "Mob Spawner"
106 msgstr "Kreaturenspawner"
107
108 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr "Kreatur MinLicht MaxLicht Menge SpielerEntfng"
111
112 #: spawner.lua
113 msgid "Spawner Not Active (enter settings)"
114 msgstr "Nicht aktiv (Einstellungen eingeben)"
115
116 #: spawner.lua
117 msgid "Spawner Active (@1)"
118 msgstr "Spawner aktiv (@1)"
119
120 #: spawner.lua
121 msgid "Mob Spawner settings failed!"
122 msgstr "Kreaturenspawner-Einstellungen gescheitert!"
123
124 #: spawner.lua
125 msgid ""
126 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
127 "distance[1-20] y_offset[-10 to 10]”"
128 msgstr ""
129 "Syntax: „name min_licht[0-14] max_licht[0-14] max_mobs_im_gebiet[0 zum "
130 "Deaktivieren] distanz[1-20] y_versatz[-10 bis 10]“"
0 # Mobs Redo translation.
1 # Copyright (C) 2017 TenPlus1
2 # This file is distributed under the same license as the mobs package.
3 # Wuzzy <Wuzzy@mail.ru>, 2017
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: PACKAGE VERSION\n"
8 "Report-Msgid-Bugs-To: \n"
9 "POT-Creation-Date: 2017-07-16 16:48+0200\n"
10 "PO-Revision-Date: 2017-07-16 16:48+0200\n"
11 "Last-Translator: Aleks <alexsinteck@icqmail.com>\n"
12 "Language-Team: \n"
13 "Language: es\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17
18 #: api.lua
19 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
20 msgstr ""
21
22 #: api.lua
23 msgid "Mob has been protected!"
24 msgstr "El mob ha sido protegido!"
25
26 #: api.lua
27 msgid "@1 (Tamed)"
28 msgstr "@1 (Domesticado)"
29
30 #: api.lua
31 msgid "Not tamed!"
32 msgstr "No domesticado!"
33
34 #: api.lua
35 msgid "@1 is owner!"
36 msgstr "@1 es el dueño!"
37
38 #: api.lua
39 msgid "Missed!"
40 msgstr "Perdido!"
41
42 #: api.lua
43 msgid "Already protected!"
44 msgstr "Ya está protegido!"
45
46 #: api.lua
47 msgid "@1 at full health (@2)"
48 msgstr "@1 con salud llena (@2)"
49
50 #: api.lua
51 msgid "@1 has been tamed!"
52 msgstr "@1 ha sido domesticado!"
53
54 #: api.lua
55 msgid "Enter name:"
56 msgstr "Ingrese nombre:"
57
58 #: api.lua
59 msgid "Rename"
60 msgstr "Renombrar"
61
62 #: crafts.lua
63 msgid "Name Tag"
64 msgstr "Nombrar etiqueta"
65
66 #: crafts.lua
67 msgid "Leather"
68 msgstr "Cuero"
69
70 #: crafts.lua
71 msgid "Raw Meat"
72 msgstr "Carne cruda"
73
74 #: crafts.lua
75 msgid "Meat"
76 msgstr "Carne"
77
78 #: crafts.lua
79 msgid "Lasso (right-click animal to put in inventory)"
80 msgstr "Lazo (click derecho en animal para colocar en inventario)"
81
82 #: crafts.lua
83 msgid "Net (right-click animal to put in inventory)"
84 msgstr "Red (click derecho en animal para colocar en inventario)"
85
86 #: crafts.lua
87 msgid "Steel Shears (right-click to shear)"
88 msgstr "Tijera de acero (click derecho para esquilar)"
89
90 #: crafts.lua
91 msgid "Mob Protection Rune"
92 msgstr "Runa de protección de Mob"
93
94 #: crafts.lua
95 msgid "Saddle"
96 msgstr "Montura"
97
98 #: crafts.lua
99 msgid "Mob Fence"
100 msgstr ""
101
102 #: spawner.lua
103 msgid "Mob Spawner"
104 msgstr "Generador de Mob"
105
106 #: spawner.lua
107 msgid "Mob MinLight MaxLight Amount PlayerDist"
108 msgstr "Mob LuzMin LuzMax Cantidad DistJugador"
109
110 #: spawner.lua
111 msgid "Spawner Not Active (enter settings)"
112 msgstr "Generador no activo (ingrese config)"
113
114 #: spawner.lua
115 msgid "Spawner Active (@1)"
116 msgstr "Generador activo (@1)"
117
118 #: spawner.lua
119 msgid "Mob Spawner settings failed!"
120 msgstr "Configuracion de generador de Mob falló!"
121
122 #: spawner.lua
123 msgid ""
124 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
125 "distance[1-20] y_offset[-10 to 10]”"
126 msgstr "Sintaxis: “nombre luz_min[0-14] luz_max[0-14] max_mobs_en_area[0 para deshabilitar] "
127 "distancia[1-20] compensacion[-10 a 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 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: \n"
8 "Report-Msgid-Bugs-To: \n"
9 "POT-Creation-Date: 2017-07-29 09:13+0200\n"
10 "PO-Revision-Date: 2017-07-29 09:20+0200\n"
11 "Language-Team: \n"
12 "MIME-Version: 1.0\n"
13 "Content-Type: text/plain; charset=UTF-8\n"
14 "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"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr "** Mode pacifique activé - Aucun monstre ne sera généré"
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr "L'animal a été protégé !"
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr "@1 (apprivoisé)"
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Non-apprivoisé !"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "Appartient à @1 !"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Raté !"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr "Déjà protégé !"
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "@1 est en pleine forme (@2) "
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 a été apprivoisé ! "
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "Saisissez un nom :"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Renommer"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "Étiquette pour collier"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Cuir"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Viande crue"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Viande"
79
80 #: crafts.lua
81 msgid "Lasso (right-click animal to put in inventory)"
82 msgstr "Lasso (clic droit sur l'animal pour le mettre dans l'inventaire)"
83
84 #: crafts.lua
85 msgid "Net (right-click animal to put in inventory)"
86 msgstr "Filet (clic droit sur l'animal pour le mettre dans l'inventaire)"
87
88 #: crafts.lua
89 msgid "Steel Shears (right-click to shear)"
90 msgstr "Ciseaux à laine (clic droit pour tondre)"
91
92 #: crafts.lua
93 msgid "Mob Protection Rune"
94 msgstr "Rune de protection des animaux"
95
96 #: crafts.lua
97 msgid "Saddle"
98 msgstr "Selle"
99
100 #: crafts.lua
101 msgid "Mob Fence"
102 msgstr "Clôture à animaux"
103
104 #: spawner.lua
105 msgid "Mob Spawner"
106 msgstr "Générateur de mob"
107
108 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr "Mob MinLumière MaxLumière Quantité DistanceJoueur"
111
112 #: spawner.lua
113 msgid "Spawner Not Active (enter settings)"
114 msgstr "Générateur non actif (entrez les paramètres)"
115
116 #: spawner.lua
117 msgid "Spawner Active (@1)"
118 msgstr "Générateur actif (@1)"
119
120 #: spawner.lua
121 msgid "Mob Spawner settings failed!"
122 msgstr "Echec des paramètres du générateur"
123
124 #: spawner.lua
125 msgid ""
126 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
127 "distance[1-20] y_offset[-10 to 10]”"
128 msgstr "Syntaxe : “nom min_lumière[0-14] max_lumière[0-14] max_mobs_dans_zone[0 pour désactiver] distance[1-20] décalage_y[-10 à 10]“"
0 # ITALIAN LOCALE FILE FOR THE MOBS REDO MODULE
1 # Copyright (c) 2014 Krupnov Pavel and 2016 TenPlus1
2 # This file is distributed under the same license as the MOBS REDO package.
3 # Hamlet <h4mlet@riseup.net>, 2017.
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: Italian locale file for the Mobs Redo module\n"
8 "Report-Msgid-Bugs-To: \n"
9 "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"
12 "Language-Team: \n"
13 "Language: it\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
18 "X-Generator: Poedit 1.6.10\n"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr ""
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr "Il mob è stato protetto!"
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr "@1 (Addomesticat*)"
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Non addomesticat*!"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "Proprietari* @1!"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Mancat*!"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr "Già protett*!"
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "@1 in piena salute (@2)"
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 è stat* addomesticat*!"
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "Inserire il nome:"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Rinominare"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "Targhetta"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Pelle"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Carne cruda"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Carne"
79
80 #: crafts.lua
81 msgid "Lasso (right-click animal to put in inventory)"
82 msgstr "Lazo (click di destro per mettere l'animale nell'inventario)"
83
84 #: crafts.lua
85 msgid "Net (right-click animal to put in inventory)"
86 msgstr "Rete (click destro per mettere l'animale nell'inventario)"
87
88 #: crafts.lua
89 msgid "Steel Shears (right-click to shear)"
90 msgstr "Cesoie d'acciaio (click destro per tosare)"
91
92 #: crafts.lua
93 msgid "Mob Protection Rune"
94 msgstr "Runa di protezione per mob"
95
96 #: crafts.lua
97 msgid "Saddle"
98 msgstr "Sella"
99
100 #: crafts.lua
101 msgid "Mob Fence"
102 msgstr ""
103
104 #: spawner.lua
105 msgid "Mob Spawner"
106 msgstr "Generatore di mob"
107
108 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr "Mob LuceMin LuceMax Ammontare DistGiocat."
111
112 #: spawner.lua
113 msgid "Spawner Not Active (enter settings)"
114 msgstr "Generatore inattivo (inserire le impostazioni)"
115
116 #: spawner.lua
117 msgid "Spawner Active (@1)"
118 msgstr "Generatore attivo (@1)"
119
120 #: spawner.lua
121 msgid "Mob Spawner settings failed!"
122 msgstr "Impostazioni del generatore di mob fallite!"
123
124 #: spawner.lua
125 msgid ""
126 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
127 "distance[1-20] y_offset[-10 to 10]”"
128 msgstr ""
129 "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]”"
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 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: \n"
8 "Report-Msgid-Bugs-To: \n"
9 "POT-Creation-Date: 2018-02-05 23:40+0800\n"
10 "PO-Revision-Date: 2018-02-05 23:51+0800\n"
11 "Language-Team: \n"
12 "MIME-Version: 1.0\n"
13 "Content-Type: text/plain; charset=UTF-8\n"
14 "Content-Transfer-Encoding: 8bit\n"
15 "X-Generator: Poedit 2.0.6\n"
16 "Last-Translator: MuhdNurHidayat (MNH48) <mnh48mail@gmail.com>\n"
17 "Plural-Forms: nplurals=1; plural=0;\n"
18 "Language: ms\n"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr "** Mod Aman Diaktifkan - Tiada Raksasa Akan Muncul"
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr "Mob telah pun dilindungi!"
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr "@1 (Jinak)"
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Belum dijinakkan!"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "Ini hak milik @1!"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Terlepas!"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr "Telah dilindungi!"
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "Mata kesihatan @1 telah penuh (@2)"
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 telah dijinakkan!"
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "Masukkan nama:"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Namakan semula"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "Tanda Nama"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Kulit"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Daging Mentah"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Daging Bakar"
79
80 #: crafts.lua
81 msgid "Lasso (right-click animal to put in inventory)"
82 msgstr "Tanjul (klik-kanan haiwan untuk masukkan ke inventori)"
83
84 #: crafts.lua
85 msgid "Net (right-click animal to put in inventory)"
86 msgstr "Jaring (klik-kanan haiwan untuk masukkan ke inventori)"
87
88 #: crafts.lua
89 msgid "Steel Shears (right-click to shear)"
90 msgstr "Ketam Keluli (klik-kanan untuk mengetam bulu biri-biri)"
91
92 #: crafts.lua
93 msgid "Mob Protection Rune"
94 msgstr "Rune Perlindungan Mob"
95
96 #: crafts.lua
97 msgid "Saddle"
98 msgstr "Pelana"
99
100 #: crafts.lua
101 msgid "Mob Fence"
102 msgstr "Pagar Mob"
103
104 #: spawner.lua
105 msgid "Mob Spawner"
106 msgstr "Pewujud Mob"
107
108 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr "Mob CahayaMin CahayaMax Amaun JarakPemain"
111
112 #: spawner.lua
113 msgid "Spawner Not Active (enter settings)"
114 msgstr "Pewujud Mob Tidak Aktif (masukkan tetapan)"
115
116 #: spawner.lua
117 msgid "Spawner Active (@1)"
118 msgstr "Pewujud Mob Aktif (@1)"
119
120 #: spawner.lua
121 msgid "Mob Spawner settings failed!"
122 msgstr "Penetapan Pewujud Mob gagal!"
123
124 #: spawner.lua
125 msgid ""
126 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
127 "distance[1-20] y_offset[-10 to 10]”"
128 msgstr ""
129 "Sintaks: \"nama cahaya_minimum[0-14] cahaya_maksimum[0-14] "
130 "amaun_mob_maksimum[0 untuk lumpuhkan] jarak[1-20] ketinggian[-10 hingga 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 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: mobs\n"
8 "Report-Msgid-Bugs-To: \n"
9 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
10 "PO-Revision-Date: 2017-07-02 14:55+0200\n"
11 "Last-Translator: Wuzzy <almikes@aol.com>\n"
12 "Language-Team: \n"
13 "Language: pt\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17 "X-Generator: Poedit 2.0.2\n"
18 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr ""
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr ""
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr ""
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Indomesticado!"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "Dono @1!"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Faltou!"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr ""
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "@1 em plena saude (@2)"
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 foi domesticado!"
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "Insira um nome:"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Renomear"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "Etiqueta"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Couro"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Carne crua"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Carne"
79
80 #: crafts.lua
81 #, fuzzy
82 msgid "Lasso (right-click animal to put in inventory)"
83 msgstr "Laço (clique-direito no animal para por no inventario)"
84
85 #: crafts.lua
86 msgid "Net (right-click animal to put in inventory)"
87 msgstr "Net (clique-direito no animal para por no inventario)"
88
89 #: crafts.lua
90 msgid "Steel Shears (right-click to shear)"
91 msgstr "Tesoura de Aço (clique-direito para tosquiar)"
92
93 #: crafts.lua
94 msgid "Mob Protection Rune"
95 msgstr ""
96
97 #: crafts.lua
98 msgid "Saddle"
99 msgstr ""
100
101 #: crafts.lua
102 msgid "Mob Fence"
103 msgstr ""
104
105 #: spawner.lua
106 msgid "Mob Spawner"
107 msgstr "Spawnador de Mob"
108
109 #: spawner.lua
110 msgid "Mob MinLight MaxLight Amount PlayerDist"
111 msgstr "Mob LuzMinima LuzMaxima Valor DistJogador"
112
113 #: spawner.lua
114 msgid "Spawner Not Active (enter settings)"
115 msgstr "Spawnador Inativo (configurar)"
116
117 #: spawner.lua
118 msgid "Spawner Active (@1)"
119 msgstr "Spawnador Ativo (@1)"
120
121 #: spawner.lua
122 msgid "Mob Spawner settings failed!"
123 msgstr "Configuraçao de Spawnador do Mob falhou!"
124
125 #: spawner.lua
126 #, fuzzy
127 msgid ""
128 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
129 "distance[1-20] y_offset[-10 to 10]”"
130 msgstr ""
131 "> nome luz_min[0-14] luz_max[0-14] max_mobs_na_area[0 para desabilitar] "
132 "distancia[1-20] y_offset[-10 a 10]"
0 # Russian translation for the mobs_redo mod.
1 # Copyright (C) 2018 TenPlus1
2 # This file is distributed under the same license as the mobs_redo package.
3 # Oleg720 <olegsiriak@yandex.ru>, 2017.
4 # CodeXP <codexp@gmx.net>, 2018.
5 #
6 #, fuzzy
7 msgid ""
8 msgstr ""
9 "Project-Id-Version: PACKAGE VERSION\n"
10 "Report-Msgid-Bugs-To: \n"
11 "POT-Creation-Date: 2017-08-13 15:47+0200\n"
12 "PO-Revision-Date: 2018-03-23 22:22+0100\n"
13 "Last-Translator: CodeXP <codexp@gmx.net>\n"
14 "Language-Team: \n"
15 "Language: ru\n"
16 "MIME-Version: 1.0\n"
17 "Content-Type: text/plain; charset=UTF-8\n"
18 "Content-Transfer-Encoding: 8bit\n"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr "** Мирный модус активирован - монстры не спаунятся"
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr "Моб защищен!"
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr "@1 (Прирученный)"
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Не прирученный"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "@1 владелец"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Промазал!"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr "Уже защищен!"
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "@1 при полном здоровье (@2)"
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 приручен"
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "Введите имя:"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Переименовать"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "Новый тэг"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Кожа"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Сырое мясо"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Мясо"
79
80 #: crafts.lua
81 msgid "Lasso (right-click animal to put in inventory)"
82 msgstr "Лассо (Правый клик - положить животное в инвентарь)"
83
84 #: crafts.lua
85 msgid "Net (right-click animal to put in inventory)"
86 msgstr "Сеть (Правый клик - положить животное в инвентарь)"
87
88 #: crafts.lua
89 msgid "Steel Shears (right-click to shear)"
90 msgstr "Ножницы (Правый клик - подстричь)"
91
92 #: crafts.lua
93 msgid "Mob Protection Rune"
94 msgstr "Защитная руна мобов"
95
96 #: crafts.lua
97 msgid "Saddle"
98 msgstr "Седло"
99
100 #: crafts.lua
101 msgid "Mob Fence"
102 msgstr "Забор от мобов"
103
104 #: spawner.lua
105 msgid "Mob Spawner"
106 msgstr "Спаунер моба"
107
108 #: spawner.lua
109 msgid "Mob MinLight MaxLight Amount PlayerDist"
110 msgstr ""
111
112 #: spawner.lua
113 msgid "Spawner Not Active (enter settings)"
114 msgstr "Спаунер не активен (введите настройки)"
115
116 #: spawner.lua
117 msgid "Spawner Active (@1)"
118 msgstr "Активные спаунер (@1)"
119
120 #: spawner.lua
121 msgid "Mob Spawner settings failed!"
122 msgstr "Настройки спаунера моба провалились"
123
124 #: spawner.lua
125 msgid ""
126 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
127 "distance[1-20] y_offset[-10 to 10]”"
128 msgstr ""
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 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
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 ""
26
27 #: api.lua
28 msgid "@1 (Tamed)"
29 msgstr ""
30
31 #: api.lua
32 msgid "Not tamed!"
33 msgstr ""
34
35 #: api.lua
36 msgid "@1 is owner!"
37 msgstr ""
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 ""
50
51 #: api.lua
52 msgid "@1 has been tamed!"
53 msgstr ""
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 ""
94
95 #: crafts.lua
96 msgid "Saddle"
97 msgstr ""
98
99 #: crafts.lua
100 msgid "Mob Fence"
101 msgstr ""
102
103 #: spawner.lua
104 msgid "Mob Spawner"
105 msgstr ""
106
107 #: spawner.lua
108 msgid "Mob MinLight MaxLight Amount PlayerDist"
109 msgstr ""
110
111 #: spawner.lua
112 msgid "Spawner Not Active (enter settings)"
113 msgstr ""
114
115 #: spawner.lua
116 msgid "Spawner Active (@1)"
117 msgstr ""
118
119 #: spawner.lua
120 msgid "Mob Spawner settings failed!"
121 msgstr ""
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 ""
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 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 #
5 msgid ""
6 msgstr ""
7 "Project-Id-Version: mobs\n"
8 "Report-Msgid-Bugs-To: \n"
9 "POT-Creation-Date: 2017-07-02 16:48+0200\n"
10 "PO-Revision-Date: 2017-07-02 14:56+0200\n"
11 "Last-Translator: Wuzzy <almikes@aol.com>\n"
12 "Language-Team: \n"
13 "Language: tr\n"
14 "MIME-Version: 1.0\n"
15 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Transfer-Encoding: 8bit\n"
17 "X-Generator: Poedit 2.0.2\n"
18 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
20 #: api.lua
21 msgid "** Peaceful Mode Active - No Monsters Will Spawn"
22 msgstr ""
23
24 #: api.lua
25 msgid "Mob has been protected!"
26 msgstr ""
27
28 #: api.lua
29 msgid "@1 (Tamed)"
30 msgstr ""
31
32 #: api.lua
33 msgid "Not tamed!"
34 msgstr "Evcil değil!"
35
36 #: api.lua
37 msgid "@1 is owner!"
38 msgstr "Sahibi @1!"
39
40 #: api.lua
41 msgid "Missed!"
42 msgstr "Kaçırdın!"
43
44 #: api.lua
45 msgid "Already protected!"
46 msgstr ""
47
48 #: api.lua
49 msgid "@1 at full health (@2)"
50 msgstr "@1 tam canında (@2)"
51
52 #: api.lua
53 msgid "@1 has been tamed!"
54 msgstr "@1 tamamen evcilleştirilmiştir!"
55
56 #: api.lua
57 msgid "Enter name:"
58 msgstr "İsim gir:"
59
60 #: api.lua
61 msgid "Rename"
62 msgstr "Yeniden adlandır"
63
64 #: crafts.lua
65 msgid "Name Tag"
66 msgstr "İsim etiketi"
67
68 #: crafts.lua
69 msgid "Leather"
70 msgstr "Deri"
71
72 #: crafts.lua
73 msgid "Raw Meat"
74 msgstr "Çiğ et"
75
76 #: crafts.lua
77 msgid "Meat"
78 msgstr "Et"
79
80 #: crafts.lua
81 #, fuzzy
82 msgid "Lasso (right-click animal to put in inventory)"
83 msgstr "Kement (hayvana sağ tıklayarak envantere koy)"
84
85 #: crafts.lua
86 msgid "Net (right-click animal to put in inventory)"
87 msgstr "Ağ (hayvana sağ tıklayarak envantere koy)"
88
89 #: crafts.lua
90 msgid "Steel Shears (right-click to shear)"
91 msgstr "Çelik makas (sağ tıklayarak kes)"
92
93 #: crafts.lua
94 msgid "Mob Protection Rune"
95 msgstr ""
96
97 #: crafts.lua
98 msgid "Saddle"
99 msgstr ""
100
101 #: crafts.lua
102 msgid "Mob Fence"
103 msgstr "Canavar Yaratıcı"
104
105 #: spawner.lua
106 msgid "Mob Spawner"
107 msgstr "Canavar Yaratıcı"
108
109 #: spawner.lua
110 msgid "Mob MinLight MaxLight Amount PlayerDist"
111 msgstr "Mob MinIşık MaxIşık Miktar OyuncuMesafesi"
112
113 #: spawner.lua
114 msgid "Spawner Not Active (enter settings)"
115 msgstr "Yaratıcı aktif değil (ayarlara gir)"
116
117 #: spawner.lua
118 msgid "Spawner Active (@1)"
119 msgstr "Yaratıcı aktif (@1)"
120
121 #: spawner.lua
122 msgid "Mob Spawner settings failed!"
123 msgstr "Yaratıcı ayarları uygulanamadı."
124
125 #: spawner.lua
126 #, fuzzy
127 msgid ""
128 "Syntax: “name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] "
129 "distance[1-20] y_offset[-10 to 10]”"
130 msgstr ""
131 "> isim min_isik[0-14] max_isik[0-14] alandaki_max_canavar_sayisi[kapatmak "
132 "icin 0] mesafe[1-20] y_cikinti[-10 ve 10 arası]"
0
1 if minetest.get_modpath("lucky_block") then
2
3 lucky_block:add_blocks({
4 {"dro", {"mobs:meat_raw"}, 5},
5 {"dro", {"mobs:meat"}, 5},
6 {"dro", {"mobs:nametag"}, 1},
7 {"dro", {"mobs:leather"}, 5},
8 {"dro", {"default:stick"}, 10},
9 {"dro", {"mobs:net"}, 1},
10 {"dro", {"mobs:lasso"}, 1},
11 {"dro", {"mobs:shears"}, 1},
12 {"dro", {"mobs:protector"}, 1},
13 {"dro", {"mobs:fence_wood"}, 10},
14 {"dro", {"mobs:fence_top"}, 12},
15 {"lig"},
16 })
17 end
0 name = mobs
0
1 -- lib_mount by Blert2112 (edited by TenPlus1)
2
3 local enable_crash = false
4 local crash_threshold = 6.5 -- ignored if enable_crash=false
5
6 ------------------------------------------------------------------------------
7
8 --
9 -- Helper functions
10 --
11
12 local node_ok = function(pos, fallback)
13
14 fallback = fallback or mobs.fallback_node
15
16 local node = minetest.get_node_or_nil(pos)
17
18 if node and minetest.registered_nodes[node.name] then
19 return node
20 end
21
22 return {name = fallback}
23 end
24
25
26 local function node_is(pos)
27
28 local node = node_ok(pos)
29
30 if node.name == "air" then
31 return "air"
32 end
33
34 if minetest.get_item_group(node.name, "lava") ~= 0 then
35 return "lava"
36 end
37
38 if minetest.get_item_group(node.name, "liquid") ~= 0 then
39 return "liquid"
40 end
41
42 if minetest.registered_nodes[node.name].walkable == true then
43 return "walkable"
44 end
45
46 return "other"
47 end
48
49
50 local function get_sign(i)
51
52 i = i or 0
53
54 if i == 0 then
55 return 0
56 else
57 return i / math.abs(i)
58 end
59 end
60
61
62 local function get_velocity(v, yaw, y)
63
64 local x = -math.sin(yaw) * v
65 local z = math.cos(yaw) * v
66
67 return {x = x, y = y, z = z}
68 end
69
70
71 local function get_v(v)
72 return math.sqrt(v.x * v.x + v.z * v.z)
73 end
74
75
76 local function force_detach(player)
77
78 local attached_to = player:get_attach()
79
80 if not attached_to then
81 return
82 end
83
84 local entity = attached_to:get_luaentity()
85
86 if entity.driver
87 and entity.driver == player then
88
89 entity.driver = nil
90 end
91
92 player:set_detach()
93 default.player_attached[player:get_player_name()] = false
94 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} })
97
98 end
99
100 -------------------------------------------------------------------------------
101
102
103 minetest.register_on_leaveplayer(function(player)
104 force_detach(player)
105 end)
106
107 minetest.register_on_shutdown(function()
108 local players = minetest.get_connected_players()
109 for i = 1, #players do
110 force_detach(players[i])
111 end
112 end)
113
114 minetest.register_on_dieplayer(function(player)
115 force_detach(player)
116 return true
117 end)
118
119 -------------------------------------------------------------------------------
120
121 function mobs.attach(entity, player)
122
123 local attach_at, eye_offset = {}, {}
124
125 entity.player_rotation = entity.player_rotation or {x = 0, y = 0, z = 0}
126 entity.driver_attach_at = entity.driver_attach_at or {x = 0, y = 0, z = 0}
127 entity.driver_eye_offset = entity.driver_eye_offset or {x = 0, y = 0, z = 0}
128 entity.driver_scale = entity.driver_scale or {x = 1, y = 1}
129
130 local rot_view = 0
131
132 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
138 entity.driver = player
139
140 force_detach(player)
141
142 player:set_attach(entity.object, "", attach_at, entity.player_rotation)
143 default.player_attached[player:get_player_name()] = true
144 player:set_eye_offset(eye_offset, {x = 0, y = 0, z = 0})
145
146 player:set_properties({
147 visual_size = {
148 x = entity.driver_scale.x,
149 y = entity.driver_scale.y
150 }
151 })
152
153 minetest.after(0.2, function()
154 default.player_set_animation(player, "sit" , 30)
155 end)
156
157 --player:set_look_yaw(entity.object:get_yaw() - rot_view)
158 player:set_look_horizontal(entity.object:get_yaw() - rot_view)
159 end
160
161
162 function mobs.detach(player, offset)
163
164 force_detach(player)
165
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
172 minetest.after(0.1, function()
173 player:set_pos(pos)
174 end)
175 end
176
177
178 function mobs.drive(entity, moving_anim, stand_anim, can_fly, dtime)
179
180 local rot_steer, rot_view = math.pi/2, 0
181
182 if entity.player_rotation.y == 90 then
183 rot_steer, rot_view = 0, math.pi/2
184 end
185
186 local acce_y = 0
187 local velo = entity.object:get_velocity()
188
189 entity.v = get_v(velo) * get_sign(entity.v)
190
191 -- process controls
192 if entity.driver then
193
194 --print ("---velo", get_v(velo))
195
196 local ctrl = entity.driver:get_player_control()
197
198 -- move forwards
199 if ctrl.up then
200
201 entity.v = entity.v + entity.accel / 10
202
203 -- move backwards
204 elseif ctrl.down then
205
206 if entity.max_speed_reverse == 0 and entity.v == 0 then
207 return
208 end
209
210 entity.v = entity.v - entity.accel / 10
211 end
212
213 -- fix mob rotation
214 entity.object:set_yaw(entity.driver:get_look_horizontal() - entity.rotate)
215
216 if can_fly then
217
218 -- fly up
219 if ctrl.jump then
220 velo.y = velo.y + 1
221 if velo.y > entity.accel then velo.y = entity.accel end
222
223 elseif velo.y > 0 then
224 velo.y = velo.y - 0.1
225 if velo.y < 0 then velo.y = 0 end
226 end
227
228 -- fly down
229 if ctrl.sneak then
230 velo.y = velo.y - 1
231 if velo.y < -entity.accel then velo.y = -entity.accel end
232
233 elseif velo.y < 0 then
234 velo.y = velo.y + 0.1
235 if velo.y > 0 then velo.y = 0 end
236 end
237
238 else
239
240 -- jump
241 if ctrl.jump then
242
243 if velo.y == 0 then
244 velo.y = velo.y + entity.jump_height
245 acce_y = acce_y + (acce_y * 3) + 1
246 end
247 end
248
249 end
250 end
251
252 -- if not moving then set animation and return
253 if entity.v == 0 and velo.x == 0 and velo.y == 0 and velo.z == 0 then
254
255 if stand_anim then
256 mobs:set_animation(entity, stand_anim)
257 end
258
259 return
260 end
261
262 -- set moving animation
263 if moving_anim then
264 mobs:set_animation(entity, moving_anim)
265 end
266
267 -- Stop!
268 local s = get_sign(entity.v)
269
270 entity.v = entity.v - 0.02 * s
271
272 if s ~= get_sign(entity.v) then
273
274 entity.object:set_velocity({x = 0, y = 0, z = 0})
275 entity.v = 0
276 return
277 end
278
279 -- enforce speed limit forward and reverse
280 local max_spd = entity.max_speed_reverse
281
282 if get_sign(entity.v) >= 0 then
283 max_spd = entity.max_speed_forward
284 end
285
286 if math.abs(entity.v) > max_spd then
287 entity.v = entity.v - get_sign(entity.v)
288 end
289
290 -- Set position, velocity and acceleration
291 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}
294
295 p.y = p.y - 0.5
296
297 local ni = node_is(p)
298 local v = entity.v
299
300 if ni == "air" then
301
302 if can_fly == true then
303 new_acce.y = 0
304 end
305
306 elseif ni == "liquid" or ni == "lava" then
307
308 if ni == "lava" and entity.lava_damage ~= 0 then
309
310 entity.lava_counter = (entity.lava_counter or 0) + dtime
311
312 if entity.lava_counter > 1 then
313
314 minetest.sound_play("default_punch", {
315 object = entity.object,
316 max_hear_distance = 5
317 })
318
319 entity.object:punch(entity.object, 1.0, {
320 full_punch_interval = 1.0,
321 damage_groups = {fleshy = entity.lava_damage}
322 }, nil)
323
324 entity.lava_counter = 0
325 end
326 end
327
328 if entity.terrain_type == 2
329 or entity.terrain_type == 3 then
330
331 new_acce.y = 0
332 p.y = p.y + 1
333
334 if node_is(p) == "liquid" then
335
336 if velo.y >= 5 then
337 velo.y = 5
338 elseif velo.y < 0 then
339 new_acce.y = 20
340 else
341 new_acce.y = 5
342 end
343 else
344 if math.abs(velo.y) < 1 then
345 local pos = entity.object:get_pos()
346 pos.y = math.floor(pos.y) + 0.5
347 entity.object:set_pos(pos)
348 velo.y = 0
349 end
350 end
351 else
352 v = v * 0.25
353 end
354 end
355
356 new_velo = get_velocity(v, entity.object:get_yaw() - rot_view, velo.y)
357 new_acce.y = new_acce.y + acce_y
358
359 entity.object:set_velocity(new_velo)
360 entity.object:set_acceleration(new_acce)
361
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
379 entity.v2 = v
380 end
381
382
383 -- directional flying routine by D00Med (edited by TenPlus1)
384
385 function mobs.fly(entity, dtime, speed, shoots, arrow, moving_anim, stand_anim)
386
387 local ctrl = entity.driver:get_player_control()
388 local velo = entity.object:get_velocity()
389 local dir = entity.driver:get_look_dir()
390 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
396
397 if ctrl.up then
398 entity.object:set_velocity({
399 x = dir.x * speed,
400 y = dir.y * speed + 2,
401 z = dir.z * speed
402 })
403
404 elseif ctrl.down then
405 entity.object:set_velocity({
406 x = -dir.x * speed,
407 y = dir.y * speed + 2,
408 z = -dir.z * speed
409 })
410
411 elseif not ctrl.down or ctrl.up or ctrl.jump then
412 entity.object:set_velocity({x = 0, y = -2, z = 0})
413 end
414
415 entity.object:set_yaw(yaw + math.pi + math.pi / 2 - entity.rotate)
416
417 -- firing arrows
418 if ctrl.LMB and ctrl.sneak and shoots then
419
420 local pos = entity.object:get_pos()
421 local obj = minetest.add_entity({
422 x = pos.x + 0 + dir.x * 2.5,
423 y = pos.y + 1.5 + dir.y,
424 z = pos.z + 0 + dir.z * 2.5}, arrow)
425
426 local ent = obj:get_luaentity()
427 if ent then
428 ent.switch = 1 -- for mob specific arrows
429 ent.owner_id = tostring(entity.object) -- so arrows dont hurt entity you are riding
430 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)
433 obj:set_velocity(vec)
434 else
435 obj:remove()
436 end
437 end
438
439 -- change animation if stopped
440 if velo.x == 0 and velo.y == 0 and velo.z == 0 then
441
442 mobs:set_animation(entity, stand_anim)
443 else
444 -- moving animation
445 mobs:set_animation(entity, moving_anim)
446 end
447 end
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 # If false then mobs no longer spawn in world without spawner or spawn egg
1 mobs_spawn (Spawn Mobs) bool true
2
3 # If enabled then monsters no longer spawn in world
4 only_peaceful_mobs (Only spawn peaceful Mobs) bool false
5
6 # If enabled then punching mobs no longer shows blood effects
7 mobs_disable_blood (Disable Mob blood) bool false
8
9 # If disabled then Mobs no longer destroy world blocks
10 mobs_griefing (Griefing Mobs) bool true
11
12 # If false then Mobs no longer spawn inside player protected areas
13 mobs_spawn_protected (Spawn Mobs in protected areas) bool true
14
15 # If true Mobs will be removed once a map chunk is out of view
16 remove_far_mobs (Remove far Mobs) bool true
17
18 # Sets Mob difficulty level by multiplying punch damage
19 mob_difficulty (Mob difficulty) float 1.0
20
21 # If disabled health status no longer appears above Mob when punched
22 mob_show_health (Show Mob health) bool true
23
24 # Contains a value used to multiply Mob spawn values
25 mob_chance_multiplier (Mob chance multiplier) float 1.0
26
27 # When false Mob no longer drop items when killed
28 mobs_drop_items (Mob drops) bool true
Binary diff not shown
0 Creative Commons sounds from Freesound.org
1
2 mobs_swing.ogg by qubodup
3 - http://freesound.org/people/qubodup/sounds/60012/
4
5 mobs_spell.ogg by littlerobotsoundfactory
6 - http://freesound.org/people/LittleRobotSoundFactory/sounds/270396/
Binary diff not shown
Binary diff not shown
0
1 -- intllib
2 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
4
5 -- mob spawner
6
7 local spawner_default = "mobs_animal:pumba 10 15 0 0"
8
9 minetest.register_node("mobs:spawner", {
10 tiles = {"mob_spawner.png"},
11 drawtype = "glasslike",
12 paramtype = "light",
13 walkable = true,
14 description = S("Mob Spawner"),
15 groups = {cracky = 1},
16
17 on_construct = function(pos)
18
19 local meta = minetest.get_meta(pos)
20
21 -- text entry formspec
22 meta:set_string("formspec",
23 "field[text;" .. S("Mob MinLight MaxLight Amount PlayerDist") .. ";${command}]")
24 meta:set_string("infotext", S("Spawner Not Active (enter settings)"))
25 meta:set_string("command", spawner_default)
26 end,
27
28 on_right_click = function(pos, placer)
29
30 if minetest.is_protected(pos, placer:get_player_name()) then
31 return
32 end
33 end,
34
35 on_receive_fields = function(pos, formname, fields, sender)
36
37 if not fields.text or fields.text == "" then
38 return
39 end
40
41 local meta = minetest.get_meta(pos)
42 local comm = fields.text:split(" ")
43 local name = sender:get_player_name()
44
45 if minetest.is_protected(pos, name) then
46 minetest.record_protection_violation(pos, name)
47 return
48 end
49
50 local mob = comm[1] -- mob to spawn
51 local mlig = tonumber(comm[2]) -- min light
52 local xlig = tonumber(comm[3]) -- max light
53 local num = tonumber(comm[4]) -- total mobs in area
54 local pla = tonumber(comm[5]) -- player distance (0 to disable)
55 local yof = tonumber(comm[6]) or 0 -- Y offset to spawn mob
56
57 if mob and mob ~= "" and mobs.spawning_mobs[mob] == true
58 and num and num >= 0 and num <= 10
59 and mlig and mlig >= 0 and mlig <= 15
60 and xlig and xlig >= 0 and xlig <= 15
61 and pla and pla >=0 and pla <= 20
62 and yof and yof > -10 and yof < 10 then
63
64 meta:set_string("command", fields.text)
65 meta:set_string("infotext", S("Spawner Active (@1)", mob))
66
67 else
68 minetest.chat_send_player(name, S("Mob Spawner settings failed!"))
69 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]”"))
71 end
72 end,
73 })
74
75
76 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
77
78 -- spawner abm
79 minetest.register_abm({
80 label = "Mob spawner node",
81 nodenames = {"mobs:spawner"},
82 interval = 10,
83 chance = 4,
84 catch_up = false,
85
86 action = function(pos, node, active_object_count, active_object_count_wider)
87
88 -- return if too many entities already
89 if active_object_count_wider >= max_per_block then
90 return
91 end
92
93 -- get meta and command
94 local meta = minetest.get_meta(pos)
95 local comm = meta:get_string("command"):split(" ")
96
97 -- get settings from command
98 local mob = comm[1]
99 local mlig = tonumber(comm[2])
100 local xlig = tonumber(comm[3])
101 local num = tonumber(comm[4])
102 local pla = tonumber(comm[5]) or 0
103 local yof = tonumber(comm[6]) or 0
104
105 -- if amount is 0 then do nothing
106 if num == 0 then
107 return
108 end
109
110 -- are we spawning a registered mob?
111 if not mobs.spawning_mobs[mob] then
112 --print ("--- mob doesn't exist", mob)
113 return
114 end
115
116 -- check objects inside 9x9 area around spawner
117 local objs = minetest.get_objects_inside_radius(pos, 9)
118 local count = 0
119 local ent = nil
120
121 -- count mob objects of same type in area
122 for k, obj in ipairs(objs) do
123
124 ent = obj:get_luaentity()
125
126 if ent and ent.name and ent.name == mob then
127 count = count + 1
128 end
129 end
130
131 -- is there too many of same type?
132 if count >= num then
133 return
134 end
135
136 -- spawn mob if player detected and in range
137 if pla > 0 then
138
139 local in_range = 0
140 local objs = minetest.get_objects_inside_radius(pos, pla)
141
142 for _,oir in pairs(objs) do
143
144 if oir:is_player() then
145
146 in_range = 1
147
148 break
149 end
150 end
151
152 -- player not found
153 if in_range == 0 then
154 return
155 end
156 end
157
158 -- find air blocks within 5 nodes of spawner
159 local air = minetest.find_nodes_in_area(
160 {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"})
163
164 -- spawn in random air block
165 if air and #air > 0 then
166
167 local pos2 = air[math.random(#air)]
168 local lig = minetest.get_node_light(pos2) or 0
169
170 pos2.y = pos2.y + 0.5
171
172 -- only if light levels are within range
173 if lig >= mlig and lig <= xlig
174 and minetest.registered_entities[mob] then
175 minetest.add_entity(pos2, mob)
176 end
177 end
178
179 end
180 })
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown