Package list minetest-mod-mobs-redo / fresh-snapshots/upstream
Import upstream version 20181016+git20210920.1.26ec61e Debian Janitor an hour ago
33 changed file(s) with 3537 addition(s) and 5627 deletion(s). Raw diff Collapse all Expand all
+2204
-1290
api.lua less more
0
1 -- Intllib and CMI support check
0 -- Load support for intllib.
21 local MP = minetest.get_modpath(minetest.get_current_modname())
3 local S, NS = dofile(MP .. "/intllib.lua")
2 local S = minetest.get_translator and minetest.get_translator("mobs_redo") or
3 dofile(MP .. "/intllib.lua")
4
5 -- CMI support check
46 local use_cmi = minetest.global_exists("cmi")
57
68 mobs = {
79 mod = "redo",
8 version = "20181005",
10 version = "20210920",
911 intllib = S,
10 invis = minetest.global_exists("invisibility") and invisibility or {},
12 invis = minetest.global_exists("invisibility") and invisibility or {}
1113 }
1214
13 -- creative check
14 local creative_cache = minetest.settings:get_bool("creative_mode")
15 function mobs.is_creative(name)
16 return creative_cache or minetest.check_player_privs(name, {creative = true})
17 end
18
19
20 -- localize math functions
15 -- localize common functions
2116 local pi = math.pi
2217 local square = math.sqrt
2318 local sin = math.sin
2520 local abs = math.abs
2621 local min = math.min
2722 local max = math.max
28 local atann = math.atan
2923 local random = math.random
3024 local floor = math.floor
25 local ceil = math.ceil
26 local rad = math.rad
27 local atann = math.atan
3128 local atan = function(x)
3229 if not x or x ~= x then
33 --error("atan bassed NaN")
34 return 0
30 return 0 -- NaN
3531 else
3632 return atann(x)
3733 end
3834 end
39
35 local table_copy = table.copy
36 local table_remove = table.remove
37 local vadd = vector.add
38 local vdirection = vector.direction
39 local vmultiply = vector.multiply
40 local vsubtract = vector.subtract
41 local settings = minetest.settings
42
43 -- creative check
44 local creative_cache = minetest.settings:get_bool("creative_mode")
45 function mobs.is_creative(name)
46 return creative_cache or minetest.check_player_privs(name,
47 {creative = true})
48 end
4049
4150 -- Load settings
42 local damage_enabled = minetest.settings:get_bool("enable_damage")
43 local mobs_spawn = minetest.settings:get_bool("mobs_spawn") ~= false
44 local peaceful_only = minetest.settings:get_bool("only_peaceful_mobs")
45 local disable_blood = minetest.settings:get_bool("mobs_disable_blood")
46 local mobs_drop_items = minetest.settings:get_bool("mobs_drop_items") ~= false
47 local mobs_griefing = minetest.settings:get_bool("mobs_griefing") ~= false
48 local creative = minetest.settings:get_bool("creative_mode")
49 local spawn_protected = minetest.settings:get_bool("mobs_spawn_protected") ~= false
50 local remove_far = minetest.settings:get_bool("remove_far_mobs") ~= false
51 local difficulty = tonumber(minetest.settings:get("mob_difficulty")) or 1.0
52 local show_health = minetest.settings:get_bool("mob_show_health") ~= false
53 local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99)
54 local mob_chance_multiplier = tonumber(minetest.settings:get("mob_chance_multiplier") or 1)
51 local damage_enabled = settings:get_bool("enable_damage")
52 local mobs_spawn = settings:get_bool("mobs_spawn") ~= false
53 local peaceful_only = settings:get_bool("only_peaceful_mobs")
54 local disable_blood = settings:get_bool("mobs_disable_blood")
55 local mobs_drop_items = settings:get_bool("mobs_drop_items") ~= false
56 local mobs_griefing = settings:get_bool("mobs_griefing") ~= false
57 local spawn_protected = settings:get_bool("mobs_spawn_protected") ~= false
58 local spawn_monster_protected = settings:get_bool("mobs_spawn_monster_protected") ~= false
59 local remove_far = settings:get_bool("remove_far_mobs") ~= false
60 local mob_area_spawn = settings:get_bool("mob_area_spawn")
61 local difficulty = tonumber(settings:get("mob_difficulty")) or 1.0
62 local show_health = settings:get_bool("mob_show_health") ~= false
63 local max_per_block = tonumber(settings:get("max_objects_per_block") or 99)
64 local mob_nospawn_range = tonumber(settings:get("mob_nospawn_range") or 12)
65 local active_limit = tonumber(settings:get("mob_active_limit") or 0)
66 local mob_chance_multiplier = tonumber(settings:get("mob_chance_multiplier") or 1)
67 local peaceful_player_enabled = settings:get_bool("enable_peaceful_player")
68 local mob_smooth_rotate = settings:get_bool("mob_smooth_rotate") ~= false
69 local active_mobs = 0
5570
5671 -- Peaceful mode message so players will know there are no monsters
5772 if peaceful_only then
6277 end
6378
6479 -- calculate aoc range for mob count
65 local aoc_range = tonumber(minetest.settings:get("active_block_range")) * 16
80 local aoc_range = tonumber(settings:get("active_block_range")) * 16
6681
6782 -- pathfinding settings
6883 local enable_pathfinding = true
69 local stuck_timeout = 3 -- how long before mob gets stuck in place and starts searching
70 local stuck_path_timeout = 10 -- how long will mob follow path before giving up
84 local stuck_timeout = 3 -- how long before stuck mod starts searching
85 local stuck_path_timeout = 5 -- how long will mob follow path before giving up
7186
7287 -- default nodes
7388 local node_fire = "fire:basic_flame"
7792 local node_snow = "default:snow"
7893 mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt"
7994
95 local mob_class = {
96 stepheight = 1.1,
97 fly_in = "air",
98 owner = "",
99 order = "",
100 jump_height = 4,
101 lifetimer = 180, -- 3 minutes
102 physical = true,
103 collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25},
104 visual_size = {x = 1, y = 1},
105 texture_mods = "",
106 makes_footstep_sound = false,
107 view_range = 5,
108 walk_velocity = 1,
109 run_velocity = 2,
110 light_damage = 0,
111 light_damage_min = 14,
112 light_damage_max = 15,
113 water_damage = 0,
114 lava_damage = 4,
115 fire_damage = 4,
116 air_damage = 0,
117 suffocation = 2,
118 fall_damage = 1,
119 fall_speed = -10, -- must be lower than -2 (default: -10)
120 drops = {},
121 armor = 100,
122 sounds = {},
123 jump = true,
124 knock_back = true,
125 walk_chance = 50,
126 stand_chance = 30,
127 attack_chance = 5,
128 passive = false,
129 blood_amount = 5,
130 blood_texture = "mobs_blood.png",
131 shoot_offset = 0,
132 floats = 1, -- floats in water by default
133 replace_offset = 0,
134 timer = 0,
135 env_damage_timer = 0, -- only used when state = "attack"
136 tamed = false,
137 pause_timer = 0,
138 horny = false,
139 hornytimer = 0,
140 child = false,
141 gotten = false,
142 health = 0,
143 reach = 3,
144 htimer = 0,
145 docile_by_day = false,
146 time_of_day = 0.5,
147 fear_height = 0,
148 runaway_timer = 0,
149 immune_to = {},
150 explosion_timer = 3,
151 allow_fuse_reset = true,
152 stop_to_explode = true,
153 dogshoot_count = 0,
154 dogshoot_count_max = 5,
155 dogshoot_count2_max = 5,
156 group_attack = false,
157 attack_monsters = false,
158 attack_animals = false,
159 attack_players = true,
160 attack_npcs = true,
161 facing_fence = false,
162 _breed_countdown = nil,
163 _cmi_is_mob = true
164 }
165
166 local mob_class_meta = {__index = mob_class}
167
80168
81169 -- play sound
82 local mob_sound = function(self, sound)
170 function mob_class:mob_sound(sound)
171
172 local pitch = 1.0
173
174 -- higher pitch for a child
175 if self.child then pitch = pitch * 1.5 end
176
177 -- a little random pitch to be different
178 pitch = pitch + random(-10, 10) * 0.005
83179
84180 if sound then
85181 minetest.sound_play(sound, {
86182 object = self.object,
87183 gain = 1.0,
88 max_hear_distance = self.sounds.distance
89 })
184 max_hear_distance = self.sounds.distance,
185 pitch = pitch
186 }, true)
90187 end
91188 end
92189
93190
94191 -- attack player/mob
95 local do_attack = function(self, player)
192 function mob_class:do_attack(player)
96193
97194 if self.state == "attack" then
98195 return
102199 self.state = "attack"
103200
104201 if random(0, 100) < 90 then
105 mob_sound(self, self.sounds.war_cry)
202 self:mob_sound(self.sounds.war_cry)
106203 end
107204 end
108205
110207 -- calculate distance
111208 local get_distance = function(a, b)
112209
210 if not a or not b then return 50 end -- nil check
211
113212 local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z
114213
115214 return square(x * x + y * y + z * z)
117216
118217
119218 -- collision function based on jordan4ibanez' open_ai mod
120 local collision = function(self)
219 function mob_class:collision()
121220
122221 local pos = self.object:get_pos()
123 local vel = self.object:get_velocity()
124222 local x, z = 0, 0
125223 local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5
126224
127225 for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do
128226
129 if object:is_player()
130 or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then
227 if object:is_player() then
131228
132229 local pos2 = object:get_pos()
133230 local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z}
141238 end
142239
143240
241 -- check if string exists in another string or table
242 local check_for = function(look_for, look_inside)
243
244 if type(look_inside) == "string" and look_inside == look_for then
245
246 return true
247
248 elseif type(look_inside) == "table" then
249
250 for _, str in pairs(look_inside) do
251
252 if str == look_for then
253 return true
254 end
255
256 if str:find("group:") then
257
258 local group = str:split(":")[2]
259
260 if minetest.get_item_group(look_for, group) ~= 0 then
261 return true
262 end
263 end
264 end
265 end
266
267 return false
268 end
269
270
144271 -- move mob in facing direction
145 local set_velocity = function(self, v)
272 function mob_class:set_velocity(v)
273
274 -- halt mob if it has been ordered to stay
275 if self.order == "stand" then
276
277 local vel = self.object:get_velocity() or {y = 0}
278
279 self.object:set_velocity({x = 0, y = vel.y, z = 0})
280
281 return
282 end
146283
147284 local c_x, c_y = 0, 0
148285
149286 -- can mob be pushed, if so calculate direction
150287 if self.pushable then
151 c_x, c_y = unpack(collision(self))
152 end
153
154 -- halt mob if it has been ordered to stay
155 if self.order == "stand" then
156 self.object:set_velocity({x = 0, y = 0, z = 0})
157 return
158 end
159
160 local yaw = (self.object:get_yaw() or 0) + self.rotate
161
162 self.object:set_velocity({
288 c_x, c_y = unpack(self:collision())
289 end
290
291 local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0)
292
293 -- nil check for velocity
294 v = v or 0.01
295
296 -- check if standing in liquid with max viscosity of 7
297 local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7)
298
299 -- only slow mob trying to move while inside a viscous fluid that
300 -- they aren't meant to be in (fish in water, spiders in cobweb etc)
301 if v > 0 and visc and visc > 0
302 and not check_for(self.standing_in, self.fly_in) then
303 v = v / (visc + 1)
304 end
305
306 -- set velocity
307 local vel = self.object:get_velocity() or 0
308
309 local new_vel = {
163310 x = (sin(yaw) * -v) + c_x,
164 y = self.object:get_velocity().y,
165 z = (cos(yaw) * v) + c_y,
166 })
311 y = vel.y,
312 z = (cos(yaw) * v) + c_y}
313
314 self.object:set_velocity(new_vel)
315 end
316
317 -- global version of above function
318 function mobs:set_velocity(entity, v)
319 mob_class.set_velocity(entity, v)
167320 end
168321
169322
170323 -- calculate mob velocity
171 local get_velocity = function(self)
324 function mob_class:get_velocity()
172325
173326 local v = self.object:get_velocity()
174327
328 if not v then return 0 end
329
175330 return (v.x * v.x + v.z * v.z) ^ 0.5
176331 end
177332
178333
179334 -- set and return valid yaw
180 local set_yaw = function(self, yaw, delay)
335 function mob_class:set_yaw(yaw, delay)
181336
182337 if not yaw or yaw ~= yaw then
183338 yaw = 0
184339 end
185340
186 delay = delay or 0
341 delay = mob_smooth_rotate and (delay or 0) or 0
187342
188343 if delay == 0 then
344
189345 self.object:set_yaw(yaw)
346
190347 return yaw
191348 end
192349
197354 end
198355
199356 -- global function to set mob yaw
200 function mobs:yaw(self, yaw, delay)
201 set_yaw(self, yaw, delay)
357 function mobs:yaw(entity, yaw, delay)
358 mob_class.set_yaw(entity, yaw, delay)
202359 end
203360
204361
205362 -- set defined animation
206 local set_animation = function(self, anim)
207
208 if not self.animation
209 or not anim then return end
363 function mob_class:set_animation(anim, force)
364
365 if not self.animation or not anim then return end
210366
211367 self.animation.current = self.animation.current or ""
212368
213 -- only set different animation for attacks when setting to same set
214 if anim ~= "punch" and anim ~= "shoot"
369 -- only use different animation for attacks when using same set
370 if force ~= true and anim ~= "punch" and anim ~= "shoot"
215371 and string.find(self.animation.current, anim) then
216372 return
217373 end
218374
219 -- check for more than one animation
220375 local num = 0
221376
377 -- check for more than one animation (max 4)
222378 for n = 1, 4 do
223379
224380 if self.animation[anim .. n .. "_start"]
244400 self.object:set_animation({
245401 x = self.animation[anim .. "_start"],
246402 y = self.animation[anim .. "_end"]},
247 self.animation[anim .. "_speed"] or self.animation.speed_normal or 15,
403 self.animation[anim .. "_speed"] or
404 self.animation.speed_normal or 15,
248405 0, self.animation[anim .. "_loop"] ~= false)
249406 end
250407
251 -- above function exported for mount.lua
252 function mobs:set_animation(self, anim)
253 set_animation(self, anim)
408 function mobs:set_animation(entity, anim)
409 entity.set_animation(entity, anim)
410 end
411
412
413 -- check line of sight (BrunoMine)
414 local line_of_sight = function(self, pos1, pos2, stepsize)
415
416 stepsize = stepsize or 1
417
418 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
419
420 -- normal walking and flying mobs can see you through air
421 if s == true then
422 return true
423 end
424
425 -- New pos1 to be analyzed
426 local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z}
427
428 local r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
429
430 -- Checks the return
431 if r == true then return true end
432
433 -- Nodename found
434 local nn = minetest.get_node(pos).name
435
436 -- Target Distance (td) to travel
437 local td = get_distance(pos1, pos2)
438
439 -- Actual Distance (ad) traveled
440 local ad = 0
441
442 -- It continues to advance in the line of sight in search of a real
443 -- obstruction which counts as 'walkable' nodebox.
444 while minetest.registered_nodes[nn]
445 and (minetest.registered_nodes[nn].walkable == false) do
446
447 -- Check if you can still move forward
448 if td < ad + stepsize then
449 return true -- Reached the target
450 end
451
452 -- Moves the analyzed pos
453 local d = get_distance(pos1, pos2)
454
455 npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x
456 npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y
457 npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z
458
459 -- NaN checks
460 if d == 0
461 or npos1.x ~= npos1.x
462 or npos1.y ~= npos1.y
463 or npos1.z ~= npos1.z then
464 return false
465 end
466
467 ad = ad + stepsize
468
469 -- scan again
470 r, pos = minetest.line_of_sight(npos1, pos2, stepsize)
471
472 if r == true then return true end
473
474 -- New Nodename found
475 nn = minetest.get_node(pos).name
476 end
477
478 return false
254479 end
255480
256481
257482 -- check line of sight (by BrunoMine, tweaked by Astrobe)
258 local line_of_sight = function(self, pos1, pos2, stepsize)
483 local new_line_of_sight = function(self, pos1, pos2, stepsize)
259484
260485 if not pos1 or not pos2 then return end
261486
262487 stepsize = stepsize or 1
263488
264 local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize)
489 local stepv = vmultiply(vdirection(pos1, pos2), stepsize)
265490
266491 local s, pos = minetest.line_of_sight(pos1, pos2, stepsize)
267492
280505 local nn = minetest.get_node(pos).name
281506
282507 -- It continues to advance in the line of sight in search of a real
283 -- obstruction which counts as 'normal' nodebox.
508 -- obstruction which counts as 'walkable' nodebox.
284509 while minetest.registered_nodes[nn]
285510 and (minetest.registered_nodes[nn].walkable == false) do
286 -- or minetest.registered_nodes[nn].drawtype == "nodebox") do
287
288 npos1 = vector.add(npos1, stepv)
511
512 npos1 = vadd(npos1, stepv)
289513
290514 if get_distance(npos1, pos2) < stepsize then return true end
291515
301525 return false
302526 end
303527
528 -- check line of sight using raycasting (thanks Astrobe)
529 local ray_line_of_sight = function(self, pos1, pos2)
530
531 local ray = minetest.raycast(pos1, pos2, true, false)
532 local thing = ray:next()
533
534 while thing do -- thing.type, thing.ref
535
536 if thing.type == "node" then
537
538 local name = minetest.get_node(thing.under).name
539
540 if minetest.registered_items[name]
541 and minetest.registered_items[name].walkable then
542 return false
543 end
544 end
545
546 thing = ray:next()
547 end
548
549 return true
550 end
551
552
553 function mob_class:line_of_sight(pos1, pos2, stepsize)
554
555 if minetest.raycast then -- only use if minetest 5.0 is detected
556 return ray_line_of_sight(self, pos1, pos2)
557 end
558
559 return line_of_sight(self, pos1, pos2, stepsize)
560 end
561
304562 -- global function
305 function mobs:line_of_sight(self, pos1, pos2, stepsize)
306
307 return line_of_sight(self, pos1, pos2, stepsize)
563 function mobs:line_of_sight(entity, pos1, pos2, stepsize)
564 return entity:line_of_sight(pos1, pos2, stepsize)
565 end
566
567
568 function mob_class:attempt_flight_correction(override)
569
570 if self:flight_check() and override ~= true then return true end
571
572 -- We are not flying in what we are supposed to.
573 -- See if we can find intended flight medium and return to it
574 local pos = self.object:get_pos() ; if not pos then return true end
575 local searchnodes = self.fly_in
576
577 if type(searchnodes) == "string" then
578 searchnodes = {self.fly_in}
579 end
580
581 local flyable_nodes = minetest.find_nodes_in_area(
582 {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1},
583 {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes)
584 -- pos.y + 0 hopefully fixes floating swimmers
585
586 if #flyable_nodes < 1 then
587 return false
588 end
589
590 local escape_target = flyable_nodes[random(#flyable_nodes)]
591 local escape_direction = vdirection(pos, escape_target)
592
593 self.object:set_velocity(
594 vmultiply(escape_direction, 1))
595
596 return true
308597 end
309598
310599
311600 -- are we flying in what we are suppose to? (taikedz)
312 local flight_check = function(self, pos_w)
601 function mob_class:flight_check()
313602
314603 local def = minetest.registered_nodes[self.standing_in]
315604
316 if not def then return false end -- nil check
317
318 if type(self.fly_in) == "string"
319 and self.standing_in == self.fly_in then
320
605 if not def then return false end
606
607 -- are we standing inside what we should be to fly/swim ?
608 if check_for(self.standing_in, self.fly_in) then
321609 return true
322
323 elseif type(self.fly_in) == "table" then
324
325 for _,fly_in in pairs(self.fly_in) do
326
327 if self.standing_in == fly_in then
328
329 return true
330 end
331 end
332610 end
333611
334612 -- stops mobs getting stuck inside stairs and plantlike nodes
342620 end
343621
344622
623 -- turn mob to face position
624 local yaw_to_pos = function(self, target, rot)
625
626 rot = rot or 0
627
628 local pos = self.object:get_pos()
629 local vec = {x = target.x - pos.x, z = target.z - pos.z}
630 local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate
631
632 if target.x > pos.x then
633 yaw = yaw + pi
634 end
635
636 yaw = self:set_yaw(yaw, rot)
637
638 return yaw
639 end
640
641 function mobs:yaw_to_pos(self, target, rot)
642 return yaw_to_pos(self, target, rot)
643 end
644
645
646 -- if stay near set then periodically check for nodes and turn towards them
647 function mob_class:do_stay_near()
648
649 if not self.stay_near then return false end
650
651 local pos = self.object:get_pos()
652 local searchnodes = self.stay_near[1]
653 local chance = self.stay_near[2] or 10
654
655 if not pos or random(chance) > 1 then
656 return false
657 end
658
659 if type(searchnodes) == "string" then
660 searchnodes = {self.stay_near[1]}
661 end
662
663 local r = self.view_range
664 local nearby_nodes = minetest.find_nodes_in_area(
665 {x = pos.x - r, y = pos.y - 1, z = pos.z - r},
666 {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes)
667
668 if #nearby_nodes < 1 then
669 return false
670 end
671
672 yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)])
673
674 self:set_animation("walk")
675
676 self:set_velocity(self.walk_velocity)
677
678 return true
679 end
680
681
345682 -- custom particle effects
346 local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow)
683 local effect = function(pos, amount, texture, min_size, max_size,
684 radius, gravity, glow, fall)
347685
348686 radius = radius or 2
349687 min_size = min_size or 0.5
351689 gravity = gravity or -10
352690 glow = glow or 0
353691
692 if fall == true then
693 fall = 0
694 elseif fall == false then
695 fall = radius
696 else
697 fall = -radius
698 end
699
354700 minetest.add_particlespawner({
355701 amount = amount,
356702 time = 0.25,
357703 minpos = pos,
358704 maxpos = pos,
359 minvel = {x = -radius, y = -radius, z = -radius},
705 minvel = {x = -radius, y = fall, z = -radius},
360706 maxvel = {x = radius, y = radius, z = radius},
361707 minacc = {x = 0, y = gravity, z = 0},
362708 maxacc = {x = 0, y = gravity, z = 0},
365711 minsize = min_size,
366712 maxsize = max_size,
367713 texture = texture,
368 glow = glow,
714 glow = glow
369715 })
370716 end
371717
718 function mobs:effect(pos, amount, texture, min_size, max_size,
719 radius, gravity, glow, fall)
720
721 effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall)
722 end
723
724
725 -- Thanks Wuzzy for the following editable settings
726
727 local HORNY_TIME = 30
728 local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes
729 local CHILD_GROW_TIME = 60 * 20 -- 20 minutes
730
372731
373732 -- update nametag colour
374 local update_tag = function(self)
733 function mob_class:update_tag()
375734
376735 local col = "#00FF00"
377736 local qua = self.hp_max / 4
388747 col = "#FF0000"
389748 end
390749
750 local text = ""
751
752 if self.horny == true then
753
754 text = "\nLoving: " .. (self.hornytimer - (HORNY_TIME + HORNY_AGAIN_TIME))
755
756 elseif self.child == true then
757
758 text = "\nGrowing: " .. (self.hornytimer - CHILD_GROW_TIME)
759
760 elseif self._breed_countdown then
761
762 text = "\nBreeding: " .. self._breed_countdown
763
764 end
765
766 self.infotext = "Health: " .. self.health .. " / " .. self.hp_max
767 .. "\n" .. "Owner: " .. self.owner
768 .. text
769
770 -- set changes
391771 self.object:set_properties({
392772 nametag = self.nametag,
393 nametag_color = col
773 nametag_color = col,
774 infotext = self.infotext
394775 })
395
396776 end
397777
398778
399779 -- drop items
400 local item_drop = function(self)
780 function mob_class:item_drop()
781
782 -- no drops if disabled by setting or mob is child
783 if not mobs_drop_items or self.child then return end
784
785 local pos = self.object:get_pos()
786
787 -- check for drops function
788 self.drops = type(self.drops) == "function"
789 and self.drops(pos) or self.drops
401790
402791 -- check for nil or no drops
403792 if not self.drops or #self.drops == 0 then
404793 return
405794 end
406795
407 -- no drops if disabled by setting
408 if not mobs_drop_items then return end
409
410 -- no drops for child mobs
411 if self.child then return end
412
413796 -- was mob killed by player?
414 local death_by_player = self.cause_of_death and self.cause_of_death.puncher
415 and self.cause_of_death.puncher:is_player() or nil
797 local death_by_player = self.cause_of_death
798 and self.cause_of_death.puncher
799 and self.cause_of_death.puncher:is_player()
416800
417801 local obj, item, num
418 local pos = self.object:get_pos()
419802
420803 for n = 1, #self.drops do
421804
422 if random(1, self.drops[n].chance) == 1 then
805 if random(self.drops[n].chance) == 1 then
423806
424807 num = random(self.drops[n].min or 0, self.drops[n].max or 1)
425808 item = self.drops[n].name
435818 end
436819 end
437820
438 -- only drop rare items (drops.min=0) if killed by player
439 if death_by_player then
440 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
441
442 elseif self.drops[n].min ~= 0 then
821 -- only drop rare items (drops.min = 0) if killed by player
822 if death_by_player or self.drops[n].min ~= 0 then
443823 obj = minetest.add_item(pos, ItemStack(item .. " " .. num))
444824 end
445825
448828 obj:set_velocity({
449829 x = random(-10, 10) / 9,
450830 y = 6,
451 z = random(-10, 10) / 9,
831 z = random(-10, 10) / 9
452832 })
453833
454834 elseif obj then
461841 end
462842
463843
844 -- remove mob and descrease counter
845 local remove_mob = function(self, decrease)
846
847 self.object:remove()
848
849 if decrease and active_limit > 0 then
850
851 active_mobs = active_mobs - 1
852
853 if active_mobs < 0 then
854 active_mobs = 0
855 end
856 end
857 --print("-- active mobs: " .. active_mobs .. " / " .. active_limit)
858 end
859
860 -- global function for removing mobs
861 function mobs:remove(self, decrease)
862 remove_mob(self, decrease)
863 end
864
865
464866 -- check if mob is dead or only hurt
465 local check_for_death = function(self, cmi_cause)
867 function mob_class:check_for_death(cmi_cause)
868
869 -- We dead already
870 if self.state == "die" then
871 return true
872 end
466873
467874 -- has health actually changed?
468875 if self.health == self.old_health and self.health > 0 then
469 return
470 end
876 return false
877 end
878
879 local damaged = self.health < self.old_health
471880
472881 self.old_health = self.health
473882
474883 -- still got some health? play hurt sound
475884 if self.health > 0 then
476885
477 mob_sound(self, self.sounds.damage)
886 -- only play hurt sound if damaged
887 if damaged then
888 self:mob_sound(self.sounds.damage)
889 end
478890
479891 -- make sure health isn't higher than max
480892 if self.health > self.hp_max then
482894 end
483895
484896 -- backup nametag so we can show health stats
485 if not self.nametag2 then
486 self.nametag2 = self.nametag or ""
487 end
488
489 if show_health
490 and (cmi_cause and cmi_cause.type == "punch") then
491
492 self.htimer = 2
493 self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
494
495 update_tag(self)
496 end
897 -- if not self.nametag2 then
898 -- self.nametag2 = self.nametag or ""
899 -- end
900
901 -- if show_health
902 -- and (cmi_cause and cmi_cause.type == "punch") then
903
904 -- self.htimer = 2
905 -- self.nametag = "♥ " .. self.health .. " / " .. self.hp_max
906 self:update_tag()
907 -- end
497908
498909 return false
499910 end
501912 self.cause_of_death = cmi_cause
502913
503914 -- drop items
504 item_drop(self)
505
506 mob_sound(self, self.sounds.death)
915 self:item_drop()
916
917 self:mob_sound(self.sounds.death)
507918
508919 local pos = self.object:get_pos()
509920
510921 -- execute custom death function
511 if self.on_die then
512
513 self.on_die(self, pos)
922 if pos and self.on_die then
923
924 self:on_die(pos)
514925
515926 if use_cmi then
516927 cmi.notify_die(self.object, cmi_cause)
517928 end
518929
519 self.object:remove()
930 remove_mob(self, true)
520931
521932 return true
522933 end
523934
524 -- default death function and die animation (if defined)
935 -- check for custom death function and die animation
525936 if self.animation
526937 and self.animation.die_start
527938 and self.animation.die_end then
528939
529940 local frames = self.animation.die_end - self.animation.die_start
530941 local speed = self.animation.die_speed or 15
531 local length = max(frames / speed, 0)
942 local length = max((frames / speed), 0)
943 local rot = self.animation.die_rotate and 5
532944
533945 self.attack = nil
946 self.following = nil
534947 self.v_start = false
535948 self.timer = 0
536949 self.blinktimer = 0
537950 self.passive = true
538951 self.state = "die"
539 set_velocity(self, 0)
540 set_animation(self, "die")
952 self.object:set_properties({
953 pointable = false, collide_with_objects = false,
954 automatic_rotate = rot, static_save = false
955 })
956 self:set_velocity(0)
957 self:set_animation("die")
541958
542959 minetest.after(length, function(self)
543960
544 if use_cmi and self.object:get_luaentity() then
545 cmi.notify_die(self.object, cmi_cause)
546 end
547
548 self.object:remove()
961 if self.object:get_luaentity() then
962
963 if use_cmi then
964 cmi.notify_die(self.object, cmi_cause)
965 end
966
967 remove_mob(self, true)
968 end
549969 end, self)
550 else
970
971 return true
972
973 elseif pos then -- otherwise remove mod and show particle effect
551974
552975 if use_cmi then
553976 cmi.notify_die(self.object, cmi_cause)
554977 end
555978
556 self.object:remove()
557 end
558
559 effect(pos, 20, "tnt_smoke.png")
979 remove_mob(self, true)
980
981 effect(pos, 20, "tnt_smoke.png")
982 end
560983
561984 return true
562985 end
563986
564987
988 -- get node but use fallback for nil or unknown
989 local node_ok = function(pos, fallback)
990
991 fallback = fallback or mobs.fallback_node
992
993 local node = minetest.get_node_or_nil(pos)
994
995 if node and minetest.registered_nodes[node.name] then
996 return node
997 end
998
999 return minetest.registered_nodes[fallback]
1000 end
1001
1002
1003 -- Returns true is node can deal damage to self
1004 local is_node_dangerous = function(self, nodename)
1005
1006 if self.water_damage > 0
1007 and minetest.get_item_group(nodename, "water") ~= 0 then
1008 return true
1009 end
1010
1011 if self.lava_damage > 0
1012 and minetest.get_item_group(nodename, "lava") ~= 0 then
1013 return true
1014 end
1015
1016 if self.fire_damage > 0
1017 and minetest.get_item_group(nodename, "fire") ~= 0 then
1018 return true
1019 end
1020
1021 if minetest.registered_nodes[nodename].damage_per_second > 0 then
1022 return true
1023 end
1024
1025 return false
1026 end
1027
1028
5651029 -- is mob facing a cliff
566 local is_at_cliff = function(self)
1030 function mob_class:is_at_cliff()
5671031
5681032 if self.fear_height == 0 then -- 0 for no falling protection!
5691033 return false
5701034 end
5711035
1036 -- get yaw but if nil returned object no longer exists
5721037 local yaw = self.object:get_yaw()
1038
1039 if not yaw then return false end
1040
5731041 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
5741042 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
5751043 local pos = self.object:get_pos()
5761044 local ypos = pos.y + self.collisionbox[2] -- just above floor
5771045
578 if minetest.line_of_sight(
1046 local free_fall, blocker = minetest.line_of_sight(
5791047 {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z},
580 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}
581 , 1) then
582
1048 {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z})
1049
1050 -- check for straight drop
1051 if free_fall then
5831052 return true
5841053 end
5851054
586 return false
587 end
588
589
590 -- get node but use fallback for nil or unknown
591 local node_ok = function(pos, fallback)
592
593 fallback = fallback or mobs.fallback_node
594
595 local node = minetest.get_node_or_nil(pos)
596
597 if node and minetest.registered_nodes[node.name] then
598 return node
599 end
600
601 return minetest.registered_nodes[fallback]
1055 local bnode = node_ok(blocker)
1056
1057 -- will we drop onto dangerous node?
1058 if is_node_dangerous(self, bnode.name) then
1059 return true
1060 end
1061
1062 local def = minetest.registered_nodes[bnode.name]
1063
1064 return (not def and def.walkable)
6021065 end
6031066
6041067
6051068 -- environmental damage (water, lava, fire, light etc.)
606 local do_env_damage = function(self)
1069 function mob_class:do_env_damage()
6071070
6081071 -- feed/tame text timer (so mob 'full' messages dont spam chat)
6091072 if self.htimer > 0 then
6111074 end
6121075
6131076 -- reset nametag after showing health stats
614 if self.htimer < 1 and self.nametag2 then
615
616 self.nametag = self.nametag2
617 self.nametag2 = nil
618
619 update_tag(self)
620 end
621
622 local pos = self.object:get_pos()
1077 -- if self.htimer < 1 and self.nametag2 then
1078
1079 -- self.nametag = self.nametag2
1080 -- self.nametag2 = nil
1081
1082 self:update_tag()
1083 -- end
1084
1085 local pos = self.object:get_pos() ; if not pos then return end
6231086
6241087 self.time_of_day = minetest.get_timeofday()
6251088
626 -- remove mob if standing inside ignore node
1089 -- halt mob if standing inside ignore node
6271090 if self.standing_in == "ignore" then
628 self.object:remove()
629 return
1091
1092 self.object:set_velocity({x = 0, y = 0, z = 0})
1093
1094 return true
1095 end
1096
1097 -- particle appears at random mob height
1098 local py = {
1099 x = pos.x,
1100 y = pos.y + random(self.collisionbox[2], self.collisionbox[5]),
1101 z = pos.z
1102 }
1103
1104 local nodef = minetest.registered_nodes[self.standing_in]
1105
1106 -- water
1107 if self.water_damage ~= 0 and nodef.groups.water then
1108
1109 self.health = self.health - self.water_damage
1110
1111 effect(py, 5, "bubble.png", nil, nil, 1, nil)
1112
1113 if self:check_for_death({type = "environment",
1114 pos = pos, node = self.standing_in}) then
1115 return true
1116 end
1117
1118 -- lava damage
1119 elseif self.lava_damage ~= 0 and nodef.groups.lava then
1120
1121 self.health = self.health - self.lava_damage
1122
1123 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1124
1125 if self:check_for_death({type = "environment", pos = pos,
1126 node = self.standing_in, hot = true}) then
1127 return true
1128 end
1129
1130 -- fire damage
1131 elseif self.fire_damage ~= 0 and nodef.groups.fire then
1132
1133 self.health = self.health - self.fire_damage
1134
1135 effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true)
1136
1137 if self:check_for_death({type = "environment", pos = pos,
1138 node = self.standing_in, hot = true}) then
1139 return true
1140 end
1141
1142 -- damage_per_second node check (not fire and lava)
1143 elseif nodef.damage_per_second ~= 0
1144 and nodef.groups.lava == nil and nodef.groups.fire == nil then
1145
1146 self.health = self.health - nodef.damage_per_second
1147
1148 effect(py, 5, "tnt_smoke.png")
1149
1150 if self:check_for_death({type = "environment",
1151 pos = pos, node = self.standing_in}) then
1152 return true
1153 end
1154 end
1155
1156 -- air damage
1157 if self.air_damage ~= 0 and self.standing_in == "air" then
1158
1159 self.health = self.health - self.air_damage
1160
1161 effect(py, 3, "bubble.png", 1, 1, 1, 0.2)
1162
1163 if self:check_for_death({type = "environment",
1164 pos = pos, node = self.standing_in}) then
1165 return true
1166 end
6301167 end
6311168
6321169 -- is mob light sensative, or scared of the dark :P
6391176
6401177 self.health = self.health - self.light_damage
6411178
642 effect(pos, 5, "tnt_smoke.png")
643
644 if check_for_death(self, {type = "light"}) then return end
645 end
646 end
647
648 local nodef = minetest.registered_nodes[self.standing_in]
649
650 pos.y = pos.y + 1 -- for particle effect position
651
652 -- water
653 if self.water_damage
654 and nodef.groups.water then
655
656 if self.water_damage ~= 0 then
657
658 self.health = self.health - self.water_damage
659
660 effect(pos, 5, "bubble.png", nil, nil, 1, nil)
661
662 if check_for_death(self, {type = "environment",
663 pos = pos, node = self.standing_in}) then return end
664 end
665
666 -- lava or fire or ignition source
667 elseif self.lava_damage
668 and nodef.groups.igniter then
669 -- and (nodef.groups.lava
670 -- or self.standing_in == node_fire
671 -- or self.standing_in == node_permanent_flame) then
672
673 if self.lava_damage ~= 0 then
674
675 self.health = self.health - self.lava_damage
676
677 effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil)
678
679 if check_for_death(self, {type = "environment",
680 pos = pos, node = self.standing_in, hot = true}) then return end
681 end
682
683 -- damage_per_second node check
684 elseif nodef.damage_per_second ~= 0 then
685
686 self.health = self.health - nodef.damage_per_second
687
688 effect(pos, 5, "tnt_smoke.png")
689
690 if check_for_death(self, {type = "environment",
691 pos = pos, node = self.standing_in}) then return end
692 end
693 --[[
1179 effect(py, 5, "tnt_smoke.png")
1180
1181 if self:check_for_death({type = "light"}) then
1182 return true
1183 end
1184 end
1185 end
1186
6941187 --- suffocation inside solid node
695 if self.suffocation ~= 0
696 and nodef.walkable == true
697 and nodef.groups.disable_suffocation ~= 1
698 and nodef.drawtype == "normal" then
699
700 self.health = self.health - self.suffocation
701
702 if check_for_death(self, {type = "environment",
703 pos = pos, node = self.standing_in}) then return end
704 end
705 ]]
706 check_for_death(self, {type = "unknown"})
1188 if (self.suffocation and self.suffocation ~= 0)
1189 and (nodef.walkable == nil or nodef.walkable == true)
1190 and (nodef.collision_box == nil or nodef.collision_box.type == "regular")
1191 and (nodef.node_box == nil or nodef.node_box.type == "regular")
1192 and (nodef.groups.disable_suffocation ~= 1) then
1193
1194 local damage
1195
1196 if self.suffocation == true then
1197 damage = 2
1198 else
1199 damage = (self.suffocation or 2)
1200 end
1201
1202 self.health = self.health - damage
1203
1204 if self:check_for_death({type = "suffocation",
1205 pos = pos, node = self.standing_in}) then
1206 return true
1207 end
1208 end
1209
1210 return self:check_for_death({type = "unknown"})
7071211 end
7081212
7091213
7101214 -- jump if facing a solid node (not fences or gates)
711 local do_jump = function(self)
1215 function mob_class:do_jump()
7121216
7131217 if not self.jump
7141218 or self.jump_height == 0
7221226
7231227 -- something stopping us while moving?
7241228 if self.state ~= "stand"
725 and get_velocity(self) > 0.5
1229 and self:get_velocity() > 0.5
7261230 and self.object:get_velocity().y ~= 0 then
7271231 return false
7281232 end
7301234 local pos = self.object:get_pos()
7311235 local yaw = self.object:get_yaw()
7321236
733 -- what is mob standing on?
734 pos.y = pos.y + self.collisionbox[2] - 0.2
735
736 local nod = node_ok(pos)
737
738 --print ("standing on:", nod.name, pos.y)
739
740 if minetest.registered_nodes[nod.name].walkable == false then
1237 -- sanity check
1238 if not yaw then return false end
1239
1240 -- we can only jump if standing on solid node
1241 if minetest.registered_nodes[self.standing_on].walkable == false then
7411242 return false
7421243 end
7431244
7451246 local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5)
7461247 local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5)
7471248
1249 -- set y_pos to base of mob
1250 pos.y = pos.y + self.collisionbox[2]
1251
7481252 -- what is in front of mob?
7491253 local nod = node_ok({
750 x = pos.x + dir_x,
751 y = pos.y + 0.5,
752 z = pos.z + dir_z
1254 x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z
7531255 })
7541256
755 -- thin blocks that do not need to be jumped
756 if nod.name == node_snow then
757 return false
758 end
759
760 --print ("in front:", nod.name, pos.y + 0.5)
761
762 if self.walk_chance == 0
763 or minetest.registered_items[nod.name].walkable then
764
765 if not nod.name:find("fence")
766 and not nod.name:find("gate") then
767
768 local v = self.object:get_velocity()
769
770 v.y = self.jump_height
771
772 set_animation(self, "jump") -- only when defined
773
774 self.object:set_velocity(v)
775
776 -- when in air move forward
777 minetest.after(0.3, function(self, v)
778
779 if self.object:get_luaentity() then
780
781 self.object:set_acceleration({
782 x = v.x * 2,--1.5,
783 y = 0,
784 z = v.z * 2,--1.5
785 })
786 end
787 end, self, v)
788
789 if get_velocity(self) > 0 then
790 mob_sound(self, self.sounds.jump)
791 end
792 else
793 self.facing_fence = true
794 end
1257 -- what is above and in front?
1258 local nodt = node_ok({
1259 x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z
1260 })
1261
1262 local blocked = minetest.registered_nodes[nodt.name].walkable
1263
1264 -- are we facing a fence or wall
1265 if nod.name:find("fence") or nod.name:find("gate") or nod.name:find("wall") then
1266 self.facing_fence = true
1267 end
1268 --[[
1269 print("on: " .. self.standing_on
1270 .. ", front: " .. nod.name
1271 .. ", front above: " .. nodt.name
1272 .. ", blocked: " .. (blocked and "yes" or "no")
1273 .. ", fence: " .. (self.facing_fence and "yes" or "no")
1274 )
1275 ]]
1276 -- jump if standing on solid node (not snow) and not blocked
1277 if (self.walk_chance == 0 or minetest.registered_items[nod.name].walkable)
1278 and not blocked and not self.facing_fence and nod.name ~= node_snow then
1279
1280 local v = self.object:get_velocity()
1281
1282 v.y = self.jump_height
1283
1284 self:set_animation("jump") -- only when defined
1285
1286 self.object:set_velocity(v)
1287
1288 -- when in air move forward
1289 minetest.after(0.3, function(self, v)
1290
1291 if self.object:get_luaentity() then
1292
1293 self.object:set_acceleration({
1294 x = v.x * 2,
1295 y = 0,
1296 z = v.z * 2
1297 })
1298 end
1299 end, self, v)
1300
1301 if self:get_velocity() > 0 then
1302 self:mob_sound(self.sounds.jump)
1303 end
1304
1305 self.jump_count = 0
7951306
7961307 return true
1308 end
1309
1310 -- if blocked for 3 counts then turn
1311 if not self.following and (self.facing_fence or blocked) then
1312
1313 self.jump_count = (self.jump_count or 0) + 1
1314
1315 if self.jump_count > 2 then
1316
1317 local yaw = self.object:get_yaw() or 0
1318 local turn = random(0, 2) + 1.35
1319
1320 yaw = self:set_yaw(yaw + turn, 12)
1321
1322 self.jump_count = 0
1323 end
7971324 end
7981325
7991326 return false
8131340 obj_pos = objs[n]:get_pos()
8141341
8151342 dist = get_distance(pos, obj_pos)
1343
8161344 if dist < 1 then dist = 1 end
8171345
8181346 local damage = floor((4 / dist) * radius)
8271355 end
8281356
8291357
1358 -- can mob see player
1359 local is_invisible = function(self, player_name)
1360
1361 if mobs.invis[player_name] and not self.ignore_invisibility then
1362 return true
1363 end
1364 end
1365
1366
8301367 -- should mob follow what I'm holding ?
831 local follow_holding = function(self, clicker)
832
833 if mobs.invis[clicker:get_player_name()] then
1368 function mob_class:follow_holding(clicker)
1369
1370 if is_invisible(self, clicker:get_player_name()) then
8341371 return false
8351372 end
8361373
8371374 local item = clicker:get_wielded_item()
838 local t = type(self.follow)
839
840 -- single item
841 if t == "string"
842 and item:get_name() == self.follow then
1375
1376 -- are we holding an item mob can follow ?
1377 if check_for(item:get_name(), self.follow) then
8431378 return true
844
845 -- multiple items
846 elseif t == "table" then
847
848 for no = 1, #self.follow do
849
850 if self.follow[no] == item:get_name() then
851 return true
852 end
853 end
8541379 end
8551380
8561381 return false
8581383
8591384
8601385 -- find two animals of same type and breed if nearby and horny
861 local breed = function(self)
862
863 -- child takes 240 seconds before growing into adult
1386 function mob_class:breed()
1387
1388 -- child takes a long time before growing into adult
8641389 if self.child == true then
8651390
8661391 self.hornytimer = self.hornytimer + 1
8671392
868 if self.hornytimer > 240 then
1393 if self.hornytimer > CHILD_GROW_TIME then
8691394
8701395 self.child = false
8711396 self.hornytimer = 0
8751400 mesh = self.base_mesh,
8761401 visual_size = self.base_size,
8771402 collisionbox = self.base_colbox,
878 selectionbox = self.base_selbox,
1403 selectionbox = self.base_selbox
8791404 })
8801405
8811406 -- custom function when child grows up
8821407 if self.on_grown then
8831408 self.on_grown(self)
8841409 else
885 -- jump when fully grown so as not to fall into ground
886 self.object:set_velocity({
887 x = 0,
888 y = self.jump_height,
889 z = 0
890 })
1410 local pos = self.object:get_pos() ; if not pos then return end
1411 local ent = self.object:get_luaentity()
1412
1413 pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4
1414
1415 self.object:set_pos(pos)
1416
1417 -- jump slightly when fully grown so as not to fall into ground
1418 self.object:set_velocity({x = 0, y = 0.5, z = 0 })
8911419 end
8921420 end
8931421
8941422 return
8951423 end
8961424
897 -- horny animal can mate for 40 seconds,
898 -- afterwards horny animal cannot mate again for 200 seconds
1425 -- horny animal can mate for HORNY_TIME seconds,
1426 -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds
8991427 if self.horny == true
900 and self.hornytimer < 240 then
1428 and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then
9011429
9021430 self.hornytimer = self.hornytimer + 1
9031431
904 if self.hornytimer >= 240 then
1432 if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then
9051433 self.hornytimer = 0
9061434 self.horny = false
9071435 end
1436
1437 self:update_tag()
9081438 end
9091439
9101440 -- find another same animal who is also horny and mate if nearby
9111441 if self.horny == true
912 and self.hornytimer <= 40 then
1442 and self.hornytimer <= HORNY_TIME then
9131443
9141444 local pos = self.object:get_pos()
9151445
916 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1)
1446 effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8,
1447 "heart.png", 3, 4, 1, 0.1)
9171448
9181449 local objs = minetest.get_objects_inside_radius(pos, 3)
919 local num = 0
920 local ent = nil
1450 local ent
9211451
9221452 for n = 1, #objs do
9231453
9311461 if ent.name == self.name then
9321462 canmate = true
9331463 else
934 local entname = string.split(ent.name,":")
935 local selfname = string.split(self.name,":")
1464 local entname = ent.name:split(":")
1465 local selfname = self.name:split(":")
9361466
9371467 if entname[1] == selfname[1] then
938 entname = string.split(entname[2],"_")
939 selfname = string.split(selfname[2],"_")
1468 entname = entname[2]:split("_")
1469 selfname = selfname[2]:split("_")
9401470
9411471 if entname[1] == selfname[1] then
9421472 canmate = true
9451475 end
9461476 end
9471477
948 if ent
1478 -- found another similar horny animal that isn't self?
1479 if ent and ent.object ~= self.object
9491480 and canmate == true
9501481 and ent.horny == true
951 and ent.hornytimer <= 40 then
952 num = num + 1
953 end
954
955 -- found your mate? then have a baby
956 if num > 1 then
957
958 self.hornytimer = 41
959 ent.hornytimer = 41
1482 and ent.hornytimer <= HORNY_TIME then
1483
1484 local pos2 = ent.object:get_pos()
1485
1486 -- Have mobs face one another
1487 yaw_to_pos(self, pos2)
1488 yaw_to_pos(ent, self.object:get_pos())
1489
1490 self.hornytimer = HORNY_TIME + 1
1491 ent.hornytimer = HORNY_TIME + 1
1492
1493 self:update_tag()
1494
1495 -- have we reached active mob limit
1496 if active_limit > 0 and active_mobs >= active_limit then
1497 minetest.chat_send_player(self.owner,
1498 S("Active Mob Limit Reached!")
1499 .. " (" .. active_mobs
1500 .. " / " .. active_limit .. ")")
1501 return
1502 end
9601503
9611504 -- spawn baby
9621505 minetest.after(5, function(self, ent)
9691512 if self.on_breed then
9701513
9711514 -- when false skip going any further
972 if self.on_breed(self, ent) == false then
1515 if self:on_breed(ent) == false then
9731516 return
9741517 end
9751518 else
9761519 effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5)
9771520 end
9781521
1522 pos.y = pos.y + 0.5 -- spawn child a little higher
1523
9791524 local mob = minetest.add_entity(pos, self.name)
9801525 local ent2 = mob:get_luaentity()
9811526 local textures = self.base_texture
9901535 textures = textures,
9911536 visual_size = {
9921537 x = self.base_size.x * .5,
993 y = self.base_size.y * .5,
1538 y = self.base_size.y * .5
9941539 },
9951540 collisionbox = {
9961541 self.base_colbox[1] * .5,
9981543 self.base_colbox[3] * .5,
9991544 self.base_colbox[4] * .5,
10001545 self.base_colbox[5] * .5,
1001 self.base_colbox[6] * .5,
1546 self.base_colbox[6] * .5
10021547 },
10031548 selectionbox = {
10041549 self.base_selbox[1] * .5,
10061551 self.base_selbox[3] * .5,
10071552 self.base_selbox[4] * .5,
10081553 self.base_selbox[5] * .5,
1009 self.base_selbox[6] * .5,
1554 self.base_selbox[6] * .5
10101555 },
10111556 })
10121557 -- tamed and owned by parents' owner
10151560 ent2.owner = self.owner
10161561 end, self, ent)
10171562
1018 num = 0
1019
10201563 break
10211564 end
10221565 end
10251568
10261569
10271570 -- find and replace what mob is looking for (grass, wheat etc.)
1028 local replace = function(self, pos)
1571 function mob_class:replace(pos)
1572
1573 local vel = self.object:get_velocity()
1574 if not vel then return end
10291575
10301576 if not mobs_griefing
10311577 or not self.replace_rate
10321578 or not self.replace_what
10331579 or self.child == true
1034 or self.object:get_velocity().y ~= 0
1035 or random(1, self.replace_rate) > 1 then
1580 or vel.y ~= 0
1581 or random(self.replace_rate) > 1 then
10361582 return
10371583 end
10381584
10551601
10561602 if #minetest.find_nodes_in_area(pos, pos, what) > 0 then
10571603
1058 -- print ("replace node = ".. minetest.get_node(pos).name, pos.y)
1059
1060 local oldnode = {name = what}
1061 local newnode = {name = with}
1062 local on_replace_return
1604 -- print("replace node = ".. minetest.get_node(pos).name, pos.y)
10631605
10641606 if self.on_replace then
1065 on_replace_return = self.on_replace(self, pos, oldnode, newnode)
1066 end
1067
1068 if on_replace_return ~= false then
1069
1070 minetest.set_node(pos, {name = with})
1071
1072 -- when cow/sheep eats grass, replace wool and milk
1073 if self.gotten == true then
1074 self.gotten = false
1075 self.object:set_properties(self)
1076 end
1077 end
1607
1608 local oldnode = what or ""
1609 local newnode = with
1610
1611 -- pass actual node name when using table or groups
1612 if type(oldnode) == "table"
1613 or oldnode:find("group:") then
1614 oldnode = minetest.get_node(pos).name
1615 end
1616
1617 if self:on_replace(pos, oldnode, newnode) == false then
1618 return
1619 end
1620 end
1621
1622 minetest.set_node(pos, {name = with})
10781623 end
10791624 end
10801625
10811626
10821627 -- check if daytime and also if mob is docile during daylight hours
1083 local day_docile = function(self)
1628 function mob_class:day_docile()
10841629
10851630 if self.docile_by_day == false then
10861631
10971642
10981643 local los_switcher = false
10991644 local height_switcher = false
1100
1101 -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3
1102 local smart_mobs = function(self, s, p, dist, dtime)
1645 local can_dig_drop = function(pos)
1646
1647 if minetest.is_protected(pos, "") then
1648 return false
1649 end
1650
1651 local node = node_ok(pos, "air").name
1652 local ndef = minetest.registered_nodes[node]
1653
1654 if node ~= "ignore"
1655 and ndef
1656 and ndef.drawtype ~= "airlike"
1657 and not ndef.groups.level
1658 and not ndef.groups.unbreakable
1659 and not ndef.groups.liquid then
1660
1661 local drops = minetest.get_node_drops(node)
1662
1663 for _, item in ipairs(drops) do
1664
1665 minetest.add_item({
1666 x = pos.x - 0.5 + random(),
1667 y = pos.y - 0.5 + random(),
1668 z = pos.z - 0.5 + random()
1669 }, item)
1670 end
1671
1672 minetest.remove_node(pos)
1673
1674 return true
1675 end
1676
1677 return false
1678 end
1679
1680
1681 local pathfinder_mod = minetest.get_modpath("pathfinder")
1682 -- path finding and smart mob routine by rnd,
1683 -- line_of_sight and other edits by Elkien3
1684 function mob_class:smart_mobs(s, p, dist, dtime)
11031685
11041686 local s1 = self.path.lastpos
1105
1106 local target_pos = self.attack:get_pos()
1687 local target_pos = p
1688
11071689
11081690 -- is it becoming stuck?
11091691 if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then
11761758 end, self)
11771759 end
11781760
1179 if abs(vector.subtract(s,target_pos).y) > self.stepheight then
1761 if abs(vsubtract(s,target_pos).y) > self.stepheight then
11801762
11811763 if height_switcher then
11821764 use_pathfind = true
11891771 end
11901772 end
11911773
1774 -- lets try find a path, first take care of positions
1775 -- since pathfinder is very sensitive
11921776 if use_pathfind then
1193 -- lets try find a path, first take care of positions
1194 -- since pathfinder is very sensitive
1195 local sheight = self.collisionbox[5] - self.collisionbox[2]
11961777
11971778 -- round position to center of node to avoid stuck in walls
11981779 -- also adjust height for player models!
11991780 s.x = floor(s.x + 0.5)
1200 -- s.y = floor(s.y + 0.5) - sheight
12011781 s.z = floor(s.z + 0.5)
12021782
12031783 local ssight, sground = minetest.line_of_sight(s, {
12151795 p1.z = floor(p1.z + 0.5)
12161796
12171797 local dropheight = 6
1798
12181799 if self.fear_height ~= 0 then dropheight = self.fear_height end
12191800
1220 self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra")
1221
1801 local jumpheight = 0
1802
1803 if self.jump and self.jump_height >= 4 then
1804 jumpheight = min(ceil(self.jump_height / 4), 4)
1805
1806 elseif self.stepheight > 0.5 then
1807 jumpheight = 1
1808 end
1809
1810 if pathfinder_mod then
1811 self.path.way = pathfinder.find_path(s, p1, self, dtime)
1812 else
1813 self.path.way = minetest.find_path(s, p1, 16, jumpheight,
1814 dropheight, "Dijkstra")
1815 end
12221816 --[[
12231817 -- show path using particles
12241818 if self.path.way and #self.path.way > 0 then
1225 print ("-- path length:" .. tonumber(#self.path.way))
1819
1820 print("-- path length:" .. tonumber(#self.path.way))
1821
12261822 for _,pos in pairs(self.path.way) do
12271823 minetest.add_particle({
12281824 pos = pos,
12391835 ]]
12401836
12411837 self.state = ""
1242 do_attack(self, self.attack)
1838
1839 if self.attack then
1840 self:do_attack(self.attack)
1841 end
12431842
12441843 -- no path found, try something else
12451844 if not self.path.way then
12491848 -- lets make way by digging/building if not accessible
12501849 if self.pathfinding == 2 and mobs_griefing then
12511850
1252 -- is player higher than mob?
1253 if s.y < p1.y then
1851 -- is player more than 1 block higher than mob?
1852 if p1.y > (s.y + 1) then
12541853
12551854 -- build upwards
12561855 if not minetest.is_protected(s, "") then
12581857 local ndef1 = minetest.registered_nodes[self.standing_in]
12591858
12601859 if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then
1261
1262 minetest.set_node(s, {name = mobs.fallback_node})
1860 minetest.set_node(s, {name = mobs.fallback_node})
12631861 end
12641862 end
12651863
1266 local sheight = math.ceil(self.collisionbox[5]) + 1
1864 local sheight = ceil(self.collisionbox[5]) + 1
12671865
12681866 -- assume mob is 2 blocks high so it digs above its head
12691867 s.y = s.y + sheight
12701868
12711869 -- remove one block above to make room to jump
1272 if not minetest.is_protected(s, "") then
1273
1274 local node1 = node_ok(s, "air").name
1275 local ndef1 = minetest.registered_nodes[node1]
1276
1277 if node1 ~= "air"
1278 and node1 ~= "ignore"
1279 and ndef1
1280 and not ndef1.groups.level
1281 and not ndef1.groups.unbreakable
1282 and not ndef1.groups.liquid then
1283
1284 minetest.set_node(s, {name = "air"})
1285 minetest.add_item(s, ItemStack(node1))
1286
1287 end
1288 end
1870 can_dig_drop(s)
12891871
12901872 s.y = s.y - sheight
12911873 self.object:set_pos({x = s.x, y = s.y + 2, z = s.z})
1874
1875 -- is player more than 1 block lower than mob
1876 elseif p1.y < (s.y - 1) then
1877
1878 -- dig down
1879 s.y = s.y - self.collisionbox[4] - 0.2
1880
1881 can_dig_drop(s)
12921882
12931883 else -- dig 2 blocks to make door toward player direction
12941884
12991889 z = s.z + sin(yaw1)
13001890 }
13011891
1302 if not minetest.is_protected(p1, "") then
1303
1304 local node1 = node_ok(p1, "air").name
1305 local ndef1 = minetest.registered_nodes[node1]
1306
1307 if node1 ~= "air"
1308 and node1 ~= "ignore"
1309 and ndef1
1310 and not ndef1.groups.level
1311 and not ndef1.groups.unbreakable
1312 and not ndef1.groups.liquid then
1313
1314 minetest.add_item(p1, ItemStack(node1))
1315 minetest.set_node(p1, {name = "air"})
1316 end
1317
1318 p1.y = p1.y + 1
1319 node1 = node_ok(p1, "air").name
1320 ndef1 = minetest.registered_nodes[node1]
1321
1322 if node1 ~= "air"
1323 and node1 ~= "ignore"
1324 and ndef1
1325 and not ndef1.groups.level
1326 and not ndef1.groups.unbreakable
1327 and not ndef1.groups.liquid then
1328
1329 minetest.add_item(p1, ItemStack(node1))
1330 minetest.set_node(p1, {name = "air"})
1331 end
1332
1333 end
1892 -- dig bottom node first incase of door
1893 can_dig_drop(p1)
1894
1895 p1.y = p1.y + 1
1896
1897 can_dig_drop(p1)
13341898 end
13351899 end
13361900
13371901 -- will try again in 2 second
13381902 self.path.stuck_timer = stuck_timeout - 2
13391903
1340 -- frustration! cant find the damn path :(
1341 mob_sound(self, self.sounds.random)
1904 elseif s.y < p1.y and (not self.fly) then
1905 self:do_jump() --add jump to pathfinding
1906 self.path.following = true
13421907 else
13431908 -- yay i found path
1344 mob_sound(self, self.sounds.war_cry)
1345 set_velocity(self, self.walk_velocity)
1909 if self.attack then
1910 self:mob_sound(self.sounds.war_cry)
1911 else
1912 self:mob_sound(self.sounds.random)
1913 end
1914
1915 self:set_velocity(self.walk_velocity)
13461916
13471917 -- follow path now that it has it
13481918 self.path.following = true
13511921 end
13521922
13531923
1354 -- specific attacks
1355 local specific_attack = function(list, what)
1356
1357 -- no list so attack default (player, animals etc.)
1358 if list == nil then
1359 return true
1360 end
1361
1362 -- found entity on list to attack?
1363 for no = 1, #list do
1364
1365 if list[no] == what then
1924 -- peaceful player privilege support
1925 local function is_peaceful_player(player)
1926
1927 if peaceful_player_enabled then
1928
1929 local player_name = player:get_player_name()
1930
1931 if player_name
1932 and minetest.check_player_privs(player_name, "peaceful_player") then
13661933 return true
13671934 end
13681935 end
13711938 end
13721939
13731940
1374 -- general attack function for all mobs ==========
1375 local general_attack = function(self)
1941 -- general attack function for all mobs
1942 function mob_class:general_attack()
13761943
13771944 -- return if already attacking, passive or docile during day
13781945 if self.passive
1946 or self.state == "runaway"
13791947 or self.state == "attack"
1380 or day_docile(self) then
1948 or self:day_docile() then
13811949 return
13821950 end
13831951
1384 local s = self.object:get_pos()
1952 local s = self.object:get_pos() ; if not s then return end
13851953 local objs = minetest.get_objects_inside_radius(s, self.view_range)
13861954
13871955 -- remove entities we aren't interested in
13921960 -- are we a player?
13931961 if objs[n]:is_player() then
13941962
1395 -- if player invisible or mob not setup to attack then remove from list
1396 if self.attack_players == false
1963 -- if player invisible or mob cannot attack then remove from list
1964 if not damage_enabled
1965 or self.attack_players == false
13971966 or (self.owner and self.type ~= "monster")
1398 or mobs.invis[objs[n]:get_player_name()]
1399 or not specific_attack(self.specific_attack, "player") then
1967 or is_invisible(self, objs[n]:get_player_name())
1968 or (self.specific_attack
1969 and not check_for("player", self.specific_attack)) then
14001970 objs[n] = nil
14011971 --print("- pla", n)
14021972 end
14091979 or (not self.attack_animals and ent.type == "animal")
14101980 or (not self.attack_monsters and ent.type == "monster")
14111981 or (not self.attack_npcs and ent.type == "npc")
1412 or not specific_attack(self.specific_attack, ent.name) then
1982 or (self.specific_attack
1983 and not check_for(ent.name, self.specific_attack)) then
14131984 objs[n] = nil
14141985 --print("- mob", n, self.name, ent.name)
14151986 end
14392010 -- choose closest player to attack that isnt self
14402011 if dist ~= 0
14412012 and dist < min_dist
1442 and line_of_sight(self, sp, p, 2) == true then
2013 and self:line_of_sight(sp, p, 2) == true
2014 and not is_peaceful_player(player) then
14432015 min_dist = dist
14442016 min_player = player
14452017 end
14462018 end
14472019
14482020 -- attack closest player or mob
1449 if min_player and random(1, 100) > self.attack_chance then
1450 do_attack(self, min_player)
1451 end
1452 end
1453
1454
1455 -- specific runaway
1456 local specific_runaway = function(list, what)
1457
1458 -- no list so do not run
1459 if list == nil then
1460 return false
1461 end
1462
1463 -- found entity on list to attack?
1464 for no = 1, #list do
1465
1466 if list[no] == what then
1467 return true
1468 end
1469 end
1470
1471 return false
2021 if min_player and random(100) > self.attack_chance then
2022 self:do_attack(min_player)
2023 end
14722024 end
14732025
14742026
14752027 -- find someone to runaway from
1476 local runaway_from = function(self)
2028 function mob_class:do_runaway_from()
14772029
14782030 if not self.runaway_from then
14792031 return
14802032 end
14812033
1482 local s = self.object:get_pos()
2034 local s = self.object:get_pos() ; if not s then return end
14832035 local p, sp, dist, pname
14842036 local player, obj, min_player, name
14852037 local min_dist = self.view_range + 1
14912043
14922044 pname = objs[n]:get_player_name()
14932045
1494 if mobs.invis[pname]
2046 if is_invisible(self, pname)
14952047 or self.owner == pname then
14962048
14972049 name = ""
15102062
15112063 -- find specific mob to runaway from
15122064 if name ~= "" and name ~= self.name
1513 and specific_runaway(self.runaway_from, name) then
1514
1515 p = player:get_pos()
2065 and (self.runaway_from and check_for(name, self.runaway_from)) then
2066
15162067 sp = s
2068 p = player and player:get_pos() or s
15172069
15182070 -- aim higher to make looking up hills more realistic
15192071 p.y = p.y + 1
15232075
15242076 -- choose closest player/mob to runaway from
15252077 if dist < min_dist
1526 and line_of_sight(self, sp, p, 2) == true then
2078 and self:line_of_sight(sp, p, 2) == true then
15272079 min_dist = dist
15282080 min_player = player
15292081 end
15322084
15332085 if min_player then
15342086
1535 local lp = player:get_pos()
1536 local vec = {
1537 x = lp.x - s.x,
1538 y = lp.y - s.y,
1539 z = lp.z - s.z
1540 }
1541
1542 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
1543
1544 if lp.x > s.x then
1545 yaw = yaw + pi
1546 end
1547
1548 yaw = set_yaw(self, yaw, 4)
2087 yaw_to_pos(self, min_player:get_pos(), 3)
2088
15492089 self.state = "runaway"
15502090 self.runaway_timer = 3
15512091 self.following = nil
15542094
15552095
15562096 -- follow player if owner or holding item, if fish outta water then flop
1557 local follow_flop = function(self)
2097 function mob_class:follow_flop()
15582098
15592099 -- find player to follow
1560 if (self.follow ~= ""
1561 or self.order == "follow")
2100 if (self.follow ~= "" or self.order == "follow")
15622101 and not self.following
15632102 and self.state ~= "attack"
15642103 and self.state ~= "runaway" then
15652104
1566 local s = self.object:get_pos()
2105 local s = self.object:get_pos() ; if not s then return end
15672106 local players = minetest.get_connected_players()
15682107
15692108 for n = 1, #players do
15702109
15712110 if get_distance(players[n]:get_pos(), s) < self.view_range
1572 and not mobs.invis[ players[n]:get_player_name() ] then
2111 and not is_invisible(self, players[n]:get_player_name()) then
15732112
15742113 self.following = players[n]
15752114
15902129 self.following = nil
15912130 end
15922131 else
1593 -- stop following player if not holding specific item
2132 -- stop following player if not holding specific item or mob is horny
15942133 if self.following
15952134 and self.following:is_player()
1596 and follow_holding(self, self.following) == false then
2135 and (self:follow_holding(self.following) == false
2136 or self.horny) then
15972137 self.following = nil
15982138 end
15992139
16222162 if dist > self.view_range then
16232163 self.following = nil
16242164 else
1625 local vec = {
1626 x = p.x - s.x,
1627 z = p.z - s.z
1628 }
1629
1630 local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1631
1632 if p.x > s.x then yaw = yaw + pi end
1633
1634 yaw = set_yaw(self, yaw, 6)
2165 yaw_to_pos(self, p)
16352166
16362167 -- anyone but standing npc's can move along
16372168 if dist > self.reach
16382169 and self.order ~= "stand" then
16392170
1640 set_velocity(self, self.walk_velocity)
2171 self:set_velocity(self.walk_velocity)
16412172
16422173 if self.walk_chance ~= 0 then
1643 set_animation(self, "walk")
2174 self:set_animation("walk")
16442175 end
16452176 else
1646 set_velocity(self, 0)
1647 set_animation(self, "stand")
2177 self:set_velocity(0)
2178 self:set_animation("stand")
16482179 end
16492180
16502181 return
16542185
16552186 -- swimmers flop when out of their element, and swim again when back in
16562187 if self.fly then
1657 local s = self.object:get_pos()
1658 if not flight_check(self, s) then
2188
2189 if not self:attempt_flight_correction() then
16592190
16602191 self.state = "flop"
2192
2193 -- do we have a custom on_flop function?
2194 if self.on_flop then
2195
2196 if self:on_flop(self) then
2197 return
2198 end
2199 end
2200
16612201 self.object:set_velocity({x = 0, y = -5, z = 0})
16622202
1663 set_animation(self, "stand")
2203 self:set_animation("stand")
16642204
16652205 return
2206
16662207 elseif self.state == "flop" then
16672208 self.state = "stand"
16682209 end
16712212
16722213
16732214 -- dogshoot attack switch and counter function
1674 local dogswitch = function(self, dtime)
2215 function mob_class:dogswitch(dtime)
16752216
16762217 -- switch mode not activated
16772218 if not self.dogshoot_switch
17002241
17012242
17022243 -- execute current state (stand, walk, run, attacks)
1703 local do_states = function(self, dtime)
1704
1705 local yaw = self.object:get_yaw() or 0
2244 function mob_class:do_states(dtime)
2245
2246 local yaw = self.object:get_yaw() ; if not yaw then return end
17062247
17072248 if self.state == "stand" then
17082249
1709 if random(1, 4) == 1 then
1710
1711 local lp = nil
2250 if self.randomly_turn and random(4) == 1 then
2251
2252 local lp
17122253 local s = self.object:get_pos()
17132254 local objs = minetest.get_objects_inside_radius(s, 3)
17142255
17222263
17232264 -- look at any players nearby, otherwise turn randomly
17242265 if lp then
1725
1726 local vec = {
1727 x = lp.x - s.x,
1728 z = lp.z - s.z
1729 }
1730
1731 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1732
1733 if lp.x > s.x then yaw = yaw + pi end
2266 yaw = yaw_to_pos(self, lp)
17342267 else
17352268 yaw = yaw + random(-0.5, 0.5)
17362269 end
17372270
1738 yaw = set_yaw(self, yaw, 8)
1739 end
1740
1741 set_velocity(self, 0)
1742 set_animation(self, "stand")
2271 yaw = self:set_yaw(yaw, 8)
2272 end
2273
2274 self:set_velocity(0)
2275 self:set_animation("stand")
17432276
17442277 -- mobs ordered to stand stay standing
17452278 if self.order ~= "stand"
17462279 and self.walk_chance ~= 0
17472280 and self.facing_fence ~= true
1748 and random(1, 100) <= self.walk_chance
1749 and is_at_cliff(self) == false then
1750
1751 set_velocity(self, self.walk_velocity)
2281 and random(100) <= self.walk_chance
2282 and self.at_cliff == false then
2283
2284 self:set_velocity(self.walk_velocity)
17522285 self.state = "walk"
1753 set_animation(self, "walk")
2286 self:set_animation("walk")
17542287 end
17552288
17562289 elseif self.state == "walk" then
17572290
17582291 local s = self.object:get_pos()
1759 local lp = nil
2292 local lp
17602293
17612294 -- is there something I need to avoid?
17622295 if self.water_damage > 0
17632296 and self.lava_damage > 0 then
17642297
1765 lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"})
2298 lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"})
17662299
17672300 elseif self.water_damage > 0 then
17682301
17702303
17712304 elseif self.lava_damage > 0 then
17722305
1773 lp = minetest.find_node_near(s, 1, {"group:lava"})
2306 lp = minetest.find_node_near(s, 1, {"group:igniter"})
17742307 end
17752308
17762309 if lp then
17772310
1778 -- if mob in water or lava then look for land
1779 if (self.lava_damage
1780 and minetest.registered_nodes[self.standing_in].groups.lava)
1781 or (self.water_damage
1782 and minetest.registered_nodes[self.standing_in].groups.water) then
1783
1784 lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone",
1785 "group:sand", node_ice, node_snowblock})
2311 -- if mob in dangerous node then look for land
2312 if not is_node_dangerous(self, self.standing_in) then
2313
2314 lp = minetest.find_nodes_in_area_under_air(
2315 {s.x - 5, s.y - 1, s.z - 5},
2316 {s.x + 5, s.y + 2, s.z + 5},
2317 {"group:soil", "group:stone", "group:sand",
2318 node_ice, node_snowblock})
2319
2320 -- select position of random block to climb onto
2321 lp = #lp > 0 and lp[random(#lp)]
17862322
17872323 -- did we find land?
17882324 if lp then
17892325
1790 local vec = {
1791 x = lp.x - s.x,
1792 z = lp.z - s.z
1793 }
1794
1795 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1796
1797 if lp.x > s.x then yaw = yaw + pi end
1798
1799 -- look towards land and jump/move in that direction
1800 yaw = set_yaw(self, yaw, 6)
1801 do_jump(self)
1802 set_velocity(self, self.walk_velocity)
2326 yaw = yaw_to_pos(self, lp)
2327
2328 self:do_jump()
2329 self:set_velocity(self.walk_velocity)
18032330 else
18042331 yaw = yaw + random(-0.5, 0.5)
18052332 end
1806
1807 else
1808
1809 local vec = {
1810 x = lp.x - s.x,
1811 z = lp.z - s.z
1812 }
1813
1814 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1815
1816 if lp.x > s.x then yaw = yaw + pi end
1817 end
1818
1819 yaw = set_yaw(self, yaw, 8)
2333 end
2334
2335 yaw = self:set_yaw(yaw, 8)
18202336
18212337 -- otherwise randomly turn
1822 elseif random(1, 100) <= 30 then
2338 elseif self.randomly_turn and random(100) <= 30 then
18232339
18242340 yaw = yaw + random(-0.5, 0.5)
18252341
1826 yaw = set_yaw(self, yaw, 8)
2342 yaw = self:set_yaw(yaw, 8)
2343
2344 -- for flying/swimming mobs randomly move up and down also
2345 if self.fly_in
2346 and not self.following then
2347 self:attempt_flight_correction(true)
2348 end
18272349 end
18282350
18292351 -- stand for great fall in front
1830 local temp_is_cliff = is_at_cliff(self)
1831
18322352 if self.facing_fence == true
1833 or temp_is_cliff
1834 or random(1, 100) <= 30 then
1835
1836 set_velocity(self, 0)
1837 self.state = "stand"
1838 set_animation(self, "stand")
2353 or self.at_cliff
2354 or random(100) <= self.stand_chance then
2355
2356 -- don't stand if mob flies and keep_flying set
2357 if (self.fly and not self.keep_flying)
2358 or not self.fly then
2359
2360 self:set_velocity(0)
2361 self.state = "stand"
2362 self:set_animation("stand", true)
2363 end
18392364 else
1840 set_velocity(self, self.walk_velocity)
1841
1842 if flight_check(self)
2365 self:set_velocity(self.walk_velocity)
2366
2367 if self:flight_check()
18432368 and self.animation
18442369 and self.animation.fly_start
18452370 and self.animation.fly_end then
1846 set_animation(self, "fly")
2371 self:set_animation("fly")
18472372 else
1848 set_animation(self, "walk")
2373 self:set_animation("walk")
18492374 end
18502375 end
18512376
18562381
18572382 -- stop after 5 seconds or when at cliff
18582383 if self.runaway_timer > 5
1859 or is_at_cliff(self)
2384 or self.at_cliff
18602385 or self.order == "stand" then
18612386 self.runaway_timer = 0
1862 set_velocity(self, 0)
2387 self:set_velocity(0)
18632388 self.state = "stand"
1864 set_animation(self, "stand")
2389 self:set_animation("stand")
18652390 else
1866 set_velocity(self, self.run_velocity)
1867 set_animation(self, "walk")
2391 self:set_velocity(self.run_velocity)
2392 self:set_animation("walk")
18682393 end
18692394
18702395 -- attack routines (explode, dogfight, shoot, dogshoot)
18712396 elseif self.state == "attack" then
18722397
1873 -- calculate distance from mob and enemy
2398 -- get mob and enemy positions and distance between
18742399 local s = self.object:get_pos()
1875 local p = self.attack:get_pos() or s
1876 local dist = get_distance(p, s)
1877
1878 -- stop attacking if player invisible or out of range
2400 local p = self.attack and self.attack:get_pos()
2401 local dist = p and get_distance(p, s) or 500
2402
2403 -- stop attacking if player out of range or invisible
18792404 if dist > self.view_range
18802405 or not self.attack
18812406 or not self.attack:get_pos()
18822407 or self.attack:get_hp() <= 0
1883 or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then
1884
1885 -- print(" ** stop attacking **", dist, self.view_range)
2408 or (self.attack:is_player()
2409 and is_invisible(self, self.attack:get_player_name())) then
2410
2411 --print(" ** stop attacking **", dist, self.view_range)
2412
18862413 self.state = "stand"
1887 set_velocity(self, 0)
1888 set_animation(self, "stand")
2414 self:set_velocity(0)
2415 self:set_animation("stand")
18892416 self.attack = nil
18902417 self.v_start = false
18912418 self.timer = 0
18972424
18982425 if self.attack_type == "explode" then
18992426
1900 local vec = {
1901 x = p.x - s.x,
1902 z = p.z - s.z
1903 }
1904
1905 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
1906
1907 if p.x > s.x then yaw = yaw + pi end
1908
1909 yaw = set_yaw(self, yaw)
2427 yaw = yaw_to_pos(self, p)
19102428
19112429 local node_break_radius = self.explosion_radius or 1
19122430 local entity_damage_radius = self.explosion_damage_radius
19132431 or (node_break_radius * 2)
19142432
2433 -- look a little higher to fix raycast
2434 s.y = s.y + 0.5 ; p.y = p.y + 0.5
2435
19152436 -- start timer when in reach and line of sight
19162437 if not self.v_start
19172438 and dist <= self.reach
1918 and line_of_sight(self, s, p, 2) then
2439 and self:line_of_sight(s, p, 2) then
19192440
19202441 self.v_start = true
19212442 self.timer = 0
19222443 self.blinktimer = 0
1923 mob_sound(self, self.sounds.fuse)
1924 -- print ("=== explosion timer started", self.explosion_timer)
2444 self:mob_sound(self.sounds.fuse)
2445
2446 --print("=== explosion timer started", self.explosion_timer)
19252447
19262448 -- stop timer if out of reach or direct line of sight
19272449 elseif self.allow_fuse_reset
19282450 and self.v_start
1929 and (dist > self.reach
1930 or not line_of_sight(self, s, p, 2)) then
2451 and (dist > self.reach or not self:line_of_sight(s, p, 2)) then
2452
2453 --print("=== explosion timer stopped")
2454
19312455 self.v_start = false
19322456 self.timer = 0
19332457 self.blinktimer = 0
19342458 self.blinkstatus = false
1935 self.object:settexturemod("")
2459 self.object:set_texture_mod("")
19362460 end
19372461
19382462 -- walk right up to player unless the timer is active
19392463 if self.v_start and (self.stop_to_explode or dist < 1.5) then
1940 set_velocity(self, 0)
2464 self:set_velocity(0)
19412465 else
1942 set_velocity(self, self.run_velocity)
2466 self:set_velocity(self.run_velocity)
19432467 end
19442468
19452469 if self.animation and self.animation.run_start then
1946 set_animation(self, "run")
2470 self:set_animation("run")
19472471 else
1948 set_animation(self, "walk")
2472 self:set_animation("walk")
19492473 end
19502474
19512475 if self.v_start then
19582482 self.blinktimer = 0
19592483
19602484 if self.blinkstatus then
1961 self.object:settexturemod("")
2485
2486 self.object:set_texture_mod(self.texture_mods)
19622487 else
1963 self.object:settexturemod("^[brighten")
2488
2489 self.object:set_texture_mod(self.texture_mods
2490 .. "^[brighten")
19642491 end
19652492
19662493 self.blinkstatus = not self.blinkstatus
19672494 end
19682495
1969 -- print ("=== explosion timer", self.timer)
2496 --print("=== explosion timer", self.timer)
19702497
19712498 if self.timer > self.explosion_timer then
19722499
19792506 node_break_radius = 1
19802507 end
19812508
1982 self.object:remove()
2509 remove_mob(self, true)
19832510
19842511 if minetest.get_modpath("tnt") and tnt and tnt.boom
19852512 and not minetest.is_protected(pos, "") then
19872514 tnt.boom(pos, {
19882515 radius = node_break_radius,
19892516 damage_radius = entity_damage_radius,
1990 sound = self.sounds.explode,
2517 sound = self.sounds.explode
19912518 })
19922519 else
19932520
19982525 })
19992526
20002527 entity_physics(pos, entity_damage_radius)
2001 effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0)
2528
2529 effect(pos, 32, "tnt_smoke.png", nil, nil,
2530 node_break_radius, 1, 0)
20022531 end
20032532
2004 return
2533 return true
20052534 end
20062535 end
20072536
20082537 elseif self.attack_type == "dogfight"
2009 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2)
2010 or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then
2538 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2)
2539 or (self.attack_type == "dogshoot" and dist <= self.reach
2540 and self:dogswitch() == 0) then
20112541
20122542 if self.fly
20132543 and dist > self.reach then
20182548 local p_y = floor(p2.y + 1)
20192549 local v = self.object:get_velocity()
20202550
2021 if flight_check(self, s) then
2551 if self:flight_check() then
20222552
20232553 if me_y < p_y then
20242554
20542584 })
20552585 end
20562586 end
2057
20582587 end
20592588
20602589 -- rnd: new movement direction
20762605 return
20772606 end
20782607
2079 if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then
2608 if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then
20802609 -- reached waypoint, remove it from queue
2081 table.remove(self.path.way, 1)
2610 table_remove(self.path.way, 1)
20822611 end
20832612
20842613 -- set new temporary target
20852614 p = {x = p1.x, y = p1.y, z = p1.z}
20862615 end
20872616
2088 local vec = {
2089 x = p.x - s.x,
2090 z = p.z - s.z
2091 }
2092
2093 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2094
2095 if p.x > s.x then yaw = yaw + pi end
2096
2097 yaw = set_yaw(self, yaw)
2617 yaw = yaw_to_pos(self, p)
20982618
20992619 -- move towards enemy if beyond mob reach
21002620 if dist > self.reach then
21032623 if self.pathfinding -- only if mob has pathfinding enabled
21042624 and enable_pathfinding then
21052625
2106 smart_mobs(self, s, p, dist, dtime)
2626 self:smart_mobs(s, p, dist, dtime)
21072627 end
21082628
2109 if is_at_cliff(self) then
2110
2111 set_velocity(self, 0)
2112 set_animation(self, "stand")
2629 -- distance padding to stop spinning mob
2630 local pad = abs(p.x - s.x) + abs(p.z - s.z)
2631
2632 if self.at_cliff or pad < 0.2 then
2633
2634 self:set_velocity(0)
2635 self:set_animation("stand")
21132636 else
21142637
21152638 if self.path.stuck then
2116 set_velocity(self, self.walk_velocity)
2639 self:set_velocity(self.walk_velocity)
21172640 else
2118 set_velocity(self, self.run_velocity)
2641 self:set_velocity(self.run_velocity)
21192642 end
21202643
21212644 if self.animation and self.animation.run_start then
2122 set_animation(self, "run")
2645 self:set_animation("run")
21232646 else
2124 set_animation(self, "walk")
2647 self:set_animation("walk")
21252648 end
21262649 end
2127
21282650 else -- rnd: if inside reach range
21292651
21302652 self.path.stuck = false
21312653 self.path.stuck_timer = 0
21322654 self.path.following = false -- not stuck anymore
21332655
2134 set_velocity(self, 0)
2135
2136 if not self.custom_attack then
2137
2138 if self.timer > 1 then
2656 self:set_velocity(0)
2657
2658 if self.timer > 1 then
2659
2660 -- no custom attack or custom attack returns true to continue
2661 if not self.custom_attack
2662 or self:custom_attack(self, p) == true then
21392663
21402664 self.timer = 0
2141
2142 -- if self.double_melee_attack
2143 -- and random(1, 2) == 1 then
2144 -- set_animation(self, "punch2")
2145 -- else
2146 set_animation(self, "punch")
2147 -- end
2665 self:set_animation("punch")
21482666
21492667 local p2 = p
21502668 local s2 = s
21522670 p2.y = p2.y + .5
21532671 s2.y = s2.y + .5
21542672
2155 if line_of_sight(self, p2, s2) == true then
2673 if self:line_of_sight(p2, s2) == true then
21562674
21572675 -- play attack sound
2158 mob_sound(self, self.sounds.attack)
2676 self:mob_sound(self.sounds.attack)
21592677
21602678 -- punch player (or what player is attached to)
21612679 local attached = self.attack:get_attach()
2680
21622681 if attached then
21632682 self.attack = attached
21642683 end
2684
2685 local dgroup = self.damage_group or "fleshy"
2686
21652687 self.attack:punch(self.object, 1.0, {
21662688 full_punch_interval = 1.0,
2167 damage_groups = {fleshy = self.damage}
2689 damage_groups = {[dgroup] = self.damage}
21682690 }, nil)
21692691 end
21702692 end
2171 else -- call custom attack every second
2172 if self.custom_attack
2173 and self.timer > 1 then
2174
2175 self.timer = 0
2176
2177 self.custom_attack(self, p)
2178 end
21792693 end
21802694 end
21812695
21822696 elseif self.attack_type == "shoot"
2183 or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1)
2184 or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then
2697 or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1)
2698 or (self.attack_type == "dogshoot" and dist > self.reach and
2699 self:dogswitch() == 0) then
21852700
21862701 p.y = p.y - .5
21872702 s.y = s.y + .5
21882703
2189 local dist = get_distance(p, s)
2190 local vec = {
2191 x = p.x - s.x,
2192 y = p.y - s.y,
2193 z = p.z - s.z
2194 }
2195
2196 yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate
2197
2198 if p.x > s.x then yaw = yaw + pi end
2199
2200 yaw = set_yaw(self, yaw)
2201
2202 set_velocity(self, 0)
2704 local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z}
2705
2706 yaw = yaw_to_pos(self, p)
2707
2708 self:set_velocity(0)
22032709
22042710 if self.shoot_interval
22052711 and self.timer > self.shoot_interval
2206 and random(1, 100) <= 60 then
2712 and random(100) <= 60 then
22072713
22082714 self.timer = 0
2209 set_animation(self, "shoot")
2715 self:set_animation("shoot")
22102716
22112717 -- play shoot attack sound
2212 mob_sound(self, self.sounds.shoot_attack)
2718 self:mob_sound(self.sounds.shoot_attack)
22132719
22142720 local p = self.object:get_pos()
22152721
22202726 local obj = minetest.add_entity(p, self.arrow)
22212727 local ent = obj:get_luaentity()
22222728 local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5
2729
2730 -- check for custom override for arrow
2731 if self.arrow_override then
2732 self.arrow_override(ent)
2733 end
2734
22232735 local v = ent.velocity or 1 -- or set to default
22242736
22252737 ent.switch = 1
22402752
22412753
22422754 -- falling and fall damage
2243 local falling = function(self, pos)
2244
2245 if self.fly then
2755 function mob_class:falling(pos)
2756
2757 if self.fly or self.disable_falling then
22462758 return
22472759 end
22482760
22492761 -- floating in water (or falling)
22502762 local v = self.object:get_velocity()
22512763
2252 if v.y > 0 then
2253
2254 -- apply gravity when moving up
2255 self.object:set_acceleration({
2256 x = 0,
2257 y = -10,
2258 z = 0
2259 })
2260
2261 elseif v.y <= 0 and v.y > self.fall_speed then
2262
2263 -- fall downwards at set speed
2264 self.object:set_acceleration({
2265 x = 0,
2266 y = self.fall_speed,
2267 z = 0
2268 })
2269 else
2270 -- stop accelerating once max fall speed hit
2271 self.object:set_acceleration({x = 0, y = 0, z = 0})
2272 end
2273
2274 -- in water then float up
2275 if self.standing_in
2276 and minetest.registered_nodes[self.standing_in].groups.water then
2277
2278 if self.floats == 1 then
2279
2280 self.object:set_acceleration({
2281 x = 0,
2282 y = -self.fall_speed / (max(1, v.y) ^ 8), -- 8 was 2
2283 z = 0
2284 })
2285 end
2764 -- sanity check
2765 if not v then return end
2766
2767 local fall_speed = self.fall_speed
2768
2769 -- in water then use liquid viscosity for float/sink speed
2770 if self.floats == 1 and self.standing_in
2771 and minetest.registered_nodes[self.standing_in].groups.liquid then
2772
2773 local visc = min(
2774 minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1
2775
2776 self.object:set_velocity({x = v.x, y = 0.6, z = v.z})
2777 fall_speed = -1.2 / visc
22862778 else
22872779
22882780 -- fall damage onto solid ground
22972789
22982790 effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil)
22992791
2300 if check_for_death(self, {type = "fall"}) then
2301 return
2792 if self:check_for_death({type = "fall"}) then
2793 return true
23022794 end
23032795 end
23042796
23052797 self.old_y = self.object:get_pos().y
23062798 end
23072799 end
2800
2801 -- fall at set speed
2802 self.object:set_acceleration({x = 0, y = fall_speed, z = 0})
23082803 end
23092804
23102805
23122807 local tr = minetest.get_modpath("toolranks")
23132808
23142809 -- deal damage and effects when mob punched
2315 local mob_punch = function(self, hitter, tflp, tool_capabilities, dir)
2810 function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage)
23162811
23172812 -- mob health check
23182813 if self.health <= 0 then
2319 return
2814 return true
23202815 end
23212816
23222817 -- custom punch function
23232818 if self.do_punch
2324 and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then
2325 return
2819 and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then
2820 return true
23262821 end
23272822
23282823 -- error checking when mod profiling is enabled
23292824 if not tool_capabilities then
2330 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2331 return
2332 end
2333
2334 -- is mob protected?
2335 if self.protected and hitter:is_player()
2336 and minetest.is_protected(self.object:get_pos(), hitter:get_player_name()) then
2337 minetest.chat_send_player(hitter:get_player_name(), S("Mob has been protected!"))
2338 return
2825
2826 minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled")
2827
2828 return true
2829 end
2830
2831 -- is mob protected
2832 if self.protected then
2833
2834 -- did player hit mob and if so is it in protected area
2835 if hitter:is_player() then
2836
2837 local player_name = hitter:get_player_name()
2838
2839 if player_name ~= self.owner
2840 and minetest.is_protected(self.object:get_pos(), player_name) then
2841
2842 minetest.chat_send_player(hitter:get_player_name(),
2843 S("Mob has been protected!"))
2844
2845 return true
2846 end
2847
2848 -- if protection is on level 2 then dont let arrows harm mobs
2849 elseif self.protected == 2 then
2850
2851 local ent = hitter and hitter:get_luaentity()
2852
2853 if ent and ent._is_arrow then
2854
2855 return true -- arrow entity
2856
2857 elseif not ent then
2858
2859 return true -- non entity
2860 end
2861 end
23392862 end
23402863
23412864 local weapon = hitter:get_wielded_item()
23672890 end
23682891
23692892 damage = damage + (tool_capabilities.damage_groups[group] or 0)
2370 * tmp * ((armor[group] or 0) / 100.0)
2893 * tmp * ((armor[group] or 0) / 100.0)
23712894 end
23722895 end
23732896
23772900 if self.immune_to[n][1] == weapon_def.name then
23782901
23792902 damage = self.immune_to[n][2] or 0
2903
23802904 break
23812905
2382 -- if "all" then no tool does damage unless it's specified in list
2906 -- if "all" then no tools deal damage unless it's specified in list
23832907 elseif self.immune_to[n][1] == "all" then
23842908 damage = self.immune_to[n][2] or 0
23852909 end
23862910 end
23872911
2912 --print("Mob Damage is", damage)
2913
23882914 -- healing
23892915 if damage <= -1 then
2916
23902917 self.health = self.health - floor(damage)
2391 return
2392 end
2393
2394 -- print ("Mob Damage is", damage)
2918
2919 return true
2920 end
23952921
23962922 if use_cmi
23972923 and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then
2398 return
2924 return true
23992925 end
24002926
24012927 -- add weapon wear
24132939 end
24142940 end
24152941
2416 if tr then
2417 if weapon_def.original_description then
2418 weapon:add_wear(toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}))
2419 end
2942 if tr and weapon_def.original_description then
2943 toolranks.new_afteruse(weapon, hitter, nil, {wear = wear})
24202944 else
24212945 weapon:add_wear(wear)
24222946 end
24262950 -- only play hit sound and show blood effects if damage is 1 or over
24272951 if damage >= 1 then
24282952
2429 -- weapon sounds
2430 if weapon_def.sounds then
2431
2432 local s = random(0, #weapon_def.sounds)
2433
2434 minetest.sound_play(weapon_def.sounds[s], {
2435 object = self.object,
2436 max_hear_distance = 8
2437 })
2438 else
2439 minetest.sound_play("default_punch", {
2440 object = self.object,
2441 max_hear_distance = 5
2442 })
2443 end
2953 -- select tool use sound if found, or fallback to default
2954 local snd = weapon_def.sound and weapon_def.sound.use
2955 or "default_punch"
2956
2957 minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true)
24442958
24452959 -- blood_particles
24462960 if not disable_blood and self.blood_amount > 0 then
24472961
24482962 local pos = self.object:get_pos()
2963 local blood = self.blood_texture
2964 local amount = self.blood_amount
24492965
24502966 pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5
2967
2968 -- lots of damage = more blood :)
2969 if damage > 10 then
2970 amount = self.blood_amount * 2
2971 end
24512972
24522973 -- do we have a single blood texture or multiple?
24532974 if type(self.blood_texture) == "table" then
2454
2455 local blood = self.blood_texture[random(1, #self.blood_texture)]
2456
2457 effect(pos, self.blood_amount, blood, nil, nil, 1, nil)
2458 else
2459 effect(pos, self.blood_amount, self.blood_texture, nil, nil, 1, nil)
2460 end
2975 blood = self.blood_texture[random(#self.blood_texture)]
2976 end
2977
2978 effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true)
24612979 end
24622980
24632981 -- do damage
24672985 local hot = tool_capabilities and tool_capabilities.damage_groups
24682986 and tool_capabilities.damage_groups.fire
24692987
2470 if check_for_death(self, {type = "punch",
2471 puncher = hitter, hot = hot}) then
2472 return
2473 end
2474
2475 --[[ add healthy afterglow when hit (can cause hit lag with larger textures)
2476 minetest.after(0.1, function()
2477
2478 if not self.object:get_luaentity() then return end
2479
2480 self.object:settexturemod("^[colorize:#c9900070")
2481
2482 core.after(0.3, function()
2483 self.object:settexturemod("")
2484 end)
2485 end) ]]
2486
2988 if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then
2989 return true
2990 end
24872991 end -- END if damage
24882992
24892993 -- knock back effect (only on full punch)
2490 if self.knock_back
2491 and tflp >= punch_interval then
2994 if self.knock_back and tflp >= punch_interval then
24922995
24932996 local v = self.object:get_velocity()
2997
2998 -- sanity check
2999 if not v then return true end
3000
24943001 local kb = damage or 1
24953002 local up = 2
24963003
25043011 dir = dir or {x = 0, y = 0, z = 0}
25053012
25063013 -- use tool knockback value or default
2507 kb = tool_capabilities.damage_groups["knockback"] or (kb * 1.5)
2508
2509 self.object:set_velocity({
2510 x = dir.x * kb,
2511 y = up,
2512 z = dir.z * kb
2513 })
3014 kb = tool_capabilities.damage_groups["knockback"] or kb
3015
3016 self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb})
25143017
25153018 self.pause_timer = 0.25
25163019 end
25203023 and self.order ~= "stand" then
25213024
25223025 local lp = hitter:get_pos()
2523 local s = self.object:get_pos()
2524 local vec = {
2525 x = lp.x - s.x,
2526 y = lp.y - s.y,
2527 z = lp.z - s.z
2528 }
2529
2530 local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate
2531
2532 if lp.x > s.x then
2533 yaw = yaw + pi
2534 end
2535
2536 yaw = set_yaw(self, yaw, 6)
3026 local yaw = yaw_to_pos(self, lp, 3)
3027
25373028 self.state = "runaway"
25383029 self.runaway_timer = 0
25393030 self.following = nil
25473038 and self.child == false
25483039 and self.attack_players == true
25493040 and hitter:get_player_name() ~= self.owner
2550 and not mobs.invis[ name ] then
3041 and not is_invisible(self, name)
3042 and self.object ~= hitter then
25513043
25523044 -- attack whoever punched mob
25533045 self.state = ""
2554 do_attack(self, hitter)
3046 self:do_attack(hitter)
25553047
25563048 -- alert others to the attack
2557 local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range)
2558 local obj = nil
3049 local objs = minetest.get_objects_inside_radius(
3050 hitter:get_pos(), self.view_range)
3051 local obj
25593052
25603053 for n = 1, #objs do
25613054
25633056
25643057 if obj and obj._cmi_is_mob then
25653058
2566 -- only alert members of same mob
3059 -- only alert members of same mob and assigned helper
25673060 if obj.group_attack == true
25683061 and obj.state ~= "attack"
25693062 and obj.owner ~= name
2570 and obj.name == self.name then
2571 do_attack(obj, hitter)
3063 and (obj.name == self.name
3064 or obj.name == self.group_helper) then
3065
3066 obj:do_attack(hitter)
25723067 end
25733068
25743069 -- have owned mobs attack player threat
25753070 if obj.owner == name and obj.owner_loyal then
2576 do_attack(obj, self.object)
3071 obj:do_attack(self.object)
25773072 end
25783073 end
25793074 end
25823077
25833078
25843079 -- get entity staticdata
2585 local mob_staticdata = function(self)
3080 function mob_class:mob_staticdata()
3081
3082 -- this handles mob count for mobs activated, unloaded, reloaded
3083 if active_limit > 0 and self.active_toggle then
3084 active_mobs = active_mobs + self.active_toggle
3085 self.active_toggle = -self.active_toggle
3086 --print("-- staticdata", active_mobs, active_limit, self.active_toggle)
3087 end
25863088
25873089 -- remove mob when out of range unless tamed
25883090 if remove_far
25923094 and not self.tamed
25933095 and self.lifetimer < 20000 then
25943096
2595 --print ("REMOVED " .. self.name)
2596
2597 self.object:remove()
2598
2599 return ""-- nil
3097 --print("REMOVED " .. self.name)
3098
3099 remove_mob(self, true)
3100
3101 return minetest.serialize({remove_ok = true, static_save = true})
26003102 end
26013103
26023104 self.remove_ok = true
26053107 self.state = "stand"
26063108
26073109 -- used to rotate older mobs
2608 if self.drawtype
2609 and self.drawtype == "side" then
2610 self.rotate = math.rad(90)
3110 if self.drawtype and self.drawtype == "side" then
3111 self.rotate = rad(90)
26113112 end
26123113
26133114 if use_cmi then
2614 self.serialized_cmi_components = cmi.serialize_components(self._cmi_components)
2615 end
2616
2617 local tmp = {}
3115 self.serialized_cmi_components = cmi.serialize_components(
3116 self._cmi_components)
3117 end