Package list minetest-mod-mobs-redo / c9f6f437-e791-4c8f-b200-751fd3dce9f2/upstream
Import upstream version 20181016+git20210722.1.9f46182 Debian Janitor 3 months ago
33 changed file(s) with 3480 addition(s) and 5623 deletion(s). Raw diff Collapse all Expand all
+2150
-1286
api.lua less more
0
1 -- Intllib and CMI support check
0 -- Load support for intllib.
21 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
2 local S = minetest.get_translator and minetest.get_translator("mobs_redo") or
3 dofile(MP .. "/intllib.lua")
4
5 -- CMI support check
46 local use_cmi = minetest.global_exists("cmi")
57
68 mobs = {
79 mod = "redo",
8 version = "20181005",
10 version = "20210722",
911 intllib = S,
10 invis = minetest.global_exists("invisibility") and invisibility or {},
12 invis = minetest.global_exists("invisibility") and invisibility or {}
1113 }
1214
13 -- creative check
14 local creative_cache = minetest.settings:get_bool("creative_mode")
15 function mobs.is_creative(name)
16 return creative_cache or minetest.check_player_privs(name, {creative = true})
17 end
18
19
20 -- localize math functions
15 -- localize common functions
2116 local pi = math.pi
2217 local square = math.sqrt
2318 local sin = math.sin
2520 local abs = math.abs
2621 local min = math.min
2722 local max = math.max
28 local atann = math.atan
2923 local random = math.random
3024 local floor = math.floor
25 local ceil = math.ceil
26 local rad = math.rad
27 local atann = math.atan
3128 local atan = function(x)
3229 if not x or x ~= x then
33 --error("atan bassed NaN")
34 return 0
30 return 0 -- NaN
3531 else
3632 return atann(x)
3733 end
3834 end
39
35 local table_copy = table.copy
36 local table_remove = table.remove
37 local vadd = vector.add
38 local vdirection = vector.direction
39 local vmultiply = vector.multiply
40 local vsubtract = vector.subtract
41 local settings = minetest.settings
42
43 -- creative check
44 local creative_cache = minetest.settings:get_bool("creative_mode")
45 function mobs.is_creative(name)
46 return creative_cache or minetest.check_player_privs(name,
47 {creative = true})
48 end
4049
4150 -- Load settings
42 local damage_enabled = minetest.settings:get_bool("enable_damage")
43 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
44 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
45 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
46 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
47 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
48 local creative = minetest.settings:get_bool("creative_mode")
49 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
50 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
51 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
52 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
53 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
54 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
51 local damage_enabled = settings:get_bool("enable_damage")
52 local mobs_spawn = settings:get_bool("mobs_spawn") ~= false
53 local peaceful_only = settings:get_bool("only_peaceful_mobs")
54 local disable_blood = settings:get_bool("mobs_disable_blood")
55 local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false
56 local mobs_griefing = settings:get_bool("mobs_griefing") ~= false
57 local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false
58 local spawn_monster_protected = settings:get_bool("mobs_spawn_monster_protected") ~= false
59 local remove_far = settings:get_bool("remove_far_mobs") ~= false
60 local mob_area_spawn = settings:get_bool("mob_area_spawn")
61 local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0
62 local show_health = settings:get_bool("mob_show_health") ~= false
63 local max_per_block = tonumber(settings:get("max_objects_per_block") or 99)
64 local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12)
65 local active_limit = tonumber(settings:get("mob_active_limit") or 0)
66 local mob_chance_multiplier = tonumber(settings:get("mob_chance_multiplier") or 1)
67 local peaceful_player_enabled = settings:get_bool("enable_peaceful_player")
68 local mob_smooth_rotate = settings:get_bool("mob_smooth_rotate") ~= false
69 local active_mobs = 0
5570
5671 -- Peaceful mode message so players will know there are no monsters
5772 if peaceful_only then
6277 end
6378
6479 -- calculate aoc range for mob count
65 local aoc_range = tonumber(minetest.settings:get("active_block_range")) * 16
80 local aoc_range = tonumber(settings:get("active_block_range")) * 16
6681
6782 -- pathfinding settings
6883 local enable_pathfinding = true
69 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
70 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
84 local stuck_timeout = 3 -- how long before stuck mod starts searching
85 local stuck_path_timeout = 5 -- how long will mob follow path before giving up
7186
7287 -- default nodes
7388 local node_fire = "fire:basic_flame"
7792 local node_snow = "default:snow"
7893 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
7994
95 local mob_class = {
96 stepheight = 1.1,
97 fly_in = "air",
98 owner = "",
99 order = "",
100 jump_height = 4,
101 lifetimer = 180, -- 3 minutes
102 physical = true,
103 collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
104 visual_size = {x = 1, y = 1},
105 texture_mods = "",
106 makes_footstep_sound = false,
107 view_range = 5,
108 walk_velocity = 1,
109 run_velocity = 2,
110 light_damage = 0,
111 light_damage_min = 14,
112 light_damage_max = 15,
113 water_damage = 0,
114 lava_damage = 4,
115 fire_damage = 4,
116 air_damage = 0,
117 suffocation = 2,
118 fall_damage = 1,
119 fall_speed = -10, -- must be lower than -2 (default: -10)
120 drops = {},
121 armor = 100,
122 sounds = {},
123 jump = true,
124 knock_back = true,
125 walk_chance = 50,
126 stand_chance = 30,
127 attack_chance = 5,
128 passive = false,
129 blood_amount = 5,
130 blood_texture = "mobs_blood.png",
131 shoot_offset = 0,
132 floats = 1, -- floats in water by default
133 replace_offset = 0,
134 timer = 0,
135 env_damage_timer = 0, -- only used when state = "attack"
136 tamed = false,
137 pause_timer = 0,
138 horny = false,
139 hornytimer = 0,
140 child = false,
141 gotten = false,
142 health = 0,
143 reach = 3,
144 htimer = 0,
145 docile_by_day = false,
146 time_of_day = 0.5,
147 fear_height = 0,
148 runaway_timer = 0,
149 immune_to = {},
150 explosion_timer = 3,
151 allow_fuse_reset = true,
152 stop_to_explode = true,
153 dogshoot_count = 0,
154 dogshoot_count_max = 5,
155 dogshoot_count2_max = 5,
156 group_attack = false,
157 attack_monsters = false,
158 attack_animals = false,
159 attack_players = true,
160 attack_npcs = true,
161 facing_fence = false,
162 _cmi_is_mob = true
163 }
164
165 local mob_class_meta = {__index = mob_class}
166
80167
81168 -- play sound
82 local mob_sound = function(self, sound)
169 function mob_class:mob_sound(sound)
170
171 local pitch = 1.0
172
173 -- higher pitch for a child
174 if self.child then pitch = pitch * 1.5 end
175
176 -- a little random pitch to be different
177 pitch = pitch + random(-10, 10) * 0.005
83178
84179 if sound then
85180 minetest.sound_play(sound, {
86181 object = self.object,
87182 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
183 max_hear_distance = self.sounds.distance,
184 pitch = pitch
185 }, true)
90186 end
91187 end
92188
93189
94190 -- attack player/mob
95 local do_attack = function(self, player)
191 function mob_class:do_attack(player)
96192
97193 if self.state == "attack" then
98194 return
102198 self.state = "attack"
103199
104200 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
201 self:mob_sound(self.sounds.war_cry)
106202 end
107203 end
108204
110206 -- calculate distance
111207 local get_distance = function(a, b)
112208
209 if not a or not b then return 50 end -- nil check
210
113211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114212
115213 return square(x * x + y * y + z * z)
117215
118216
119217 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
218 function mob_class:collision()
121219
122220 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124221 local x, z = 0, 0
125222 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126223
127224 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128225
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
226 if object:is_player() then
131227
132228 local pos2 = object:get_pos()
133229 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
141237 end
142238
143239
240 -- check if string exists in another string or table
241 local check_for = function(look_for, look_inside)
242
243 if type(look_inside) == "string" and look_inside == look_for then
244
245 return true
246
247 elseif type(look_inside) == "table" then
248
249 for _, str in pairs(look_inside) do
250
251 if str == look_for then
252 return true
253 end
254
255 if str:find("group:") then
256
257 local group = str:split(":")[2]
258
259 if minetest.get_item_group(look_for, group) ~= 0 then
260 return true
261 end
262 end
263 end
264 end
265
266 return false
267 end
268
269
144270 -- move mob in facing direction
145 local set_velocity = function(self, v)
271 function mob_class:set_velocity(v)
272
273 -- halt mob if it has been ordered to stay
274 if self.order == "stand" then
275
276 local vel = self.object:get_velocity() or {y = 0}
277
278 self.object:set_velocity({x = 0, y = vel.y, z = 0})
279
280 return
281 end
146282
147283 local c_x, c_y = 0, 0
148284
149285 -- can mob be pushed, if so calculate direction
150286 if self.pushable then
151 c_x, c_y = unpack(collision(self))
152 end
153
154 -- halt mob if it has been ordered to stay
155 if self.order == "stand" then
156 self.object:set_velocity({x = 0, y = 0, z = 0})
157 return
158 end
159
160 local yaw = (self.object:get_yaw() or 0) + self.rotate
161
162 self.object:set_velocity({
287 c_x, c_y = unpack(self:collision())
288 end
289
290 local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
291
292 -- nil check for velocity
293 v = v or 0.01
294
295 -- check if standing in liquid with max viscosity of 7
296 local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
297
298 -- only slow mob trying to move while inside a viscous fluid that
299 -- they aren't meant to be in (fish in water, spiders in cobweb etc)
300 if v > 0 and visc and visc > 0
301 and not check_for(self.standing_in, self.fly_in) then
302 v = v / (visc + 1)
303 end
304
305 -- set velocity
306 local vel = self.object:get_velocity() or 0
307
308 local new_vel = {
163309 x = (sin(yaw) * -v) + c_x,
164 y = self.object:get_velocity().y,
165 z = (cos(yaw) * v) + c_y,
166 })
310 y = vel.y,
311 z = (cos(yaw) * v) + c_y}
312
313 self.object:set_velocity(new_vel)
314 end
315
316 -- global version of above function
317 function mobs:set_velocity(entity, v)
318 mob_class.set_velocity(entity, v)
167319 end
168320
169321
170322 -- calculate mob velocity
171 local get_velocity = function(self)
323 function mob_class:get_velocity()
172324
173325 local v = self.object:get_velocity()
174326
327 if not v then return 0 end
328
175329 return (v.x * v.x + v.z * v.z) ^ 0.5
176330 end
177331
178332
179333 -- set and return valid yaw
180 local set_yaw = function(self, yaw, delay)
334 function mob_class:set_yaw(yaw, delay)
181335
182336 if not yaw or yaw ~= yaw then
183337 yaw = 0
184338 end
185339
186 delay = delay or 0
340 delay = mob_smooth_rotate and (delay or 0) or 0
187341
188342 if delay == 0 then
343
189344 self.object:set_yaw(yaw)
345
190346 return yaw
191347 end
192348
197353 end
198354
199355 -- global function to set mob yaw
200 function mobs:yaw(self, yaw, delay)
201 set_yaw(self, yaw, delay)
356 function mobs:yaw(entity, yaw, delay)
357 mob_class.set_yaw(entity, yaw, delay)
202358 end
203359
204360
205361 -- set defined animation
206 local set_animation = function(self, anim)
207
208 if not self.animation
209 or not anim then return end
362 function mob_class:set_animation(anim, force)
363
364 if not self.animation or not anim then return end
210365
211366 self.animation.current = self.animation.current or ""
212367
213 -- only set different animation for attacks when setting to same set
214 if anim ~= "punch" and anim ~= "shoot"
368 -- only use different animation for attacks when using same set
369 if force ~= true and anim ~= "punch" and anim ~= "shoot"
215370 and string.find(self.animation.current, anim) then
216371 return
217372 end
218373
219 -- check for more than one animation
220374 local num = 0
221375
376 -- check for more than one animation (max 4)
222377 for n = 1, 4 do
223378
224379 if self.animation[anim .. n .. "_start"]
244399 self.object:set_animation({
245400 x = self.animation[anim .. "_start"],
246401 y = self.animation[anim .. "_end"]},
247 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
402 self.animation[anim .. "_speed"] or
403 self.animation.speed_normal or 15,
248404 0, self.animation[anim .. "_loop"] ~= false)
249405 end
250406
251 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
407 function mobs:set_animation(entity, anim)
408 entity.set_animation(entity, anim)
409 end
410
411
412 -- check line of sight (BrunoMine)
413 local line_of_sight = function(self, pos1, pos2, stepsize)
414
415 stepsize = stepsize or 1
416
417 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
418
419 -- normal walking and flying mobs can see you through air
420 if s == true then
421 return true
422 end
423
424 -- New pos1 to be analyzed
425 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
426
427 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
428
429 -- Checks the return
430 if r == true then return true end
431
432 -- Nodename found
433 local nn = minetest.get_node(pos).name
434
435 -- Target Distance (td) to travel
436 local td = get_distance(pos1, pos2)
437
438 -- Actual Distance (ad) traveled
439 local ad = 0
440
441 -- It continues to advance in the line of sight in search of a real
442 -- obstruction which counts as 'walkable' nodebox.
443 while minetest.registered_nodes[nn]
444 and (minetest.registered_nodes[nn].walkable == false) do
445
446 -- Check if you can still move forward
447 if td < ad + stepsize then
448 return true -- Reached the target
449 end
450
451 -- Moves the analyzed pos
452 local d = get_distance(pos1, pos2)
453
454 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
455 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
456 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
457
458 -- NaN checks
459 if d == 0
460 or npos1.x ~= npos1.x
461 or npos1.y ~= npos1.y
462 or npos1.z ~= npos1.z then
463 return false
464 end
465
466 ad = ad + stepsize
467
468 -- scan again
469 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
470
471 if r == true then return true end
472
473 -- New Nodename found
474 nn = minetest.get_node(pos).name
475 end
476
477 return false
254478 end
255479
256480
257481 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
482 local new_line_of_sight = function(self, pos1, pos2, stepsize)
259483
260484 if not pos1 or not pos2 then return end
261485
262486 stepsize = stepsize or 1
263487
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
488 local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
265489
266490 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267491
280504 local nn = minetest.get_node(pos).name
281505
282506 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
507 -- obstruction which counts as 'walkable' nodebox.
284508 while minetest.registered_nodes[nn]
285509 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
510
511 npos1 = vadd(npos1, stepv)
289512
290513 if get_distance(npos1, pos2) < stepsize then return true end
291514
301524 return false
302525 end
303526
527 -- check line of sight using raycasting (thanks Astrobe)
528 local ray_line_of_sight = function(self, pos1, pos2)
529
530 local ray = minetest.raycast(pos1, pos2, true, false)
531 local thing = ray:next()
532
533 while thing do -- thing.type, thing.ref
534
535 if thing.type == "node" then
536
537 local name = minetest.get_node(thing.under).name
538
539 if minetest.registered_items[name]
540 and minetest.registered_items[name].walkable then
541 return false
542 end
543 end
544
545 thing = ray:next()
546 end
547
548 return true
549 end
550
551
552 function mob_class:line_of_sight(pos1, pos2, stepsize)
553
554 if minetest.raycast then -- only use if minetest 5.0 is detected
555 return ray_line_of_sight(self, pos1, pos2)
556 end
557
558 return line_of_sight(self, pos1, pos2, stepsize)
559 end
560
304561 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
562 function mobs:line_of_sight(entity, pos1, pos2, stepsize)
563 return entity:line_of_sight(pos1, pos2, stepsize)
564 end
565
566
567 function mob_class:attempt_flight_correction(override)
568
569 if self:flight_check() and override ~= true then return true end
570
571 -- We are not flying in what we are supposed to.
572 -- See if we can find intended flight medium and return to it
573 local pos = self.object:get_pos() ; if not pos then return true end
574 local searchnodes = self.fly_in
575
576 if type(searchnodes) == "string" then
577 searchnodes = {self.fly_in}
578 end
579
580 local flyable_nodes = minetest.find_nodes_in_area(
581 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
582 {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes)
583 -- pos.y + 0 hopefully fixes floating swimmers
584
585 if #flyable_nodes < 1 then
586 return false
587 end
588
589 local escape_target = flyable_nodes[random(#flyable_nodes)]
590 local escape_direction = vdirection(pos, escape_target)
591
592 self.object:set_velocity(
593 vmultiply(escape_direction, 1))
594
595 return true
308596 end
309597
310598
311599 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
600 function mob_class:flight_check()
313601
314602 local def = minetest.registered_nodes[self.standing_in]
315603
316 if not def then return false end -- nil check
317
318 if type(self.fly_in) == "string"
319 and self.standing_in == self.fly_in then
320
604 if not def then return false end
605
606 -- are we standing inside what we should be to fly/swim ?
607 if check_for(self.standing_in, self.fly_in) then
321608 return true
322
323 elseif type(self.fly_in) == "table" then
324
325 for _,fly_in in pairs(self.fly_in) do
326
327 if self.standing_in == fly_in then
328
329 return true
330 end
331 end
332609 end
333610
334611 -- stops mobs getting stuck inside stairs and plantlike nodes
342619 end
343620
344621
622 -- turn mob to face position
623 local yaw_to_pos = function(self, target, rot)
624
625 rot = rot or 0
626
627 local pos = self.object:get_pos()
628 local vec = {x = target.x - pos.x, z = target.z - pos.z}
629 local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
630
631 if target.x > pos.x then
632 yaw = yaw + pi
633 end
634
635 yaw = self:set_yaw(yaw, rot)
636
637 return yaw
638 end
639
640 function mobs:yaw_to_pos(self, target, rot)
641 return yaw_to_pos(self, target, rot)
642 end
643
644
645 -- if stay near set then periodically check for nodes and turn towards them
646 function mob_class:do_stay_near()
647
648 if not self.stay_near then return false end
649
650 local pos = self.object:get_pos()
651 local searchnodes = self.stay_near[1]
652 local chance = self.stay_near[2] or 10
653
654 if not pos or random(chance) > 1 then
655 return false
656 end
657
658 if type(searchnodes) == "string" then
659 searchnodes = {self.stay_near[1]}
660 end
661
662 local r = self.view_range
663 local nearby_nodes = minetest.find_nodes_in_area(
664 {x = pos.x - r, y = pos.y - 1, z = pos.z - r},
665 {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
666
667 if #nearby_nodes < 1 then
668 return false
669 end
670
671 yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)])
672
673 self:set_animation("walk")
674
675 self:set_velocity(self.walk_velocity)
676
677 return true
678 end
679
680
345681 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
682 local effect = function(pos, amount, texture, min_size, max_size,
683 radius, gravity, glow, fall)
347684
348685 radius = radius or 2
349686 min_size = min_size or 0.5
351688 gravity = gravity or -10
352689 glow = glow or 0
353690
691 if fall == true then
692 fall = 0
693 elseif fall == false then
694 fall = radius
695 else
696 fall = -radius
697 end
698
354699 minetest.add_particlespawner({
355700 amount = amount,
356701 time = 0.25,
357702 minpos = pos,
358703 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
704 minvel = {x = -radius, y = fall, z = -radius},
360705 maxvel = {x = radius, y = radius, z = radius},
361706 minacc = {x = 0, y = gravity, z = 0},
362707 maxacc = {x = 0, y = gravity, z = 0},
365710 minsize = min_size,
366711 maxsize = max_size,
367712 texture = texture,
368 glow = glow,
713 glow = glow
369714 })
370715 end
371716
717 function mobs:effect(pos, amount, texture, min_size, max_size,
718 radius, gravity, glow, fall)
719
720 effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
721 end
722
372723
373724 -- update nametag colour
374 local update_tag = function(self)
725 function mob_class:update_tag()
375726
376727 local col = "#00FF00"
377728 local qua = self.hp_max / 4
388739 col = "#FF0000"
389740 end
390741
742 -- build infotext
743 self.infotext = "Health: " .. self.health .. " / " .. self.hp_max
744 .. "\n" .. "Owner: " .. self.owner
745
746 -- set changes
391747 self.object:set_properties({
392748 nametag = self.nametag,
393 nametag_color = col
749 nametag_color = col,
750 infotext = self.infotext
394751 })
395
396752 end
397753
398754
399755 -- drop items
400 local item_drop = function(self)
756 function mob_class:item_drop()
757
758 -- no drops if disabled by setting or mob is child
759 if not mobs_drop_items or self.child then return end
760
761 local pos = self.object:get_pos()
762
763 -- check for drops function
764 self.drops = type(self.drops) == "function"
765 and self.drops(pos) or self.drops
401766
402767 -- check for nil or no drops
403768 if not self.drops or #self.drops == 0 then
404769 return
405770 end
406771
407 -- no drops if disabled by setting
408 if not mobs_drop_items then return end
409
410 -- no drops for child mobs
411 if self.child then return end
412
413772 -- was mob killed by player?
414 local death_by_player = self.cause_of_death and self.cause_of_death.puncher
415 and self.cause_of_death.puncher:is_player() or nil
773 local death_by_player = self.cause_of_death
774 and self.cause_of_death.puncher
775 and self.cause_of_death.puncher:is_player()
416776
417777 local obj, item, num
418 local pos = self.object:get_pos()
419778
420779 for n = 1, #self.drops do
421780
422 if random(1, self.drops[n].chance) == 1 then
781 if random(self.drops[n].chance) == 1 then
423782
424783 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425784 item = self.drops[n].name
435794 end
436795 end
437796
438 -- only drop rare items (drops.min=0) if killed by player
439 if death_by_player then
440 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
441
442 elseif self.drops[n].min ~= 0 then
797 -- only drop rare items (drops.min = 0) if killed by player
798 if death_by_player or self.drops[n].min ~= 0 then
443799 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
444800 end
445801
448804 obj:set_velocity({
449805 x = random(-10, 10) / 9,
450806 y = 6,
451 z = random(-10, 10) / 9,
807 z = random(-10, 10) / 9
452808 })
453809
454810 elseif obj then
461817 end
462818
463819
820 -- remove mob and descrease counter
821 local remove_mob = function(self, decrease)
822
823 self.object:remove()
824
825 if decrease and active_limit > 0 then
826
827 active_mobs = active_mobs - 1
828
829 if active_mobs < 0 then
830 active_mobs = 0
831 end
832 end
833 --print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
834 end
835
836 -- global function for removing mobs
837 function mobs:remove(self, decrease)
838 remove_mob(self, decrease)
839 end
840
841
464842 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
843 function mob_class:check_for_death(cmi_cause)
844
845 -- We dead already
846 if self.state == "die" then
847 return true
848 end
466849
467850 -- has health actually changed?
468851 if self.health == self.old_health and self.health > 0 then
469 return
470 end
852 return false
853 end
854
855 local damaged = self.health < self.old_health
471856
472857 self.old_health = self.health
473858
474859 -- still got some health? play hurt sound
475860 if self.health > 0 then
476861
477 mob_sound(self, self.sounds.damage)
862 -- only play hurt sound if damaged
863 if damaged then
864 self:mob_sound(self.sounds.damage)
865 end
478866
479867 -- make sure health isn't higher than max
480868 if self.health > self.hp_max then
482870 end
483871
484872 -- backup nametag so we can show health stats
485 if not self.nametag2 then
486 self.nametag2 = self.nametag or ""
487 end
488
489 if show_health
490 and (cmi_cause and cmi_cause.type == "punch") then
491
492 self.htimer = 2
493 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
494
495 update_tag(self)
496 end
873 -- if not self.nametag2 then
874 -- self.nametag2 = self.nametag or ""
875 -- end
876
877 -- if show_health
878 -- and (cmi_cause and cmi_cause.type == "punch") then
879
880 -- self.htimer = 2
881 -- self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
882 self:update_tag()
883 -- end
497884
498885 return false
499886 end
501888 self.cause_of_death = cmi_cause
502889
503890 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
891 self:item_drop()
892
893 self:mob_sound(self.sounds.death)
507894
508895 local pos = self.object:get_pos()
509896
510897 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
898 if pos and self.on_die then
899
900 self:on_die(pos)
514901
515902 if use_cmi then
516903 cmi.notify_die(self.object, cmi_cause)
517904 end
518905
519 self.object:remove()
906 remove_mob(self, true)
520907
521908 return true
522909 end
523910
524 -- default death function and die animation (if defined)
911 -- check for custom death function and die animation
525912 if self.animation
526913 and self.animation.die_start
527914 and self.animation.die_end then
528915
529916 local frames = self.animation.die_end - self.animation.die_start
530917 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
918 local length = max((frames / speed), 0)
919 local rot = self.animation.die_rotate and 5
532920
533921 self.attack = nil
922 self.following = nil
534923 self.v_start = false
535924 self.timer = 0
536925 self.blinktimer = 0
537926 self.passive = true
538927 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
928 self.object:set_properties({
929 pointable = false, collide_with_objects = false,
930 automatic_rotate = rot, static_save = false
931 })
932 self:set_velocity(0)
933 self:set_animation("die")
541934
542935 minetest.after(length, function(self)
543936
544 if use_cmi and self.object:get_luaentity() then
545 cmi.notify_die(self.object, cmi_cause)
546 end
547
548 self.object:remove()
937 if self.object:get_luaentity() then
938
939 if use_cmi then
940 cmi.notify_die(self.object, cmi_cause)
941 end
942
943 remove_mob(self, true)
944 end
549945 end, self)
550 else
946
947 return true
948
949 elseif pos then -- otherwise remove mod and show particle effect
551950
552951 if use_cmi then
553952 cmi.notify_die(self.object, cmi_cause)
554953 end
555954
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
955 remove_mob(self, true)
956
957 effect(pos, 20, "tnt_smoke.png")
958 end
560959
561960 return true
562961 end
563962
564963
964 -- get node but use fallback for nil or unknown
965 local node_ok = function(pos, fallback)
966
967 fallback = fallback or mobs.fallback_node
968
969 local node = minetest.get_node_or_nil(pos)
970
971 if node and minetest.registered_nodes[node.name] then
972 return node
973 end
974
975 return minetest.registered_nodes[fallback]
976 end
977
978
979 -- Returns true is node can deal damage to self
980 local is_node_dangerous = function(self, nodename)
981
982 if self.water_damage > 0
983 and minetest.get_item_group(nodename, "water") ~= 0 then
984 return true
985 end
986
987 if self.lava_damage > 0
988 and minetest.get_item_group(nodename, "lava") ~= 0 then
989 return true
990 end
991
992 if self.fire_damage > 0
993 and minetest.get_item_group(nodename, "fire") ~= 0 then
994 return true
995 end
996
997 if minetest.registered_nodes[nodename].damage_per_second > 0 then
998 return true
999 end
1000
1001 return false
1002 end
1003
1004
5651005 -- is mob facing a cliff
566 local is_at_cliff = function(self)
1006 function mob_class:is_at_cliff()
5671007
5681008 if self.fear_height == 0 then -- 0 for no falling protection!
5691009 return false
5701010 end
5711011
1012 -- get yaw but if nil returned object no longer exists
5721013 local yaw = self.object:get_yaw()
1014
1015 if not yaw then return false end
1016
5731017 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
5741018 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
5751019 local pos = self.object:get_pos()
5761020 local ypos = pos.y + self.collisionbox[2] -- just above floor
5771021
578 if minetest.line_of_sight(
1022 local free_fall, blocker = minetest.line_of_sight(
5791023 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
580 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
581 , 1) then
582
1024 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
1025
1026 -- check for straight drop
1027 if free_fall then
5831028 return true
5841029 end
5851030
586 return false
587 end
588
589
590 -- get node but use fallback for nil or unknown
591 local node_ok = function(pos, fallback)
592
593 fallback = fallback or mobs.fallback_node
594
595 local node = minetest.get_node_or_nil(pos)
596
597 if node and minetest.registered_nodes[node.name] then
598 return node
599 end
600
601 return minetest.registered_nodes[fallback]
1031 local bnode = node_ok(blocker)
1032
1033 -- will we drop onto dangerous node?
1034 if is_node_dangerous(self, bnode.name) then
1035 return true
1036 end
1037
1038 local def = minetest.registered_nodes[bnode.name]
1039
1040 return (not def and def.walkable)
6021041 end
6031042
6041043
6051044 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
1045 function mob_class:do_env_damage()
6071046
6081047 -- feed/tame text timer (so mob 'full' messages dont spam chat)
6091048 if self.htimer > 0 then
6111050 end
6121051
6131052 -- reset nametag after showing health stats
614 if self.htimer < 1 and self.nametag2 then
615
616 self.nametag = self.nametag2
617 self.nametag2 = nil
618
619 update_tag(self)
620 end
621
622 local pos = self.object:get_pos()
1053 -- if self.htimer < 1 and self.nametag2 then
1054
1055 -- self.nametag = self.nametag2
1056 -- self.nametag2 = nil
1057
1058 self:update_tag()
1059 -- end
1060
1061 local pos = self.object:get_pos() ; if not pos then return end
6231062
6241063 self.time_of_day = minetest.get_timeofday()
6251064
626 -- remove mob if standing inside ignore node
1065 -- halt mob if standing inside ignore node
6271066 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
1067
1068 self.object:set_velocity({x = 0, y = 0, z = 0})
1069
1070 return true
1071 end
1072
1073 -- particle appears at random mob height
1074 local py = {
1075 x = pos.x,
1076 y = pos.y + random(self.collisionbox[2], self.collisionbox[5]),
1077 z = pos.z
1078 }
1079
1080 local nodef = minetest.registered_nodes[self.standing_in]
1081
1082 -- water
1083 if self.water_damage ~= 0 and nodef.groups.water then
1084
1085 self.health = self.health - self.water_damage
1086
1087 effect(py, 5, "bubble.png", nil, nil, 1, nil)
1088
1089 if self:check_for_death({type = "environment",
1090 pos = pos, node = self.standing_in}) then
1091 return true
1092 end
1093
1094 -- lava damage
1095 elseif self.lava_damage ~= 0 and nodef.groups.lava then
1096
1097 self.health = self.health - self.lava_damage
1098
1099 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1100
1101 if self:check_for_death({type = "environment", pos = pos,
1102 node = self.standing_in, hot = true}) then
1103 return true
1104 end
1105
1106 -- fire damage
1107 elseif self.fire_damage ~= 0 and nodef.groups.fire then
1108
1109 self.health = self.health - self.fire_damage
1110
1111 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1112
1113 if self:check_for_death({type = "environment", pos = pos,
1114 node = self.standing_in, hot = true}) then
1115 return true
1116 end
1117
1118 -- damage_per_second node check (not fire and lava)
1119 elseif nodef.damage_per_second ~= 0
1120 and nodef.groups.lava == nil and nodef.groups.fire == nil then
1121
1122 self.health = self.health - nodef.damage_per_second
1123
1124 effect(py, 5, "tnt_smoke.png")
1125
1126 if self:check_for_death({type = "environment",
1127 pos = pos, node = self.standing_in}) then
1128 return true
1129 end
1130 end
1131
1132 -- air damage
1133 if self.air_damage ~= 0 and self.standing_in == "air" then
1134
1135 self.health = self.health - self.air_damage
1136
1137 effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
1138
1139 if self:check_for_death({type = "environment",
1140 pos = pos, node = self.standing_in}) then
1141 return true
1142 end
6301143 end
6311144
6321145 -- is mob light sensative, or scared of the dark :P
6391152
6401153 self.health = self.health - self.light_damage
6411154
642 effect(pos, 5, "tnt_smoke.png")
643
644 if check_for_death(self, {type = "light"}) then return end
645 end
646 end
647
648 local nodef = minetest.registered_nodes[self.standing_in]
649
650 pos.y = pos.y + 1 -- for particle effect position
651
652 -- water
653 if self.water_damage
654 and nodef.groups.water then
655
656 if self.water_damage ~= 0 then
657
658 self.health = self.health - self.water_damage
659
660 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
661
662 if check_for_death(self, {type = "environment",
663 pos = pos, node = self.standing_in}) then return end
664 end
665
666 -- lava or fire or ignition source
667 elseif self.lava_damage
668 and nodef.groups.igniter then
669 -- and (nodef.groups.lava
670 -- or self.standing_in == node_fire
671 -- or self.standing_in == node_permanent_flame) then
672
673 if self.lava_damage ~= 0 then
674
675 self.health = self.health - self.lava_damage
676
677 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
678
679 if check_for_death(self, {type = "environment",
680 pos = pos, node = self.standing_in, hot = true}) then return end
681 end
682
683 -- damage_per_second node check
684 elseif nodef.damage_per_second ~= 0 then
685
686 self.health = self.health - nodef.damage_per_second
687
688 effect(pos, 5, "tnt_smoke.png")
689
690 if check_for_death(self, {type = "environment",
691 pos = pos, node = self.standing_in}) then return end
692 end
693 --[[
1155 effect(py, 5, "tnt_smoke.png")
1156
1157 if self:check_for_death({type = "light"}) then
1158 return true
1159 end
1160 end
1161 end
1162
6941163 --- suffocation inside solid node
695 if self.suffocation ~= 0
696 and nodef.walkable == true
697 and nodef.groups.disable_suffocation ~= 1
698 and nodef.drawtype == "normal" then
699
700 self.health = self.health - self.suffocation
701
702 if check_for_death(self, {type = "environment",
703 pos = pos, node = self.standing_in}) then return end
704 end
705 ]]
706 check_for_death(self, {type = "unknown"})
1164 if (self.suffocation and self.suffocation ~= 0)
1165 and (nodef.walkable == nil or nodef.walkable == true)
1166 and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
1167 and (nodef.node_box == nil or nodef.node_box.type == "regular")
1168 and (nodef.groups.disable_suffocation ~= 1) then
1169
1170 local damage
1171
1172 if self.suffocation == true then
1173 damage = 2
1174 else
1175 damage = (self.suffocation or 2)
1176 end
1177
1178 self.health = self.health - damage
1179
1180 if self:check_for_death({type = "suffocation",
1181 pos = pos, node = self.standing_in}) then
1182 return true
1183 end
1184 end
1185
1186 return self:check_for_death({type = "unknown"})
7071187 end
7081188
7091189
7101190 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
1191 function mob_class:do_jump()
7121192
7131193 if not self.jump
7141194 or self.jump_height == 0
7221202
7231203 -- something stopping us while moving?
7241204 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
1205 and self:get_velocity() > 0.5
7261206 and self.object:get_velocity().y ~= 0 then
7271207 return false
7281208 end
7301210 local pos = self.object:get_pos()
7311211 local yaw = self.object:get_yaw()
7321212
733 -- what is mob standing on?
734 pos.y = pos.y + self.collisionbox[2] - 0.2
735
736 local nod = node_ok(pos)
737
738 --print ("standing on:", nod.name, pos.y)
739
740 if minetest.registered_nodes[nod.name].walkable == false then
1213 -- sanity check
1214 if not yaw then return false end
1215
1216 -- we can only jump if standing on solid node
1217 if minetest.registered_nodes[self.standing_on].walkable == false then
7411218 return false
7421219 end
7431220
7451222 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
7461223 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
7471224
1225 -- set y_pos to base of mob
1226 pos.y = pos.y + self.collisionbox[2]
1227
7481228 -- what is in front of mob?
7491229 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
1230 x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
7531231 })
7541232
755 -- thin blocks that do not need to be jumped
756 if nod.name == node_snow then
757 return false
758 end
759
760 --print ("in front:", nod.name, pos.y + 0.5)
761
762 if self.walk_chance == 0
763 or minetest.registered_items[nod.name].walkable then
764
765 if not nod.name:find("fence")
766 and not nod.name:find("gate") then
767
768 local v = self.object:get_velocity()
769
770 v.y = self.jump_height
771
772 set_animation(self, "jump") -- only when defined
773
774 self.object:set_velocity(v)
775
776 -- when in air move forward
777 minetest.after(0.3, function(self, v)
778
779 if self.object:get_luaentity() then
780
781 self.object:set_acceleration({
782 x = v.x * 2,--1.5,
783 y = 0,
784 z = v.z * 2,--1.5
785 })
786 end
787 end, self, v)
788
789 if get_velocity(self) > 0 then
790 mob_sound(self, self.sounds.jump)
791 end
792 else
793 self.facing_fence = true
794 end
1233 -- what is above and in front?
1234 local nodt = node_ok({
1235 x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
1236 })
1237
1238 local blocked = minetest.registered_nodes[nodt.name].walkable
1239
1240 -- are we facing a fence or wall
1241 if nod.name:find("fence") or nod.name:find("gate") or nod.name:find("wall") then
1242 self.facing_fence = true
1243 end
1244 --[[
1245 print("on: " .. self.standing_on
1246 .. ", front: " .. nod.name
1247 .. ", front above: " .. nodt.name
1248 .. ", blocked: " .. (blocked and "yes" or "no")
1249 .. ", fence: " .. (self.facing_fence and "yes" or "no")
1250 )
1251 ]]
1252 -- jump if standing on solid node (not snow) and not blocked
1253 if (self.walk_chance == 0 or minetest.registered_items[nod.name].walkable)
1254 and not blocked and not self.facing_fence and nod.name ~= node_snow then
1255
1256 local v = self.object:get_velocity()
1257
1258 v.y = self.jump_height
1259
1260 self:set_animation("jump") -- only when defined
1261
1262 self.object:set_velocity(v)
1263
1264 -- when in air move forward
1265 minetest.after(0.3, function(self, v)
1266
1267 if self.object:get_luaentity() then
1268
1269 self.object:set_acceleration({
1270 x = v.x * 2,
1271 y = 0,
1272 z = v.z * 2
1273 })
1274 end
1275 end, self, v)
1276
1277 if self:get_velocity() > 0 then
1278 self:mob_sound(self.sounds.jump)
1279 end
1280
1281 self.jump_count = 0
7951282
7961283 return true
1284 end
1285
1286 -- if blocked for 3 counts then turn
1287 if not self.following and (self.facing_fence or blocked) then
1288
1289 self.jump_count = (self.jump_count or 0) + 1
1290
1291 if self.jump_count > 2 then
1292
1293 local yaw = self.object:get_yaw() or 0
1294 local turn = random(0, 2) + 1.35
1295
1296 yaw = self:set_yaw(yaw + turn, 12)
1297
1298 self.jump_count = 0
1299 end
7971300 end
7981301
7991302 return false
8131316 obj_pos = objs[n]:get_pos()
8141317
8151318 dist = get_distance(pos, obj_pos)
1319
8161320 if dist < 1 then dist = 1 end
8171321
8181322 local damage = floor((4 / dist) * radius)
8271331 end
8281332
8291333
1334 -- can mob see player
1335 local is_invisible = function(self, player_name)
1336
1337 if mobs.invis[player_name] and not self.ignore_invisibility then
1338 return true
1339 end
1340 end
1341
1342
8301343 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
1344 function mob_class:follow_holding(clicker)
1345
1346 if is_invisible(self, clicker:get_player_name()) then
8341347 return false
8351348 end
8361349
8371350 local item = clicker:get_wielded_item()
838 local t = type(self.follow)
839
840 -- single item
841 if t == "string"
842 and item:get_name() == self.follow then
1351
1352 -- are we holding an item mob can follow ?
1353 if check_for(item:get_name(), self.follow) then
8431354 return true
844
845 -- multiple items
846 elseif t == "table" then
847
848 for no = 1, #self.follow do
849
850 if self.follow[no] == item:get_name() then
851 return true
852 end
853 end
8541355 end
8551356
8561357 return false
8571358 end
8581359
1360 -- Thanks Wuzzy for the following editable settings
1361 local HORNY_TIME = 30
1362 local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes
1363 local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
8591364
8601365 -- find two animals of same type and breed if nearby and horny
861 local breed = function(self)
862
863 -- child takes 240 seconds before growing into adult
1366 function mob_class:breed()
1367
1368 -- child takes a long time before growing into adult
8641369 if self.child == true then
8651370
8661371 self.hornytimer = self.hornytimer + 1
8671372
868 if self.hornytimer > 240 then
1373 if self.hornytimer > CHILD_GROW_TIME then
8691374
8701375 self.child = false
8711376 self.hornytimer = 0
8751380 mesh = self.base_mesh,
8761381 visual_size = self.base_size,
8771382 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
1383 selectionbox = self.base_selbox
8791384 })
8801385
8811386 -- custom function when child grows up
8821387 if self.on_grown then
8831388 self.on_grown(self)
8841389 else
885 -- jump when fully grown so as not to fall into ground
886 self.object:set_velocity({
887 x = 0,
888 y = self.jump_height,
889 z = 0
890 })
1390 local pos = self.object:get_pos() ; if not pos then return end
1391 local ent = self.object:get_luaentity()
1392
1393 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
1394
1395 self.object:set_pos(pos)
1396
1397 -- jump slightly when fully grown so as not to fall into ground
1398 self.object:set_velocity({x = 0, y = 0.5, z = 0 })
8911399 end
8921400 end
8931401
8941402 return
8951403 end
8961404
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
1405 -- horny animal can mate for HORNY_TIME seconds,
1406 -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
8991407 if self.horny == true
900 and self.hornytimer < 240 then
1408 and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
9011409
9021410 self.hornytimer = self.hornytimer + 1
9031411
904 if self.hornytimer >= 240 then
1412 if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
9051413 self.hornytimer = 0
9061414 self.horny = false
9071415 end
9091417
9101418 -- find another same animal who is also horny and mate if nearby
9111419 if self.horny == true
912 and self.hornytimer <= 40 then
1420 and self.hornytimer <= HORNY_TIME then
9131421
9141422 local pos = self.object:get_pos()
9151423
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
1424 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
1425 "heart.png", 3, 4, 1, 0.1)
9171426
9181427 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
1428 local ent
9211429
9221430 for n = 1, #objs do
9231431
9311439 if ent.name == self.name then
9321440 canmate = true
9331441 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
1442 local entname = ent.name:split(":")
1443 local selfname = self.name:split(":")
9361444
9371445 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
1446 entname = entname[2]:split("_")
1447 selfname = selfname[2]:split("_")
9401448
9411449 if entname[1] == selfname[1] then
9421450 canmate = true
9451453 end
9461454 end
9471455
948 if ent
1456 -- found another similar horny animal that isn't self?
1457 if ent and ent.object ~= self.object
9491458 and canmate == true
9501459 and ent.horny == true
951 and ent.hornytimer <= 40 then
952 num = num + 1
953 end
954
955 -- found your mate? then have a baby
956 if num > 1 then
957
958 self.hornytimer = 41
959 ent.hornytimer = 41
1460 and ent.hornytimer <= HORNY_TIME then
1461
1462 local pos2 = ent.object:get_pos()
1463
1464 -- Have mobs face one another
1465 yaw_to_pos(self, pos2)
1466 yaw_to_pos(ent, self.object:get_pos())
1467
1468 self.hornytimer = HORNY_TIME + 1
1469 ent.hornytimer = HORNY_TIME + 1
1470
1471 -- have we reached active mob limit
1472 if active_limit > 0 and active_mobs >= active_limit then
1473 minetest.chat_send_player(self.owner,
1474 S("Active Mob Limit Reached!")
1475 .. " (" .. active_mobs
1476 .. " / " .. active_limit .. ")")
1477 return
1478 end
9601479
9611480 -- spawn baby
9621481 minetest.after(5, function(self, ent)
9691488 if self.on_breed then
9701489
9711490 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
1491 if self:on_breed(ent) == false then
9731492 return
9741493 end
9751494 else
9761495 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
9771496 end
9781497
1498 pos.y = pos.y + 0.5 -- spawn child a little higher
1499
9791500 local mob = minetest.add_entity(pos, self.name)
9801501 local ent2 = mob:get_luaentity()
9811502 local textures = self.base_texture
9901511 textures = textures,
9911512 visual_size = {
9921513 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
1514 y = self.base_size.y * .5
9941515 },
9951516 collisionbox = {
9961517 self.base_colbox[1] * .5,
9981519 self.base_colbox[3] * .5,
9991520 self.base_colbox[4] * .5,
10001521 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1522 self.base_colbox[6] * .5
10021523 },
10031524 selectionbox = {
10041525 self.base_selbox[1] * .5,
10061527 self.base_selbox[3] * .5,
10071528 self.base_selbox[4] * .5,
10081529 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1530 self.base_selbox[6] * .5
10101531 },
10111532 })
10121533 -- tamed and owned by parents' owner
10151536 ent2.owner = self.owner
10161537 end, self, ent)
10171538
1018 num = 0
1019
10201539 break
10211540 end
10221541 end
10251544
10261545
10271546 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1547 function mob_class:replace(pos)
1548
1549 local vel = self.object:get_velocity()
1550 if not vel then return end
10291551
10301552 if not mobs_griefing
10311553 or not self.replace_rate
10321554 or not self.replace_what
10331555 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1556 or vel.y ~= 0
1557 or random(self.replace_rate) > 1 then
10361558 return
10371559 end
10381560
10551577
10561578 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
10571579
1058 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1059
1060 local oldnode = {name = what}
1061 local newnode = {name = with}
1062 local on_replace_return
1580 -- print("replace node = ".. minetest.get_node(pos).name, pos.y)
10631581
10641582 if self.on_replace then
1065 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1066 end
1067
1068 if on_replace_return ~= false then
1069
1070 minetest.set_node(pos, {name = with})
1071
1072 -- when cow/sheep eats grass, replace wool and milk
1073 if self.gotten == true then
1074 self.gotten = false
1075 self.object:set_properties(self)
1076 end
1077 end
1583
1584 local oldnode = what or ""
1585 local newnode = with
1586
1587 -- pass actual node name when using table or groups
1588 if type(oldnode) == "table"
1589 or oldnode:find("group:") then
1590 oldnode = minetest.get_node(pos).name
1591 end
1592
1593 if self:on_replace(pos, oldnode, newnode) == false then
1594 return
1595 end
1596 end
1597
1598 minetest.set_node(pos, {name = with})
10781599 end
10791600 end
10801601
10811602
10821603 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1604 function mob_class:day_docile()
10841605
10851606 if self.docile_by_day == false then
10861607
10971618
10981619 local los_switcher = false
10991620 local height_switcher = false
1100
1101 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1102 local smart_mobs = function(self, s, p, dist, dtime)
1621 local can_dig_drop = function(pos)
1622
1623 if minetest.is_protected(pos, "") then
1624 return false
1625 end
1626
1627 local node = node_ok(pos, "air").name
1628 local ndef = minetest.registered_nodes[node]
1629
1630 if node ~= "ignore"
1631 and ndef
1632 and ndef.drawtype ~= "airlike"
1633 and not ndef.groups.level
1634 and not ndef.groups.unbreakable
1635 and not ndef.groups.liquid then
1636
1637 local drops = minetest.get_node_drops(node)
1638
1639 for _, item in ipairs(drops) do
1640
1641 minetest.add_item({
1642 x = pos.x - 0.5 + random(),
1643 y = pos.y - 0.5 + random(),
1644 z = pos.z - 0.5 + random()
1645 }, item)
1646 end
1647
1648 minetest.remove_node(pos)
1649
1650 return true
1651 end
1652
1653 return false
1654 end
1655
1656
1657 local pathfinder_mod = minetest.get_modpath("pathfinder")
1658 -- path finding and smart mob routine by rnd,
1659 -- line_of_sight and other edits by Elkien3
1660 function mob_class:smart_mobs(s, p, dist, dtime)
11031661
11041662 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1663 local target_pos = p
1664
11071665
11081666 -- is it becoming stuck?
11091667 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
11761734 end, self)
11771735 end
11781736
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1737 if abs(vsubtract(s,target_pos).y) > self.stepheight then
11801738
11811739 if height_switcher then
11821740 use_pathfind = true
11891747 end
11901748 end
11911749
1750 -- lets try find a path, first take care of positions
1751 -- since pathfinder is very sensitive
11921752 if use_pathfind then
1193 -- lets try find a path, first take care of positions
1194 -- since pathfinder is very sensitive
1195 local sheight = self.collisionbox[5] - self.collisionbox[2]
11961753
11971754 -- round position to center of node to avoid stuck in walls
11981755 -- also adjust height for player models!
11991756 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
12011757 s.z = floor(s.z + 0.5)
12021758
12031759 local ssight, sground = minetest.line_of_sight(s, {
12151771 p1.z = floor(p1.z + 0.5)
12161772
12171773 local dropheight = 6
1774
12181775 if self.fear_height ~= 0 then dropheight = self.fear_height end
12191776
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1221
1777 local jumpheight = 0
1778
1779 if self.jump and self.jump_height >= 4 then
1780 jumpheight = min(ceil(self.jump_height / 4), 4)
1781
1782 elseif self.stepheight > 0.5 then
1783 jumpheight = 1
1784 end
1785
1786 if pathfinder_mod then
1787 self.path.way = pathfinder.find_path(s, p1, self, dtime)
1788 else
1789 self.path.way = minetest.find_path(s, p1, 16, jumpheight,
1790 dropheight, "Dijkstra")
1791 end
12221792 --[[
12231793 -- show path using particles
12241794 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1795
1796 print("-- path length:" .. tonumber(#self.path.way))
1797
12261798 for _,pos in pairs(self.path.way) do
12271799 minetest.add_particle({
12281800 pos = pos,
12391811 ]]
12401812
12411813 self.state = ""
1242 do_attack(self, self.attack)
1814
1815 if self.attack then
1816 self:do_attack(self.attack)
1817 end
12431818
12441819 -- no path found, try something else
12451820 if not self.path.way then
12491824 -- lets make way by digging/building if not accessible
12501825 if self.pathfinding == 2 and mobs_griefing then
12511826
1252 -- is player higher than mob?
1253 if s.y < p1.y then
1827 -- is player more than 1 block higher than mob?
1828 if p1.y > (s.y + 1) then
12541829
12551830 -- build upwards
12561831 if not minetest.is_protected(s, "") then
12581833 local ndef1 = minetest.registered_nodes[self.standing_in]
12591834
12601835 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1261
1262 minetest.set_node(s, {name = mobs.fallback_node})
1836 minetest.set_node(s, {name = mobs.fallback_node})
12631837 end
12641838 end
12651839
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1840 local sheight = ceil(self.collisionbox[5]) + 1
12671841
12681842 -- assume mob is 2 blocks high so it digs above its head
12691843 s.y = s.y + sheight
12701844
12711845 -- remove one block above to make room to jump
1272 if not minetest.is_protected(s, "") then
1273
1274 local node1 = node_ok(s, "air").name
1275 local ndef1 = minetest.registered_nodes[node1]
1276
1277 if node1 ~= "air"
1278 and node1 ~= "ignore"
1279 and ndef1
1280 and not ndef1.groups.level
1281 and not ndef1.groups.unbreakable
1282 and not ndef1.groups.liquid then
1283
1284 minetest.set_node(s, {name = "air"})
1285 minetest.add_item(s, ItemStack(node1))
1286
1287 end
1288 end
1846 can_dig_drop(s)
12891847
12901848 s.y = s.y - sheight
12911849 self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
1850
1851 -- is player more than 1 block lower than mob
1852 elseif p1.y < (s.y - 1) then
1853
1854 -- dig down
1855 s.y = s.y - self.collisionbox[4] - 0.2
1856
1857 can_dig_drop(s)
12921858
12931859 else -- dig 2 blocks to make door toward player direction
12941860
12991865 z = s.z + sin(yaw1)
13001866 }
13011867
1302 if not minetest.is_protected(p1, "") then
1303
1304 local node1 = node_ok(p1, "air").name
1305 local ndef1 = minetest.registered_nodes[node1]
1306
1307 if node1 ~= "air"
1308 and node1 ~= "ignore"
1309 and ndef1
1310 and not ndef1.groups.level
1311 and not ndef1.groups.unbreakable
1312 and not ndef1.groups.liquid then
1313
1314 minetest.add_item(p1, ItemStack(node1))
1315 minetest.set_node(p1, {name = "air"})
1316 end
1317
1318 p1.y = p1.y + 1
1319 node1 = node_ok(p1, "air").name
1320 ndef1 = minetest.registered_nodes[node1]
1321
1322 if node1 ~= "air"
1323 and node1 ~= "ignore"
1324 and ndef1
1325 and not ndef1.groups.level
1326 and not ndef1.groups.unbreakable
1327 and not ndef1.groups.liquid then
1328
1329 minetest.add_item(p1, ItemStack(node1))
1330 minetest.set_node(p1, {name = "air"})
1331 end
1332
1333 end
1868 -- dig bottom node first incase of door
1869 can_dig_drop(p1)
1870
1871 p1.y = p1.y + 1
1872
1873 can_dig_drop(p1)
13341874 end
13351875 end
13361876
13371877 -- will try again in 2 second
13381878 self.path.stuck_timer = stuck_timeout - 2
13391879
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1880 elseif s.y < p1.y and (not self.fly) then
1881 self:do_jump() --add jump to pathfinding
1882 self.path.following = true
13421883 else
13431884 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1885 if self.attack then
1886 self:mob_sound(self.sounds.war_cry)
1887 else
1888 self:mob_sound(self.sounds.random)
1889 end
1890
1891 self:set_velocity(self.walk_velocity)
13461892
13471893 -- follow path now that it has it
13481894 self.path.following = true
13511897 end
13521898
13531899
1354 -- specific attacks
1355 local specific_attack = function(list, what)
1356
1357 -- no list so attack default (player, animals etc.)
1358 if list == nil then
1359 return true
1360 end
1361
1362 -- found entity on list to attack?
1363 for no = 1, #list do
1364
1365 if list[no] == what then
1900 -- peaceful player privilege support
1901 local function is_peaceful_player(player)
1902
1903 if peaceful_player_enabled then
1904
1905 local player_name = player:get_player_name()
1906
1907 if player_name
1908 and minetest.check_player_privs(player_name, "peaceful_player") then
13661909 return true
13671910 end
13681911 end
13711914 end
13721915
13731916
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1917 -- general attack function for all mobs
1918 function mob_class:general_attack()
13761919
13771920 -- return if already attacking, passive or docile during day
13781921 if self.passive
1922 or self.state == "runaway"
13791923 or self.state == "attack"
1380 or day_docile(self) then
1924 or self:day_docile() then
13811925 return
13821926 end
13831927
1384 local s = self.object:get_pos()
1928 local s = self.object:get_pos() ; if not s then return end
13851929 local objs = minetest.get_objects_inside_radius(s, self.view_range)
13861930
13871931 -- remove entities we aren't interested in
13921936 -- are we a player?
13931937 if objs[n]:is_player() then
13941938
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1939 -- if player invisible or mob cannot attack then remove from list
1940 if not damage_enabled
1941 or self.attack_players == false
13971942 or (self.owner and self.type ~= "monster")
1398 or mobs.invis[objs[n]:get_player_name()]
1399 or not specific_attack(self.specific_attack, "player") then
1943 or is_invisible(self, objs[n]:get_player_name())
1944 or (self.specific_attack
1945 and not check_for("player", self.specific_attack)) then
14001946 objs[n] = nil
14011947 --print("- pla", n)
14021948 end
14091955 or (not self.attack_animals and ent.type == "animal")
14101956 or (not self.attack_monsters and ent.type == "monster")
14111957 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1958 or (self.specific_attack
1959 and not check_for(ent.name, self.specific_attack)) then
14131960 objs[n] = nil
14141961 --print("- mob", n, self.name, ent.name)
14151962 end
14391986 -- choose closest player to attack that isnt self
14401987 if dist ~= 0
14411988 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
1989 and self:line_of_sight(sp, p, 2) == true
1990 and not is_peaceful_player(player) then
14431991 min_dist = dist
14441992 min_player = player
14451993 end
14461994 end
14471995
14481996 -- attack closest player or mob
1449 if min_player and random(1, 100) > self.attack_chance then
1450 do_attack(self, min_player)
1451 end
1452 end
1453
1454
1455 -- specific runaway
1456 local specific_runaway = function(list, what)
1457
1458 -- no list so do not run
1459 if list == nil then
1460 return false
1461 end
1462
1463 -- found entity on list to attack?
1464 for no = 1, #list do
1465
1466 if list[no] == what then
1467 return true
1468 end
1469 end
1470
1471 return false
1997 if min_player and random(100) > self.attack_chance then
1998 self:do_attack(min_player)
1999 end
14722000 end
14732001
14742002
14752003 -- find someone to runaway from
1476 local runaway_from = function(self)
2004 function mob_class:do_runaway_from()
14772005
14782006 if not self.runaway_from then
14792007 return
14802008 end
14812009
1482 local s = self.object:get_pos()
2010 local s = self.object:get_pos() ; if not s then return end
14832011 local p, sp, dist, pname
14842012 local player, obj, min_player, name
14852013 local min_dist = self.view_range + 1
14912019
14922020 pname = objs[n]:get_player_name()
14932021
1494 if mobs.invis[pname]
2022 if is_invisible(self, pname)
14952023 or self.owner == pname then
14962024
14972025 name = ""
15102038
15112039 -- find specific mob to runaway from
15122040 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
2041 and (self.runaway_from and check_for(name, self.runaway_from)) then
2042
15162043 sp = s
2044 p = player and player:get_pos() or s
15172045
15182046 -- aim higher to make looking up hills more realistic
15192047 p.y = p.y + 1
15232051
15242052 -- choose closest player/mob to runaway from
15252053 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
2054 and self:line_of_sight(sp, p, 2) == true then
15272055 min_dist = dist
15282056 min_player = player
15292057 end
15322060
15332061 if min_player then
15342062
1535 local lp = player:get_pos()
1536 local vec = {
1537 x = lp.x - s.x,
1538 y = lp.y - s.y,
1539 z = lp.z - s.z
1540 }
1541
1542 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1543
1544 if lp.x > s.x then
1545 yaw = yaw + pi
1546 end
1547
1548 yaw = set_yaw(self, yaw, 4)
2063 yaw_to_pos(self, min_player:get_pos(), 3)
2064
15492065 self.state = "runaway"
15502066 self.runaway_timer = 3
15512067 self.following = nil
15542070
15552071
15562072 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
2073 function mob_class:follow_flop()
15582074
15592075 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
2076 if (self.follow ~= "" or self.order == "follow")
15622077 and not self.following
15632078 and self.state ~= "attack"
15642079 and self.state ~= "runaway" then
15652080
1566 local s = self.object:get_pos()
2081 local s = self.object:get_pos() ; if not s then return end
15672082 local players = minetest.get_connected_players()
15682083
15692084 for n = 1, #players do
15702085
15712086 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
2087 and not is_invisible(self, players[n]:get_player_name()) then
15732088
15742089 self.following = players[n]
15752090
15902105 self.following = nil
15912106 end
15922107 else
1593 -- stop following player if not holding specific item
2108 -- stop following player if not holding specific item or mob is horny
15942109 if self.following
15952110 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
2111 and (self:follow_holding(self.following) == false
2112 or self.horny) then
15972113 self.following = nil
15982114 end
15992115
16222138 if dist > self.view_range then
16232139 self.following = nil
16242140 else
1625 local vec = {
1626 x = p.x - s.x,
1627 z = p.z - s.z
1628 }
1629
1630 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1631
1632 if p.x > s.x then yaw = yaw + pi end
1633
1634 yaw = set_yaw(self, yaw, 6)
2141 yaw_to_pos(self, p)
16352142
16362143 -- anyone but standing npc's can move along
16372144 if dist > self.reach
16382145 and self.order ~= "stand" then
16392146
1640 set_velocity(self, self.walk_velocity)
2147 self:set_velocity(self.walk_velocity)
16412148
16422149 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
2150 self:set_animation("walk")
16442151 end
16452152 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
2153 self:set_velocity(0)
2154 self:set_animation("stand")
16482155 end
16492156
16502157 return
16542161
16552162 -- swimmers flop when out of their element, and swim again when back in
16562163 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
2164
2165 if not self:attempt_flight_correction() then
16592166
16602167 self.state = "flop"
2168
2169 -- do we have a custom on_flop function?
2170 if self.on_flop then
2171
2172 if self:on_flop(self) then
2173 return
2174 end
2175 end
2176
16612177 self.object:set_velocity({x = 0, y = -5, z = 0})
16622178
1663 set_animation(self, "stand")
2179 self:set_animation("stand")
16642180
16652181 return
2182
16662183 elseif self.state == "flop" then
16672184 self.state = "stand"
16682185 end
16712188
16722189
16732190 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
2191 function mob_class:dogswitch(dtime)
16752192
16762193 -- switch mode not activated
16772194 if not self.dogshoot_switch
17002217
17012218
17022219 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
2220 function mob_class:do_states(dtime)
2221
2222 local yaw = self.object:get_yaw() ; if not yaw then return end
17062223
17072224 if self.state == "stand" then
17082225
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
2226 if self.randomly_turn and random(4) == 1 then
2227
2228 local lp
17122229 local s = self.object:get_pos()
17132230 local objs = minetest.get_objects_inside_radius(s, 3)
17142231
17222239
17232240 -- look at any players nearby, otherwise turn randomly
17242241 if lp then
1725
1726 local vec = {
1727 x = lp.x - s.x,
1728 z = lp.z - s.z
1729 }
1730
1731 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1732
1733 if lp.x > s.x then yaw = yaw + pi end
2242 yaw = yaw_to_pos(self, lp)
17342243 else
17352244 yaw = yaw + random(-0.5, 0.5)
17362245 end
17372246
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
2247 yaw = self:set_yaw(yaw, 8)
2248 end
2249
2250 self:set_velocity(0)
2251 self:set_animation("stand")
17432252
17442253 -- mobs ordered to stand stay standing
17452254 if self.order ~= "stand"
17462255 and self.walk_chance ~= 0
17472256 and self.facing_fence ~= true
1748 and random(1, 100) <= self.walk_chance
1749 and is_at_cliff(self) == false then
1750
1751 set_velocity(self, self.walk_velocity)
2257 and random(100) <= self.walk_chance
2258 and self.at_cliff == false then
2259
2260 self:set_velocity(self.walk_velocity)
17522261 self.state = "walk"
1753 set_animation(self, "walk")
2262 self:set_animation("walk")
17542263 end
17552264
17562265 elseif self.state == "walk" then
17572266
17582267 local s = self.object:get_pos()
1759 local lp = nil
2268 local lp
17602269
17612270 -- is there something I need to avoid?
17622271 if self.water_damage > 0
17632272 and self.lava_damage > 0 then
17642273
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
2274 lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"})
17662275
17672276 elseif self.water_damage > 0 then
17682277
17702279
17712280 elseif self.lava_damage > 0 then
17722281
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
2282 lp = minetest.find_node_near(s, 1, {"group:igniter"})
17742283 end
17752284
17762285 if lp then
17772286
1778 -- if mob in water or lava then look for land
1779 if (self.lava_damage
1780 and minetest.registered_nodes[self.standing_in].groups.lava)
1781 or (self.water_damage
1782 and minetest.registered_nodes[self.standing_in].groups.water) then
1783
1784 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1785 "group:sand", node_ice, node_snowblock})
2287 -- if mob in dangerous node then look for land
2288 if not is_node_dangerous(self, self.standing_in) then
2289
2290 lp = minetest.find_nodes_in_area_under_air(
2291 {s.x - 5, s.y - 1, s.z - 5},
2292 {s.x + 5, s.y + 2, s.z + 5},
2293 {"group:soil", "group:stone", "group:sand",
2294 node_ice, node_snowblock})
2295
2296 -- select position of random block to climb onto
2297 lp = #lp > 0 and lp[random(#lp)]
17862298
17872299 -- did we find land?
17882300 if lp then
17892301
1790 local vec = {
1791 x = lp.x - s.x,
1792 z = lp.z - s.z
1793 }
1794
1795 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1796
1797 if lp.x > s.x then yaw = yaw + pi end
1798
1799 -- look towards land and jump/move in that direction
1800 yaw = set_yaw(self, yaw, 6)
1801 do_jump(self)
1802 set_velocity(self, self.walk_velocity)
2302 yaw = yaw_to_pos(self, lp)
2303
2304 self:do_jump()
2305 self:set_velocity(self.walk_velocity)
18032306 else
18042307 yaw = yaw + random(-0.5, 0.5)
18052308 end
1806
1807 else
1808
1809 local vec = {
1810 x = lp.x - s.x,
1811 z = lp.z - s.z
1812 }
1813
1814 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1815
1816 if lp.x > s.x then yaw = yaw + pi end
1817 end
1818
1819 yaw = set_yaw(self, yaw, 8)
2309 end
2310
2311 yaw = self:set_yaw(yaw, 8)
18202312
18212313 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
2314 elseif self.randomly_turn and random(100) <= 30 then
18232315
18242316 yaw = yaw + random(-0.5, 0.5)
18252317
1826 yaw = set_yaw(self, yaw, 8)
2318 yaw = self:set_yaw(yaw, 8)
2319
2320 -- for flying/swimming mobs randomly move up and down also
2321 if self.fly_in
2322 and not self.following then
2323 self:attempt_flight_correction(true)
2324 end
18272325 end
18282326
18292327 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
18322328 if self.facing_fence == true
1833 or temp_is_cliff
1834 or random(1, 100) <= 30 then
1835
1836 set_velocity(self, 0)
1837 self.state = "stand"
1838 set_animation(self, "stand")
2329 or self.at_cliff
2330 or random(100) <= self.stand_chance then
2331
2332 -- don't stand if mob flies and keep_flying set
2333 if (self.fly and not self.keep_flying)
2334 or not self.fly then
2335
2336 self:set_velocity(0)
2337 self.state = "stand"
2338 self:set_animation("stand", true)
2339 end
18392340 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
2341 self:set_velocity(self.walk_velocity)
2342
2343 if self:flight_check()
18432344 and self.animation
18442345 and self.animation.fly_start
18452346 and self.animation.fly_end then
1846 set_animation(self, "fly")
2347 self:set_animation("fly")
18472348 else
1848 set_animation(self, "walk")
2349 self:set_animation("walk")
18492350 end
18502351 end
18512352
18562357
18572358 -- stop after 5 seconds or when at cliff
18582359 if self.runaway_timer > 5
1859 or is_at_cliff(self)
2360 or self.at_cliff
18602361 or self.order == "stand" then
18612362 self.runaway_timer = 0
1862 set_velocity(self, 0)
2363 self:set_velocity(0)
18632364 self.state = "stand"
1864 set_animation(self, "stand")
2365 self:set_animation("stand")
18652366 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
2367 self:set_velocity(self.run_velocity)
2368 self:set_animation("walk")
18682369 end
18692370
18702371 -- attack routines (explode, dogfight, shoot, dogshoot)
18712372 elseif self.state == "attack" then
18722373
1873 -- calculate distance from mob and enemy
2374 -- get mob and enemy positions and distance between
18742375 local s = self.object:get_pos()
1875 local p = self.attack:get_pos() or s
1876 local dist = get_distance(p, s)
1877
1878 -- stop attacking if player invisible or out of range
2376 local p = self.attack and self.attack:get_pos()
2377 local dist = p and get_distance(p, s) or 500
2378
2379 -- stop attacking if player out of range or invisible
18792380 if dist > self.view_range
18802381 or not self.attack
18812382 or not self.attack:get_pos()
18822383 or self.attack:get_hp() <= 0
1883 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1884
1885 -- print(" ** stop attacking **", dist, self.view_range)
2384 or (self.attack:is_player()
2385 and is_invisible(self, self.attack:get_player_name())) then
2386
2387 --print(" ** stop attacking **", dist, self.view_range)
2388
18862389 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
2390 self:set_velocity(0)
2391 self:set_animation("stand")
18892392 self.attack = nil
18902393 self.v_start = false
18912394 self.timer = 0
18972400
18982401 if self.attack_type == "explode" then
18992402
1900 local vec = {
1901 x = p.x - s.x,
1902 z = p.z - s.z
1903 }
1904
1905 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1906
1907 if p.x > s.x then yaw = yaw + pi end
1908
1909 yaw = set_yaw(self, yaw)
2403 yaw = yaw_to_pos(self, p)
19102404
19112405 local node_break_radius = self.explosion_radius or 1
19122406 local entity_damage_radius = self.explosion_damage_radius
19132407 or (node_break_radius * 2)
19142408
2409 -- look a little higher to fix raycast
2410 s.y = s.y + 0.5 ; p.y = p.y + 0.5
2411
19152412 -- start timer when in reach and line of sight
19162413 if not self.v_start
19172414 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
2415 and self:line_of_sight(s, p, 2) then
19192416
19202417 self.v_start = true
19212418 self.timer = 0
19222419 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
2420 self:mob_sound(self.sounds.fuse)
2421
2422 --print("=== explosion timer started", self.explosion_timer)
19252423
19262424 -- stop timer if out of reach or direct line of sight
19272425 elseif self.allow_fuse_reset
19282426 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
2427 and (dist > self.reach or not self:line_of_sight(s, p, 2)) then
2428
2429 --print("=== explosion timer stopped")
2430
19312431 self.v_start = false
19322432 self.timer = 0
19332433 self.blinktimer = 0
19342434 self.blinkstatus = false
1935 self.object:settexturemod("")
2435 self.object:set_texture_mod("")
19362436 end
19372437
19382438 -- walk right up to player unless the timer is active
19392439 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
2440 self:set_velocity(0)
19412441 else
1942 set_velocity(self, self.run_velocity)
2442 self:set_velocity(self.run_velocity)
19432443 end
19442444
19452445 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
2446 self:set_animation("run")
19472447 else
1948 set_animation(self, "walk")
2448 self:set_animation("walk")
19492449 end
19502450
19512451 if self.v_start then
19582458 self.blinktimer = 0
19592459
19602460 if self.blinkstatus then
1961 self.object:settexturemod("")
2461
2462 self.object:set_texture_mod(self.texture_mods)
19622463 else
1963 self.object:settexturemod("^[brighten")
2464
2465 self.object:set_texture_mod(self.texture_mods
2466 .. "^[brighten")
19642467 end
19652468
19662469 self.blinkstatus = not self.blinkstatus
19672470 end
19682471
1969 -- print ("=== explosion timer", self.timer)
2472 --print("=== explosion timer", self.timer)
19702473
19712474 if self.timer > self.explosion_timer then
19722475
19792482 node_break_radius = 1
19802483 end
19812484
1982 self.object:remove()
2485 remove_mob(self, true)
19832486
19842487 if minetest.get_modpath("tnt") and tnt and tnt.boom
19852488 and not minetest.is_protected(pos, "") then
19872490 tnt.boom(pos, {
19882491 radius = node_break_radius,
19892492 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
2493 sound = self.sounds.explode
19912494 })
19922495 else
19932496
19982501 })
19992502
20002503 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2504
2505 effect(pos, 32, "tnt_smoke.png", nil, nil,
2506 node_break_radius, 1, 0)
20022507 end
20032508
2004 return
2509 return true
20052510 end
20062511 end
20072512
20082513 elseif self.attack_type == "dogfight"
2009 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2010 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2514 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
2515 or (self.attack_type == "dogshoot" and dist <= self.reach
2516 and self:dogswitch() == 0) then
20112517
20122518 if self.fly
20132519 and dist > self.reach then
20182524 local p_y = floor(p2.y + 1)
20192525 local v = self.object:get_velocity()
20202526
2021 if flight_check(self, s) then
2527 if self:flight_check() then
20222528
20232529 if me_y < p_y then
20242530
20542560 })
20552561 end
20562562 end
2057
20582563 end
20592564
20602565 -- rnd: new movement direction
20762581 return
20772582 end
20782583
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2584 if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
20802585 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2586 table_remove(self.path.way, 1)
20822587 end
20832588
20842589 -- set new temporary target
20852590 p = {x = p1.x, y = p1.y, z = p1.z}
20862591 end
20872592
2088 local vec = {
2089 x = p.x - s.x,
2090 z = p.z - s.z
2091 }
2092
2093 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2094
2095 if p.x > s.x then yaw = yaw + pi end
2096
2097 yaw = set_yaw(self, yaw)
2593 yaw = yaw_to_pos(self, p)
20982594
20992595 -- move towards enemy if beyond mob reach
21002596 if dist > self.reach then
21032599 if self.pathfinding -- only if mob has pathfinding enabled
21042600 and enable_pathfinding then
21052601
2106 smart_mobs(self, s, p, dist, dtime)
2602 self:smart_mobs(s, p, dist, dtime)
21072603 end
21082604
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2605 -- distance padding to stop spinning mob
2606 local pad = abs(p.x - s.x) + abs(p.z - s.z)
2607
2608 if self.at_cliff or pad < 0.2 then
2609
2610 self:set_velocity(0)
2611 self:set_animation("stand")
21132612 else
21142613
21152614 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2615 self:set_velocity(self.walk_velocity)
21172616 else
2118 set_velocity(self, self.run_velocity)
2617 self:set_velocity(self.run_velocity)
21192618 end
21202619
21212620 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2621 self:set_animation("run")
21232622 else
2124 set_animation(self, "walk")
2623 self:set_animation("walk")
21252624 end
21262625 end
2127
21282626 else -- rnd: if inside reach range
21292627
21302628 self.path.stuck = false
21312629 self.path.stuck_timer = 0
21322630 self.path.following = false -- not stuck anymore
21332631
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2632 self:set_velocity(0)
2633
2634 if self.timer > 1 then
2635
2636 -- no custom attack or custom attack returns true to continue
2637 if not self.custom_attack
2638 or self:custom_attack(self, p) == true then
21392639
21402640 self.timer = 0
2141
2142 -- if self.double_melee_attack
2143 -- and random(1, 2) == 1 then
2144 -- set_animation(self, "punch2")
2145 -- else
2146 set_animation(self, "punch")
2147 -- end
2641 self:set_animation("punch")
21482642
21492643 local p2 = p
21502644 local s2 = s
21522646 p2.y = p2.y + .5
21532647 s2.y = s2.y + .5
21542648
2155 if line_of_sight(self, p2, s2) == true then
2649 if self:line_of_sight(p2, s2) == true then
21562650
21572651 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2652 self:mob_sound(self.sounds.attack)
21592653
21602654 -- punch player (or what player is attached to)
21612655 local attached = self.attack:get_attach()
2656
21622657 if attached then
21632658 self.attack = attached
21642659 end
2660
2661 local dgroup = self.damage_group or "fleshy"
2662
21652663 self.attack:punch(self.object, 1.0, {
21662664 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2665 damage_groups = {[dgroup] = self.damage}
21682666 }, nil)
21692667 end
21702668 end
2171 else -- call custom attack every second
2172 if self.custom_attack
2173 and self.timer > 1 then
2174
2175 self.timer = 0
2176
2177 self.custom_attack(self, p)
2178 end
21792669 end
21802670 end
21812671
21822672 elseif self.attack_type == "shoot"
2183 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2184 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2673 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
2674 or (self.attack_type == "dogshoot" and dist > self.reach and
2675 self:dogswitch() == 0) then
21852676
21862677 p.y = p.y - .5
21872678 s.y = s.y + .5
21882679
2189 local dist = get_distance(p, s)
2190 local vec = {
2191 x = p.x - s.x,
2192 y = p.y - s.y,
2193 z = p.z - s.z
2194 }
2195
2196 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2197
2198 if p.x > s.x then yaw = yaw + pi end
2199
2200 yaw = set_yaw(self, yaw)
2201
2202 set_velocity(self, 0)
2680 local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
2681
2682 yaw = yaw_to_pos(self, p)
2683
2684 self:set_velocity(0)
22032685
22042686 if self.shoot_interval
22052687 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2688 and random(100) <= 60 then
22072689
22082690 self.timer = 0
2209 set_animation(self, "shoot")
2691 self:set_animation("shoot")
22102692
22112693 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2694 self:mob_sound(self.sounds.shoot_attack)
22132695
22142696 local p = self.object:get_pos()
22152697
22402722
22412723
22422724 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2725 function mob_class:falling(pos)
2726
2727 if self.fly or self.disable_falling then
22462728 return
22472729 end
22482730
22492731 -- floating in water (or falling)
22502732 local v = self.object:get_velocity()
22512733
2252 if v.y > 0 then
2253
2254 -- apply gravity when moving up
2255 self.object:set_acceleration({
2256 x = 0,
2257 y = -10,
2258 z = 0
2259 })
2260
2261 elseif v.y <= 0 and v.y > self.fall_speed then
2262
2263 -- fall downwards at set speed
2264 self.object:set_acceleration({
2265 x = 0,
2266 y = self.fall_speed,
2267 z = 0
2268 })
2269 else
2270 -- stop accelerating once max fall speed hit
2271 self.object:set_acceleration({x = 0, y = 0, z = 0})
2272 end
2273
2274 -- in water then float up
2275 if self.standing_in
2276 and minetest.registered_nodes[self.standing_in].groups.water then
2277
2278 if self.floats == 1 then
2279
2280 self.object:set_acceleration({
2281 x = 0,
2282 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2283 z = 0
2284 })
2285 end
2734 -- sanity check
2735 if not v then return end
2736
2737 local fall_speed = self.fall_speed
2738
2739 -- in water then use liquid viscosity for float/sink speed
2740 if self.floats == 1 and self.standing_in
2741 and minetest.registered_nodes[self.standing_in].groups.liquid then
2742
2743 local visc = min(
2744 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2745
2746 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2747 fall_speed = -1.2 / visc
22862748 else
22872749
22882750 -- fall damage onto solid ground
22972759
22982760 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992761
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2762 if self:check_for_death({type = "fall"}) then
2763 return true
23022764 end
23032765 end
23042766
23052767 self.old_y = self.object:get_pos().y
23062768 end
23072769 end
2770
2771 -- fall at set speed
2772 self.object:set_acceleration({x = 0, y = fall_speed, z = 0})
23082773 end
23092774
23102775
23122777 local tr = minetest.get_modpath("toolranks")
23132778
23142779 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2780 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162781
23172782 -- mob health check
23182783 if self.health <= 0 then
2319 return
2784 return true
23202785 end
23212786
23222787 -- custom punch function
23232788 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2789 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2790 return true
23262791 end
23272792
23282793 -- error checking when mod profiling is enabled
23292794 if not tool_capabilities then
2330 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2331 return
2332 end
2333
2334 -- is mob protected?
2335 if self.protected and hitter:is_player()
2336 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2337 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2338 return
2795
2796 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2797
2798 return true
2799 end
2800
2801 -- is mob protected
2802 if self.protected then
2803
2804 -- did player hit mob and if so is it in protected area
2805 if hitter:is_player() then
2806
2807 local player_name = hitter:get_player_name()
2808
2809 if player_name ~= self.owner
2810 and minetest.is_protected(self.object:get_pos(), player_name) then
2811
2812 minetest.chat_send_player(hitter:get_player_name(),
2813 S("Mob has been protected!"))
2814
2815 return true
2816 end
2817
2818 -- if protection is on level 2 then dont let arrows harm mobs
2819 elseif self.protected == 2 then
2820
2821 local ent = hitter and hitter:get_luaentity()
2822
2823 if ent and ent._is_arrow then
2824
2825 return true -- arrow entity
2826
2827 elseif not ent then
2828
2829 return true -- non entity
2830 end
2831 end
23392832 end
23402833
23412834 local weapon = hitter:get_wielded_item()
23672860 end
23682861
23692862 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2863 * tmp * ((armor[group] or 0) / 100.0)
23712864 end
23722865 end
23732866
23772870 if self.immune_to[n][1] == weapon_def.name then
23782871
23792872 damage = self.immune_to[n][2] or 0
2873
23802874 break
23812875
2382 -- if "all" then no tool does damage unless it's specified in list
2876 -- if "all" then no tools deal damage unless it's specified in list
23832877 elseif self.immune_to[n][1] == "all" then
23842878 damage = self.immune_to[n][2] or 0
23852879 end
23862880 end
23872881
2882 --print("Mob Damage is", damage)
2883
23882884 -- healing
23892885 if damage <= -1 then
2886
23902887 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2888
2889 return true
2890 end
23952891
23962892 if use_cmi
23972893 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2894 return true
23992895 end
24002896
24012897 -- add weapon wear
24132909 end
24142910 end
24152911
2416 if tr then
2417 if weapon_def.original_description then
2418 weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
2419 end
2912 if tr and weapon_def.original_description then
2913 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24202914 else
24212915 weapon:add_wear(wear)
24222916 end
24262920 -- only play hit sound and show blood effects if damage is 1 or over
24272921 if damage >= 1 then
24282922
2429 -- weapon sounds
2430 if weapon_def.sounds then
2431
2432 local s = random(0, #weapon_def.sounds)
2433
2434 minetest.sound_play(weapon_def.sounds[s], {
2435 object = self.object,
2436 max_hear_distance = 8
2437 })
2438 else
2439 minetest.sound_play("default_punch", {
2440 object = self.object,
2441 max_hear_distance = 5
2442 })
2443 end
2923 -- select tool use sound if found, or fallback to default
2924 local snd = weapon_def.sound and weapon_def.sound.use
2925 or "default_punch"
2926
2927 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442928
24452929 -- blood_particles
24462930 if not disable_blood and self.blood_amount > 0 then
24472931
24482932 local pos = self.object:get_pos()
2933 local blood = self.blood_texture
2934 local amount = self.blood_amount
24492935
24502936 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2937
2938 -- lots of damage = more blood :)
2939 if damage > 10 then
2940 amount = self.blood_amount * 2
2941 end
24512942
24522943 -- do we have a single blood texture or multiple?
24532944 if type(self.blood_texture) == "table" then
2454
2455 local blood = self.blood_texture[random(1, #self.blood_texture)]
2456
2457 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2458 else
2459 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2460 end
2945 blood = self.blood_texture[random(#self.blood_texture)]
2946 end
2947
2948 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612949 end
24622950
24632951 -- do damage
24672955 local hot = tool_capabilities and tool_capabilities.damage_groups
24682956 and tool_capabilities.damage_groups.fire
24692957
2470 if check_for_death(self, {type = "punch",
2471 puncher = hitter, hot = hot}) then
2472 return
2473 end
2474
2475 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2476 minetest.after(0.1, function()
2477
2478 if not self.object:get_luaentity() then return end
2479
2480 self.object:settexturemod("^[colorize:#c9900070")
2481
2482 core.after(0.3, function()
2483 self.object:settexturemod("")
2484 end)
2485 end) ]]
2486
2958 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2959 return true
2960 end
24872961 end -- END if damage
24882962
24892963 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2964 if self.knock_back and tflp >= punch_interval then
24922965
24932966 local v = self.object:get_velocity()
2967
2968 -- sanity check
2969 if not v then return true end
2970
24942971 local kb = damage or 1
24952972 local up = 2
24962973
25042981 dir = dir or {x = 0, y = 0, z = 0}
25052982
25062983 -- use tool knockback value or default
2507 kb = tool_capabilities.damage_groups["knockback"] or (kb * 1.5)
2508
2509 self.object:set_velocity({
2510 x = dir.x * kb,
2511 y = up,
2512 z = dir.z * kb
2513 })
2984 kb = tool_capabilities.damage_groups["knockback"] or kb
2985
2986 self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb})
25142987
25152988 self.pause_timer = 0.25
25162989 end
25202993 and self.order ~= "stand" then
25212994
25222995 local lp = hitter:get_pos()
2523 local s = self.object:get_pos()
2524 local vec = {
2525 x = lp.x - s.x,
2526 y = lp.y - s.y,
2527 z = lp.z - s.z
2528 }
2529
2530 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2531
2532 if lp.x > s.x then
2533 yaw = yaw + pi
2534 end
2535
2536 yaw = set_yaw(self, yaw, 6)
2996 local yaw = yaw_to_pos(self, lp, 3)
2997
25372998 self.state = "runaway"
25382999 self.runaway_timer = 0
25393000 self.following = nil
25473008 and self.child == false
25483009 and self.attack_players == true
25493010 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3011 and not is_invisible(self, name)
3012 and self.object ~= hitter then
25513013
25523014 -- attack whoever punched mob
25533015 self.state = ""
2554 do_attack(self, hitter)
3016 self:do_attack(hitter)
25553017
25563018 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3019 local objs = minetest.get_objects_inside_radius(
3020 hitter:get_pos(), self.view_range)
3021 local obj
25593022
25603023 for n = 1, #objs do
25613024
25633026
25643027 if obj and obj._cmi_is_mob then
25653028
2566 -- only alert members of same mob
3029 -- only alert members of same mob and assigned helper
25673030 if obj.group_attack == true
25683031 and obj.state ~= "attack"
25693032 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3033 and (obj.name == self.name
3034 or obj.name == self.group_helper) then
3035
3036 obj:do_attack(hitter)
25723037 end
25733038
25743039 -- have owned mobs attack player threat
25753040 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3041 obj:do_attack(self.object)
25773042 end
25783043 end
25793044 end
25823047
25833048
25843049 -- get entity staticdata
2585 local mob_staticdata = function(self)
3050 function mob_class:mob_staticdata()
3051
3052 -- this handles mob count for mobs activated, unloaded, reloaded
3053 if active_limit > 0 and self.active_toggle then
3054 active_mobs = active_mobs + self.active_toggle
3055 self.active_toggle = -self.active_toggle
3056 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3057 end
25863058
25873059 -- remove mob when out of range unless tamed
25883060 if remove_far
25923064 and not self.tamed
25933065 and self.lifetimer < 20000 then
25943066
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3067 --print("REMOVED " .. self.name)
3068
3069 remove_mob(self, true)
3070
3071 return minetest.serialize({remove_ok = true, static_save = true})
26003072 end
26013073
26023074 self.remove_ok = true
26053077 self.state = "stand"
26063078
26073079 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3080 if self.drawtype and self.drawtype == "side" then
3081 self.rotate = rad(90)
26113082 end
26123083
26133084 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3085 self.serialized_cmi_components = cmi.serialize_components(
3086 self._cmi_components)
3087 end
3088
3089 local tmp, t = {}
26183090
26193091 for _,stat in pairs(self) do
26203092
2621 local t = type(stat)
3093 t = type(stat)
26223094
26233095 if t ~= "function"
26243096 and t ~= "nil"
26253097 and t ~= "userdata"
3098 and _ ~= "object"
26263099 and _ ~= "_cmi_components" then
26273100 tmp[_] = self[_]
26283101 end
26293102 end
26303103
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3104 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3105
26323106 return minetest.serialize(tmp)
26333107 end
26343108
26353109
26363110 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
3111 function mob_class:mob_activate(staticdata, def, dtime)
3112
3113 -- if dtime == 0 then entity has just been created
3114 -- anything higher means it is respawning (thanks SorceryKid)
3115 if dtime == 0 and active_limit > 0 then
3116 self.active_toggle = 1
3117 end
3118
3119 -- remove mob if not tamed and mob total reached
3120 if active_limit > 0 and active_mobs >= active_limit and not self.tamed then
3121