Package list minetest-mod-mobs-redo / e3a29f7
Import upstream version 20181016+git20210601.1.f131806 Debian Janitor 5 months ago
32 changed file(s) with 3337 addition(s) and 5512 deletion(s). Raw diff Collapse all Expand all
+2046
-1159
api.lua less more
0
1 -- Intllib and CMI support check
0 -- Load support for intllib.
21 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
2 local S = minetest.get_translator and minetest.get_translator("mobs_redo") or
3 dofile(MP .. "/intllib.lua")
4
5 -- CMI support check
46 local use_cmi = minetest.global_exists("cmi")
57
68 mobs = {
79 mod = "redo",
8 version = "20181005",
10 version = "20210601",
911 intllib = S,
10 invis = minetest.global_exists("invisibility") and invisibility or {},
12 invis = minetest.global_exists("invisibility") and invisibility or {}
1113 }
1214
13 -- creative check
14 local creative_cache = minetest.settings:get_bool("creative_mode")
15 function mobs.is_creative(name)
16 return creative_cache or minetest.check_player_privs(name, {creative = true})
17 end
18
19
20 -- localize math functions
15 -- localize common functions
2116 local pi = math.pi
2217 local square = math.sqrt
2318 local sin = math.sin
2520 local abs = math.abs
2621 local min = math.min
2722 local max = math.max
28 local atann = math.atan
2923 local random = math.random
3024 local floor = math.floor
25 local ceil = math.ceil
26 local rad = math.rad
27 local atann = math.atan
3128 local atan = function(x)
3229 if not x or x ~= x then
3330 --error("atan bassed NaN")
3633 return atann(x)
3734 end
3835 end
39
36 local table_copy = table.copy
37 local table_remove = table.remove
38 local vadd = vector.add
39 local vdirection = vector.direction
40 local vmultiply = vector.multiply
41 local vsubtract = vector.subtract
42 local settings = minetest.settings
43
44 -- creative check
45 local creative_cache = minetest.settings:get_bool("creative_mode")
46 function mobs.is_creative(name)
47 return creative_cache or minetest.check_player_privs(name,
48 {creative = true})
49 end
4050
4151 -- Load settings
42 local damage_enabled = minetest.settings:get_bool("enable_damage")
43 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
44 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
45 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
46 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
47 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
48 local creative = minetest.settings:get_bool("creative_mode")
49 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
50 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
51 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
52 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
53 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
54 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
52 local damage_enabled = settings:get_bool("enable_damage")
53 local mobs_spawn = settings:get_bool("mobs_spawn") ~= false
54 local peaceful_only = settings:get_bool("only_peaceful_mobs")
55 local disable_blood = settings:get_bool("mobs_disable_blood")
56 local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false
57 local mobs_griefing = settings:get_bool("mobs_griefing") ~= false
58 local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false
59 local remove_far = settings:get_bool("remove_far_mobs") ~= false
60 local mob_area_spawn = settings:get_bool("mob_area_spawn")
61 local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0
62 local show_health = settings:get_bool("mob_show_health") ~= false
63 local max_per_block = tonumber(settings:get("max_objects_per_block") or 99)
64 local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12)
65 local active_limit = tonumber(settings:get("mob_active_limit") or 0)
66 local mob_chance_multiplier = tonumber(settings:get("mob_chance_multiplier") or 1)
67 local peaceful_player_enabled = settings:get_bool("enable_peaceful_player")
68 local mob_smooth_rotate = settings:get_bool("mob_smooth_rotate") ~= false
69 local active_mobs = 0
5570
5671 -- Peaceful mode message so players will know there are no monsters
5772 if peaceful_only then
6277 end
6378
6479 -- calculate aoc range for mob count
65 local aoc_range = tonumber(minetest.settings:get("active_block_range")) * 16
80 local aoc_range = tonumber(settings:get("active_block_range")) * 16
6681
6782 -- pathfinding settings
6883 local enable_pathfinding = true
69 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
84 local stuck_timeout = 3 -- how long before stuck mod starts searching
7085 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
7186
7287 -- default nodes
7792 local node_snow = "default:snow"
7893 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
7994
95 local mob_class = {
96 stepheight = 1.1,
97 fly_in = "air",
98 owner = "",
99 order = "",
100 jump_height = 4,
101 lifetimer = 180, -- 3 minutes
102 physical = true,
103 collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
104 visual_size = {x = 1, y = 1},
105 texture_mods = "",
106 makes_footstep_sound = false,
107 view_range = 5,
108 walk_velocity = 1,
109 run_velocity = 2,
110 light_damage = 0,
111 light_damage_min = 14,
112 light_damage_max = 15,
113 water_damage = 0,
114 lava_damage = 4,
115 fire_damage = 4,
116 air_damage = 0,
117 suffocation = 2,
118 fall_damage = 1,
119 fall_speed = -10, -- must be lower than -2 (default: -10)
120 drops = {},
121 armor = 100,
122 sounds = {},
123 jump = true,
124 knock_back = true,
125 walk_chance = 50,
126 stand_chance = 30,
127 attack_chance = 5,
128 passive = false,
129 blood_amount = 5,
130 blood_texture = "mobs_blood.png",
131 shoot_offset = 0,
132 floats = 1, -- floats in water by default
133 replace_offset = 0,
134 timer = 0,
135 env_damage_timer = 0, -- only used when state = "attack"
136 tamed = false,
137 pause_timer = 0,
138 horny = false,
139 hornytimer = 0,
140 child = false,
141 gotten = false,
142 health = 0,
143 reach = 3,
144 htimer = 0,
145 docile_by_day = false,
146 time_of_day = 0.5,
147 fear_height = 0,
148 runaway_timer = 0,
149 immune_to = {},
150 explosion_timer = 3,
151 allow_fuse_reset = true,
152 stop_to_explode = true,
153 dogshoot_count = 0,
154 dogshoot_count_max = 5,
155 dogshoot_count2_max = 5,
156 group_attack = false,
157 attack_monsters = false,
158 attack_animals = false,
159 attack_players = true,
160 attack_npcs = true,
161 facing_fence = false,
162 _cmi_is_mob = true
163 }
164
165 local mob_class_meta = {__index = mob_class}
166
80167
81168 -- play sound
82 local mob_sound = function(self, sound)
169 function mob_class:mob_sound(sound)
170
171 local pitch = 1.0
172
173 -- higher pitch for a child
174 if self.child then pitch = pitch * 1.5 end
175
176 -- a little random pitch to be different
177 pitch = pitch + random(-10, 10) * 0.005
83178
84179 if sound then
85180 minetest.sound_play(sound, {
86181 object = self.object,
87182 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
183 max_hear_distance = self.sounds.distance,
184 pitch = pitch
185 }, true)
90186 end
91187 end
92188
93189
94190 -- attack player/mob
95 local do_attack = function(self, player)
191 function mob_class:do_attack(player)
96192
97193 if self.state == "attack" then
98194 return
102198 self.state = "attack"
103199
104200 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
201 self:mob_sound(self.sounds.war_cry)
106202 end
107203 end
108204
110206 -- calculate distance
111207 local get_distance = function(a, b)
112208
209 if not a or not b then return 50 end -- nil check
210
113211 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114212
115213 return square(x * x + y * y + z * z)
117215
118216
119217 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
218 function mob_class:collision()
121219
122220 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124221 local x, z = 0, 0
125222 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126223
127224 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128225
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
226 if object:is_player() then
227 -- or (object:get_luaentity()
228 -- and object:get_luaentity()._cmi_is_mob == true
229 -- and object ~= self.object) then
131230
132231 local pos2 = object:get_pos()
133232 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
141240 end
142241
143242
243 -- check if string exists in another string or table
244 local check_for = function(look_for, look_inside)
245
246 if type(look_inside) == "string" and look_inside == look_for then
247
248 return true
249
250 elseif type(look_inside) == "table" then
251
252 for _, str in pairs(look_inside) do
253
254 if str == look_for then
255 return true
256 end
257
258 if str:find("group:") then
259
260 local group = str:split(":")[2]
261
262 if minetest.get_item_group(look_for, group) ~= 0 then
263 return true
264 end
265 end
266 end
267 end
268
269 return false
270 end
271
272
144273 -- move mob in facing direction
145 local set_velocity = function(self, v)
274 function mob_class:set_velocity(v)
275
276 -- halt mob if it has been ordered to stay
277 if self.order == "stand" then
278
279 self.object:set_velocity({x = 0, y = 0, z = 0})
280
281 return
282 end
146283
147284 local c_x, c_y = 0, 0
148285
149286 -- can mob be pushed, if so calculate direction
150287 if self.pushable then
151 c_x, c_y = unpack(collision(self))
152 end
153
154 -- halt mob if it has been ordered to stay
155 if self.order == "stand" then
156 self.object:set_velocity({x = 0, y = 0, z = 0})
157 return
158 end
159
160 local yaw = (self.object:get_yaw() or 0) + self.rotate
161
162 self.object:set_velocity({
288 c_x, c_y = unpack(self:collision())
289 end
290
291 local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
292
293 -- nil check for velocity
294 v = v or 0.01
295
296 -- check if standing in liquid with max viscosity of 7
297 local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
298
299 -- only slow mob trying to move while inside a viscous fluid that
300 -- they aren't meant to be in (fish in water, spiders in cobweb etc)
301 if v > 0 and visc and visc > 0
302 and not check_for(self.standing_in, self.fly_in) then
303 v = v / (visc + 1)
304 end
305
306 -- set velocity
307 local vel = self.object:get_velocity() or 0
308
309 local new_vel = {
163310 x = (sin(yaw) * -v) + c_x,
164 y = self.object:get_velocity().y,
165 z = (cos(yaw) * v) + c_y,
166 })
311 y = vel.y,
312 z = (cos(yaw) * v) + c_y}
313
314 self.object:set_velocity(new_vel)
315 end
316
317 -- global version of above function
318 function mobs:set_velocity(entity, v)
319 mob_class.set_velocity(entity, v)
167320 end
168321
169322
170323 -- calculate mob velocity
171 local get_velocity = function(self)
324 function mob_class:get_velocity()
172325
173326 local v = self.object:get_velocity()
174327
328 if not v then return 0 end
329
175330 return (v.x * v.x + v.z * v.z) ^ 0.5
176331 end
177332
178333
179334 -- set and return valid yaw
180 local set_yaw = function(self, yaw, delay)
335 function mob_class:set_yaw(yaw, delay)
181336
182337 if not yaw or yaw ~= yaw then
183338 yaw = 0
184339 end
185340
186 delay = delay or 0
341 delay = mob_smooth_rotate and (delay or 0) or 0
187342
188343 if delay == 0 then
344
189345 self.object:set_yaw(yaw)
346
190347 return yaw
191348 end
192349
197354 end
198355
199356 -- global function to set mob yaw
200 function mobs:yaw(self, yaw, delay)
201 set_yaw(self, yaw, delay)
357 function mobs:yaw(entity, yaw, delay)
358 mob_class.set_yaw(entity, yaw, delay)
202359 end
203360
204361
205362 -- set defined animation
206 local set_animation = function(self, anim)
207
208 if not self.animation
209 or not anim then return end
363 function mob_class:set_animation(anim, force)
364
365 if not self.animation or not anim then return end
210366
211367 self.animation.current = self.animation.current or ""
212368
213 -- only set different animation for attacks when setting to same set
214 if anim ~= "punch" and anim ~= "shoot"
369 -- only use different animation for attacks when using same set
370 if force ~= true and anim ~= "punch" and anim ~= "shoot"
215371 and string.find(self.animation.current, anim) then
216372 return
217373 end
218374
219 -- check for more than one animation
220375 local num = 0
221376
377 -- check for more than one animation (max 4)
222378 for n = 1, 4 do
223379
224380 if self.animation[anim .. n .. "_start"]
244400 self.object:set_animation({
245401 x = self.animation[anim .. "_start"],
246402 y = self.animation[anim .. "_end"]},
247 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
403 self.animation[anim .. "_speed"] or
404 self.animation.speed_normal or 15,
248405 0, self.animation[anim .. "_loop"] ~= false)
249406 end
250407
251408 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
409 function mobs:set_animation(entity, anim)
410 entity.set_animation(entity, anim)
411 end
412
413
414 -- check line of sight (BrunoMine)
415 local line_of_sight = function(self, pos1, pos2, stepsize)
416
417 stepsize = stepsize or 1
418
419 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
420
421 -- normal walking and flying mobs can see you through air
422 if s == true then
423 return true
424 end
425
426 -- New pos1 to be analyzed
427 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
428
429 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
430
431 -- Checks the return
432 if r == true then return true end
433
434 -- Nodename found
435 local nn = minetest.get_node(pos).name
436
437 -- Target Distance (td) to travel
438 local td = get_distance(pos1, pos2)
439
440 -- Actual Distance (ad) traveled
441 local ad = 0
442
443 -- It continues to advance in the line of sight in search of a real
444 -- obstruction which counts as 'walkable' nodebox.
445 while minetest.registered_nodes[nn]
446 and (minetest.registered_nodes[nn].walkable == false) do
447
448 -- Check if you can still move forward
449 if td < ad + stepsize then
450 return true -- Reached the target
451 end
452
453 -- Moves the analyzed pos
454 local d = get_distance(pos1, pos2)
455
456 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
457 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
458 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
459
460 -- NaN checks
461 if d == 0
462 or npos1.x ~= npos1.x
463 or npos1.y ~= npos1.y
464 or npos1.z ~= npos1.z then
465 return false
466 end
467
468 ad = ad + stepsize
469
470 -- scan again
471 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
472
473 if r == true then return true end
474
475 -- New Nodename found
476 nn = minetest.get_node(pos).name
477 end
478
479 return false
254480 end
255481
256482
257483 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
484 local new_line_of_sight = function(self, pos1, pos2, stepsize)
259485
260486 if not pos1 or not pos2 then return end
261487
262488 stepsize = stepsize or 1
263489
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
490 local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
265491
266492 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267493
280506 local nn = minetest.get_node(pos).name
281507
282508 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
509 -- obstruction which counts as 'walkable' nodebox.
284510 while minetest.registered_nodes[nn]
285511 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
512
513 npos1 = vadd(npos1, stepv)
289514
290515 if get_distance(npos1, pos2) < stepsize then return true end
291516
301526 return false
302527 end
303528
529 -- check line of sight using raycasting (thanks Astrobe)
530 local ray_line_of_sight = function(self, pos1, pos2)
531
532 local ray = minetest.raycast(pos1, pos2, true, false)
533 local thing = ray:next()
534
535 while thing do -- thing.type, thing.ref
536
537 if thing.type == "node" then
538
539 local name = minetest.get_node(thing.under).name
540
541 if minetest.registered_items[name]
542 and minetest.registered_items[name].walkable then
543 return false
544 end
545 end
546
547 thing = ray:next()
548 end
549
550 return true
551 end
552
553
554 function mob_class:line_of_sight(pos1, pos2, stepsize)
555
556 if minetest.raycast then -- only use if minetest 5.0 is detected
557 return ray_line_of_sight(self, pos1, pos2)
558 end
559
560 return line_of_sight(self, pos1, pos2, stepsize)
561 end
562
304563 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
564 function mobs:line_of_sight(entity, pos1, pos2, stepsize)
565 return entity:line_of_sight(pos1, pos2, stepsize)
566 end
567
568
569 function mob_class:attempt_flight_correction(override)
570
571 if self:flight_check() and override ~= true then return true end
572
573 -- We are not flying in what we are supposed to.
574 -- See if we can find intended flight medium and return to it
575 local pos = self.object:get_pos() ; if not pos then return true end
576 local searchnodes = self.fly_in
577
578 if type(searchnodes) == "string" then
579 searchnodes = {self.fly_in}
580 end
581
582 local flyable_nodes = minetest.find_nodes_in_area(
583 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
584 {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes)
585 -- pos.y + 0 hopefully fixes floating swimmers
586
587 if #flyable_nodes < 1 then
588 return false
589 end
590
591 local escape_target = flyable_nodes[random(#flyable_nodes)]
592 local escape_direction = vdirection(pos, escape_target)
593
594 self.object:set_velocity(
595 vmultiply(escape_direction, 1)) --self.run_velocity))
596
597 return true
308598 end
309599
310600
311601 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
602 function mob_class:flight_check()
313603
314604 local def = minetest.registered_nodes[self.standing_in]
315605
316 if not def then return false end -- nil check
317
318 if type(self.fly_in) == "string"
319 and self.standing_in == self.fly_in then
320
606 if not def then return false end
607
608 -- are we standing inside what we should be to fly/swim ?
609 if check_for(self.standing_in, self.fly_in) then
321610 return true
322
323 elseif type(self.fly_in) == "table" then
324
325 for _,fly_in in pairs(self.fly_in) do
326
327 if self.standing_in == fly_in then
328
329 return true
330 end
331 end
332611 end
333612
334613 -- stops mobs getting stuck inside stairs and plantlike nodes
342621 end
343622
344623
624 -- turn mob to face position
625 local yaw_to_pos = function(self, target, rot)
626
627 rot = rot or 0
628
629 local pos = self.object:get_pos()
630 local vec = {x = target.x - pos.x, z = target.z - pos.z}
631 local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
632
633 if target.x > pos.x then
634 yaw = yaw + pi
635 end
636
637 yaw = self:set_yaw(yaw, rot)
638
639 return yaw
640 end
641
642 function mobs:yaw_to_pos(self, target, rot)
643 return yaw_to_pos(self, target, rot)
644 end
645
646
647 -- if stay near set then check periodically for nodes and turn towards them
648 function mob_class:do_stay_near()
649
650 if not self.stay_near then return false end
651
652 local pos = self.object:get_pos()
653 local searchnodes = self.stay_near[1]
654 local chance = self.stay_near[2] or 10
655
656 if not pos or random(chance) > 1 then
657 return false
658 end
659
660 if type(searchnodes) == "string" then
661 searchnodes = {self.stay_near[1]}
662 end
663
664 local r = self.view_range
665 local nearby_nodes = minetest.find_nodes_in_area(
666 {x = pos.x - r, y = pos.y - 1, z = pos.z - r},
667 {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
668
669 if #nearby_nodes < 1 then
670 return false
671 end
672
673 yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)])
674
675 self:set_animation("walk")
676
677 self:set_velocity(self.walk_velocity)
678
679 return true
680 end
681
682
345683 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
684 local effect = function(pos, amount, texture, min_size, max_size,
685 radius, gravity, glow, fall)
347686
348687 radius = radius or 2
349688 min_size = min_size or 0.5
351690 gravity = gravity or -10
352691 glow = glow or 0
353692
693 if fall == true then
694 fall = 0
695 elseif fall == false then
696 fall = radius
697 else
698 fall = -radius
699 end
700
354701 minetest.add_particlespawner({
355702 amount = amount,
356703 time = 0.25,
357704 minpos = pos,
358705 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
706 minvel = {x = -radius, y = fall, z = -radius},
360707 maxvel = {x = radius, y = radius, z = radius},
361708 minacc = {x = 0, y = gravity, z = 0},
362709 maxacc = {x = 0, y = gravity, z = 0},
365712 minsize = min_size,
366713 maxsize = max_size,
367714 texture = texture,
368 glow = glow,
715 glow = glow
369716 })
370717 end
371718
719 function mobs:effect(pos, amount, texture, min_size, max_size,
720 radius, gravity, glow, fall)
721
722 effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
723 end
724
372725
373726 -- update nametag colour
374 local update_tag = function(self)
727 function mob_class:update_tag()
375728
376729 local col = "#00FF00"
377730 local qua = self.hp_max / 4
392745 nametag = self.nametag,
393746 nametag_color = col
394747 })
395
396748 end
397749
398750
399751 -- drop items
400 local item_drop = function(self)
752 function mob_class:item_drop()
753
754 -- no drops if disabled by setting or mob is child
755 if not mobs_drop_items or self.child then return end
756
757 local pos = self.object:get_pos()
758
759 -- check for drops function
760 self.drops = type(self.drops) == "function"
761 and self.drops(pos) or self.drops
401762
402763 -- check for nil or no drops
403764 if not self.drops or #self.drops == 0 then
404765 return
405766 end
406767
407 -- no drops if disabled by setting
408 if not mobs_drop_items then return end
409
410 -- no drops for child mobs
411 if self.child then return end
412
413768 -- was mob killed by player?
414 local death_by_player = self.cause_of_death and self.cause_of_death.puncher
415 and self.cause_of_death.puncher:is_player() or nil
769 local death_by_player = self.cause_of_death
770 and self.cause_of_death.puncher
771 and self.cause_of_death.puncher:is_player()
416772
417773 local obj, item, num
418 local pos = self.object:get_pos()
419774
420775 for n = 1, #self.drops do
421776
422 if random(1, self.drops[n].chance) == 1 then
777 if random(self.drops[n].chance) == 1 then
423778
424779 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425780 item = self.drops[n].name
435790 end
436791 end
437792
438 -- only drop rare items (drops.min=0) if killed by player
793 -- only drop rare items (drops.min = 0) if killed by player
439794 if death_by_player then
440795 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
441796
448803 obj:set_velocity({
449804 x = random(-10, 10) / 9,
450805 y = 6,
451 z = random(-10, 10) / 9,
806 z = random(-10, 10) / 9
452807 })
453808
454809 elseif obj then
461816 end
462817
463818
819 -- remove mob and descrease counter
820 local remove_mob = function(self, decrease)
821
822 self.object:remove()
823
824 if decrease and active_limit > 0 then
825
826 active_mobs = active_mobs - 1
827
828 if active_mobs < 0 then
829 active_mobs = 0
830 end
831 end
832 --print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
833 end
834
835 -- global function for removing mobs
836 function mobs:remove(self, decrease)
837 remove_mob(self, decrease)
838 end
839
840
464841 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
842 function mob_class:check_for_death(cmi_cause)
843
844 -- We dead already
845 if self.state == "die" then
846 return true
847 end
466848
467849 -- has health actually changed?
468850 if self.health == self.old_health and self.health > 0 then
469 return
470 end
851 return false
852 end
853
854 local damaged = self.health < self.old_health
471855
472856 self.old_health = self.health
473857
474858 -- still got some health? play hurt sound
475859 if self.health > 0 then
476860
477 mob_sound(self, self.sounds.damage)
861 -- only play hurt sound if damaged
862 if damaged then
863 self:mob_sound(self.sounds.damage)
864 end
478865
479866 -- make sure health isn't higher than max
480867 if self.health > self.hp_max then
492879 self.htimer = 2
493880 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
494881
495 update_tag(self)
882 self:update_tag()
496883 end
497884
498885 return false
501888 self.cause_of_death = cmi_cause
502889
503890 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
891 self:item_drop()
892
893 self:mob_sound(self.sounds.death)
507894
508895 local pos = self.object:get_pos()
509896
510897 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
898 if pos and self.on_die then
899
900 self:on_die(pos)
514901
515902 if use_cmi then
516903 cmi.notify_die(self.object, cmi_cause)
517904 end
518905
519 self.object:remove()
906 remove_mob(self, true)
520907
521908 return true
522909 end
523910
524 -- default death function and die animation (if defined)
911 -- check for custom death function and die animation
525912 if self.animation
526913 and self.animation.die_start
527914 and self.animation.die_end then
528915
529916 local frames = self.animation.die_end - self.animation.die_start
530917 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
918 local length = max((frames / speed), 0)
919 local rot = self.animation.die_rotate and 5
532920
533921 self.attack = nil
922 self.following = nil
534923 self.v_start = false
535924 self.timer = 0
536925 self.blinktimer = 0
537926 self.passive = true
538927 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
928 self.object:set_properties({
929 pointable = false, collide_with_objects = false,
930 automatic_rotate = rot, static_save = false
931 })
932 self:set_velocity(0)
933 self:set_animation("die")
541934
542935 minetest.after(length, function(self)
543936
544 if use_cmi and self.object:get_luaentity() then
545 cmi.notify_die(self.object, cmi_cause)
546 end
547
548 self.object:remove()
937 if self.object:get_luaentity() then
938
939 if use_cmi then
940 cmi.notify_die(self.object, cmi_cause)
941 end
942
943 remove_mob(self, true)
944 end
549945 end, self)
550 else
946
947 return true
948
949 elseif pos then -- otherwise remove mod and show particle effect
551950
552951 if use_cmi then
553952 cmi.notify_die(self.object, cmi_cause)
554953 end
555954
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
955 remove_mob(self, true)
956
957 effect(pos, 20, "tnt_smoke.png")
958 end
560959
561960 return true
562961 end
563962
564963
964 -- get node but use fallback for nil or unknown
965 local node_ok = function(pos, fallback)
966
967 fallback = fallback or mobs.fallback_node
968
969 local node = minetest.get_node_or_nil(pos)
970
971 if node and minetest.registered_nodes[node.name] then
972 return node
973 end
974
975 return minetest.registered_nodes[fallback]
976 end
977
978
979 -- Returns true is node can deal damage to self
980 local is_node_dangerous = function(self, nodename)
981
982 if self.water_damage > 0
983 and minetest.get_item_group(nodename, "water") ~= 0 then
984 return true
985 end
986
987 if self.lava_damage > 0
988 and minetest.get_item_group(nodename, "lava") ~= 0 then
989 return true
990 end
991
992 if self.fire_damage > 0
993 and minetest.get_item_group(nodename, "fire") ~= 0 then
994 return true
995 end
996
997 if minetest.registered_nodes[nodename].damage_per_second > 0 then
998 return true
999 end
1000
1001 return false
1002 end
1003
1004
5651005 -- is mob facing a cliff
566 local is_at_cliff = function(self)
1006 function mob_class:is_at_cliff()
5671007
5681008 if self.fear_height == 0 then -- 0 for no falling protection!
5691009 return false
5701010 end
5711011
1012 -- get yaw but if nil returned object no longer exists
5721013 local yaw = self.object:get_yaw()
1014
1015 if not yaw then return false end
1016
5731017 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
5741018 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
5751019 local pos = self.object:get_pos()
5761020 local ypos = pos.y + self.collisionbox[2] -- just above floor
5771021
578 if minetest.line_of_sight(
1022 local free_fall, blocker = minetest.line_of_sight(
5791023 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
580 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
581 , 1) then
582
1024 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
1025
1026 -- check for straight drop
1027 if free_fall then
5831028 return true
5841029 end
5851030
586 return false
587 end
588
589
590 -- get node but use fallback for nil or unknown
591 local node_ok = function(pos, fallback)
592
593 fallback = fallback or mobs.fallback_node
594
595 local node = minetest.get_node_or_nil(pos)
596
597 if node and minetest.registered_nodes[node.name] then
598 return node
599 end
600
601 return minetest.registered_nodes[fallback]
1031 local bnode = node_ok(blocker)
1032
1033 -- will we drop onto dangerous node?
1034 if is_node_dangerous(self, bnode.name) then
1035 return true
1036 end
1037
1038 local def = minetest.registered_nodes[bnode.name]
1039
1040 return (not def and def.walkable)
6021041 end
6031042
6041043
6051044 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
1045 function mob_class:do_env_damage()
6071046
6081047 -- feed/tame text timer (so mob 'full' messages dont spam chat)
6091048 if self.htimer > 0 then
6161055 self.nametag = self.nametag2
6171056 self.nametag2 = nil
6181057
619 update_tag(self)
620 end
621
622 local pos = self.object:get_pos()
1058 self:update_tag()
1059 end
1060
1061 local pos = self.object:get_pos() ; if not pos then return end
6231062
6241063 self.time_of_day = minetest.get_timeofday()
6251064
626 -- remove mob if standing inside ignore node
1065 -- halt mob if standing inside ignore node
6271066 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
1067
1068 self.object:set_velocity({x = 0, y = 0, z = 0})
1069
1070 return true
1071 end
1072
1073 -- particle appears at random mob height
1074 local py = {
1075 x = pos.x,
1076 y = pos.y + random(self.collisionbox[2], self.collisionbox[5]),
1077 z = pos.z
1078 }
1079
1080 local nodef = minetest.registered_nodes[self.standing_in]
1081
1082 -- water
1083 if self.water_damage ~= 0
1084 and nodef.groups.water then
1085
1086 self.health = self.health - self.water_damage
1087
1088 effect(py, 5, "bubble.png", nil, nil, 1, nil)
1089
1090 if self:check_for_death({type = "environment",
1091 pos = pos, node = self.standing_in}) then
1092 return true
1093 end
1094
1095 -- lava damage
1096 elseif self.lava_damage ~= 0
1097 and nodef.groups.lava then
1098
1099 self.health = self.health - self.lava_damage
1100
1101 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1102
1103 if self:check_for_death({type = "environment", pos = pos,
1104 node = self.standing_in, hot = true}) then
1105 return true
1106 end
1107
1108 -- fire damage
1109 elseif self.fire_damage ~= 0
1110 and nodef.groups.fire then
1111
1112 self.health = self.health - self.fire_damage
1113
1114 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1115
1116 if self:check_for_death({type = "environment", pos = pos,
1117 node = self.standing_in, hot = true}) then
1118 return true
1119 end
1120
1121 -- damage_per_second node check (not fire and lava)
1122 elseif nodef.damage_per_second ~= 0
1123 and nodef.groups.lava == nil and nodef.groups.fire == nil then
1124
1125 self.health = self.health - nodef.damage_per_second
1126
1127 effect(py, 5, "tnt_smoke.png")
1128
1129 if self:check_for_death({type = "environment",
1130 pos = pos, node = self.standing_in}) then
1131 return true
1132 end
1133 end
1134
1135 -- air damage
1136 if self.air_damage ~= 0 and self.standing_in == "air" then
1137
1138 self.health = self.health - self.air_damage
1139
1140 effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
1141
1142 if self:check_for_death({type = "environment",
1143 pos = pos, node = self.standing_in}) then
1144 return true
1145 end
6301146 end
6311147
6321148 -- is mob light sensative, or scared of the dark :P
6391155
6401156 self.health = self.health - self.light_damage
6411157
642 effect(pos, 5, "tnt_smoke.png")
643
644 if check_for_death(self, {type = "light"}) then return end
645 end
646 end
647
648 local nodef = minetest.registered_nodes[self.standing_in]
649
650 pos.y = pos.y + 1 -- for particle effect position
651
652 -- water
653 if self.water_damage
654 and nodef.groups.water then
655
656 if self.water_damage ~= 0 then
657
658 self.health = self.health - self.water_damage
659
660 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
661
662 if check_for_death(self, {type = "environment",
663 pos = pos, node = self.standing_in}) then return end
664 end
665
666 -- lava or fire or ignition source
667 elseif self.lava_damage
668 and nodef.groups.igniter then
669 -- and (nodef.groups.lava
670 -- or self.standing_in == node_fire
671 -- or self.standing_in == node_permanent_flame) then
672
673 if self.lava_damage ~= 0 then
674
675 self.health = self.health - self.lava_damage
676
677 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
678
679 if check_for_death(self, {type = "environment",
680 pos = pos, node = self.standing_in, hot = true}) then return end
681 end
682
683 -- damage_per_second node check
684 elseif nodef.damage_per_second ~= 0 then
685
686 self.health = self.health - nodef.damage_per_second
687
688 effect(pos, 5, "tnt_smoke.png")
689
690 if check_for_death(self, {type = "environment",
691 pos = pos, node = self.standing_in}) then return end
692 end
693 --[[
1158 effect(py, 5, "tnt_smoke.png")
1159
1160 if self:check_for_death({type = "light"}) then
1161 return true
1162 end
1163 end
1164 end
1165
6941166 --- suffocation inside solid node
695 if self.suffocation ~= 0
696 and nodef.walkable == true
697 and nodef.groups.disable_suffocation ~= 1
698 and nodef.drawtype == "normal" then
699
700 self.health = self.health - self.suffocation
701
702 if check_for_death(self, {type = "environment",
703 pos = pos, node = self.standing_in}) then return end
704 end
705 ]]
706 check_for_death(self, {type = "unknown"})
1167 if (self.suffocation and self.suffocation ~= 0)
1168 and (nodef.walkable == nil or nodef.walkable == true)
1169 and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
1170 and (nodef.node_box == nil or nodef.node_box.type == "regular")
1171 and (nodef.groups.disable_suffocation ~= 1) then
1172
1173 local damage
1174
1175 if self.suffocation == true then
1176 damage = 2
1177 else
1178 damage = (self.suffocation or 2)
1179 end
1180
1181 self.health = self.health - damage
1182
1183 if self:check_for_death({type = "suffocation",
1184 pos = pos, node = self.standing_in}) then
1185 return true
1186 end
1187 end
1188
1189 return self:check_for_death({type = "unknown"})
7071190 end
7081191
7091192
7101193 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
1194 function mob_class:do_jump()
7121195
7131196 if not self.jump
7141197 or self.jump_height == 0
7221205
7231206 -- something stopping us while moving?
7241207 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
1208 and self:get_velocity() > 0.5
7261209 and self.object:get_velocity().y ~= 0 then
7271210 return false
7281211 end
7301213 local pos = self.object:get_pos()
7311214 local yaw = self.object:get_yaw()
7321215
733 -- what is mob standing on?
734 pos.y = pos.y + self.collisionbox[2] - 0.2
735
736 local nod = node_ok(pos)
737
738 --print ("standing on:", nod.name, pos.y)
739
740 if minetest.registered_nodes[nod.name].walkable == false then
1216 -- sanity check
1217 if not yaw then return false end
1218
1219 -- we can only jump if standing on solid node
1220 if minetest.registered_nodes[self.standing_on].walkable == false then
7411221 return false
7421222 end
7431223
7451225 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
7461226 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
7471227
1228 -- set y_pos to base of mob
1229 pos.y = pos.y + self.collisionbox[2]
1230
7481231 -- what is in front of mob?
7491232 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
1233 x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
7531234 })
7541235
755 -- thin blocks that do not need to be jumped
756 if nod.name == node_snow then
757 return false
758 end
759
760 --print ("in front:", nod.name, pos.y + 0.5)
761
762 if self.walk_chance == 0
763 or minetest.registered_items[nod.name].walkable then
1236 -- what is above and in front?
1237 local nodt = node_ok({
1238 x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
1239 })
1240
1241 local blocked = minetest.registered_nodes[nodt.name].walkable
1242
1243 --print("standing on:", self.standing_on, pos.y - 0.25)
1244 --print("in front:", nod.name, pos.y + 0.5)
1245 --print("in front above:", nodt.name, pos.y + 1.5)
1246
1247 -- jump if standing on solid node (not snow) and not blocked above
1248 if (self.walk_chance == 0
1249 or minetest.registered_items[nod.name].walkable)
1250 and not blocked
1251 and nod.name ~= node_snow then
7641252
7651253 if not nod.name:find("fence")
766 and not nod.name:find("gate") then
1254 and not nod.name:find("gate")
1255 and not nod.name:find("wall") then
7671256
7681257 local v = self.object:get_velocity()
7691258
7701259 v.y = self.jump_height
7711260
772 set_animation(self, "jump") -- only when defined
1261 self:set_animation("jump") -- only when defined
7731262
7741263 self.object:set_velocity(v)
7751264
7791268 if self.object:get_luaentity() then
7801269
7811270 self.object:set_acceleration({
782 x = v.x * 2,--1.5,
1271 x = v.x * 2,
7831272 y = 0,
784 z = v.z * 2,--1.5
1273 z = v.z * 2
7851274 })
7861275 end
7871276 end, self, v)
7881277
789 if get_velocity(self) > 0 then
790 mob_sound(self, self.sounds.jump)
791 end
1278 if self:get_velocity() > 0 then
1279 self:mob_sound(self.sounds.jump)
1280 end
1281
1282 return true
7921283 else
7931284 self.facing_fence = true
7941285 end
795
796 return true
1286 end
1287
1288 -- if blocked against a block/wall for 5 counts then turn
1289 if not self.following
1290 and (self.facing_fence or blocked) then
1291
1292 self.jump_count = (self.jump_count or 0) + 1
1293
1294 if self.jump_count > 4 then
1295
1296 local yaw = self.object:get_yaw() or 0
1297 local turn = random(0, 2) + 1.35
1298
1299 yaw = self:set_yaw(yaw + turn, 12)
1300
1301 self.jump_count = 0
1302 end
7971303 end
7981304
7991305 return false
8131319 obj_pos = objs[n]:get_pos()
8141320
8151321 dist = get_distance(pos, obj_pos)
1322
8161323 if dist < 1 then dist = 1 end
8171324
8181325 local damage = floor((4 / dist) * radius)
8271334 end
8281335
8291336
1337 -- can mob see player
1338 local is_invisible = function(self, player_name)
1339
1340 if mobs.invis[player_name] and not self.ignore_invisibility then
1341 return true
1342 end
1343 end
1344
1345
8301346 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
1347 function mob_class:follow_holding(clicker)
1348
1349 if is_invisible(self, clicker:get_player_name()) then
8341350 return false
8351351 end
8361352
8371353 local item = clicker:get_wielded_item()
838 local t = type(self.follow)
839
840 -- single item
841 if t == "string"
842 and item:get_name() == self.follow then
1354
1355 -- are we holding an item mob can follow ?
1356 if check_for(item:get_name(), self.follow) then
8431357 return true
844
845 -- multiple items
846 elseif t == "table" then
847
848 for no = 1, #self.follow do
849
850 if self.follow[no] == item:get_name() then
851 return true
852 end
853 end
8541358 end
8551359
8561360 return false
8571361 end
8581362
1363 -- Thanks Wuzzy for the following editable settings
1364 local HORNY_TIME = 30
1365 local HORNY_AGAIN_TIME = 300
1366 local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
8591367
8601368 -- find two animals of same type and breed if nearby and horny
861 local breed = function(self)
862
863 -- child takes 240 seconds before growing into adult
1369 function mob_class:breed()
1370
1371 -- child takes a long time before growing into adult
8641372 if self.child == true then
8651373
8661374 self.hornytimer = self.hornytimer + 1
8671375
868 if self.hornytimer > 240 then
1376 if self.hornytimer > CHILD_GROW_TIME then
8691377
8701378 self.child = false
8711379 self.hornytimer = 0
8751383 mesh = self.base_mesh,
8761384 visual_size = self.base_size,
8771385 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
1386 selectionbox = self.base_selbox
8791387 })
8801388
8811389 -- custom function when child grows up
8831391 self.on_grown(self)
8841392 else
8851393 -- jump when fully grown so as not to fall into ground
886 self.object:set_velocity({
887 x = 0,
888 y = self.jump_height,
889 z = 0
890 })
1394 -- self.object:set_velocity({
1395 -- x = 0,
1396 -- y = self.jump_height,
1397 -- z = 0
1398 -- })
1399 local pos = self.object:get_pos() ; if not pos then return end
1400 local ent = self.object:get_luaentity()
1401 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
1402 self.object:set_pos(pos)
8911403 end
8921404 end
8931405
8941406 return
8951407 end
8961408
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
1409 -- horny animal can mate for HORNY_TIME seconds,
1410 -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
8991411 if self.horny == true
900 and self.hornytimer < 240 then
1412 and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
9011413
9021414 self.hornytimer = self.hornytimer + 1
9031415
904 if self.hornytimer >= 240 then
1416 if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
9051417 self.hornytimer = 0
9061418 self.horny = false
9071419 end
9091421
9101422 -- find another same animal who is also horny and mate if nearby
9111423 if self.horny == true
912 and self.hornytimer <= 40 then
1424 and self.hornytimer <= HORNY_TIME then
9131425
9141426 local pos = self.object:get_pos()
9151427
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
1428 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
1429 "heart.png", 3, 4, 1, 0.1)
9171430
9181431 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
1432 local ent
9211433
9221434 for n = 1, #objs do
9231435
9311443 if ent.name == self.name then
9321444 canmate = true
9331445 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
1446 local entname = ent.name:split(":")
1447 local selfname = self.name:split(":")
9361448
9371449 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
1450 entname = entname[2]:split("_")
1451 selfname = selfname[2]:split("_")
9401452
9411453 if entname[1] == selfname[1] then
9421454 canmate = true
9451457 end
9461458 end
9471459
948 if ent
1460 -- found another similar horny animal that isn't self?
1461 if ent and ent.object ~= self.object
9491462 and canmate == true
9501463 and ent.horny == true
951 and ent.hornytimer <= 40 then
952 num = num + 1
953 end
954
955 -- found your mate? then have a baby
956 if num > 1 then
957
958 self.hornytimer = 41
959 ent.hornytimer = 41
1464 and ent.hornytimer <= HORNY_TIME then
1465
1466 local pos2 = ent.object:get_pos()
1467
1468 -- Have mobs face one another
1469 yaw_to_pos(self, pos2)
1470 yaw_to_pos(ent, self.object:get_pos())
1471
1472 self.hornytimer = HORNY_TIME + 1
1473 ent.hornytimer = HORNY_TIME + 1
1474
1475 -- have we reached active mob limit
1476 if active_limit > 0 and active_mobs >= active_limit then
1477 minetest.chat_send_player(self.owner,
1478 S("Active Mob Limit Reached!")
1479 .. " (" .. active_mobs
1480 .. " / " .. active_limit .. ")")
1481 return
1482 end
9601483
9611484 -- spawn baby
9621485 minetest.after(5, function(self, ent)
9691492 if self.on_breed then
9701493
9711494 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
1495 if self:on_breed(ent) == false then
9731496 return
9741497 end
9751498 else
9761499 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
9771500 end
9781501
1502 pos.y = pos.y + 0.5 -- spawn child a little higher
1503
9791504 local mob = minetest.add_entity(pos, self.name)
9801505 local ent2 = mob:get_luaentity()
9811506 local textures = self.base_texture
9901515 textures = textures,
9911516 visual_size = {
9921517 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
1518 y = self.base_size.y * .5
9941519 },
9951520 collisionbox = {
9961521 self.base_colbox[1] * .5,
9981523 self.base_colbox[3] * .5,
9991524 self.base_colbox[4] * .5,
10001525 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1526 self.base_colbox[6] * .5
10021527 },
10031528 selectionbox = {
10041529 self.base_selbox[1] * .5,
10061531 self.base_selbox[3] * .5,
10071532 self.base_selbox[4] * .5,
10081533 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1534 self.base_selbox[6] * .5
10101535 },
10111536 })
10121537 -- tamed and owned by parents' owner
10151540 ent2.owner = self.owner
10161541 end, self, ent)
10171542
1018 num = 0
1019
10201543 break
10211544 end
10221545 end
10251548
10261549
10271550 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1551 function mob_class:replace(pos)
1552
1553 local vel = self.object:get_velocity()
1554 if not vel then return end
10291555
10301556 if not mobs_griefing
10311557 or not self.replace_rate
10321558 or not self.replace_what
10331559 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1560 or vel.y ~= 0
1561 or random(self.replace_rate) > 1 then
10361562 return
10371563 end
10381564
10551581
10561582 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
10571583
1058 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1059
1060 local oldnode = {name = what}
1061 local newnode = {name = with}
1062 local on_replace_return
1584 -- print("replace node = ".. minetest.get_node(pos).name, pos.y)
10631585
10641586 if self.on_replace then
1065 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1066 end
1067
1068 if on_replace_return ~= false then
1069
1070 minetest.set_node(pos, {name = with})
1071
1072 -- when cow/sheep eats grass, replace wool and milk
1073 if self.gotten == true then
1074 self.gotten = false
1075 self.object:set_properties(self)
1076 end
1077 end
1587
1588 local oldnode = what or ""
1589 local newnode = with
1590
1591 -- pass actual node name when using table or groups
1592 if type(oldnode) == "table"
1593 or oldnode:find("group:") then
1594 oldnode = minetest.get_node(pos).name
1595 end
1596
1597 if self:on_replace(pos, oldnode, newnode) == false then
1598 return
1599 end
1600 end
1601
1602 minetest.set_node(pos, {name = with})
10781603 end
10791604 end
10801605
10811606
10821607 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1608 function mob_class:day_docile()
10841609
10851610 if self.docile_by_day == false then
10861611
10981623 local los_switcher = false
10991624 local height_switcher = false
11001625
1101 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1102 local smart_mobs = function(self, s, p, dist, dtime)
1626 -- path finding and smart mob routine by rnd,
1627 -- line_of_sight and other edits by Elkien3
1628 function mob_class:smart_mobs(s, p, dist, dtime)
11031629
11041630 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1631 local target_pos = p
1632
11071633
11081634 -- is it becoming stuck?
11091635 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
11761702 end, self)
11771703 end
11781704
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1705 if abs(vsubtract(s,target_pos).y) > self.stepheight then
11801706
11811707 if height_switcher then
11821708 use_pathfind = true
11891715 end
11901716 end
11911717
1718 -- lets try find a path, first take care of positions
1719 -- since pathfinder is very sensitive
11921720 if use_pathfind then
1193 -- lets try find a path, first take care of positions
1194 -- since pathfinder is very sensitive
1195 local sheight = self.collisionbox[5] - self.collisionbox[2]
11961721
11971722 -- round position to center of node to avoid stuck in walls
11981723 -- also adjust height for player models!
11991724 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
12011725 s.z = floor(s.z + 0.5)
12021726
12031727 local ssight, sground = minetest.line_of_sight(s, {
12151739 p1.z = floor(p1.z + 0.5)
12161740
12171741 local dropheight = 6
1742
12181743 if self.fear_height ~= 0 then dropheight = self.fear_height end
12191744
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1745 local jumpheight = 0
1746
1747 if self.jump and self.jump_height >= 4 then
1748 jumpheight = min(ceil(self.jump_height / 4), 4)
1749
1750 elseif self.stepheight > 0.5 then
1751 jumpheight = 1
1752 end
1753
1754 self.path.way = minetest.find_path(s, p1, 16, jumpheight,
1755 dropheight, "Dijkstra")
12211756
12221757 --[[
12231758 -- show path using particles
12241759 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1760 print("-- path length:" .. tonumber(#self.path.way))
12261761 for _,pos in pairs(self.path.way) do
12271762 minetest.add_particle({
12281763 pos = pos,
12391774 ]]
12401775
12411776 self.state = ""
1242 do_attack(self, self.attack)
1777
1778 if self.attack then
1779 self:do_attack(self.attack)
1780 end
12431781
12441782 -- no path found, try something else
12451783 if not self.path.way then
12631801 end
12641802 end
12651803
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1804 local sheight = ceil(self.collisionbox[5]) + 1
12671805
12681806 -- assume mob is 2 blocks high so it digs above its head
12691807 s.y = s.y + sheight
13291867 minetest.add_item(p1, ItemStack(node1))
13301868 minetest.set_node(p1, {name = "air"})
13311869 end
1332
13331870 end
13341871 end
13351872 end
13371874 -- will try again in 2 second
13381875 self.path.stuck_timer = stuck_timeout - 2
13391876
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1877 elseif s.y < p1.y and (not self.fly) then
1878 self:do_jump() --add jump to pathfinding
1879 self.path.following = true
13421880 else
13431881 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1882 if self.attack then
1883 self:mob_sound(self.sounds.war_cry)
1884 else
1885 self:mob_sound(self.sounds.random)
1886 end
1887
1888 self:set_velocity(self.walk_velocity)
13461889
13471890 -- follow path now that it has it
13481891 self.path.following = true
13511894 end
13521895
13531896
1354 -- specific attacks
1355 local specific_attack = function(list, what)
1356
1357 -- no list so attack default (player, animals etc.)
1358 if list == nil then
1359 return true
1360 end
1361
1362 -- found entity on list to attack?
1363 for no = 1, #list do
1364
1365 if list[no] == what then
1897 -- peaceful player privilege support
1898 local function is_peaceful_player(player)
1899
1900 if peaceful_player_enabled then
1901
1902 local player_name = player:get_player_name()
1903
1904 if player_name
1905 and minetest.check_player_privs(player_name, "peaceful_player") then
13661906 return true
13671907 end
13681908 end
13711911 end
13721912
13731913
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1914 -- general attack function for all mobs
1915 function mob_class:general_attack()
13761916
13771917 -- return if already attacking, passive or docile during day
13781918 if self.passive
1919 or self.state == "runaway"
13791920 or self.state == "attack"
1380 or day_docile(self) then
1921 or self:day_docile() then
13811922 return
13821923 end
13831924
1384 local s = self.object:get_pos()
1925 local s = self.object:get_pos() ; if not s then return end
13851926 local objs = minetest.get_objects_inside_radius(s, self.view_range)
13861927
13871928 -- remove entities we aren't interested in
13921933 -- are we a player?
13931934 if objs[n]:is_player() then
13941935
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1936 -- if player invisible or mob cannot attack then remove from list
1937 if not damage_enabled
1938 or self.attack_players == false
13971939 or (self.owner and self.type ~= "monster")
1398 or mobs.invis[objs[n]:get_player_name()]
1399 or not specific_attack(self.specific_attack, "player") then
1940 or is_invisible(self, objs[n]:get_player_name())
1941 or (self.specific_attack
1942 and not check_for("player", self.specific_attack)) then
14001943 objs[n] = nil
14011944 --print("- pla", n)
14021945 end
14091952 or (not self.attack_animals and ent.type == "animal")
14101953 or (not self.attack_monsters and ent.type == "monster")
14111954 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1955 or (self.specific_attack
1956 and not check_for(ent.name, self.specific_attack)) then
14131957 objs[n] = nil
14141958 --print("- mob", n, self.name, ent.name)
14151959 end
14391983 -- choose closest player to attack that isnt self
14401984 if dist ~= 0
14411985 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
1986 and self:line_of_sight(sp, p, 2) == true
1987 and not is_peaceful_player(player) then
14431988 min_dist = dist
14441989 min_player = player
14451990 end
14461991 end
14471992
14481993 -- attack closest player or mob
1449 if min_player and random(1, 100) > self.attack_chance then
1450 do_attack(self, min_player)
1451 end
1452 end
1453
1454
1455 -- specific runaway
1456 local specific_runaway = function(list, what)
1457
1458 -- no list so do not run
1459 if list == nil then
1460 return false
1461 end
1462
1463 -- found entity on list to attack?
1464 for no = 1, #list do
1465
1466 if list[no] == what then
1467 return true
1468 end
1469 end
1470
1471 return false
1994 if min_player and random(100) > self.attack_chance then
1995 self:do_attack(min_player)
1996 end
14721997 end
14731998
14741999
14752000 -- find someone to runaway from
1476 local runaway_from = function(self)
2001 function mob_class:do_runaway_from()
14772002
14782003 if not self.runaway_from then
14792004 return
14802005 end
14812006
1482 local s = self.object:get_pos()
2007 local s = self.object:get_pos() ; if not s then return end
14832008 local p, sp, dist, pname
14842009 local player, obj, min_player, name
14852010 local min_dist = self.view_range + 1
14912016
14922017 pname = objs[n]:get_player_name()
14932018
1494 if mobs.invis[pname]
2019 if is_invisible(self, pname)
14952020 or self.owner == pname then
14962021
14972022 name = ""
15102035
15112036 -- find specific mob to runaway from
15122037 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
2038 and (self.runaway_from and check_for(name, self.runaway_from)) then
2039
15162040 sp = s
2041 p = player and player:get_pos() or s
15172042
15182043 -- aim higher to make looking up hills more realistic
15192044 p.y = p.y + 1
15232048
15242049 -- choose closest player/mob to runaway from
15252050 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
2051 and self:line_of_sight(sp, p, 2) == true then
15272052 min_dist = dist
15282053 min_player = player
15292054 end
15322057
15332058 if min_player then
15342059
1535 local lp = player:get_pos()
1536 local vec = {
1537 x = lp.x - s.x,
1538 y = lp.y - s.y,
1539 z = lp.z - s.z
1540 }
1541
1542 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1543
1544 if lp.x > s.x then
1545 yaw = yaw + pi
1546 end
1547
1548 yaw = set_yaw(self, yaw, 4)
2060 yaw_to_pos(self, min_player:get_pos(), 3)
2061
15492062 self.state = "runaway"
15502063 self.runaway_timer = 3
15512064 self.following = nil
15542067
15552068
15562069 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
2070 function mob_class:follow_flop()
15582071
15592072 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
2073 if (self.follow ~= "" or self.order == "follow")
15622074 and not self.following
15632075 and self.state ~= "attack"
15642076 and self.state ~= "runaway" then
15652077
1566 local s = self.object:get_pos()
2078 local s = self.object:get_pos() ; if not s then return end
15672079 local players = minetest.get_connected_players()
15682080
15692081 for n = 1, #players do
15702082
15712083 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
2084 and not is_invisible(self, players[n]:get_player_name()) then
15732085
15742086 self.following = players[n]
15752087
15902102 self.following = nil
15912103 end
15922104 else
1593 -- stop following player if not holding specific item
2105 -- stop following player if not holding specific item or mob is horny
15942106 if self.following
15952107 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
2108 and (self:follow_holding(self.following) == false
2109 or self.horny) then
15972110 self.following = nil
15982111 end
15992112
16222135 if dist > self.view_range then
16232136 self.following = nil
16242137 else
1625 local vec = {
1626 x = p.x - s.x,
1627 z = p.z - s.z
1628 }
1629
1630 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1631
1632 if p.x > s.x then yaw = yaw + pi end
1633
1634 yaw = set_yaw(self, yaw, 6)
2138 yaw_to_pos(self, p)
16352139
16362140 -- anyone but standing npc's can move along
16372141 if dist > self.reach
16382142 and self.order ~= "stand" then
16392143
1640 set_velocity(self, self.walk_velocity)
2144 self:set_velocity(self.walk_velocity)
16412145
16422146 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
2147 self:set_animation("walk")
16442148 end
16452149 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
2150 self:set_velocity(0)
2151 self:set_animation("stand")
16482152 end
16492153
16502154 return
16542158
16552159 -- swimmers flop when out of their element, and swim again when back in
16562160 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
2161
2162 if not self:attempt_flight_correction() then
16592163
16602164 self.state = "flop"
2165
2166 -- do we have a custom on_flop function?
2167 if self.on_flop then
2168
2169 if self:on_flop(self) then
2170 return
2171 end
2172 end
2173
16612174 self.object:set_velocity({x = 0, y = -5, z = 0})
16622175
1663 set_animation(self, "stand")
2176 self:set_animation("stand")
16642177
16652178 return
2179
16662180 elseif self.state == "flop" then
16672181 self.state = "stand"
16682182 end
16712185
16722186
16732187 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
2188 function mob_class:dogswitch(dtime)
16752189
16762190 -- switch mode not activated
16772191 if not self.dogshoot_switch
17002214
17012215
17022216 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
2217 function mob_class:do_states(dtime)
2218
2219 local yaw = self.object:get_yaw() ; if not yaw then return end
17062220
17072221 if self.state == "stand" then
17082222
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
2223 if self.randomly_turn and random(4) == 1 then
2224
2225 local lp
17122226 local s = self.object:get_pos()
17132227 local objs = minetest.get_objects_inside_radius(s, 3)
17142228
17222236
17232237 -- look at any players nearby, otherwise turn randomly
17242238 if lp then
1725
1726 local vec = {
1727 x = lp.x - s.x,
1728 z = lp.z - s.z
1729 }
1730
1731 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1732
1733 if lp.x > s.x then yaw = yaw + pi end
2239 yaw = yaw_to_pos(self, lp)
17342240 else
17352241 yaw = yaw + random(-0.5, 0.5)
17362242 end
17372243
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
2244 yaw = self:set_yaw(yaw, 8)
2245 end
2246
2247 self:set_velocity(0)
2248 self:set_animation("stand")
17432249
17442250 -- mobs ordered to stand stay standing
17452251 if self.order ~= "stand"
17462252 and self.walk_chance ~= 0
17472253 and self.facing_fence ~= true
1748 and random(1, 100) <= self.walk_chance
1749 and is_at_cliff(self) == false then
1750
1751 set_velocity(self, self.walk_velocity)
2254 and random(100) <= self.walk_chance
2255 and self.at_cliff == false then
2256
2257 self:set_velocity(self.walk_velocity)
17522258 self.state = "walk"
1753 set_animation(self, "walk")
2259 self:set_animation("walk")
17542260 end
17552261
17562262 elseif self.state == "walk" then
17572263
17582264 local s = self.object:get_pos()
1759 local lp = nil
2265 local lp
17602266
17612267 -- is there something I need to avoid?
17622268 if self.water_damage > 0
17632269 and self.lava_damage > 0 then
17642270
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
2271 lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"})
17662272
17672273 elseif self.water_damage > 0 then
17682274
17702276
17712277 elseif self.lava_damage > 0 then
17722278
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
2279 lp = minetest.find_node_near(s, 1, {"group:igniter"})
17742280 end
17752281
17762282 if lp then
17772283
1778 -- if mob in water or lava then look for land
1779 if (self.lava_damage
1780 and minetest.registered_nodes[self.standing_in].groups.lava)
1781 or (self.water_damage
1782 and minetest.registered_nodes[self.standing_in].groups.water) then
1783
1784 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1785 "group:sand", node_ice, node_snowblock})
2284 -- if mob in dangerous node then look for land
2285 if not is_node_dangerous(self, self.standing_in) then
2286
2287 lp = minetest.find_nodes_in_area_under_air(
2288 {s.x - 5, s.y - 1, s.z - 5},
2289 {s.x + 5, s.y + 2, s.z + 5},
2290 {"group:soil", "group:stone", "group:sand",
2291 node_ice, node_snowblock})
2292
2293 -- select position of random block to climb onto
2294 lp = #lp > 0 and lp[random(#lp)]
17862295
17872296 -- did we find land?
17882297 if lp then
17892298
1790 local vec = {
1791 x = lp.x - s.x,
1792 z = lp.z - s.z
1793 }
1794
1795 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1796
1797 if lp.x > s.x then yaw = yaw + pi end
1798
1799 -- look towards land and jump/move in that direction
1800 yaw = set_yaw(self, yaw, 6)
1801 do_jump(self)
1802 set_velocity(self, self.walk_velocity)
2299 yaw = yaw_to_pos(self, lp)
2300
2301 self:do_jump()
2302 self:set_velocity(self.walk_velocity)
18032303 else
18042304 yaw = yaw + random(-0.5, 0.5)
18052305 end
1806
1807 else
1808
1809 local vec = {
1810 x = lp.x - s.x,
1811 z = lp.z - s.z
1812 }
1813
1814 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1815
1816 if lp.x > s.x then yaw = yaw + pi end
1817 end
1818
1819 yaw = set_yaw(self, yaw, 8)
2306 end
2307
2308 yaw = self:set_yaw(yaw, 8)
18202309
18212310 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
2311 elseif self.randomly_turn and random(100) <= 30 then
18232312
18242313 yaw = yaw + random(-0.5, 0.5)
18252314
1826 yaw = set_yaw(self, yaw, 8)
2315 yaw = self:set_yaw(yaw, 8)
2316
2317 -- for flying/swimming mobs randomly move up and down also
2318 if self.fly_in
2319 and not self.following then
2320 self:attempt_flight_correction(true)
2321 end
18272322 end
18282323
18292324 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
18322325 if self.facing_fence == true
1833 or temp_is_cliff
1834 or random(1, 100) <= 30 then
1835
1836 set_velocity(self, 0)
1837 self.state = "stand"
1838 set_animation(self, "stand")
2326 or self.at_cliff
2327 or random(100) <= self.stand_chance then
2328
2329 -- don't stand if mob flies and keep_flying set
2330 if (self.fly and not self.keep_flying)
2331 or not self.fly then
2332
2333 self:set_velocity(0)
2334 self.state = "stand"
2335 self:set_animation("stand", true)
2336 end
18392337 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
2338 self:set_velocity(self.walk_velocity)
2339
2340 if self:flight_check()
18432341 and self.animation
18442342 and self.animation.fly_start
18452343 and self.animation.fly_end then
1846 set_animation(self, "fly")
2344 self:set_animation("fly")
18472345 else
1848 set_animation(self, "walk")
2346 self:set_animation("walk")
18492347 end
18502348 end
18512349
18562354
18572355 -- stop after 5 seconds or when at cliff
18582356 if self.runaway_timer > 5
1859 or is_at_cliff(self)
2357 or self.at_cliff
18602358 or self.order == "stand" then
18612359 self.runaway_timer = 0
1862 set_velocity(self, 0)
2360 self:set_velocity(0)
18632361 self.state = "stand"
1864 set_animation(self, "stand")
2362 self:set_animation("stand")
18652363 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
2364 self:set_velocity(self.run_velocity)
2365 self:set_animation("walk")
18682366 end
18692367
18702368 -- attack routines (explode, dogfight, shoot, dogshoot)
18712369 elseif self.state == "attack" then
18722370
1873 -- calculate distance from mob and enemy
2371 -- get mob and enemy positions and distance between
18742372 local s = self.object:get_pos()
1875 local p = self.attack:get_pos() or s
1876 local dist = get_distance(p, s)
1877
1878 -- stop attacking if player invisible or out of range
2373 local p = self.attack and self.attack:get_pos()
2374 local dist = p and get_distance(p, s) or 500
2375
2376 -- stop attacking if player out of range or invisible
18792377 if dist > self.view_range
18802378 or not self.attack
18812379 or not self.attack:get_pos()
18822380 or self.attack:get_hp() <= 0
1883 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1884
1885 -- print(" ** stop attacking **", dist, self.view_range)
2381 or (self.attack:is_player()
2382 and is_invisible(self, self.attack:get_player_name())) then
2383
2384 --print(" ** stop attacking **", dist, self.view_range)
2385
18862386 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
2387 self:set_velocity(0)
2388 self:set_animation("stand")
18892389 self.attack = nil
18902390 self.v_start = false
18912391 self.timer = 0
18972397
18982398 if self.attack_type == "explode" then
18992399
1900 local vec = {
1901 x = p.x - s.x,
1902 z = p.z - s.z
1903 }
1904
1905 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1906
1907 if p.x > s.x then yaw = yaw + pi end
1908
1909 yaw = set_yaw(self, yaw)
2400 yaw = yaw_to_pos(self, p)
19102401
19112402 local node_break_radius = self.explosion_radius or 1
19122403 local entity_damage_radius = self.explosion_damage_radius
19132404 or (node_break_radius * 2)
19142405
2406 -- look a little higher to fix raycast
2407 s.y = s.y + 0.5 ; p.y = p.y + 0.5
2408
19152409 -- start timer when in reach and line of sight
19162410 if not self.v_start
19172411 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
2412 and self:line_of_sight(s, p, 2) then
19192413
19202414 self.v_start = true
19212415 self.timer = 0
19222416 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
2417 self:mob_sound(self.sounds.fuse)
2418
2419 --print("=== explosion timer started", self.explosion_timer)
19252420
19262421 -- stop timer if out of reach or direct line of sight
19272422 elseif self.allow_fuse_reset
19282423 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
2424 and (dist > self.reach or not self:line_of_sight(s, p, 2)) then
2425
2426 --print("=== explosion timer stopped")
2427
19312428 self.v_start = false
19322429 self.timer = 0
19332430 self.blinktimer = 0
19342431 self.blinkstatus = false
1935 self.object:settexturemod("")
2432 self.object:set_texture_mod("")
19362433 end
19372434
19382435 -- walk right up to player unless the timer is active
19392436 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
2437 self:set_velocity(0)
19412438 else
1942 set_velocity(self, self.run_velocity)
2439 self:set_velocity(self.run_velocity)
19432440 end
19442441
19452442 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
2443 self:set_animation("run")
19472444 else
1948 set_animation(self, "walk")
2445 self:set_animation("walk")
19492446 end
19502447
19512448 if self.v_start then
19582455 self.blinktimer = 0
19592456
19602457 if self.blinkstatus then
1961 self.object:settexturemod("")
2458
2459 self.object:set_texture_mod(self.texture_mods)
19622460 else
1963 self.object:settexturemod("^[brighten")
2461
2462 self.object:set_texture_mod(self.texture_mods
2463 .. "^[brighten")
19642464 end
19652465
19662466 self.blinkstatus = not self.blinkstatus
19672467 end
19682468
1969 -- print ("=== explosion timer", self.timer)
2469 --print("=== explosion timer", self.timer)
19702470
19712471 if self.timer > self.explosion_timer then
19722472
19792479 node_break_radius = 1
19802480 end
19812481
1982 self.object:remove()
2482 remove_mob(self, true)
19832483
19842484 if minetest.get_modpath("tnt") and tnt and tnt.boom
19852485 and not minetest.is_protected(pos, "") then
19872487 tnt.boom(pos, {
19882488 radius = node_break_radius,
19892489 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
2490 sound = self.sounds.explode
19912491 })
19922492 else
19932493
19982498 })
19992499
20002500 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2501
2502 effect(pos, 32, "tnt_smoke.png", nil, nil,
2503 node_break_radius, 1, 0)
20022504 end
20032505
2004 return
2506 return true
20052507 end
20062508 end
20072509
20082510 elseif self.attack_type == "dogfight"
2009 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2010 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2511 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
2512 or (self.attack_type == "dogshoot" and dist <= self.reach
2513 and self:dogswitch() == 0) then
20112514
20122515 if self.fly
20132516 and dist > self.reach then
20182521 local p_y = floor(p2.y + 1)
20192522 local v = self.object:get_velocity()
20202523
2021 if flight_check(self, s) then
2524 if self:flight_check() then
20222525
20232526 if me_y < p_y then
20242527
20542557 })
20552558 end
20562559 end
2057
20582560 end
20592561
20602562 -- rnd: new movement direction
20762578 return
20772579 end
20782580
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2581 if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
20802582 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2583 table_remove(self.path.way, 1)
20822584 end
20832585
20842586 -- set new temporary target
20852587 p = {x = p1.x, y = p1.y, z = p1.z}
20862588 end
20872589
2088 local vec = {
2089 x = p.x - s.x,
2090 z = p.z - s.z
2091 }
2092
2093 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2094
2095 if p.x > s.x then yaw = yaw + pi end
2096
2097 yaw = set_yaw(self, yaw)
2590 yaw = yaw_to_pos(self, p)
20982591
20992592 -- move towards enemy if beyond mob reach
21002593 if dist > self.reach then
21032596 if self.pathfinding -- only if mob has pathfinding enabled
21042597 and enable_pathfinding then
21052598
2106 smart_mobs(self, s, p, dist, dtime)
2599 self:smart_mobs(s, p, dist, dtime)
21072600 end
21082601
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2602 if self.at_cliff then
2603
2604 self:set_velocity(0)
2605 self:set_animation("stand")
21132606 else
21142607
21152608 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2609 self:set_velocity(self.walk_velocity)
21172610 else
2118 set_velocity(self, self.run_velocity)
2611 self:set_velocity(self.run_velocity)
21192612 end
21202613
21212614 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2615 self:set_animation("run")
21232616 else
2124 set_animation(self, "walk")
2617 self:set_animation("walk")
21252618 end
21262619 end
21272620
21312624 self.path.stuck_timer = 0
21322625 self.path.following = false -- not stuck anymore
21332626
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2627 self:set_velocity(0)
2628
2629 if self.timer > 1 then
2630
2631 -- no custom attack or custom attack returns true to continue
2632 if not self.custom_attack
2633 or self:custom_attack(self, p) == true then
21392634
21402635 self.timer = 0
2141
2142 -- if self.double_melee_attack
2143 -- and random(1, 2) == 1 then
2144 -- set_animation(self, "punch2")
2145 -- else
2146 set_animation(self, "punch")
2147 -- end
2636 self:set_animation("punch")
21482637
21492638 local p2 = p
21502639 local s2 = s
21522641 p2.y = p2.y + .5
21532642 s2.y = s2.y + .5
21542643
2155 if line_of_sight(self, p2, s2) == true then
2644 if self:line_of_sight(p2, s2) == true then
21562645
21572646 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2647 self:mob_sound(self.sounds.attack)
21592648
21602649 -- punch player (or what player is attached to)
21612650 local attached = self.attack:get_attach()
2651
21622652 if attached then
21632653 self.attack = attached
21642654 end
2655
2656 local dgroup = self.damage_group or "fleshy"
2657
21652658 self.attack:punch(self.object, 1.0, {
21662659 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2660 damage_groups = {[dgroup] = self.damage}
21682661 }, nil)
21692662 end
21702663 end
2171 else -- call custom attack every second
2172 if self.custom_attack
2173 and self.timer > 1 then
2174
2175 self.timer = 0
2176
2177 self.custom_attack(self, p)
2178 end
21792664 end
21802665 end
21812666
21822667 elseif self.attack_type == "shoot"
2183 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2184 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2668 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
2669 or (self.attack_type == "dogshoot" and dist > self.reach and
2670 self:dogswitch() == 0) then
21852671
21862672 p.y = p.y - .5
21872673 s.y = s.y + .5
21882674
2189 local dist = get_distance(p, s)
2190 local vec = {
2191 x = p.x - s.x,
2192 y = p.y - s.y,
2193 z = p.z - s.z
2194 }
2195
2196 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2197
2198 if p.x > s.x then yaw = yaw + pi end
2199
2200 yaw = set_yaw(self, yaw)
2201
2202 set_velocity(self, 0)
2675 local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
2676
2677 yaw = yaw_to_pos(self, p)
2678
2679 self:set_velocity(0)
22032680
22042681 if self.shoot_interval
22052682 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2683 and random(100) <= 60 then
22072684
22082685 self.timer = 0
2209 set_animation(self, "shoot")
2686 self:set_animation("shoot")
22102687
22112688 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2689 self:mob_sound(self.sounds.shoot_attack)
22132690
22142691 local p = self.object:get_pos()
22152692
22402717
22412718
22422719 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2720 function mob_class:falling(pos)
2721
2722 if self.fly or self.disable_falling then
22462723 return
22472724 end
22482725
22492726 -- floating in water (or falling)
22502727 local v = self.object:get_velocity()
22512728
2252 if v.y > 0 then
2253
2254 -- apply gravity when moving up
2255 self.object:set_acceleration({
2256 x = 0,
2257 y = -10,
2258 z = 0
2259 })
2260
2261 elseif v.y <= 0 and v.y > self.fall_speed then
2262
2263 -- fall downwards at set speed
2264 self.object:set_acceleration({
2265 x = 0,
2266 y = self.fall_speed,
2267 z = 0
2268 })
2269 else
2270 -- stop accelerating once max fall speed hit
2271 self.object:set_acceleration({x = 0, y = 0, z = 0})
2272 end
2273
2274 -- in water then float up
2275 if self.standing_in
2276 and minetest.registered_nodes[self.standing_in].groups.water then
2277
2278 if self.floats == 1 then
2279
2280 self.object:set_acceleration({
2281 x = 0,
2282 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2283 z = 0
2284 })
2285 end
2729 -- sanity check
2730 if not v then return end
2731
2732 local fall_speed = self.fall_speed
2733
2734 -- in water then use liquid viscosity for float/sink speed
2735 if self.floats == 1 and self.standing_in
2736 and minetest.registered_nodes[self.standing_in].groups.liquid then
2737
2738 local visc = min(
2739 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2740
2741 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2742 fall_speed = -1.2 / visc
22862743 else
22872744
22882745 -- fall damage onto solid ground
22972754
22982755 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992756
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2757 if self:check_for_death({type = "fall"}) then
2758 return true
23022759 end
23032760 end
23042761
23052762 self.old_y = self.object:get_pos().y
23062763 end
23072764 end
2765
2766 -- fall at set speed
2767 self.object:set_acceleration({
2768 x = 0,
2769 y = fall_speed,
2770 z = 0
2771 })
23082772 end
23092773
23102774
23122776 local tr = minetest.get_modpath("toolranks")
23132777
23142778 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2779 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162780
23172781 -- mob health check
23182782 if self.health <= 0 then
2319 return
2783 return true
23202784 end
23212785
23222786 -- custom punch function
23232787 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2788 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2789 return true
23262790 end
23272791
23282792 -- error checking when mod profiling is enabled
23292793 if not tool_capabilities then
2330 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2331 return
2332 end
2333
2334 -- is mob protected?
2335 if self.protected and hitter:is_player()
2336 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2337 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2338 return
2794 minetest.log("warning",
2795 "[mobs] Mod profiling enabled, damage not enabled")
2796 return true
2797 end
2798
2799 -- is mob protected
2800 if self.protected then
2801
2802 -- did player hit mob and if so is it in protected area
2803 if hitter:is_player() then
2804
2805 local player_name = hitter:get_player_name()
2806
2807 if player_name ~= self.owner
2808 and minetest.is_protected(self.object:get_pos(), player_name) then
2809
2810 minetest.chat_send_player(hitter:get_player_name(),
2811 S("Mob has been protected!"))
2812
2813 return true
2814 end
2815
2816 -- if protection is on level 2 then dont let arrows harm mobs
2817 elseif self.protected == 2 then
2818
2819 local ent = hitter and hitter:get_luaentity()
2820
2821 if ent and ent._is_arrow then
2822
2823 return true -- arrow entity
2824
2825 elseif not ent then
2826
2827 return true -- non entity
2828 end
2829 end
23392830 end
23402831
23412832 local weapon = hitter:get_wielded_item()
23672858 end
23682859
23692860 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2861 * tmp * ((armor[group] or 0) / 100.0)
23712862 end
23722863 end
23732864
23792870 damage = self.immune_to[n][2] or 0
23802871 break
23812872
2382 -- if "all" then no tool does damage unless it's specified in list
2873 -- if "all" then no tools deal damage unless it's specified in list
23832874 elseif self.immune_to[n][1] == "all" then
23842875 damage = self.immune_to[n][2] or 0
23852876 end
23862877 end
2878
2879 --print("Mob Damage is", damage)
23872880
23882881 -- healing
23892882 if damage <= -1 then
23902883 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2884 return true
2885 end
23952886
23962887 if use_cmi
2397 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2888 and cmi.notify_punch(
2889 self.object, hitter, tflp, tool_capabilities, dir, damage) then
2890 return true
23992891 end
24002892
24012893 -- add weapon wear
24152907
24162908 if tr then
24172909 if weapon_def.original_description then
2418 weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
2910 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24192911 end
24202912 else
24212913 weapon:add_wear(wear)
24262918 -- only play hit sound and show blood effects if damage is 1 or over
24272919 if damage >= 1 then
24282920
2429 -- weapon sounds
2430 if weapon_def.sounds then
2431
2432 local s = random(0, #weapon_def.sounds)
2433
2434 minetest.sound_play(weapon_def.sounds[s], {
2435 object = self.object,
2436 max_hear_distance = 8
2437 })
2438 else
2439 minetest.sound_play("default_punch", {
2440 object = self.object,
2441 max_hear_distance = 5
2442 })
2443 end
2921 -- select tool use sound if found, or fallback to default
2922 local snd = weapon_def.sound and weapon_def.sound.use
2923 or "default_punch"
2924
2925 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442926
24452927 -- blood_particles
24462928 if not disable_blood and self.blood_amount > 0 then
24472929
24482930 local pos = self.object:get_pos()
2931 local blood = self.blood_texture
2932 local amount = self.blood_amount
24492933
24502934 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2935
2936 -- lots of damage = more blood :)
2937 if damage > 10 then
2938 amount = self.blood_amount * 2
2939 end
24512940
24522941 -- do we have a single blood texture or multiple?
24532942 if type(self.blood_texture) == "table" then
2454
2455 local blood = self.blood_texture[random(1, #self.blood_texture)]
2456
2457 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2458 else
2459 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2460 end
2943 blood = self.blood_texture[random(#self.blood_texture)]
2944 end
2945
2946 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612947 end
24622948
24632949 -- do damage
24672953 local hot = tool_capabilities and tool_capabilities.damage_groups
24682954 and tool_capabilities.damage_groups.fire
24692955
2470 if check_for_death(self, {type = "punch",
2471 puncher = hitter, hot = hot}) then
2472 return
2473 end
2474
2475 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2956 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2957 return true
2958 end
2959
2960 --[[ add healthy afterglow when hit (causes lag with large textures)
24762961 minetest.after(0.1, function()
24772962
24782963 if not self.object:get_luaentity() then return end
24792964
2480 self.object:settexturemod("^[colorize:#c9900070")
2481
2482 core.after(0.3, function()
2483 self.object:settexturemod("")
2965 self.object:set_texture_mod("^[colorize:#c9900070")
2966
2967 minetest.after(0.3, function()
2968 if not self.object:get_luaentity() then return end
2969 self.object:set_texture_mod(self.texture_mods)
24842970 end)
24852971 end) ]]
24862972
24872973 end -- END if damage
24882974
24892975 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2976 if self.knock_back and tflp >= punch_interval then
24922977
24932978 local v = self.object:get_velocity()
2979
2980 -- sanity check
2981 if not v then return true end
2982
24942983 local kb = damage or 1
24952984 local up = 2
24962985
25042993 dir = dir or {x = 0, y = 0, z = 0}
25052994
25062995 -- use tool knockback value or default
2507 kb = tool_capabilities.damage_groups["knockback"] or (kb * 1.5)
2996 kb = tool_capabilities.damage_groups["knockback"] or kb
25082997
25092998 self.object:set_velocity({
25102999 x = dir.x * kb,
25203009 and self.order ~= "stand" then
25213010
25223011 local lp = hitter:get_pos()
2523 local s = self.object:get_pos()
2524 local vec = {
2525 x = lp.x - s.x,
2526 y = lp.y - s.y,
2527 z = lp.z - s.z
2528 }
2529
2530 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2531
2532 if lp.x > s.x then
2533 yaw = yaw + pi
2534 end
2535
2536 yaw = set_yaw(self, yaw, 6)
3012 local yaw = yaw_to_pos(self, lp, 3)
3013
25373014 self.state = "runaway"
25383015 self.runaway_timer = 0
25393016 self.following = nil
25473024 and self.child == false
25483025 and self.attack_players == true
25493026 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3027 and not is_invisible(self, name)
3028 and self.object ~= hitter then
25513029
25523030 -- attack whoever punched mob
25533031 self.state = ""
2554 do_attack(self, hitter)
3032 self:do_attack(hitter)
25553033
25563034 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3035 local objs = minetest.get_objects_inside_radius(
3036 hitter:get_pos(), self.view_range)
3037 local obj
25593038
25603039 for n = 1, #objs do
25613040
25633042
25643043 if obj and obj._cmi_is_mob then
25653044
2566 -- only alert members of same mob
3045 -- only alert members of same mob and assigned helper
25673046 if obj.group_attack == true
25683047 and obj.state ~= "attack"
25693048 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3049 and (obj.name == self.name
3050 or obj.name == self.group_helper) then
3051
3052 obj:do_attack(hitter)
25723053 end
25733054
25743055 -- have owned mobs attack player threat
25753056 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3057 obj:do_attack(self.object)
25773058 end
25783059 end
25793060 end
25823063
25833064
25843065 -- get entity staticdata
2585 local mob_staticdata = function(self)
3066 function mob_class:mob_staticdata()
3067
3068 -- this handles mob count for mobs activated, unloaded, reloaded
3069 if active_limit > 0 and self.active_toggle then
3070 active_mobs = active_mobs + self.active_toggle
3071 self.active_toggle = -self.active_toggle
3072 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3073 end
25863074
25873075 -- remove mob when out of range unless tamed
25883076 if remove_far
25923080 and not self.tamed
25933081 and self.lifetimer < 20000 then
25943082
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3083 --print("REMOVED " .. self.name)
3084
3085 remove_mob(self, true)
3086
3087 return minetest.serialize({remove_ok = true, static_save = true})
26003088 end
26013089
26023090 self.remove_ok = true
26053093 self.state = "stand"
26063094
26073095 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3096 if self.drawtype and self.drawtype == "side" then
3097 self.rotate = rad(90)
26113098 end
26123099
26133100 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3101 self.serialized_cmi_components = cmi.serialize_components(
3102 self._cmi_components)
3103 end
3104
3105 local tmp, t = {}
26183106
26193107 for _,stat in pairs(self) do
26203108
2621 local t = type(stat)
3109 t = type(stat)
26223110
26233111 if t ~= "function"
26243112 and t ~= "nil"
26253113 and t ~= "userdata"
3114 and _ ~= "object"
26263115 and _ ~= "_cmi_components" then
26273116 tmp[_] = self[_]
26283117 end
26293118 end
26303119
2631 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3120 --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n')
3121
26323122 return minetest.serialize(tmp)
26333123 end
26343124
26353125
26363126 -- activate mob and reload settings
2637 local mob_activate = function(self, staticdata, def, dtime)
3127 function mob_class:mob_activate(staticdata, def, dtime)
3128
3129 -- if dtime == 0 then entity has just been created
3130 -- anything higher means it is respawning (thanks SorceryKid)
3131 if dtime == 0 and active_limit > 0 then
3132 self.active_toggle = 1
3133 end
3134
3135 -- remove mob if not tamed and mob total reached
3136 if active_limit > 0 and active_mobs >= active_limit and not self.tamed then
3137
3138 remove_mob(self)
3139 --print("-- mob limit reached, removing " .. self.name)
3140 return
3141 end
26383142
26393143 -- remove monsters in peaceful mode
2640 if self.type == "monster"
2641 and peaceful_only then
2642
2643 self.object:remove()
3144 if self.type == "monster" and peaceful_only then
3145
3146 remove_mob(self, true)
26443147
26453148 return
26463149 end
26493152 local tmp = minetest.deserialize(staticdata)
26503153
26513154 if tmp then
3155
3156 local t
3157
26523158 for _,stat in pairs(tmp) do
2653 self[_] = stat
2654 end
2655 end
3159
3160 t = type(stat)
3161
3162 if t ~= "function"
3163 and t ~= "nil"
3164 and t ~= "userdata" then
3165 self[_] = stat
3166 end
3167 end
3168 end
3169
3170 -- force current model into mob
3171 self.mesh = def.mesh
3172 self.base_mesh = def.mesh
3173 self.collisionbox = def.collisionbox
3174 self.selectionbox = def.selectionbox
26563175
26573176 -- select random texture, set model and size
26583177 if not self.base_texture then
26623181 def.textures = {def.textures}
26633182 end
26643183
2665 self.base_texture = def.textures and def.textures[random(1, #def.textures)]
3184 self.base_texture = def.textures and def.textures[random(#def.textures)]
26663185 self.base_mesh = def.mesh
26673186 self.base_size = self.visual_size
26683187 self.base_colbox = self.collisionbox
26823201 local selbox = self.base_selbox
26833202
26843203 -- specific texture if gotten
2685 if self.gotten == true
2686 and def.gotten_texture then
3204 if self.gotten == true and def.gotten_texture then
26873205 textures = def.gotten_texture
26883206 end
26893207
26903208 -- specific mesh if gotten
2691 if self.gotten == true
2692 and def.gotten_mesh then
3209 if self.gotten == true and def.gotten_mesh then
26933210 mesh = def.gotten_mesh
26943211 end
26953212
26963213 -- set child objects to half size
26973214 if self.child == true then
26983215
2699 vis_size = {
2700 x = self.base_size.x * .5,
2701 y = self.base_size.y * .5,
2702 }
3216 vis_size = {x = self.base_size.x * .5, y = self.base_size.y * .5}
27033217
27043218 if def.child_texture then
27053219 textures = def.child_texture[1]
27063220 end
27073221
27083222 colbox = {
2709 self.base_colbox[1] * .5,
2710 self.base_colbox[2] * .5,
2711 self.base_colbox[3] * .5,
2712 self.base_colbox[4] * .5,
2713 self.base_colbox[5] * .5,
2714 self.base_colbox[6] * .5
2715 }
3223 self.base_colbox[1] * .5, self.base_colbox[2] * .5,
3224 self.base_colbox[3] * .5, self.base_colbox[4] * .5,
3225 self.base_colbox[5] * .5, self.base_colbox[6] * .5}
3226
27163227 selbox = {
2717 self.base_selbox[1] * .5,
2718 self.base_selbox[2] * .5,
2719 self.base_selbox[3] * .5,
2720 self.base_selbox[4] * .5,
2721 self.base_selbox[5] * .5,
2722 self.base_selbox[6] * .5
2723 }
3228 self.base_selbox[1] * .5, self.base_selbox[2] * .5,
3229 self.base_selbox[3] * .5, self.base_selbox[4] * .5,
3230 self.base_selbox[5] * .5, self.base_selbox[6] * .5}
27243231 end
27253232
27263233 if self.health == 0 then
2727 self.health = random (self.hp_min, self.hp_max)
3234 self.health = random(self.hp_min, self.hp_max)
27283235 end
27293236
27303237 -- pathfinding init
27353242 self.path.following = false -- currently following path?
27363243 self.path.stuck_timer = 0 -- if stuck for too long search for path
27373244
3245 -- Armor groups (immortal = 1 for custom damage handling)
3246 local armor
3247 if type(self.armor) == "table" then
3248 armor = table_copy(self.armor)
3249 -- armor.immortal = 1
3250 else
3251 -- armor = {immortal = 1, fleshy = self.armor}
3252 armor = {fleshy = self.armor}
3253 end
3254 self.object:set_armor_groups(armor)
3255
27383256 -- mob defaults
2739 self.object:set_armor_groups({immortal = 1, fleshy = self.armor})
27403257 self.old_y = self.object:get_pos().y
27413258 self.old_health = self.health
27423259 self.sounds.distance = self.sounds.distance or 10
27463263 self.selectionbox = selbox
27473264 self.visual_size = vis_size
27483265 self.standing_in = "air"
3266 self.standing_on = "air"
27493267
27503268 -- check existing nametag
27513269 if not self.nametag then
27543272
27553273 -- set anything changed above
27563274 self.object:set_properties(self)
2757 set_yaw(self, (random(0, 360) - 180) / 180 * pi, 6)
2758 update_tag(self)
2759 set_animation(self, "stand")
3275 self:set_yaw((random(0, 360) - 180) / 180 * pi, 6)
3276 self:update_tag()
3277 self:set_animation("stand")
3278
3279 -- apply any texture mods
3280 self.object:set_texture_mod(self.texture_mods)
3281
3282 -- set 5.x flag to remove monsters when map area unloaded
3283 if remove_far and self.type == "monster" then
3284 self.static_save = false
3285 end
27603286
27613287 -- run on_spawn function if found
27623288 if self.on_spawn and not self.on_spawn_run then
27633289 if self.on_spawn(self) then
2764 self.on_spawn_run = true -- if true, set flag to run once only
3290 self.on_spawn_run = true -- if true, set flag to run once only
27653291 end
27663292 end
27673293
27713297 end
27723298
27733299 if use_cmi then
2774 self._cmi_components = cmi.activate_components(self.serialized_cmi_components)
3300 self._cmi_components = cmi.activate_components(
3301 self.serialized_cmi_components)
27753302 cmi.notify_activate(self.object, dtime)
27763303 end
27773304 end
27783305
27793306
27803307 -- handle mob lifetimer and expiration
2781 local mob_expire = function(self, pos, dtime)
3308 function mob_class:mob_expire(pos, dtime)
27823309
27833310 -- when lifetimer expires remove mob (except npc and tamed)
27843311 if self.type ~= "npc"
28093336
28103337 effect(pos, 15, "tnt_smoke.png", 2, 4, 2, 0)
28113338
2812 self.object:remove()
3339 remove_mob(self, true)
28133340
28143341 return
28153342 end
28183345
28193346
28203347 -- main mob function
2821 local mob_step = function(self, dtime)
3348 function mob_class:on_step(dtime, moveresult)
3349
3350 --[[ moveresult contains this for physical mobs
3351 {
3352 touching_ground = boolean,
3353 collides = boolean,
3354 standing_on_object = boolean,
3355 collisions = {
3356 {
3357 type = string, -- "node" or "object",
3358 axis = string, -- "x", "y&q