Import upstream version 20181016+git20210801.1.deee28f
Debian Janitor
1 year, 5 months ago
0 | ||
1 | -- Intllib and CMI support check | |
0 | -- Load support for intllib. | |
2 | 1 | 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 | |
4 | 6 | local use_cmi = minetest.global_exists("cmi") |
5 | 7 | |
6 | 8 | mobs = { |
7 | 9 | mod = "redo", |
8 | version = "20181005", | |
10 | version = "20210801", | |
9 | 11 | intllib = S, |
10 | invis = minetest.global_exists("invisibility") and invisibility or {}, | |
12 | invis = minetest.global_exists("invisibility") and invisibility or {} | |
11 | 13 | } |
12 | 14 | |
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 | |
21 | 16 | local pi = math.pi |
22 | 17 | local square = math.sqrt |
23 | 18 | local sin = math.sin |
25 | 20 | local abs = math.abs |
26 | 21 | local min = math.min |
27 | 22 | local max = math.max |
28 | local atann = math.atan | |
29 | 23 | local random = math.random |
30 | 24 | local floor = math.floor |
25 | local ceil = math.ceil | |
26 | local rad = math.rad | |
27 | local atann = math.atan | |
31 | 28 | local atan = function(x) |
32 | 29 | if not x or x ~= x then |
33 | --error("atan bassed NaN") | |
34 | return 0 | |
30 | return 0 -- NaN | |
35 | 31 | else |
36 | 32 | return atann(x) |
37 | 33 | end |
38 | 34 | 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 | |
40 | 49 | |
41 | 50 | -- 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 | |
55 | 70 | |
56 | 71 | -- Peaceful mode message so players will know there are no monsters |
57 | 72 | if peaceful_only then |
62 | 77 | end |
63 | 78 | |
64 | 79 | -- 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 | |
66 | 81 | |
67 | 82 | -- pathfinding settings |
68 | 83 | 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 | |
71 | 86 | |
72 | 87 | -- default nodes |
73 | 88 | local node_fire = "fire:basic_flame" |
77 | 92 | local node_snow = "default:snow" |
78 | 93 | mobs.fallback_node = minetest.registered_aliases["mapgen_dirt"] or "default:dirt" |
79 | 94 | |
95 | local mob_class = { | |
96 | stepheight = 1.1, | |
97 | fly_in = "air", | |
98 | owner = "", | |
99 | order = "", | |
100 | jump_height = 4, | |
101 | lifetimer = 180, -- 3 minutes | |
102 | physical = true, | |
103 | collisionbox = {-0.25, -0.25, -0.25, 0.25, 0.25, 0.25}, | |
104 | visual_size = {x = 1, y = 1}, | |
105 | texture_mods = "", | |
106 | makes_footstep_sound = false, | |
107 | view_range = 5, | |
108 | walk_velocity = 1, | |
109 | run_velocity = 2, | |
110 | light_damage = 0, | |
111 | light_damage_min = 14, | |
112 | light_damage_max = 15, | |
113 | water_damage = 0, | |
114 | lava_damage = 4, | |
115 | fire_damage = 4, | |
116 | air_damage = 0, | |
117 | suffocation = 2, | |
118 | fall_damage = 1, | |
119 | fall_speed = -10, -- must be lower than -2 (default: -10) | |
120 | drops = {}, | |
121 | armor = 100, | |
122 | sounds = {}, | |
123 | jump = true, | |
124 | knock_back = true, | |
125 | walk_chance = 50, | |
126 | stand_chance = 30, | |
127 | attack_chance = 5, | |
128 | passive = false, | |
129 | blood_amount = 5, | |
130 | blood_texture = "mobs_blood.png", | |
131 | shoot_offset = 0, | |
132 | floats = 1, -- floats in water by default | |
133 | replace_offset = 0, | |
134 | timer = 0, | |
135 | env_damage_timer = 0, -- only used when state = "attack" | |
136 | tamed = false, | |
137 | pause_timer = 0, | |
138 | horny = false, | |
139 | hornytimer = 0, | |
140 | child = false, | |
141 | gotten = false, | |
142 | health = 0, | |
143 | reach = 3, | |
144 | htimer = 0, | |
145 | docile_by_day = false, | |
146 | time_of_day = 0.5, | |
147 | fear_height = 0, | |
148 | runaway_timer = 0, | |
149 | immune_to = {}, | |
150 | explosion_timer = 3, | |
151 | allow_fuse_reset = true, | |
152 | stop_to_explode = true, | |
153 | dogshoot_count = 0, | |
154 | dogshoot_count_max = 5, | |
155 | dogshoot_count2_max = 5, | |
156 | group_attack = false, | |
157 | attack_monsters = false, | |
158 | attack_animals = false, | |
159 | attack_players = true, | |
160 | attack_npcs = true, | |
161 | facing_fence = false, | |
162 | _cmi_is_mob = true | |
163 | } | |
164 | ||
165 | local mob_class_meta = {__index = mob_class} | |
166 | ||
80 | 167 | |
81 | 168 | -- play sound |
82 | local mob_sound = function(self, sound) | |
169 | function mob_class:mob_sound(sound) | |
170 | ||
171 | local pitch = 1.0 | |
172 | ||
173 | -- higher pitch for a child | |
174 | if self.child then pitch = pitch * 1.5 end | |
175 | ||
176 | -- a little random pitch to be different | |
177 | pitch = pitch + random(-10, 10) * 0.005 | |
83 | 178 | |
84 | 179 | if sound then |
85 | 180 | minetest.sound_play(sound, { |
86 | 181 | object = self.object, |
87 | 182 | gain = 1.0, |
88 | max_hear_distance = self.sounds.distance | |
89 | }) | |
183 | max_hear_distance = self.sounds.distance, | |
184 | pitch = pitch | |
185 | }, true) | |
90 | 186 | end |
91 | 187 | end |
92 | 188 | |
93 | 189 | |
94 | 190 | -- attack player/mob |
95 | local do_attack = function(self, player) | |
191 | function mob_class:do_attack(player) | |
96 | 192 | |
97 | 193 | if self.state == "attack" then |
98 | 194 | return |
102 | 198 | self.state = "attack" |
103 | 199 | |
104 | 200 | if random(0, 100) < 90 then |
105 | mob_sound(self, self.sounds.war_cry) | |
201 | self:mob_sound(self.sounds.war_cry) | |
106 | 202 | end |
107 | 203 | end |
108 | 204 | |
110 | 206 | -- calculate distance |
111 | 207 | local get_distance = function(a, b) |
112 | 208 | |
209 | if not a or not b then return 50 end -- nil check | |
210 | ||
113 | 211 | local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z |
114 | 212 | |
115 | 213 | return square(x * x + y * y + z * z) |
117 | 215 | |
118 | 216 | |
119 | 217 | -- collision function based on jordan4ibanez' open_ai mod |
120 | local collision = function(self) | |
218 | function mob_class:collision() | |
121 | 219 | |
122 | 220 | local pos = self.object:get_pos() |
123 | local vel = self.object:get_velocity() | |
124 | 221 | local x, z = 0, 0 |
125 | 222 | local width = -self.collisionbox[1] + self.collisionbox[4] + 0.5 |
126 | 223 | |
127 | 224 | for _,object in ipairs(minetest.get_objects_inside_radius(pos, width)) do |
128 | 225 | |
129 | if object:is_player() | |
130 | or (object:get_luaentity()._cmi_is_mob == true and object ~= self.object) then | |
226 | if object:is_player() then | |
131 | 227 | |
132 | 228 | local pos2 = object:get_pos() |
133 | 229 | local vec = {x = pos.x - pos2.x, z = pos.z - pos2.z} |
141 | 237 | end |
142 | 238 | |
143 | 239 | |
240 | -- check if string exists in another string or table | |
241 | local check_for = function(look_for, look_inside) | |
242 | ||
243 | if type(look_inside) == "string" and look_inside == look_for then | |
244 | ||
245 | return true | |
246 | ||
247 | elseif type(look_inside) == "table" then | |
248 | ||
249 | for _, str in pairs(look_inside) do | |
250 | ||
251 | if str == look_for then | |
252 | return true | |
253 | end | |
254 | ||
255 | if str:find("group:") then | |
256 | ||
257 | local group = str:split(":")[2] | |
258 | ||
259 | if minetest.get_item_group(look_for, group) ~= 0 then | |
260 | return true | |
261 | end | |
262 | end | |
263 | end | |
264 | end | |
265 | ||
266 | return false | |
267 | end | |
268 | ||
269 | ||
144 | 270 | -- move mob in facing direction |
145 | local set_velocity = function(self, v) | |
271 | function mob_class:set_velocity(v) | |
272 | ||
273 | -- halt mob if it has been ordered to stay | |
274 | if self.order == "stand" then | |
275 | ||
276 | local vel = self.object:get_velocity() or {y = 0} | |
277 | ||
278 | self.object:set_velocity({x = 0, y = vel.y, z = 0}) | |
279 | ||
280 | return | |
281 | end | |
146 | 282 | |
147 | 283 | local c_x, c_y = 0, 0 |
148 | 284 | |
149 | 285 | -- can mob be pushed, if so calculate direction |
150 | 286 | if self.pushable then |
151 | c_x, c_y = unpack(collision(self)) | |
152 | end | |
153 | ||
154 | -- halt mob if it has been ordered to stay | |
155 | if self.order == "stand" then | |
156 | self.object:set_velocity({x = 0, y = 0, z = 0}) | |
157 | return | |
158 | end | |
159 | ||
160 | local yaw = (self.object:get_yaw() or 0) + self.rotate | |
161 | ||
162 | self.object:set_velocity({ | |
287 | c_x, c_y = unpack(self:collision()) | |
288 | end | |
289 | ||
290 | local yaw = (self.object:get_yaw() or 0) + (self.rotate or 0) | |
291 | ||
292 | -- nil check for velocity | |
293 | v = v or 0.01 | |
294 | ||
295 | -- check if standing in liquid with max viscosity of 7 | |
296 | local visc = min(minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) | |
297 | ||
298 | -- only slow mob trying to move while inside a viscous fluid that | |
299 | -- they aren't meant to be in (fish in water, spiders in cobweb etc) | |
300 | if v > 0 and visc and visc > 0 | |
301 | and not check_for(self.standing_in, self.fly_in) then | |
302 | v = v / (visc + 1) | |
303 | end | |
304 | ||
305 | -- set velocity | |
306 | local vel = self.object:get_velocity() or 0 | |
307 | ||
308 | local new_vel = { | |
163 | 309 | x = (sin(yaw) * -v) + c_x, |
164 | y = self.object:get_velocity().y, | |
165 | z = (cos(yaw) * v) + c_y, | |
166 | }) | |
310 | y = vel.y, | |
311 | z = (cos(yaw) * v) + c_y} | |
312 | ||
313 | self.object:set_velocity(new_vel) | |
314 | end | |
315 | ||
316 | -- global version of above function | |
317 | function mobs:set_velocity(entity, v) | |
318 | mob_class.set_velocity(entity, v) | |
167 | 319 | end |
168 | 320 | |
169 | 321 | |
170 | 322 | -- calculate mob velocity |
171 | local get_velocity = function(self) | |
323 | function mob_class:get_velocity() | |
172 | 324 | |
173 | 325 | local v = self.object:get_velocity() |
174 | 326 | |
327 | if not v then return 0 end | |
328 | ||
175 | 329 | return (v.x * v.x + v.z * v.z) ^ 0.5 |
176 | 330 | end |
177 | 331 | |
178 | 332 | |
179 | 333 | -- set and return valid yaw |
180 | local set_yaw = function(self, yaw, delay) | |
334 | function mob_class:set_yaw(yaw, delay) | |
181 | 335 | |
182 | 336 | if not yaw or yaw ~= yaw then |
183 | 337 | yaw = 0 |
184 | 338 | end |
185 | 339 | |
186 | delay = delay or 0 | |
340 | delay = mob_smooth_rotate and (delay or 0) or 0 | |
187 | 341 | |
188 | 342 | if delay == 0 then |
343 | ||
189 | 344 | self.object:set_yaw(yaw) |
345 | ||
190 | 346 | return yaw |
191 | 347 | end |
192 | 348 | |
197 | 353 | end |
198 | 354 | |
199 | 355 | -- global function to set mob yaw |
200 | function mobs:yaw(self, yaw, delay) | |
201 | set_yaw(self, yaw, delay) | |
356 | function mobs:yaw(entity, yaw, delay) | |
357 | mob_class.set_yaw(entity, yaw, delay) | |
202 | 358 | end |
203 | 359 | |
204 | 360 | |
205 | 361 | -- set defined animation |
206 | local set_animation = function(self, anim) | |
207 | ||
208 | if not self.animation | |
209 | or not anim then return end | |
362 | function mob_class:set_animation(anim, force) | |
363 | ||
364 | if not self.animation or not anim then return end | |
210 | 365 | |
211 | 366 | self.animation.current = self.animation.current or "" |
212 | 367 | |
213 | -- only set different animation for attacks when setting to same set | |
214 | if anim ~= "punch" and anim ~= "shoot" | |
368 | -- only use different animation for attacks when using same set | |
369 | if force ~= true and anim ~= "punch" and anim ~= "shoot" | |
215 | 370 | and string.find(self.animation.current, anim) then |
216 | 371 | return |
217 | 372 | end |
218 | 373 | |
219 | -- check for more than one animation | |
220 | 374 | local num = 0 |
221 | 375 | |
376 | -- check for more than one animation (max 4) | |
222 | 377 | for n = 1, 4 do |
223 | 378 | |
224 | 379 | if self.animation[anim .. n .. "_start"] |
244 | 399 | self.object:set_animation({ |
245 | 400 | x = self.animation[anim .. "_start"], |
246 | 401 | y = self.animation[anim .. "_end"]}, |
247 | self.animation[anim .. "_speed"] or self.animation.speed_normal or 15, | |
402 | self.animation[anim .. "_speed"] or | |
403 | self.animation.speed_normal or 15, | |
248 | 404 | 0, self.animation[anim .. "_loop"] ~= false) |
249 | 405 | end |
250 | 406 | |
251 | -- above function exported for mount.lua | |
252 | function mobs:set_animation(self, anim) | |
253 | set_animation(self, anim) | |
407 | function mobs:set_animation(entity, anim) | |
408 | entity.set_animation(entity, anim) | |
409 | end | |
410 | ||
411 | ||
412 | -- check line of sight (BrunoMine) | |
413 | local line_of_sight = function(self, pos1, pos2, stepsize) | |
414 | ||
415 | stepsize = stepsize or 1 | |
416 | ||
417 | local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) | |
418 | ||
419 | -- normal walking and flying mobs can see you through air | |
420 | if s == true then | |
421 | return true | |
422 | end | |
423 | ||
424 | -- New pos1 to be analyzed | |
425 | local npos1 = {x = pos1.x, y = pos1.y, z = pos1.z} | |
426 | ||
427 | local r, pos = minetest.line_of_sight(npos1, pos2, stepsize) | |
428 | ||
429 | -- Checks the return | |
430 | if r == true then return true end | |
431 | ||
432 | -- Nodename found | |
433 | local nn = minetest.get_node(pos).name | |
434 | ||
435 | -- Target Distance (td) to travel | |
436 | local td = get_distance(pos1, pos2) | |
437 | ||
438 | -- Actual Distance (ad) traveled | |
439 | local ad = 0 | |
440 | ||
441 | -- It continues to advance in the line of sight in search of a real | |
442 | -- obstruction which counts as 'walkable' nodebox. | |
443 | while minetest.registered_nodes[nn] | |
444 | and (minetest.registered_nodes[nn].walkable == false) do | |
445 | ||
446 | -- Check if you can still move forward | |
447 | if td < ad + stepsize then | |
448 | return true -- Reached the target | |
449 | end | |
450 | ||
451 | -- Moves the analyzed pos | |
452 | local d = get_distance(pos1, pos2) | |
453 | ||
454 | npos1.x = ((pos2.x - pos1.x) / d * stepsize) + pos1.x | |
455 | npos1.y = ((pos2.y - pos1.y) / d * stepsize) + pos1.y | |
456 | npos1.z = ((pos2.z - pos1.z) / d * stepsize) + pos1.z | |
457 | ||
458 | -- NaN checks | |
459 | if d == 0 | |
460 | or npos1.x ~= npos1.x | |
461 | or npos1.y ~= npos1.y | |
462 | or npos1.z ~= npos1.z then | |
463 | return false | |
464 | end | |
465 | ||
466 | ad = ad + stepsize | |
467 | ||
468 | -- scan again | |
469 | r, pos = minetest.line_of_sight(npos1, pos2, stepsize) | |
470 | ||
471 | if r == true then return true end | |
472 | ||
473 | -- New Nodename found | |
474 | nn = minetest.get_node(pos).name | |
475 | end | |
476 | ||
477 | return false | |
254 | 478 | end |
255 | 479 | |
256 | 480 | |
257 | 481 | -- check line of sight (by BrunoMine, tweaked by Astrobe) |
258 | local line_of_sight = function(self, pos1, pos2, stepsize) | |
482 | local new_line_of_sight = function(self, pos1, pos2, stepsize) | |
259 | 483 | |
260 | 484 | if not pos1 or not pos2 then return end |
261 | 485 | |
262 | 486 | stepsize = stepsize or 1 |
263 | 487 | |
264 | local stepv = vector.multiply(vector.direction(pos1, pos2), stepsize) | |
488 | local stepv = vmultiply(vdirection(pos1, pos2), stepsize) | |
265 | 489 | |
266 | 490 | local s, pos = minetest.line_of_sight(pos1, pos2, stepsize) |
267 | 491 | |
280 | 504 | local nn = minetest.get_node(pos).name |
281 | 505 | |
282 | 506 | -- It continues to advance in the line of sight in search of a real |
283 | -- obstruction which counts as 'normal' nodebox. | |
507 | -- obstruction which counts as 'walkable' nodebox. | |
284 | 508 | while minetest.registered_nodes[nn] |
285 | 509 | and (minetest.registered_nodes[nn].walkable == false) do |
286 | -- or minetest.registered_nodes[nn].drawtype == "nodebox") do | |
287 | ||
288 | npos1 = vector.add(npos1, stepv) | |
510 | ||
511 | npos1 = vadd(npos1, stepv) | |
289 | 512 | |
290 | 513 | if get_distance(npos1, pos2) < stepsize then return true end |
291 | 514 | |
301 | 524 | return false |
302 | 525 | end |
303 | 526 | |
527 | -- check line of sight using raycasting (thanks Astrobe) | |
528 | local ray_line_of_sight = function(self, pos1, pos2) | |
529 | ||
530 | local ray = minetest.raycast(pos1, pos2, true, false) | |
531 | local thing = ray:next() | |
532 | ||
533 | while thing do -- thing.type, thing.ref | |
534 | ||
535 | if thing.type == "node" then | |
536 | ||
537 | local name = minetest.get_node(thing.under).name | |
538 | ||
539 | if minetest.registered_items[name] | |
540 | and minetest.registered_items[name].walkable then | |
541 | return false | |
542 | end | |
543 | end | |
544 | ||
545 | thing = ray:next() | |
546 | end | |
547 | ||
548 | return true | |
549 | end | |
550 | ||
551 | ||
552 | function mob_class:line_of_sight(pos1, pos2, stepsize) | |
553 | ||
554 | if minetest.raycast then -- only use if minetest 5.0 is detected | |
555 | return ray_line_of_sight(self, pos1, pos2) | |
556 | end | |
557 | ||
558 | return line_of_sight(self, pos1, pos2, stepsize) | |
559 | end | |
560 | ||
304 | 561 | -- global function |
305 | function mobs:line_of_sight(self, pos1, pos2, stepsize) | |
306 | ||
307 | return line_of_sight(self, pos1, pos2, stepsize) | |
562 | function mobs:line_of_sight(entity, pos1, pos2, stepsize) | |
563 | return entity:line_of_sight(pos1, pos2, stepsize) | |
564 | end | |
565 | ||
566 | ||
567 | function mob_class:attempt_flight_correction(override) | |
568 | ||
569 | if self:flight_check() and override ~= true then return true end | |
570 | ||
571 | -- We are not flying in what we are supposed to. | |
572 | -- See if we can find intended flight medium and return to it | |
573 | local pos = self.object:get_pos() ; if not pos then return true end | |
574 | local searchnodes = self.fly_in | |
575 | ||
576 | if type(searchnodes) == "string" then | |
577 | searchnodes = {self.fly_in} | |
578 | end | |
579 | ||
580 | local flyable_nodes = minetest.find_nodes_in_area( | |
581 | {x = pos.x - 1, y = pos.y - 1, z = pos.z - 1}, | |
582 | {x = pos.x + 1, y = pos.y + 0, z = pos.z + 1}, searchnodes) | |
583 | -- pos.y + 0 hopefully fixes floating swimmers | |
584 | ||
585 | if #flyable_nodes < 1 then | |
586 | return false | |
587 | end | |
588 | ||
589 | local escape_target = flyable_nodes[random(#flyable_nodes)] | |
590 | local escape_direction = vdirection(pos, escape_target) | |
591 | ||
592 | self.object:set_velocity( | |
593 | vmultiply(escape_direction, 1)) | |
594 | ||
595 | return true | |
308 | 596 | end |
309 | 597 | |
310 | 598 | |
311 | 599 | -- are we flying in what we are suppose to? (taikedz) |
312 | local flight_check = function(self, pos_w) | |
600 | function mob_class:flight_check() | |
313 | 601 | |
314 | 602 | local def = minetest.registered_nodes[self.standing_in] |
315 | 603 | |
316 | if not def then return false end -- nil check | |
317 | ||
318 | if type(self.fly_in) == "string" | |
319 | and self.standing_in == self.fly_in then | |
320 | ||
604 | if not def then return false end | |
605 | ||
606 | -- are we standing inside what we should be to fly/swim ? | |
607 | if check_for(self.standing_in, self.fly_in) then | |
321 | 608 | return true |
322 | ||
323 | elseif type(self.fly_in) == "table" then | |
324 | ||
325 | for _,fly_in in pairs(self.fly_in) do | |
326 | ||
327 | if self.standing_in == fly_in then | |
328 | ||
329 | return true | |
330 | end | |
331 | end | |
332 | 609 | end |
333 | 610 | |
334 | 611 | -- stops mobs getting stuck inside stairs and plantlike nodes |
342 | 619 | end |
343 | 620 | |
344 | 621 | |
622 | -- turn mob to face position | |
623 | local yaw_to_pos = function(self, target, rot) | |
624 | ||
625 | rot = rot or 0 | |
626 | ||
627 | local pos = self.object:get_pos() | |
628 | local vec = {x = target.x - pos.x, z = target.z - pos.z} | |
629 | local yaw = (atan(vec.z / vec.x) + rot + pi / 2) - self.rotate | |
630 | ||
631 | if target.x > pos.x then | |
632 | yaw = yaw + pi | |
633 | end | |
634 | ||
635 | yaw = self:set_yaw(yaw, rot) | |
636 | ||
637 | return yaw | |
638 | end | |
639 | ||
640 | function mobs:yaw_to_pos(self, target, rot) | |
641 | return yaw_to_pos(self, target, rot) | |
642 | end | |
643 | ||
644 | ||
645 | -- if stay near set then periodically check for nodes and turn towards them | |
646 | function mob_class:do_stay_near() | |
647 | ||
648 | if not self.stay_near then return false end | |
649 | ||
650 | local pos = self.object:get_pos() | |
651 | local searchnodes = self.stay_near[1] | |
652 | local chance = self.stay_near[2] or 10 | |
653 | ||
654 | if not pos or random(chance) > 1 then | |
655 | return false | |
656 | end | |
657 | ||
658 | if type(searchnodes) == "string" then | |
659 | searchnodes = {self.stay_near[1]} | |
660 | end | |
661 | ||
662 | local r = self.view_range | |
663 | local nearby_nodes = minetest.find_nodes_in_area( | |
664 | {x = pos.x - r, y = pos.y - 1, z = pos.z - r}, | |
665 | {x = pos.x + r, y = pos.y + 1, z = pos.z + r}, searchnodes) | |
666 | ||
667 | if #nearby_nodes < 1 then | |
668 | return false | |
669 | end | |
670 | ||
671 | yaw_to_pos(self, nearby_nodes[random(#nearby_nodes)]) | |
672 | ||
673 | self:set_animation("walk") | |
674 | ||
675 | self:set_velocity(self.walk_velocity) | |
676 | ||
677 | return true | |
678 | end | |
679 | ||
680 | ||
345 | 681 | -- custom particle effects |
346 | local effect = function(pos, amount, texture, min_size, max_size, radius, gravity, glow) | |
682 | local effect = function(pos, amount, texture, min_size, max_size, | |
683 | radius, gravity, glow, fall) | |
347 | 684 | |
348 | 685 | radius = radius or 2 |
349 | 686 | min_size = min_size or 0.5 |
351 | 688 | gravity = gravity or -10 |
352 | 689 | glow = glow or 0 |
353 | 690 | |
691 | if fall == true then | |
692 | fall = 0 | |
693 | elseif fall == false then | |
694 | fall = radius | |
695 | else | |
696 | fall = -radius | |
697 | end | |
698 | ||
354 | 699 | minetest.add_particlespawner({ |
355 | 700 | amount = amount, |
356 | 701 | time = 0.25, |
357 | 702 | minpos = pos, |
358 | 703 | maxpos = pos, |
359 | minvel = {x = -radius, y = -radius, z = -radius}, | |
704 | minvel = {x = -radius, y = fall, z = -radius}, | |
360 | 705 | maxvel = {x = radius, y = radius, z = radius}, |
361 | 706 | minacc = {x = 0, y = gravity, z = 0}, |
362 | 707 | maxacc = {x = 0, y = gravity, z = 0}, |
365 | 710 | minsize = min_size, |
366 | 711 | maxsize = max_size, |
367 | 712 | texture = texture, |
368 | glow = glow, | |
713 | glow = glow | |
369 | 714 | }) |
370 | 715 | end |
371 | 716 | |
717 | function mobs:effect(pos, amount, texture, min_size, max_size, | |
718 | radius, gravity, glow, fall) | |
719 | ||
720 | effect(pos, amount, texture, min_size, max_size, radius, gravity, glow, fall) | |
721 | end | |
722 | ||
372 | 723 | |
373 | 724 | -- update nametag colour |
374 | local update_tag = function(self) | |
725 | function mob_class:update_tag() | |
375 | 726 | |
376 | 727 | local col = "#00FF00" |
377 | 728 | local qua = self.hp_max / 4 |
388 | 739 | col = "#FF0000" |
389 | 740 | end |
390 | 741 | |
742 | -- build infotext | |
743 | self.infotext = "Health: " .. self.health .. " / " .. self.hp_max | |
744 | .. "\n" .. "Owner: " .. self.owner | |
745 | ||
746 | -- set changes | |
391 | 747 | self.object:set_properties({ |
392 | 748 | nametag = self.nametag, |
393 | nametag_color = col | |
749 | nametag_color = col, | |
750 | infotext = self.infotext | |
394 | 751 | }) |
395 | ||
396 | 752 | end |
397 | 753 | |
398 | 754 | |
399 | 755 | -- drop items |
400 | local item_drop = function(self) | |
756 | function mob_class:item_drop() | |
757 | ||
758 | -- no drops if disabled by setting or mob is child | |
759 | if not mobs_drop_items or self.child then return end | |
760 | ||
761 | local pos = self.object:get_pos() | |
762 | ||
763 | -- check for drops function | |
764 | self.drops = type(self.drops) == "function" | |
765 | and self.drops(pos) or self.drops | |
401 | 766 | |
402 | 767 | -- check for nil or no drops |
403 | 768 | if not self.drops or #self.drops == 0 then |
404 | 769 | return |
405 | 770 | end |
406 | 771 | |
407 | -- no drops if disabled by setting | |
408 | if not mobs_drop_items then return end | |
409 | ||
410 | -- no drops for child mobs | |
411 | if self.child then return end | |
412 | ||
413 | 772 | -- was mob killed by player? |
414 | local death_by_player = self.cause_of_death and self.cause_of_death.puncher | |
415 | and self.cause_of_death.puncher:is_player() or nil | |
773 | local death_by_player = self.cause_of_death | |
774 | and self.cause_of_death.puncher | |
775 | and self.cause_of_death.puncher:is_player() | |
416 | 776 | |
417 | 777 | local obj, item, num |
418 | local pos = self.object:get_pos() | |
419 | 778 | |
420 | 779 | for n = 1, #self.drops do |
421 | 780 | |
422 | if random(1, self.drops[n].chance) == 1 then | |
781 | if random(self.drops[n].chance) == 1 then | |
423 | 782 | |
424 | 783 | num = random(self.drops[n].min or 0, self.drops[n].max or 1) |
425 | 784 | item = self.drops[n].name |
435 | 794 | end |
436 | 795 | end |
437 | 796 | |
438 | -- only drop rare items (drops.min=0) if killed by player | |
439 | if death_by_player then | |
440 | obj = minetest.add_item(pos, ItemStack(item .. " " .. num)) | |
441 | ||
442 | elseif self.drops[n].min ~= 0 then | |
797 | -- only drop rare items (drops.min = 0) if killed by player | |
798 | if death_by_player or self.drops[n].min ~= 0 then | |
443 | 799 | obj = minetest.add_item(pos, ItemStack(item .. " " .. num)) |
444 | 800 | end |
445 | 801 | |
448 | 804 | obj:set_velocity({ |
449 | 805 | x = random(-10, 10) / 9, |
450 | 806 | y = 6, |
451 | z = random(-10, 10) / 9, | |
807 | z = random(-10, 10) / 9 | |
452 | 808 | }) |
453 | 809 | |
454 | 810 | elseif obj then |
461 | 817 | end |
462 | 818 | |
463 | 819 | |
820 | -- remove mob and descrease counter | |
821 | local remove_mob = function(self, decrease) | |
822 | ||
823 | self.object:remove() | |
824 | ||
825 | if decrease and active_limit > 0 then | |
826 | ||
827 | active_mobs = active_mobs - 1 | |
828 | ||
829 | if active_mobs < 0 then | |
830 | active_mobs = 0 | |
831 | end | |
832 | end | |
833 | --print("-- active mobs: " .. active_mobs .. " / " .. active_limit) | |
834 | end | |
835 | ||
836 | -- global function for removing mobs | |
837 | function mobs:remove(self, decrease) | |
838 | remove_mob(self, decrease) | |
839 | end | |
840 | ||
841 | ||
464 | 842 | -- check if mob is dead or only hurt |
465 | local check_for_death = function(self, cmi_cause) | |
843 | function mob_class:check_for_death(cmi_cause) | |
844 | ||
845 | -- We dead already | |
846 | if self.state == "die" then | |
847 | return true | |
848 | end | |
466 | 849 | |
467 | 850 | -- has health actually changed? |
468 | 851 | if self.health == self.old_health and self.health > 0 then |
469 | return | |
470 | end | |
852 | return false | |
853 | end | |
854 | ||
855 | local damaged = self.health < self.old_health | |
471 | 856 | |
472 | 857 | self.old_health = self.health |
473 | 858 | |
474 | 859 | -- still got some health? play hurt sound |
475 | 860 | if self.health > 0 then |
476 | 861 | |
477 | mob_sound(self, self.sounds.damage) | |
862 | -- only play hurt sound if damaged | |
863 | if damaged then | |
864 | self:mob_sound(self.sounds.damage) | |
865 | end | |
478 | 866 | |
479 | 867 | -- make sure health isn't higher than max |
480 | 868 | if self.health > self.hp_max then |
482 | 870 | end |
483 | 871 | |
484 | 872 | -- backup nametag so we can show health stats |
485 | if not self.nametag2 then | |
486 | self.nametag2 = self.nametag or "" | |
487 | end | |
488 | ||
489 | if show_health | |
490 | and (cmi_cause and cmi_cause.type == "punch") then | |
491 | ||
492 | self.htimer = 2 | |
493 | self.nametag = "♥ " .. self.health .. " / " .. self.hp_max | |
494 | ||
495 | update_tag(self) | |
496 | end | |
873 | -- if not self.nametag2 then | |
874 | -- self.nametag2 = self.nametag or "" | |
875 | -- end | |
876 | ||
877 | -- if show_health | |
878 | -- and (cmi_cause and cmi_cause.type == "punch") then | |
879 | ||
880 | -- self.htimer = 2 | |
881 | -- self.nametag = "♥ " .. self.health .. " / " .. self.hp_max | |
882 | self:update_tag() | |
883 | -- end | |
497 | 884 | |
498 | 885 | return false |
499 | 886 | end |
501 | 888 | self.cause_of_death = cmi_cause |
502 | 889 | |
503 | 890 | -- drop items |
504 | item_drop(self) | |
505 | ||
506 | mob_sound(self, self.sounds.death) | |
891 | self:item_drop() | |
892 | ||
893 | self:mob_sound(self.sounds.death) | |
507 | 894 | |
508 | 895 | local pos = self.object:get_pos() |
509 | 896 | |
510 | 897 | -- execute custom death function |
511 | if self.on_die then | |
512 | ||
513 | self.on_die(self, pos) | |
898 | if pos and self.on_die then | |
899 | ||
900 | self:on_die(pos) | |
514 | 901 | |
515 | 902 | if use_cmi then |
516 | 903 | cmi.notify_die(self.object, cmi_cause) |
517 | 904 | end |
518 | 905 | |
519 | self.object:remove() | |
906 | remove_mob(self, true) | |
520 | 907 | |
521 | 908 | return true |
522 | 909 | end |
523 | 910 | |
524 | -- default death function and die animation (if defined) | |
911 | -- check for custom death function and die animation | |
525 | 912 | if self.animation |
526 | 913 | and self.animation.die_start |
527 | 914 | and self.animation.die_end then |
528 | 915 | |
529 | 916 | local frames = self.animation.die_end - self.animation.die_start |
530 | 917 | local speed = self.animation.die_speed or 15 |
531 | local length = max(frames / speed, 0) | |
918 | local length = max((frames / speed), 0) | |
919 | local rot = self.animation.die_rotate and 5 | |
532 | 920 | |
533 | 921 | self.attack = nil |
922 | self.following = nil | |
534 | 923 | self.v_start = false |
535 | 924 | self.timer = 0 |
536 | 925 | self.blinktimer = 0 |
537 | 926 | self.passive = true |
538 | 927 | self.state = "die" |
539 | set_velocity(self, 0) | |
540 | set_animation(self, "die") | |
928 | self.object:set_properties({ | |
929 | pointable = false, collide_with_objects = false, | |
930 | automatic_rotate = rot, static_save = false | |
931 | }) | |
932 | self:set_velocity(0) | |
933 | self:set_animation("die") | |
541 | 934 | |
542 | 935 | minetest.after(length, function(self) |
543 | 936 | |
544 | if use_cmi and self.object:get_luaentity() then | |
545 | cmi.notify_die(self.object, cmi_cause) | |
546 | end | |
547 | ||
548 | self.object:remove() | |
937 | if self.object:get_luaentity() then | |
938 | ||
939 | if use_cmi then | |
940 | cmi.notify_die(self.object, cmi_cause) | |
941 | end | |
942 | ||
943 | remove_mob(self, true) | |
944 | end | |
549 | 945 | end, self) |
550 | else | |
946 | ||
947 | return true | |
948 | ||
949 | elseif pos then -- otherwise remove mod and show particle effect | |
551 | 950 | |
552 | 951 | if use_cmi then |
553 | 952 | cmi.notify_die(self.object, cmi_cause) |
554 | 953 | end |
555 | 954 | |
556 | self.object:remove() | |
557 | end | |
558 | ||
559 | effect(pos, 20, "tnt_smoke.png") | |
955 | remove_mob(self, true) | |
956 | ||
957 | effect(pos, 20, "tnt_smoke.png") | |
958 | end | |
560 | 959 | |
561 | 960 | return true |
562 | 961 | end |
563 | 962 | |
564 | 963 | |
964 | -- get node but use fallback for nil or unknown | |
965 | local node_ok = function(pos, fallback) | |
966 | ||
967 | fallback = fallback or mobs.fallback_node | |
968 | ||
969 | local node = minetest.get_node_or_nil(pos) | |
970 | ||
971 | if node and minetest.registered_nodes[node.name] then | |
972 | return node | |
973 | end | |
974 | ||
975 | return minetest.registered_nodes[fallback] | |
976 | end | |
977 | ||
978 | ||
979 | -- Returns true is node can deal damage to self | |
980 | local is_node_dangerous = function(self, nodename) | |
981 | ||
982 | if self.water_damage > 0 | |
983 | and minetest.get_item_group(nodename, "water") ~= 0 then | |
984 | return true | |
985 | end | |
986 | ||
987 | if self.lava_damage > 0 | |
988 | and minetest.get_item_group(nodename, "lava") ~= 0 then | |
989 | return true | |
990 | end | |
991 | ||
992 | if self.fire_damage > 0 | |
993 | and minetest.get_item_group(nodename, "fire") ~= 0 then | |
994 | return true | |
995 | end | |
996 | ||
997 | if minetest.registered_nodes[nodename].damage_per_second > 0 then | |
998 | return true | |
999 | end | |
1000 | ||
1001 | return false | |
1002 | end | |
1003 | ||
1004 | ||
565 | 1005 | -- is mob facing a cliff |
566 | local is_at_cliff = function(self) | |
1006 | function mob_class:is_at_cliff() | |
567 | 1007 | |
568 | 1008 | if self.fear_height == 0 then -- 0 for no falling protection! |
569 | 1009 | return false |
570 | 1010 | end |
571 | 1011 | |
1012 | -- get yaw but if nil returned object no longer exists | |
572 | 1013 | local yaw = self.object:get_yaw() |
1014 | ||
1015 | if not yaw then return false end | |
1016 | ||
573 | 1017 | local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) |
574 | 1018 | local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) |
575 | 1019 | local pos = self.object:get_pos() |
576 | 1020 | local ypos = pos.y + self.collisionbox[2] -- just above floor |
577 | 1021 | |
578 | if minetest.line_of_sight( | |
1022 | local free_fall, blocker = minetest.line_of_sight( | |
579 | 1023 | {x = pos.x + dir_x, y = ypos, z = pos.z + dir_z}, |
580 | {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z} | |
581 | , 1) then | |
582 | ||
1024 | {x = pos.x + dir_x, y = ypos - self.fear_height, z = pos.z + dir_z}) | |
1025 | ||
1026 | -- check for straight drop | |
1027 | if free_fall then | |
583 | 1028 | return true |
584 | 1029 | end |
585 | 1030 | |
586 | return false | |
587 | end | |
588 | ||
589 | ||
590 | -- get node but use fallback for nil or unknown | |
591 | local node_ok = function(pos, fallback) | |
592 | ||
593 | fallback = fallback or mobs.fallback_node | |
594 | ||
595 | local node = minetest.get_node_or_nil(pos) | |
596 | ||
597 | if node and minetest.registered_nodes[node.name] then | |
598 | return node | |
599 | end | |
600 | ||
601 | return minetest.registered_nodes[fallback] | |
1031 | local bnode = node_ok(blocker) | |
1032 | ||
1033 | -- will we drop onto dangerous node? | |
1034 | if is_node_dangerous(self, bnode.name) then | |
1035 | return true | |
1036 | end | |
1037 | ||
1038 | local def = minetest.registered_nodes[bnode.name] | |
1039 | ||
1040 | return (not def and def.walkable) | |
602 | 1041 | end |
603 | 1042 | |
604 | 1043 | |
605 | 1044 | -- environmental damage (water, lava, fire, light etc.) |
606 | local do_env_damage = function(self) | |
1045 | function mob_class:do_env_damage() | |
607 | 1046 | |
608 | 1047 | -- feed/tame text timer (so mob 'full' messages dont spam chat) |
609 | 1048 | if self.htimer > 0 then |
611 | 1050 | end |
612 | 1051 | |
613 | 1052 | -- reset nametag after showing health stats |
614 | if self.htimer < 1 and self.nametag2 then | |
615 | ||
616 | self.nametag = self.nametag2 | |
617 | self.nametag2 = nil | |
618 | ||
619 | update_tag(self) | |
620 | end | |
621 | ||
622 | local pos = self.object:get_pos() | |
1053 | -- if self.htimer < 1 and self.nametag2 then | |
1054 | ||
1055 | -- self.nametag = self.nametag2 | |
1056 | -- self.nametag2 = nil | |
1057 | ||
1058 | self:update_tag() | |
1059 | -- end | |
1060 | ||
1061 | local pos = self.object:get_pos() ; if not pos then return end | |
623 | 1062 | |
624 | 1063 | self.time_of_day = minetest.get_timeofday() |
625 | 1064 | |
626 | -- remove mob if standing inside ignore node | |
1065 | -- halt mob if standing inside ignore node | |
627 | 1066 | if self.standing_in == "ignore" then |
628 | self.object:remove() | |
629 | return | |
1067 | ||
1068 | self.object:set_velocity({x = 0, y = 0, z = 0}) | |
1069 | ||
1070 | return true | |
1071 | end | |
1072 | ||
1073 | -- particle appears at random mob height | |
1074 | local py = { | |
1075 | x = pos.x, | |
1076 | y = pos.y + random(self.collisionbox[2], self.collisionbox[5]), | |
1077 | z = pos.z | |
1078 | } | |
1079 | ||
1080 | local nodef = minetest.registered_nodes[self.standing_in] | |
1081 | ||
1082 | -- water | |
1083 | if self.water_damage ~= 0 and nodef.groups.water then | |
1084 | ||
1085 | self.health = self.health - self.water_damage | |
1086 | ||
1087 | effect(py, 5, "bubble.png", nil, nil, 1, nil) | |
1088 | ||
1089 | if self:check_for_death({type = "environment", | |
1090 | pos = pos, node = self.standing_in}) then | |
1091 | return true | |
1092 | end | |
1093 | ||
1094 | -- lava damage | |
1095 | elseif self.lava_damage ~= 0 and nodef.groups.lava then | |
1096 | ||
1097 | self.health = self.health - self.lava_damage | |
1098 | ||
1099 | effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true) | |
1100 | ||
1101 | if self:check_for_death({type = "environment", pos = pos, | |
1102 | node = self.standing_in, hot = true}) then | |
1103 | return true | |
1104 | end | |
1105 | ||
1106 | -- fire damage | |
1107 | elseif self.fire_damage ~= 0 and nodef.groups.fire then | |
1108 | ||
1109 | self.health = self.health - self.fire_damage | |
1110 | ||
1111 | effect(py, 15, "fire_basic_flame.png", 1, 5, 1, 0.2, 15, true) | |
1112 | ||
1113 | if self:check_for_death({type = "environment", pos = pos, | |
1114 | node = self.standing_in, hot = true}) then | |
1115 | return true | |
1116 | end | |
1117 | ||
1118 | -- damage_per_second node check (not fire and lava) | |
1119 | elseif nodef.damage_per_second ~= 0 | |
1120 | and nodef.groups.lava == nil and nodef.groups.fire == nil then | |
1121 | ||
1122 | self.health = self.health - nodef.damage_per_second | |
1123 | ||
1124 | effect(py, 5, "tnt_smoke.png") | |
1125 | ||
1126 | if self:check_for_death({type = "environment", | |
1127 | pos = pos, node = self.standing_in}) then | |
1128 | return true | |
1129 | end | |
1130 | end | |
1131 | ||
1132 | -- air damage | |
1133 | if self.air_damage ~= 0 and self.standing_in == "air" then | |
1134 | ||
1135 | self.health = self.health - self.air_damage | |
1136 | ||
1137 | effect(py, 3, "bubble.png", 1, 1, 1, 0.2) | |
1138 | ||
1139 | if self:check_for_death({type = "environment", | |
1140 | pos = pos, node = self.standing_in}) then | |
1141 | return true | |
1142 | end | |
630 | 1143 | end |
631 | 1144 | |
632 | 1145 | -- is mob light sensative, or scared of the dark :P |
639 | 1152 | |
640 | 1153 | self.health = self.health - self.light_damage |
641 | 1154 | |
642 | effect(pos, 5, "tnt_smoke.png") | |
643 | ||
644 | if check_for_death(self, {type = "light"}) then return end | |
645 | end | |
646 | end | |
647 | ||
648 | local nodef = minetest.registered_nodes[self.standing_in] | |
649 | ||
650 | pos.y = pos.y + 1 -- for particle effect position | |
651 | ||
652 | -- water | |
653 | if self.water_damage | |
654 | and nodef.groups.water then | |
655 | ||
656 | if self.water_damage ~= 0 then | |
657 | ||
658 | self.health = self.health - self.water_damage | |
659 | ||
660 | effect(pos, 5, "bubble.png", nil, nil, 1, nil) | |
661 | ||
662 | if check_for_death(self, {type = "environment", | |
663 | pos = pos, node = self.standing_in}) then return end | |
664 | end | |
665 | ||
666 | -- lava or fire or ignition source | |
667 | elseif self.lava_damage | |
668 | and nodef.groups.igniter then | |
669 | -- and (nodef.groups.lava | |
670 | -- or self.standing_in == node_fire | |
671 | -- or self.standing_in == node_permanent_flame) then | |
672 | ||
673 | if self.lava_damage ~= 0 then | |
674 | ||
675 | self.health = self.health - self.lava_damage | |
676 | ||
677 | effect(pos, 5, "fire_basic_flame.png", nil, nil, 1, nil) | |
678 | ||
679 | if check_for_death(self, {type = "environment", | |
680 | pos = pos, node = self.standing_in, hot = true}) then return end | |
681 | end | |
682 | ||
683 | -- damage_per_second node check | |
684 | elseif nodef.damage_per_second ~= 0 then | |
685 | ||
686 | self.health = self.health - nodef.damage_per_second | |
687 | ||
688 | effect(pos, 5, "tnt_smoke.png") | |
689 | ||
690 | if check_for_death(self, {type = "environment", | |
691 | pos = pos, node = self.standing_in}) then return end | |
692 | end | |
693 | --[[ | |
1155 | effect(py, 5, "tnt_smoke.png") | |
1156 | ||
1157 | if self:check_for_death({type = "light"}) then | |
1158 | return true | |
1159 | end | |
1160 | end | |
1161 | end | |
1162 | ||
694 | 1163 | --- suffocation inside solid node |
695 | if self.suffocation ~= 0 | |
696 | and nodef.walkable == true | |
697 | and nodef.groups.disable_suffocation ~= 1 | |
698 | and nodef.drawtype == "normal" then | |
699 | ||
700 | self.health = self.health - self.suffocation | |
701 | ||
702 | if check_for_death(self, {type = "environment", | |
703 | pos = pos, node = self.standing_in}) then return end | |
704 | end | |
705 | ]] | |
706 | check_for_death(self, {type = "unknown"}) | |
1164 | if (self.suffocation and self.suffocation ~= 0) | |
1165 | and (nodef.walkable == nil or nodef.walkable == true) | |
1166 | and (nodef.collision_box == nil or nodef.collision_box.type == "regular") | |
1167 | and (nodef.node_box == nil or nodef.node_box.type == "regular") | |
1168 | and (nodef.groups.disable_suffocation ~= 1) then | |
1169 | ||
1170 | local damage | |
1171 | ||
1172 | if self.suffocation == true then | |
1173 | damage = 2 | |
1174 | else | |
1175 | damage = (self.suffocation or 2) | |
1176 | end | |
1177 | ||
1178 | self.health = self.health - damage | |
1179 | ||
1180 | if self:check_for_death({type = "suffocation", | |
1181 | pos = pos, node = self.standing_in}) then | |
1182 | return true | |
1183 | end | |
1184 | end | |
1185 | ||
1186 | return self:check_for_death({type = "unknown"}) | |
707 | 1187 | end |
708 | 1188 | |
709 | 1189 | |
710 | 1190 | -- jump if facing a solid node (not fences or gates) |
711 | local do_jump = function(self) | |
1191 | function mob_class:do_jump() | |
712 | 1192 | |
713 | 1193 | if not self.jump |
714 | 1194 | or self.jump_height == 0 |
722 | 1202 | |
723 | 1203 | -- something stopping us while moving? |
724 | 1204 | if self.state ~= "stand" |
725 | and get_velocity(self) > 0.5 | |
1205 | and self:get_velocity() > 0.5 | |
726 | 1206 | and self.object:get_velocity().y ~= 0 then |
727 | 1207 | return false |
728 | 1208 | end |
730 | 1210 | local pos = self.object:get_pos() |
731 | 1211 | local yaw = self.object:get_yaw() |
732 | 1212 | |
733 | -- what is mob standing on? | |
734 | pos.y = pos.y + self.collisionbox[2] - 0.2 | |
735 | ||
736 | local nod = node_ok(pos) | |
737 | ||
738 | --print ("standing on:", nod.name, pos.y) | |
739 | ||
740 | if minetest.registered_nodes[nod.name].walkable == false then | |
1213 | -- sanity check | |
1214 | if not yaw then return false end | |
1215 | ||
1216 | -- we can only jump if standing on solid node | |
1217 | if minetest.registered_nodes[self.standing_on].walkable == false then | |
741 | 1218 | return false |
742 | 1219 | end |
743 | 1220 | |
745 | 1222 | local dir_x = -sin(yaw) * (self.collisionbox[4] + 0.5) |
746 | 1223 | local dir_z = cos(yaw) * (self.collisionbox[4] + 0.5) |
747 | 1224 | |
1225 | -- set y_pos to base of mob | |
1226 | pos.y = pos.y + self.collisionbox[2] | |
1227 | ||
748 | 1228 | -- what is in front of mob? |
749 | 1229 | local nod = node_ok({ |
750 | x = pos.x + dir_x, | |
751 | y = pos.y + 0.5, | |
752 | z = pos.z + dir_z | |
1230 | x = pos.x + dir_x, y = pos.y + 0.5, z = pos.z + dir_z | |
753 | 1231 | }) |
754 | 1232 | |
755 | -- thin blocks that do not need to be jumped | |
756 | if nod.name == node_snow then | |
757 | return false | |
758 | end | |
759 | ||
760 | --print ("in front:", nod.name, pos.y + 0.5) | |
761 | ||
762 | if self.walk_chance == 0 | |
763 | or minetest.registered_items[nod.name].walkable then | |
764 | ||
765 | if not nod.name:find("fence") | |
766 | and not nod.name:find("gate") then | |
767 | ||
768 | local v = self.object:get_velocity() | |
769 | ||
770 | v.y = self.jump_height | |
771 | ||
772 | set_animation(self, "jump") -- only when defined | |
773 | ||
774 | self.object:set_velocity(v) | |
775 | ||
776 | -- when in air move forward | |
777 | minetest.after(0.3, function(self, v) | |
778 | ||
779 | if self.object:get_luaentity() then | |
780 | ||
781 | self.object:set_acceleration({ | |
782 | x = v.x * 2,--1.5, | |
783 | y = 0, | |
784 | z = v.z * 2,--1.5 | |
785 | }) | |
786 | end | |
787 | end, self, v) | |
788 | ||
789 | if get_velocity(self) > 0 then | |
790 | mob_sound(self, self.sounds.jump) | |
791 | end | |
792 | else | |
793 | self.facing_fence = true | |
794 | end | |
1233 | -- what is above and in front? | |
1234 | local nodt = node_ok({ | |
1235 | x = pos.x + dir_x, y = pos.y + 1.5, z = pos.z + dir_z | |
1236 | }) | |
1237 | ||
1238 | local blocked = minetest.registered_nodes[nodt.name].walkable | |
1239 | ||
1240 | -- are we facing a fence or wall | |
1241 | if nod.name:find("fence") or nod.name:find("gate") or nod.name:find("wall") then | |
1242 | self.facing_fence = true | |
1243 | end | |
1244 | --[[ | |
1245 | print("on: " .. self.standing_on | |
1246 | .. ", front: " .. nod.name | |
1247 | .. ", front above: " .. nodt.name | |
1248 | .. ", blocked: " .. (blocked and "yes" or "no") | |
1249 | .. ", fence: " .. (self.facing_fence and "yes" or "no") | |
1250 | ) | |
1251 | ]] | |
1252 | -- jump if standing on solid node (not snow) and not blocked | |
1253 | if (self.walk_chance == 0 or minetest.registered_items[nod.name].walkable) | |
1254 | and not blocked and not self.facing_fence and nod.name ~= node_snow then | |
1255 | ||
1256 | local v = self.object:get_velocity() | |
1257 | ||
1258 | v.y = self.jump_height | |
1259 | ||
1260 | self:set_animation("jump") -- only when defined | |
1261 | ||
1262 | self.object:set_velocity(v) | |
1263 | ||
1264 | -- when in air move forward | |
1265 | minetest.after(0.3, function(self, v) | |
1266 | ||
1267 | if self.object:get_luaentity() then | |
1268 | ||
1269 | self.object:set_acceleration({ | |
1270 | x = v.x * 2, | |
1271 | y = 0, | |
1272 | z = v.z * 2 | |
1273 | }) | |
1274 | end | |
1275 | end, self, v) | |
1276 | ||
1277 | if self:get_velocity() > 0 then | |
1278 | self:mob_sound(self.sounds.jump) | |
1279 | end | |
1280 | ||
1281 | self.jump_count = 0 | |
795 | 1282 | |
796 | 1283 | return true |
1284 | end | |
1285 | ||
1286 | -- if blocked for 3 counts then turn | |
1287 | if not self.following and (self.facing_fence or blocked) then | |
1288 | ||
1289 | self.jump_count = (self.jump_count or 0) + 1 | |
1290 | ||
1291 | if self.jump_count > 2 then | |
1292 | ||
1293 | local yaw = self.object:get_yaw() or 0 | |
1294 | local turn = random(0, 2) + 1.35 | |
1295 | ||
1296 | yaw = self:set_yaw(yaw + turn, 12) | |
1297 | ||
1298 | self.jump_count = 0 | |
1299 | end | |
797 | 1300 | end |
798 | 1301 | |
799 | 1302 | return false |
813 | 1316 | obj_pos = objs[n]:get_pos() |
814 | 1317 | |
815 | 1318 | dist = get_distance(pos, obj_pos) |
1319 | ||
816 | 1320 | if dist < 1 then dist = 1 end |
817 | 1321 | |
818 | 1322 | local damage = floor((4 / dist) * radius) |
827 | 1331 | end |
828 | 1332 | |
829 | 1333 | |
1334 | -- can mob see player | |
1335 | local is_invisible = function(self, player_name) | |
1336 | ||
1337 | if mobs.invis[player_name] and not self.ignore_invisibility then | |
1338 | return true | |
1339 | end | |
1340 | end | |
1341 | ||
1342 | ||
830 | 1343 | -- should mob follow what I'm holding ? |
831 | local follow_holding = function(self, clicker) | |
832 | ||
833 | if mobs.invis[clicker:get_player_name()] then | |
1344 | function mob_class:follow_holding(clicker) | |
1345 | ||
1346 | if is_invisible(self, clicker:get_player_name()) then | |
834 | 1347 | return false |
835 | 1348 | end |
836 | 1349 | |
837 | 1350 | local item = clicker:get_wielded_item() |
838 | local t = type(self.follow) | |
839 | ||
840 | -- single item | |
841 | if t == "string" | |
842 | and item:get_name() == self.follow then | |
1351 | ||
1352 | -- are we holding an item mob can follow ? | |
1353 | if check_for(item:get_name(), self.follow) then | |
843 | 1354 | return true |
844 | ||
845 | -- multiple items | |
846 | elseif t == "table" then | |
847 | ||
848 | for no = 1, #self.follow do | |
849 | ||
850 | if self.follow[no] == item:get_name() then | |
851 | return true | |
852 | end | |
853 | end | |
854 | 1355 | end |
855 | 1356 | |
856 | 1357 | return false |
857 | 1358 | end |
858 | 1359 | |
1360 | -- Thanks Wuzzy for the following editable settings | |
1361 | local HORNY_TIME = 30 | |
1362 | local HORNY_AGAIN_TIME = 60 * 5 -- 5 minutes | |
1363 | local CHILD_GROW_TIME = 60 * 20 -- 20 minutes | |
859 | 1364 | |
860 | 1365 | -- find two animals of same type and breed if nearby and horny |
861 | local breed = function(self) | |
862 | ||
863 | -- child takes 240 seconds before growing into adult | |
1366 | function mob_class:breed() | |
1367 | ||
1368 | -- child takes a long time before growing into adult | |
864 | 1369 | if self.child == true then |
865 | 1370 | |
866 | 1371 | self.hornytimer = self.hornytimer + 1 |
867 | 1372 | |
868 | if self.hornytimer > 240 then | |
1373 | if self.hornytimer > CHILD_GROW_TIME then | |
869 | 1374 | |
870 | 1375 | self.child = false |
871 | 1376 | self.hornytimer = 0 |
875 | 1380 | mesh = self.base_mesh, |
876 | 1381 | visual_size = self.base_size, |
877 | 1382 | collisionbox = self.base_colbox, |
878 | selectionbox = self.base_selbox, | |
1383 | selectionbox = self.base_selbox | |
879 | 1384 | }) |
880 | 1385 | |
881 | 1386 | -- custom function when child grows up |
882 | 1387 | if self.on_grown then |
883 | 1388 | self.on_grown(self) |
884 | 1389 | else |
885 | -- jump when fully grown so as not to fall into ground | |
886 | self.object:set_velocity({ | |
887 | x = 0, | |
888 | y = self.jump_height, | |
889 | z = 0 | |
890 | }) | |
1390 | local pos = self.object:get_pos() ; if not pos then return end | |
1391 | local ent = self.object:get_luaentity() | |
1392 | ||
1393 | pos.y = pos.y + (ent.collisionbox[2] * -1) - 0.4 | |
1394 | ||
1395 | self.object:set_pos(pos) | |
1396 | ||
1397 | -- jump slightly when fully grown so as not to fall into ground | |
1398 | self.object:set_velocity({x = 0, y = 0.5, z = 0 }) | |
891 | 1399 | end |
892 | 1400 | end |
893 | 1401 | |
894 | 1402 | return |
895 | 1403 | end |
896 | 1404 | |
897 | -- horny animal can mate for 40 seconds, | |
898 | -- afterwards horny animal cannot mate again for 200 seconds | |
1405 | -- horny animal can mate for HORNY_TIME seconds, | |
1406 | -- afterwards horny animal cannot mate again for HORNY_AGAIN_TIME seconds | |
899 | 1407 | if self.horny == true |
900 | and self.hornytimer < 240 then | |
1408 | and self.hornytimer < HORNY_TIME + HORNY_AGAIN_TIME then | |
901 | 1409 | |
902 | 1410 | self.hornytimer = self.hornytimer + 1 |
903 | 1411 | |
904 | if self.hornytimer >= 240 then | |
1412 | if self.hornytimer >= HORNY_TIME + HORNY_AGAIN_TIME then | |
905 | 1413 | self.hornytimer = 0 |
906 | 1414 | self.horny = false |
907 | 1415 | end |
909 | 1417 | |
910 | 1418 | -- find another same animal who is also horny and mate if nearby |
911 | 1419 | if self.horny == true |
912 | and self.hornytimer <= 40 then | |
1420 | and self.hornytimer <= HORNY_TIME then | |
913 | 1421 | |
914 | 1422 | local pos = self.object:get_pos() |
915 | 1423 | |
916 | effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, "heart.png", 3, 4, 1, 0.1) | |
1424 | effect({x = pos.x, y = pos.y + 1, z = pos.z}, 8, | |
1425 | "heart.png", 3, 4, 1, 0.1) | |
917 | 1426 | |
918 | 1427 | local objs = minetest.get_objects_inside_radius(pos, 3) |
919 | local num = 0 | |
920 | local ent = nil | |
1428 | local ent | |
921 | 1429 | |
922 | 1430 | for n = 1, #objs do |
923 | 1431 | |
931 | 1439 | if ent.name == self.name then |
932 | 1440 | canmate = true |
933 | 1441 | else |
934 | local entname = string.split(ent.name,":") | |
935 | local selfname = string.split(self.name,":") | |
1442 | local entname = ent.name:split(":") | |
1443 | local selfname = self.name:split(":") | |
936 | 1444 | |
937 | 1445 | if entname[1] == selfname[1] then |
938 | entname = string.split(entname[2],"_") | |
939 | selfname = string.split(selfname[2],"_") | |
1446 | entname = entname[2]:split("_") | |
1447 | selfname = selfname[2]:split("_") | |
940 | 1448 | |
941 | 1449 | if entname[1] == selfname[1] then |
942 | 1450 | canmate = true |
945 | 1453 | end |
946 | 1454 | end |
947 | 1455 | |
948 | if ent | |
1456 | -- found another similar horny animal that isn't self? | |
1457 | if ent and ent.object ~= self.object | |
949 | 1458 | and canmate == true |
950 | 1459 | and ent.horny == true |
951 | and ent.hornytimer <= 40 then | |
952 | num = num + 1 | |
953 | end | |
954 | ||
955 | -- found your mate? then have a baby | |
956 | if num > 1 then | |
957 | ||
958 | self.hornytimer = 41 | |
959 | ent.hornytimer = 41 | |
1460 | and ent.hornytimer <= HORNY_TIME then | |
1461 | ||
1462 | local pos2 = ent.object:get_pos() | |
1463 | ||
1464 | -- Have mobs face one another | |
1465 | yaw_to_pos(self, pos2) | |
1466 | yaw_to_pos(ent, self.object:get_pos()) | |
1467 | ||
1468 | self.hornytimer = HORNY_TIME + 1 | |
1469 | ent.hornytimer = HORNY_TIME + 1 | |
1470 | ||
1471 | -- have we reached active mob limit | |
1472 | if active_limit > 0 and active_mobs >= active_limit then | |
1473 | minetest.chat_send_player(self.owner, | |
1474 | S("Active Mob Limit Reached!") | |
1475 | .. " (" .. active_mobs | |
1476 | .. " / " .. active_limit .. ")") | |
1477 | return | |
1478 | end | |
960 | 1479 | |
961 | 1480 | -- spawn baby |
962 | 1481 | minetest.after(5, function(self, ent) |
969 | 1488 | if self.on_breed then |
970 | 1489 | |
971 | 1490 | -- when false skip going any further |
972 | if self.on_breed(self, ent) == false then | |
1491 | if self:on_breed(ent) == false then | |
973 | 1492 | return |
974 | 1493 | end |
975 | 1494 | else |
976 | 1495 | effect(pos, 15, "tnt_smoke.png", 1, 2, 2, 15, 5) |
977 | 1496 | end |
978 | 1497 | |
1498 | pos.y = pos.y + 0.5 -- spawn child a little higher | |
1499 | ||
979 | 1500 | local mob = minetest.add_entity(pos, self.name) |
980 | 1501 | local ent2 = mob:get_luaentity() |
981 | 1502 | local textures = self.base_texture |
990 | 1511 | textures = textures, |
991 | 1512 | visual_size = { |
992 | 1513 | x = self.base_size.x * .5, |
993 | y = self.base_size.y * .5, | |
1514 | y = self.base_size.y * .5 | |
994 | 1515 | }, |
995 | 1516 | collisionbox = { |
996 | 1517 | self.base_colbox[1] * .5, |
998 | 1519 | self.base_colbox[3] * .5, |
999 | 1520 | self.base_colbox[4] * .5, |
1000 | 1521 | self.base_colbox[5] * .5, |
1001 | self.base_colbox[6] * .5, | |
1522 | self.base_colbox[6] * .5 | |
1002 | 1523 | }, |
1003 | 1524 | selectionbox = { |
1004 | 1525 | self.base_selbox[1] * .5, |
1006 | 1527 | self.base_selbox[3] * .5, |
1007 | 1528 | self.base_selbox[4] * .5, |
1008 | 1529 | self.base_selbox[5] * .5, |
1009 | self.base_selbox[6] * .5, | |
1530 | self.base_selbox[6] * .5 | |
1010 | 1531 | }, |
1011 | 1532 | }) |
1012 | 1533 | -- tamed and owned by parents' owner |
1015 | 1536 | ent2.owner = self.owner |
1016 | 1537 | end, self, ent) |
1017 | 1538 | |
1018 | num = 0 | |
1019 | ||
1020 | 1539 | break |
1021 | 1540 | end |
1022 | 1541 | end |
1025 | 1544 | |
1026 | 1545 | |
1027 | 1546 | -- find and replace what mob is looking for (grass, wheat etc.) |
1028 | local replace = function(self, pos) | |
1547 | function mob_class:replace(pos) | |
1548 | ||
1549 | local vel = self.object:get_velocity() | |
1550 | if not vel then return end | |
1029 | 1551 | |
1030 | 1552 | if not mobs_griefing |
1031 | 1553 | or not self.replace_rate |
1032 | 1554 | or not self.replace_what |
1033 | 1555 | or self.child == true |
1034 | or self.object:get_velocity().y ~= 0 | |
1035 | or random(1, self.replace_rate) > 1 then | |
1556 | or vel.y ~= 0 | |
1557 | or random(self.replace_rate) > 1 then | |
1036 | 1558 | return |
1037 | 1559 | end |
1038 | 1560 | |
1055 | 1577 | |
1056 | 1578 | if #minetest.find_nodes_in_area(pos, pos, what) > 0 then |
1057 | 1579 | |
1058 | -- print ("replace node = ".. minetest.get_node(pos).name, pos.y) | |
1059 | ||
1060 | local oldnode = {name = what} | |
1061 | local newnode = {name = with} | |
1062 | local on_replace_return | |
1580 | -- print("replace node = ".. minetest.get_node(pos).name, pos.y) | |
1063 | 1581 | |
1064 | 1582 | if self.on_replace then |
1065 | on_replace_return = self.on_replace(self, pos, oldnode, newnode) | |
1066 | end | |
1067 | ||
1068 | if on_replace_return ~= false then | |
1069 | ||
1070 | minetest.set_node(pos, {name = with}) | |
1071 | ||
1072 | -- when cow/sheep eats grass, replace wool and milk | |
1073 | if self.gotten == true then | |
1074 | self.gotten = false | |
1075 | self.object:set_properties(self) | |
1076 | end | |
1077 | end | |
1583 | ||
1584 | local oldnode = what or "" | |
1585 | local newnode = with | |
1586 | ||
1587 | -- pass actual node name when using table or groups | |
1588 | if type(oldnode) == "table" | |
1589 | or oldnode:find("group:") then | |
1590 | oldnode = minetest.get_node(pos).name | |
1591 | end | |
1592 | ||
1593 | if self:on_replace(pos, oldnode, newnode) == false then | |
1594 | return | |
1595 | end | |
1596 | end | |
1597 | ||
1598 | minetest.set_node(pos, {name = with}) | |
1078 | 1599 | end |
1079 | 1600 | end |
1080 | 1601 | |
1081 | 1602 | |
1082 | 1603 | -- check if daytime and also if mob is docile during daylight hours |
1083 | local day_docile = function(self) | |
1604 | function mob_class:day_docile() | |
1084 | 1605 | |
1085 | 1606 | if self.docile_by_day == false then |
1086 | 1607 | |
1097 | 1618 | |
1098 | 1619 | local los_switcher = false |
1099 | 1620 | local height_switcher = false |
1100 | ||
1101 | -- path finding and smart mob routine by rnd, line_of_sight and other edits by Elkien3 | |
1102 | local smart_mobs = function(self, s, p, dist, dtime) | |
1621 | local can_dig_drop = function(pos) | |
1622 | ||
1623 | if minetest.is_protected(pos, "") then | |
1624 | return false | |
1625 | end | |
1626 | ||
1627 | local node = node_ok(pos, "air").name | |
1628 | local ndef = minetest.registered_nodes[node] | |
1629 | ||
1630 | if node ~= "ignore" | |
1631 | and ndef | |
1632 | and ndef.drawtype ~= "airlike" | |
1633 | and not ndef.groups.level | |
1634 | and not ndef.groups.unbreakable | |
1635 | and not ndef.groups.liquid then | |
1636 | ||
1637 | local drops = minetest.get_node_drops(node) | |
1638 | ||
1639 | for _, item in ipairs(drops) do | |
1640 | ||
1641 | minetest.add_item({ | |
1642 | x = pos.x - 0.5 + random(), | |
1643 | y = pos.y - 0.5 + random(), | |
1644 | z = pos.z - 0.5 + random() | |
1645 | }, item) | |
1646 | end | |
1647 | ||
1648 | minetest.remove_node(pos) | |
1649 | ||
1650 | return true | |
1651 | end | |
1652 | ||
1653 | return false | |
1654 | end | |
1655 | ||
1656 | ||
1657 | local pathfinder_mod = minetest.get_modpath("pathfinder") | |
1658 | -- path finding and smart mob routine by rnd, | |
1659 | -- line_of_sight and other edits by Elkien3 | |
1660 | function mob_class:smart_mobs(s, p, dist, dtime) | |
1103 | 1661 | |
1104 | 1662 | local s1 = self.path.lastpos |
1105 | ||
1106 | local target_pos = self.attack:get_pos() | |
1663 | local target_pos = p | |
1664 | ||
1107 | 1665 | |
1108 | 1666 | -- is it becoming stuck? |
1109 | 1667 | if abs(s1.x - s.x) + abs(s1.z - s.z) < .5 then |
1176 | 1734 | end, self) |
1177 | 1735 | end |
1178 | 1736 | |
1179 | if abs(vector.subtract(s,target_pos).y) > self.stepheight then | |
1737 | if abs(vsubtract(s,target_pos).y) > self.stepheight then | |
1180 | 1738 | |
1181 | 1739 | if height_switcher then |
1182 | 1740 | use_pathfind = true |
1189 | 1747 | end |
1190 | 1748 | end |
1191 | 1749 | |
1750 | -- lets try find a path, first take care of positions | |
1751 | -- since pathfinder is very sensitive | |
1192 | 1752 | if use_pathfind then |
1193 | -- lets try find a path, first take care of positions | |
1194 | -- since pathfinder is very sensitive | |
1195 | local sheight = self.collisionbox[5] - self.collisionbox[2] | |
1196 | 1753 | |
1197 | 1754 | -- round position to center of node to avoid stuck in walls |
1198 | 1755 | -- also adjust height for player models! |
1199 | 1756 | s.x = floor(s.x + 0.5) |
1200 | -- s.y = floor(s.y + 0.5) - sheight | |
1201 | 1757 | s.z = floor(s.z + 0.5) |
1202 | 1758 | |
1203 | 1759 | local ssight, sground = minetest.line_of_sight(s, { |
1215 | 1771 | p1.z = floor(p1.z + 0.5) |
1216 | 1772 | |
1217 | 1773 | local dropheight = 6 |
1774 | ||
1218 | 1775 | if self.fear_height ~= 0 then dropheight = self.fear_height end |
1219 | 1776 | |
1220 | self.path.way = minetest.find_path(s, p1, 16, self.stepheight, dropheight, "Dijkstra") | |
1221 | ||
1777 | local jumpheight = 0 | |
1778 | ||
1779 | if self.jump and self.jump_height >= 4 then | |
1780 | jumpheight = min(ceil(self.jump_height / 4), 4) | |
1781 | ||
1782 | elseif self.stepheight > 0.5 then | |
1783 | jumpheight = 1 | |
1784 | end | |
1785 | ||
1786 | if pathfinder_mod then | |
1787 | self.path.way = pathfinder.find_path(s, p1, self, dtime) | |
1788 | else | |
1789 | self.path.way = minetest.find_path(s, p1, 16, jumpheight, | |
1790 | dropheight, "Dijkstra") | |
1791 | end | |
1222 | 1792 | --[[ |
1223 | 1793 | -- show path using particles |
1224 | 1794 | if self.path.way and #self.path.way > 0 then |
1225 | print ("-- path length:" .. tonumber(#self.path.way)) | |
1795 | ||
1796 | print("-- path length:" .. tonumber(#self.path.way)) | |
1797 | ||
1226 | 1798 | for _,pos in pairs(self.path.way) do |
1227 | 1799 | minetest.add_particle({ |
1228 | 1800 | pos = pos, |
1239 | 1811 | ]] |
1240 | 1812 | |
1241 | 1813 | self.state = "" |
1242 | do_attack(self, self.attack) | |
1814 | ||
1815 | if self.attack then | |
1816 | self:do_attack(self.attack) | |
1817 | end | |
1243 | 1818 | |
1244 | 1819 | -- no path found, try something else |
1245 | 1820 | if not self.path.way then |
1249 | 1824 | -- lets make way by digging/building if not accessible |
1250 | 1825 | if self.pathfinding == 2 and mobs_griefing then |
1251 | 1826 | |
1252 | -- is player higher than mob? | |
1253 | if s.y < p1.y then | |
1827 | -- is player more than 1 block higher than mob? | |
1828 | if p1.y > (s.y + 1) then | |
1254 | 1829 | |
1255 | 1830 | -- build upwards |
1256 | 1831 | if not minetest.is_protected(s, "") then |
1258 | 1833 | local ndef1 = minetest.registered_nodes[self.standing_in] |
1259 | 1834 | |
1260 | 1835 | if ndef1 and (ndef1.buildable_to or ndef1.groups.liquid) then |
1261 | ||
1262 | minetest.set_node(s, {name = mobs.fallback_node}) | |
1836 | minetest.set_node(s, {name = mobs.fallback_node}) | |
1263 | 1837 | end |
1264 | 1838 | end |
1265 | 1839 | |
1266 | local sheight = math.ceil(self.collisionbox[5]) + 1 | |
1840 | local sheight = ceil(self.collisionbox[5]) + 1 | |
1267 | 1841 | |
1268 | 1842 | -- assume mob is 2 blocks high so it digs above its head |
1269 | 1843 | s.y = s.y + sheight |
1270 | 1844 | |
1271 | 1845 | -- remove one block above to make room to jump |
1272 | if not minetest.is_protected(s, "") then | |
1273 | ||
1274 | local node1 = node_ok(s, "air").name | |
1275 | local ndef1 = minetest.registered_nodes[node1] | |
1276 | ||
1277 | if node1 ~= "air" | |
1278 | and node1 ~= "ignore" | |
1279 | and ndef1 | |
1280 | and not ndef1.groups.level | |
1281 | and not ndef1.groups.unbreakable | |
1282 | and not ndef1.groups.liquid then | |
1283 | ||
1284 | minetest.set_node(s, {name = "air"}) | |
1285 | minetest.add_item(s, ItemStack(node1)) | |
1286 | ||
1287 | end | |
1288 | end | |
1846 | can_dig_drop(s) | |
1289 | 1847 | |
1290 | 1848 | s.y = s.y - sheight |
1291 | 1849 | self.object:set_pos({x = s.x, y = s.y + 2, z = s.z}) |
1850 | ||
1851 | -- is player more than 1 block lower than mob | |
1852 | elseif p1.y < (s.y - 1) then | |
1853 | ||
1854 | -- dig down | |
1855 | s.y = s.y - self.collisionbox[4] - 0.2 | |
1856 | ||
1857 | can_dig_drop(s) | |
1292 | 1858 | |
1293 | 1859 | else -- dig 2 blocks to make door toward player direction |
1294 | 1860 | |
1299 | 1865 | z = s.z + sin(yaw1) |
1300 | 1866 | } |
1301 | 1867 | |
1302 | if not minetest.is_protected(p1, "") then | |
1303 | ||
1304 | local node1 = node_ok(p1, "air").name | |
1305 | local ndef1 = minetest.registered_nodes[node1] | |
1306 | ||
1307 | if node1 ~= "air" | |
1308 | and node1 ~= "ignore" | |
1309 | and ndef1 | |
1310 | and not ndef1.groups.level | |
1311 | and not ndef1.groups.unbreakable | |
1312 | and not ndef1.groups.liquid then | |
1313 | ||
1314 | minetest.add_item(p1, ItemStack(node1)) | |
1315 | minetest.set_node(p1, {name = "air"}) | |
1316 | end | |
1317 | ||
1318 | p1.y = p1.y + 1 | |
1319 | node1 = node_ok(p1, "air").name | |
1320 | ndef1 = minetest.registered_nodes[node1] | |
1321 | ||
1322 | if node1 ~= "air" | |
1323 | and node1 ~= "ignore" | |
1324 | and ndef1 | |
1325 | and not ndef1.groups.level | |
1326 | and not ndef1.groups.unbreakable | |
1327 | and not ndef1.groups.liquid then | |
1328 | ||
1329 | minetest.add_item(p1, ItemStack(node1)) | |
1330 | minetest.set_node(p1, {name = "air"}) | |
1331 | end | |
1332 | ||
1333 | end | |
1868 | -- dig bottom node first incase of door | |
1869 | can_dig_drop(p1) | |
1870 | ||
1871 | p1.y = p1.y + 1 | |
1872 | ||
1873 | can_dig_drop(p1) | |
1334 | 1874 | end |
1335 | 1875 | end |
1336 | 1876 | |
1337 | 1877 | -- will try again in 2 second |
1338 | 1878 | self.path.stuck_timer = stuck_timeout - 2 |
1339 | 1879 | |
1340 | -- frustration! cant find the damn path :( | |
1341 | mob_sound(self, self.sounds.random) | |
1880 | elseif s.y < p1.y and (not self.fly) then | |
1881 | self:do_jump() --add jump to pathfinding | |
1882 | self.path.following = true | |
1342 | 1883 | else |
1343 | 1884 | -- yay i found path |
1344 | mob_sound(self, self.sounds.war_cry) | |
1345 | set_velocity(self, self.walk_velocity) | |
1885 | if self.attack then | |
1886 | self:mob_sound(self.sounds.war_cry) | |
1887 | else | |
1888 | self:mob_sound(self.sounds.random) | |
1889 | end | |
1890 | ||
1891 | self:set_velocity(self.walk_velocity) | |
1346 | 1892 | |
1347 | 1893 | -- follow path now that it has it |
1348 | 1894 | self.path.following = true |
1351 | 1897 | end |
1352 | 1898 | |
1353 | 1899 | |
1354 | -- specific attacks | |
1355 | local specific_attack = function(list, what) | |
1356 | ||
1357 | -- no list so attack default (player, animals etc.) | |
1358 | if list == nil then | |
1359 | return true | |
1360 | end | |
1361 | ||
1362 | -- found entity on list to attack? | |
1363 | for no = 1, #list do | |
1364 | ||
1365 | if list[no] == what then | |
1900 | -- peaceful player privilege support | |
1901 | local function is_peaceful_player(player) | |
1902 | ||
1903 | if peaceful_player_enabled then | |
1904 | ||
1905 | local player_name = player:get_player_name() | |
1906 | ||
1907 | if player_name | |
1908 | and minetest.check_player_privs(player_name, "peaceful_player") then | |
1366 | 1909 | return true |
1367 | 1910 | end |
1368 | 1911 | end |
1371 | 1914 | end |
1372 | 1915 | |
1373 | 1916 | |
1374 | -- general attack function for all mobs ========== | |
1375 | local general_attack = function(self) | |
1917 | -- general attack function for all mobs | |
1918 | function mob_class:general_attack() | |
1376 | 1919 | |
1377 | 1920 | -- return if already attacking, passive or docile during day |
1378 | 1921 | if self.passive |
1922 | or self.state == "runaway" | |
1379 | 1923 | or self.state == "attack" |
1380 | or day_docile(self) then | |
1924 | or self:day_docile() then | |
1381 | 1925 | return |
1382 | 1926 | end |
1383 | 1927 | |
1384 | local s = self.object:get_pos() | |
1928 | local s = self.object:get_pos() ; if not s then return end | |
1385 | 1929 | local objs = minetest.get_objects_inside_radius(s, self.view_range) |
1386 | 1930 | |
1387 | 1931 | -- remove entities we aren't interested in |
1392 | 1936 | -- are we a player? |
1393 | 1937 | if objs[n]:is_player() then |
1394 | 1938 | |
1395 | -- if player invisible or mob not setup to attack then remove from list | |
1396 | if self.attack_players == false | |
1939 | -- if player invisible or mob cannot attack then remove from list | |
1940 | if not damage_enabled | |
1941 | or self.attack_players == false | |
1397 | 1942 | or (self.owner and self.type ~= "monster") |
1398 | or mobs.invis[objs[n]:get_player_name()] | |
1399 | or not specific_attack(self.specific_attack, "player") then | |
1943 | or is_invisible(self, objs[n]:get_player_name()) | |
1944 | or (self.specific_attack | |
1945 | and not check_for("player", self.specific_attack)) then | |
1400 | 1946 | objs[n] = nil |
1401 | 1947 | --print("- pla", n) |
1402 | 1948 | end |
1409 | 1955 | or (not self.attack_animals and ent.type == "animal") |
1410 | 1956 | or (not self.attack_monsters and ent.type == "monster") |
1411 | 1957 | or (not self.attack_npcs and ent.type == "npc") |
1412 | or not specific_attack(self.specific_attack, ent.name) then | |
1958 | or (self.specific_attack | |
1959 | and not check_for(ent.name, self.specific_attack)) then | |
1413 | 1960 | objs[n] = nil |
1414 | 1961 | --print("- mob", n, self.name, ent.name) |
1415 | 1962 | end |
1439 | 1986 | -- choose closest player to attack that isnt self |
1440 | 1987 | if dist ~= 0 |
1441 | 1988 | and dist < min_dist |
1442 | and line_of_sight(self, sp, p, 2) == true then | |
1989 | and self:line_of_sight(sp, p, 2) == true | |
1990 | and not is_peaceful_player(player) then | |
1443 | 1991 | min_dist = dist |
1444 | 1992 | min_player = player |
1445 | 1993 | end |
1446 | 1994 | end |
1447 | 1995 | |
1448 | 1996 | -- attack closest player or mob |
1449 | if min_player and random(1, 100) > self.attack_chance then | |
1450 | do_attack(self, min_player) | |
1451 | end | |
1452 | end | |
1453 | ||
1454 | ||
1455 | -- specific runaway | |
1456 | local specific_runaway = function(list, what) | |
1457 | ||
1458 | -- no list so do not run | |
1459 | if list == nil then | |
1460 | return false | |
1461 | end | |
1462 | ||
1463 | -- found entity on list to attack? | |
1464 | for no = 1, #list do | |
1465 | ||
1466 | if list[no] == what then | |
1467 | return true | |
1468 | end | |
1469 | end | |
1470 | ||
1471 | return false | |
1997 | if min_player and random(100) > self.attack_chance then | |
1998 | self:do_attack(min_player) | |
1999 | end | |
1472 | 2000 | end |
1473 | 2001 | |
1474 | 2002 | |
1475 | 2003 | -- find someone to runaway from |
1476 | local runaway_from = function(self) | |
2004 | function mob_class:do_runaway_from() | |
1477 | 2005 | |
1478 | 2006 | if not self.runaway_from then |
1479 | 2007 | return |
1480 | 2008 | end |
1481 | 2009 | |
1482 | local s = self.object:get_pos() | |
2010 | local s = self.object:get_pos() ; if not s then return end | |
1483 | 2011 | local p, sp, dist, pname |
1484 | 2012 | local player, obj, min_player, name |
1485 | 2013 | local min_dist = self.view_range + 1 |
1491 | 2019 | |
1492 | 2020 | pname = objs[n]:get_player_name() |
1493 | 2021 | |
1494 | if mobs.invis[pname] | |
2022 | if is_invisible(self, pname) | |
1495 | 2023 | or self.owner == pname then |
1496 | 2024 | |
1497 | 2025 | name = "" |
1510 | 2038 | |
1511 | 2039 | -- find specific mob to runaway from |
1512 | 2040 | if name ~= "" and name ~= self.name |
1513 | and specific_runaway(self.runaway_from, name) then | |
1514 | ||
1515 | p = player:get_pos() | |
2041 | and (self.runaway_from and check_for(name, self.runaway_from)) then | |
2042 | ||
1516 | 2043 | sp = s |
2044 | p = player and player:get_pos() or s | |
1517 | 2045 | |
1518 | 2046 | -- aim higher to make looking up hills more realistic |
1519 | 2047 | p.y = p.y + 1 |
1523 | 2051 | |
1524 | 2052 | -- choose closest player/mob to runaway from |
1525 | 2053 | if dist < min_dist |
1526 | and line_of_sight(self, sp, p, 2) == true then | |
2054 | and self:line_of_sight(sp, p, 2) == true then | |
1527 | 2055 | min_dist = dist |
1528 | 2056 | min_player = player |
1529 | 2057 | end |
1532 | 2060 | |
1533 | 2061 | if min_player then |
1534 | 2062 | |
1535 | local lp = player:get_pos() | |
1536 | local vec = { | |
1537 | x = lp.x - s.x, | |
1538 | y = lp.y - s.y, | |
1539 | z = lp.z - s.z | |
1540 | } | |
1541 | ||
1542 | local yaw = (atan(vec.z / vec.x) + 3 * pi / 2) - self.rotate | |
1543 | ||
1544 | if lp.x > s.x then | |
1545 | yaw = yaw + pi | |
1546 | end | |
1547 | ||
1548 | yaw = set_yaw(self, yaw, 4) | |
2063 | yaw_to_pos(self, min_player:get_pos(), 3) | |
2064 | ||
1549 | 2065 | self.state = "runaway" |
1550 | 2066 | self.runaway_timer = 3 |
1551 | 2067 | self.following = nil |
1554 | 2070 | |
1555 | 2071 | |
1556 | 2072 | -- follow player if owner or holding item, if fish outta water then flop |
1557 | local follow_flop = function(self) | |
2073 | function mob_class:follow_flop() | |
1558 | 2074 | |
1559 | 2075 | -- find player to follow |
1560 | if (self.follow ~= "" | |
1561 | or self.order == "follow") | |
2076 | if (self.follow ~= "" or self.order == "follow") | |
1562 | 2077 | and not self.following |
1563 | 2078 | and self.state ~= "attack" |
1564 | 2079 | and self.state ~= "runaway" then |
1565 | 2080 | |
1566 | local s = self.object:get_pos() | |
2081 | local s = self.object:get_pos() ; if not s then return end | |
1567 | 2082 | local players = minetest.get_connected_players() |
1568 | 2083 | |
1569 | 2084 | for n = 1, #players do |
1570 | 2085 | |
1571 | 2086 | if get_distance(players[n]:get_pos(), s) < self.view_range |
1572 | and not mobs.invis[ players[n]:get_player_name() ] then | |
2087 | and not is_invisible(self, players[n]:get_player_name()) then | |
1573 | 2088 | |
1574 | 2089 | self.following = players[n] |
1575 | 2090 | |
1590 | 2105 | self.following = nil |
1591 | 2106 | end |
1592 | 2107 | else |
1593 | -- stop following player if not holding specific item | |
2108 | -- stop following player if not holding specific item or mob is horny | |
1594 | 2109 | if self.following |
1595 | 2110 | and self.following:is_player() |
1596 | and follow_holding(self, self.following) == false then | |
2111 | and (self:follow_holding(self.following) == false | |
2112 | or self.horny) then | |
1597 | 2113 | self.following = nil |
1598 | 2114 | end |
1599 | 2115 | |
1622 | 2138 | if dist > self.view_range then |
1623 | 2139 | self.following = nil |
1624 | 2140 | else |
1625 | local vec = { | |
1626 | x = p.x - s.x, | |
1627 | z = p.z - s.z | |
1628 | } | |
1629 | ||
1630 | local yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
1631 | ||
1632 | if p.x > s.x then yaw = yaw + pi end | |
1633 | ||
1634 | yaw = set_yaw(self, yaw, 6) | |
2141 | yaw_to_pos(self, p) | |
1635 | 2142 | |
1636 | 2143 | -- anyone but standing npc's can move along |
1637 | 2144 | if dist > self.reach |
1638 | 2145 | and self.order ~= "stand" then |
1639 | 2146 | |
1640 | set_velocity(self, self.walk_velocity) | |
2147 | self:set_velocity(self.walk_velocity) | |
1641 | 2148 | |
1642 | 2149 | if self.walk_chance ~= 0 then |
1643 | set_animation(self, "walk") | |
2150 | self:set_animation("walk") | |
1644 | 2151 | end |
1645 | 2152 | else |
1646 | set_velocity(self, 0) | |
1647 | set_animation(self, "stand") | |
2153 | self:set_velocity(0) | |
2154 | self:set_animation("stand") | |
1648 | 2155 | end |
1649 | 2156 | |
1650 | 2157 | return |
1654 | 2161 | |
1655 | 2162 | -- swimmers flop when out of their element, and swim again when back in |
1656 | 2163 | if self.fly then |
1657 | local s = self.object:get_pos() | |
1658 | if not flight_check(self, s) then | |
2164 | ||
2165 | if not self:attempt_flight_correction() then | |
1659 | 2166 | |
1660 | 2167 | self.state = "flop" |
2168 | ||
2169 | -- do we have a custom on_flop function? | |
2170 | if self.on_flop then | |
2171 | ||
2172 | if self:on_flop(self) then | |
2173 | return | |
2174 | end | |
2175 | end | |
2176 | ||
1661 | 2177 | self.object:set_velocity({x = 0, y = -5, z = 0}) |
1662 | 2178 | |
1663 | set_animation(self, "stand") | |
2179 | self:set_animation("stand") | |
1664 | 2180 | |
1665 | 2181 | return |
2182 | ||
1666 | 2183 | elseif self.state == "flop" then |
1667 | 2184 | self.state = "stand" |
1668 | 2185 | end |
1671 | 2188 | |
1672 | 2189 | |
1673 | 2190 | -- dogshoot attack switch and counter function |
1674 | local dogswitch = function(self, dtime) | |
2191 | function mob_class:dogswitch(dtime) | |
1675 | 2192 | |
1676 | 2193 | -- switch mode not activated |
1677 | 2194 | if not self.dogshoot_switch |
1700 | 2217 | |
1701 | 2218 | |
1702 | 2219 | -- execute current state (stand, walk, run, attacks) |
1703 | local do_states = function(self, dtime) | |
1704 | ||
1705 | local yaw = self.object:get_yaw() or 0 | |
2220 | function mob_class:do_states(dtime) | |
2221 | ||
2222 | local yaw = self.object:get_yaw() ; if not yaw then return end | |
1706 | 2223 | |
1707 | 2224 | if self.state == "stand" then |
1708 | 2225 | |
1709 | if random(1, 4) == 1 then | |
1710 | ||
1711 | local lp = nil | |
2226 | if self.randomly_turn and random(4) == 1 then | |
2227 | ||
2228 | local lp | |
1712 | 2229 | local s = self.object:get_pos() |
1713 | 2230 | local objs = minetest.get_objects_inside_radius(s, 3) |
1714 | 2231 | |
1722 | 2239 | |
1723 | 2240 | -- look at any players nearby, otherwise turn randomly |
1724 | 2241 | if lp then |
1725 | ||
1726 | local vec = { | |
1727 | x = lp.x - s.x, | |
1728 | z = lp.z - s.z | |
1729 | } | |
1730 | ||
1731 | yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
1732 | ||
1733 | if lp.x > s.x then yaw = yaw + pi end | |
2242 | yaw = yaw_to_pos(self, lp) | |
1734 | 2243 | else |
1735 | 2244 | yaw = yaw + random(-0.5, 0.5) |
1736 | 2245 | end |
1737 | 2246 | |
1738 | yaw = set_yaw(self, yaw, 8) | |
1739 | end | |
1740 | ||
1741 | set_velocity(self, 0) | |
1742 | set_animation(self, "stand") | |
2247 | yaw = self:set_yaw(yaw, 8) | |
2248 | end | |
2249 | ||
2250 | self:set_velocity(0) | |
2251 | self:set_animation("stand") | |
1743 | 2252 | |
1744 | 2253 | -- mobs ordered to stand stay standing |
1745 | 2254 | if self.order ~= "stand" |
1746 | 2255 | and self.walk_chance ~= 0 |
1747 | 2256 | and self.facing_fence ~= true |
1748 | and random(1, 100) <= self.walk_chance | |
1749 | and is_at_cliff(self) == false then | |
1750 | ||
1751 | set_velocity(self, self.walk_velocity) | |
2257 | and random(100) <= self.walk_chance | |
2258 | and self.at_cliff == false then | |
2259 | ||
2260 | self:set_velocity(self.walk_velocity) | |
1752 | 2261 | self.state = "walk" |
1753 | set_animation(self, "walk") | |
2262 | self:set_animation("walk") | |
1754 | 2263 | end |
1755 | 2264 | |
1756 | 2265 | elseif self.state == "walk" then |
1757 | 2266 | |
1758 | 2267 | local s = self.object:get_pos() |
1759 | local lp = nil | |
2268 | local lp | |
1760 | 2269 | |
1761 | 2270 | -- is there something I need to avoid? |
1762 | 2271 | if self.water_damage > 0 |
1763 | 2272 | and self.lava_damage > 0 then |
1764 | 2273 | |
1765 | lp = minetest.find_node_near(s, 1, {"group:water", "group:lava"}) | |
2274 | lp = minetest.find_node_near(s, 1, {"group:water", "group:igniter"}) | |
1766 | 2275 | |
1767 | 2276 | elseif self.water_damage > 0 then |
1768 | 2277 | |
1770 | 2279 | |
1771 | 2280 | elseif self.lava_damage > 0 then |
1772 | 2281 | |
1773 | lp = minetest.find_node_near(s, 1, {"group:lava"}) | |
2282 | lp = minetest.find_node_near(s, 1, {"group:igniter"}) | |
1774 | 2283 | end |
1775 | 2284 | |
1776 | 2285 | if lp then |
1777 | 2286 | |
1778 | -- if mob in water or lava then look for land | |
1779 | if (self.lava_damage | |
1780 | and minetest.registered_nodes[self.standing_in].groups.lava) | |
1781 | or (self.water_damage | |
1782 | and minetest.registered_nodes[self.standing_in].groups.water) then | |
1783 | ||
1784 | lp = minetest.find_node_near(s, 5, {"group:soil", "group:stone", | |
1785 | "group:sand", node_ice, node_snowblock}) | |
2287 | -- if mob in dangerous node then look for land | |
2288 | if not is_node_dangerous(self, self.standing_in) then | |
2289 | ||
2290 | lp = minetest.find_nodes_in_area_under_air( | |
2291 | {s.x - 5, s.y - 1, s.z - 5}, | |
2292 | {s.x + 5, s.y + 2, s.z + 5}, | |
2293 | {"group:soil", "group:stone", "group:sand", | |
2294 | node_ice, node_snowblock}) | |
2295 | ||
2296 | -- select position of random block to climb onto | |
2297 | lp = #lp > 0 and lp[random(#lp)] | |
1786 | 2298 | |
1787 | 2299 | -- did we find land? |
1788 | 2300 | if lp then |
1789 | 2301 | |
1790 | local vec = { | |
1791 | x = lp.x - s.x, | |
1792 | z = lp.z - s.z | |
1793 | } | |
1794 | ||
1795 | yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
1796 | ||
1797 | if lp.x > s.x then yaw = yaw + pi end | |
1798 | ||
1799 | -- look towards land and jump/move in that direction | |
1800 | yaw = set_yaw(self, yaw, 6) | |
1801 | do_jump(self) | |
1802 | set_velocity(self, self.walk_velocity) | |
2302 | yaw = yaw_to_pos(self, lp) | |
2303 | ||
2304 | self:do_jump() | |
2305 | self:set_velocity(self.walk_velocity) | |
1803 | 2306 | else |
1804 | 2307 | yaw = yaw + random(-0.5, 0.5) |
1805 | 2308 | end |
1806 | ||
1807 | else | |
1808 | ||
1809 | local vec = { | |
1810 | x = lp.x - s.x, | |
1811 | z = lp.z - s.z | |
1812 | } | |
1813 | ||
1814 | yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
1815 | ||
1816 | if lp.x > s.x then yaw = yaw + pi end | |
1817 | end | |
1818 | ||
1819 | yaw = set_yaw(self, yaw, 8) | |
2309 | end | |
2310 | ||
2311 | yaw = self:set_yaw(yaw, 8) | |
1820 | 2312 | |
1821 | 2313 | -- otherwise randomly turn |
1822 | elseif random(1, 100) <= 30 then | |
2314 | elseif self.randomly_turn and random(100) <= 30 then | |
1823 | 2315 | |
1824 | 2316 | yaw = yaw + random(-0.5, 0.5) |
1825 | 2317 | |
1826 | yaw = set_yaw(self, yaw, 8) | |
2318 | yaw = self:set_yaw(yaw, 8) | |
2319 | ||
2320 | -- for flying/swimming mobs randomly move up and down also | |
2321 | if self.fly_in | |
2322 | and not self.following then | |
2323 | self:attempt_flight_correction(true) | |
2324 | end | |
1827 | 2325 | end |
1828 | 2326 | |
1829 | 2327 | -- stand for great fall in front |
1830 | local temp_is_cliff = is_at_cliff(self) | |
1831 | ||
1832 | 2328 | if self.facing_fence == true |
1833 | or temp_is_cliff | |
1834 | or random(1, 100) <= 30 then | |
1835 | ||
1836 | set_velocity(self, 0) | |
1837 | self.state = "stand" | |
1838 | set_animation(self, "stand") | |
2329 | or self.at_cliff | |
2330 | or random(100) <= self.stand_chance then | |
2331 | ||
2332 | -- don't stand if mob flies and keep_flying set | |
2333 | if (self.fly and not self.keep_flying) | |
2334 | or not self.fly then | |
2335 | ||
2336 | self:set_velocity(0) | |
2337 | self.state = "stand" | |
2338 | self:set_animation("stand", true) | |
2339 | end | |
1839 | 2340 | else |
1840 | set_velocity(self, self.walk_velocity) | |
1841 | ||
1842 | if flight_check(self) | |
2341 | self:set_velocity(self.walk_velocity) | |
2342 | ||
2343 | if self:flight_check() | |
1843 | 2344 | and self.animation |
1844 | 2345 | and self.animation.fly_start |
1845 | 2346 | and self.animation.fly_end then |
1846 | set_animation(self, "fly") | |
2347 | self:set_animation("fly") | |
1847 | 2348 | else |
1848 | set_animation(self, "walk") | |
2349 | self:set_animation("walk") | |
1849 | 2350 | end |
1850 | 2351 | end |
1851 | 2352 | |
1856 | 2357 | |
1857 | 2358 | -- stop after 5 seconds or when at cliff |
1858 | 2359 | if self.runaway_timer > 5 |
1859 | or is_at_cliff(self) | |
2360 | or self.at_cliff | |
1860 | 2361 | or self.order == "stand" then |
1861 | 2362 | self.runaway_timer = 0 |
1862 | set_velocity(self, 0) | |
2363 | self:set_velocity(0) | |
1863 | 2364 | self.state = "stand" |
1864 | set_animation(self, "stand") | |
2365 | self:set_animation("stand") | |
1865 | 2366 | else |
1866 | set_velocity(self, self.run_velocity) | |
1867 | set_animation(self, "walk") | |
2367 | self:set_velocity(self.run_velocity) | |
2368 | self:set_animation("walk") | |
1868 | 2369 | end |
1869 | 2370 | |
1870 | 2371 | -- attack routines (explode, dogfight, shoot, dogshoot) |
1871 | 2372 | elseif self.state == "attack" then |
1872 | 2373 | |
1873 | -- calculate distance from mob and enemy | |
2374 | -- get mob and enemy positions and distance between | |
1874 | 2375 | local s = self.object:get_pos() |
1875 | local p = self.attack:get_pos() or s | |
1876 | local dist = get_distance(p, s) | |
1877 | ||
1878 | -- stop attacking if player invisible or out of range | |
2376 | local p = self.attack and self.attack:get_pos() | |
2377 | local dist = p and get_distance(p, s) or 500 | |
2378 | ||
2379 | -- stop attacking if player out of range or invisible | |
1879 | 2380 | if dist > self.view_range |
1880 | 2381 | or not self.attack |
1881 | 2382 | or not self.attack:get_pos() |
1882 | 2383 | or self.attack:get_hp() <= 0 |
1883 | or (self.attack:is_player() and mobs.invis[ self.attack:get_player_name() ]) then | |
1884 | ||
1885 | -- print(" ** stop attacking **", dist, self.view_range) | |
2384 | or (self.attack:is_player() | |
2385 | and is_invisible(self, self.attack:get_player_name())) then | |
2386 | ||
2387 | --print(" ** stop attacking **", dist, self.view_range) | |
2388 | ||
1886 | 2389 | self.state = "stand" |
1887 | set_velocity(self, 0) | |
1888 | set_animation(self, "stand") | |
2390 | self:set_velocity(0) | |
2391 | self:set_animation("stand") | |
1889 | 2392 | self.attack = nil |
1890 | 2393 | self.v_start = false |
1891 | 2394 | self.timer = 0 |
1897 | 2400 | |
1898 | 2401 | if self.attack_type == "explode" then |
1899 | 2402 | |
1900 | local vec = { | |
1901 | x = p.x - s.x, | |
1902 | z = p.z - s.z | |
1903 | } | |
1904 | ||
1905 | yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
1906 | ||
1907 | if p.x > s.x then yaw = yaw + pi end | |
1908 | ||
1909 | yaw = set_yaw(self, yaw) | |
2403 | yaw = yaw_to_pos(self, p) | |
1910 | 2404 | |
1911 | 2405 | local node_break_radius = self.explosion_radius or 1 |
1912 | 2406 | local entity_damage_radius = self.explosion_damage_radius |
1913 | 2407 | or (node_break_radius * 2) |
1914 | 2408 | |
2409 | -- look a little higher to fix raycast | |
2410 | s.y = s.y + 0.5 ; p.y = p.y + 0.5 | |
2411 | ||
1915 | 2412 | -- start timer when in reach and line of sight |
1916 | 2413 | if not self.v_start |
1917 | 2414 | and dist <= self.reach |
1918 | and line_of_sight(self, s, p, 2) then | |
2415 | and self:line_of_sight(s, p, 2) then | |
1919 | 2416 | |
1920 | 2417 | self.v_start = true |
1921 | 2418 | self.timer = 0 |
1922 | 2419 | self.blinktimer = 0 |
1923 | mob_sound(self, self.sounds.fuse) | |
1924 | -- print ("=== explosion timer started", self.explosion_timer) | |
2420 | self:mob_sound(self.sounds.fuse) | |
2421 | ||
2422 | --print("=== explosion timer started", self.explosion_timer) | |
1925 | 2423 | |
1926 | 2424 | -- stop timer if out of reach or direct line of sight |
1927 | 2425 | elseif self.allow_fuse_reset |
1928 | 2426 | and self.v_start |
1929 | and (dist > self.reach | |
1930 | or not line_of_sight(self, s, p, 2)) then | |
2427 | and (dist > self.reach or not self:line_of_sight(s, p, 2)) then | |
2428 | ||
2429 | --print("=== explosion timer stopped") | |
2430 | ||
1931 | 2431 | self.v_start = false |
1932 | 2432 | self.timer = 0 |
1933 | 2433 | self.blinktimer = 0 |
1934 | 2434 | self.blinkstatus = false |
1935 | self.object:settexturemod("") | |
2435 | self.object:set_texture_mod("") | |
1936 | 2436 | end |
1937 | 2437 | |
1938 | 2438 | -- walk right up to player unless the timer is active |
1939 | 2439 | if self.v_start and (self.stop_to_explode or dist < 1.5) then |
1940 | set_velocity(self, 0) | |
2440 | self:set_velocity(0) | |
1941 | 2441 | else |
1942 | set_velocity(self, self.run_velocity) | |
2442 | self:set_velocity(self.run_velocity) | |
1943 | 2443 | end |
1944 | 2444 | |
1945 | 2445 | if self.animation and self.animation.run_start then |
1946 | set_animation(self, "run") | |
2446 | self:set_animation("run") | |
1947 | 2447 | else |
1948 | set_animation(self, "walk") | |
2448 | self:set_animation("walk") | |
1949 | 2449 | end |
1950 | 2450 | |
1951 | 2451 | if self.v_start then |
1958 | 2458 | self.blinktimer = 0 |
1959 | 2459 | |
1960 | 2460 | if self.blinkstatus then |
1961 | self.object:settexturemod("") | |
2461 | ||
2462 | self.object:set_texture_mod(self.texture_mods) | |
1962 | 2463 | else |
1963 | self.object:settexturemod("^[brighten") | |
2464 | ||
2465 | self.object:set_texture_mod(self.texture_mods | |
2466 | .. "^[brighten") | |
1964 | 2467 | end |
1965 | 2468 | |
1966 | 2469 | self.blinkstatus = not self.blinkstatus |
1967 | 2470 | end |
1968 | 2471 | |
1969 | -- print ("=== explosion timer", self.timer) | |
2472 | --print("=== explosion timer", self.timer) | |
1970 | 2473 | |
1971 | 2474 | if self.timer > self.explosion_timer then |
1972 | 2475 | |
1979 | 2482 | node_break_radius = 1 |
1980 | 2483 | end |
1981 | 2484 | |
1982 | self.object:remove() | |
2485 | remove_mob(self, true) | |
1983 | 2486 | |
1984 | 2487 | if minetest.get_modpath("tnt") and tnt and tnt.boom |
1985 | 2488 | and not minetest.is_protected(pos, "") then |
1987 | 2490 | tnt.boom(pos, { |
1988 | 2491 | radius = node_break_radius, |
1989 | 2492 | damage_radius = entity_damage_radius, |
1990 | sound = self.sounds.explode, | |
2493 | sound = self.sounds.explode | |
1991 | 2494 | }) |
1992 | 2495 | else |
1993 | 2496 | |
1998 | 2501 | }) |
1999 | 2502 | |
2000 | 2503 | entity_physics(pos, entity_damage_radius) |
2001 | effect(pos, 32, "tnt_smoke.png", nil, nil, node_break_radius, 1, 0) | |
2504 | ||
2505 | effect(pos, 32, "tnt_smoke.png", nil, nil, | |
2506 | node_break_radius, 1, 0) | |
2002 | 2507 | end |
2003 | 2508 | |
2004 | return | |
2509 | return true | |
2005 | 2510 | end |
2006 | 2511 | end |
2007 | 2512 | |
2008 | 2513 | elseif self.attack_type == "dogfight" |
2009 | or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 2) | |
2010 | or (self.attack_type == "dogshoot" and dist <= self.reach and dogswitch(self) == 0) then | |
2514 | or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 2) | |
2515 | or (self.attack_type == "dogshoot" and dist <= self.reach | |
2516 | and self:dogswitch() == 0) then | |
2011 | 2517 | |
2012 | 2518 | if self.fly |
2013 | 2519 | and dist > self.reach then |
2018 | 2524 | local p_y = floor(p2.y + 1) |
2019 | 2525 | local v = self.object:get_velocity() |
2020 | 2526 | |
2021 | if flight_check(self, s) then | |
2527 | if self:flight_check() then | |
2022 | 2528 | |
2023 | 2529 | if me_y < p_y then |
2024 | 2530 | |
2054 | 2560 | }) |
2055 | 2561 | end |
2056 | 2562 | end |
2057 | ||
2058 | 2563 | end |
2059 | 2564 | |
2060 | 2565 | -- rnd: new movement direction |
2076 | 2581 | return |
2077 | 2582 | end |
2078 | 2583 | |
2079 | if abs(p1.x-s.x) + abs(p1.z - s.z) < 0.6 then | |
2584 | if abs(p1.x - s.x) + abs(p1.z - s.z) < 0.6 then | |
2080 | 2585 | -- reached waypoint, remove it from queue |
2081 | table.remove(self.path.way, 1) | |
2586 | table_remove(self.path.way, 1) | |
2082 | 2587 | end |
2083 | 2588 | |
2084 | 2589 | -- set new temporary target |
2085 | 2590 | p = {x = p1.x, y = p1.y, z = p1.z} |
2086 | 2591 | end |
2087 | 2592 | |
2088 | local vec = { | |
2089 | x = p.x - s.x, | |
2090 | z = p.z - s.z | |
2091 | } | |
2092 | ||
2093 | yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
2094 | ||
2095 | if p.x > s.x then yaw = yaw + pi end | |
2096 | ||
2097 | yaw = set_yaw(self, yaw) | |
2593 | yaw = yaw_to_pos(self, p) | |
2098 | 2594 | |
2099 | 2595 | -- move towards enemy if beyond mob reach |
2100 | 2596 | if dist > self.reach then |
2103 | 2599 | if self.pathfinding -- only if mob has pathfinding enabled |
2104 | 2600 | and enable_pathfinding then |
2105 | 2601 | |
2106 | smart_mobs(self, s, p, dist, dtime) | |
2602 | self:smart_mobs(s, p, dist, dtime) | |
2107 | 2603 | end |
2108 | 2604 | |
2109 | if is_at_cliff(self) then | |
2110 | ||
2111 | set_velocity(self, 0) | |
2112 | set_animation(self, "stand") | |
2605 | -- distance padding to stop spinning mob | |
2606 | local pad = abs(p.x - s.x) + abs(p.z - s.z) | |
2607 | ||
2608 | if self.at_cliff or pad < 0.2 then | |
2609 | ||
2610 | self:set_velocity(0) | |
2611 | self:set_animation("stand") | |
2113 | 2612 | else |
2114 | 2613 | |
2115 | 2614 | if self.path.stuck then |
2116 | set_velocity(self, self.walk_velocity) | |
2615 | self:set_velocity(self.walk_velocity) | |
2117 | 2616 | else |
2118 | set_velocity(self, self.run_velocity) | |
2617 | self:set_velocity(self.run_velocity) | |
2119 | 2618 | end |
2120 | 2619 | |
2121 | 2620 | if self.animation and self.animation.run_start then |
2122 | set_animation(self, "run") | |
2621 | self:set_animation("run") | |
2123 | 2622 | else |
2124 | set_animation(self, "walk") | |
2623 | self:set_animation("walk") | |
2125 | 2624 | end |
2126 | 2625 | end |
2127 | ||
2128 | 2626 | else -- rnd: if inside reach range |
2129 | 2627 | |
2130 | 2628 | self.path.stuck = false |
2131 | 2629 | self.path.stuck_timer = 0 |
2132 | 2630 | self.path.following = false -- not stuck anymore |
2133 | 2631 | |
2134 | set_velocity(self, 0) | |
2135 | ||
2136 | if not self.custom_attack then | |
2137 | ||
2138 | if self.timer > 1 then | |
2632 | self:set_velocity(0) | |
2633 | ||
2634 | if self.timer > 1 then | |
2635 | ||
2636 | -- no custom attack or custom attack returns true to continue | |
2637 | if not self.custom_attack | |
2638 | or self:custom_attack(self, p) == true then | |
2139 | 2639 | |
2140 | 2640 | self.timer = 0 |
2141 | ||
2142 | -- if self.double_melee_attack | |
2143 | -- and random(1, 2) == 1 then | |
2144 | -- set_animation(self, "punch2") | |
2145 | -- else | |
2146 | set_animation(self, "punch") | |
2147 | -- end | |
2641 | self:set_animation("punch") | |
2148 | 2642 | |
2149 | 2643 | local p2 = p |
2150 | 2644 | local s2 = s |
2152 | 2646 | p2.y = p2.y + .5 |
2153 | 2647 | s2.y = s2.y + .5 |
2154 | 2648 | |
2155 | if line_of_sight(self, p2, s2) == true then | |
2649 | if self:line_of_sight(p2, s2) == true then | |
2156 | 2650 | |
2157 | 2651 | -- play attack sound |
2158 | mob_sound(self, self.sounds.attack) | |
2652 | self:mob_sound(self.sounds.attack) | |
2159 | 2653 | |
2160 | 2654 | -- punch player (or what player is attached to) |
2161 | 2655 | local attached = self.attack:get_attach() |
2656 | ||
2162 | 2657 | if attached then |
2163 | 2658 | self.attack = attached |
2164 | 2659 | end |
2660 | ||
2661 | local dgroup = self.damage_group or "fleshy" | |
2662 | ||
2165 | 2663 | self.attack:punch(self.object, 1.0, { |
2166 | 2664 | full_punch_interval = 1.0, |
2167 | damage_groups = {fleshy = self.damage} | |
2665 | damage_groups = {[dgroup] = self.damage} | |
2168 | 2666 | }, nil) |
2169 | 2667 | end |
2170 | 2668 | end |
2171 | else -- call custom attack every second | |
2172 | if self.custom_attack | |
2173 | and self.timer > 1 then | |
2174 | ||
2175 | self.timer = 0 | |
2176 | ||
2177 | self.custom_attack(self, p) | |
2178 | end | |
2179 | 2669 | end |
2180 | 2670 | end |
2181 | 2671 | |
2182 | 2672 | elseif self.attack_type == "shoot" |
2183 | or (self.attack_type == "dogshoot" and dogswitch(self, dtime) == 1) | |
2184 | or (self.attack_type == "dogshoot" and dist > self.reach and dogswitch(self) == 0) then | |
2673 | or (self.attack_type == "dogshoot" and self:dogswitch(dtime) == 1) | |
2674 | or (self.attack_type == "dogshoot" and dist > self.reach and | |
2675 | self:dogswitch() == 0) then | |
2185 | 2676 | |
2186 | 2677 | p.y = p.y - .5 |
2187 | 2678 | s.y = s.y + .5 |
2188 | 2679 | |
2189 | local dist = get_distance(p, s) | |
2190 | local vec = { | |
2191 | x = p.x - s.x, | |
2192 | y = p.y - s.y, | |
2193 | z = p.z - s.z | |
2194 | } | |
2195 | ||
2196 | yaw = (atan(vec.z / vec.x) + pi / 2) - self.rotate | |
2197 | ||
2198 | if p.x > s.x then yaw = yaw + pi end | |
2199 | ||
2200 | yaw = set_yaw(self, yaw) | |
2201 | ||
2202 | set_velocity(self, 0) | |
2680 | local vec = {x = p.x - s.x, y = p.y - s.y, z = p.z - s.z} | |
2681 | ||
2682 | yaw = yaw_to_pos(self, p) | |
2683 | ||
2684 | self:set_velocity(0) | |
2203 | 2685 | |
2204 | 2686 | if self.shoot_interval |
2205 | 2687 | and self.timer > self.shoot_interval |
2206 | and random(1, 100) <= 60 then | |
2688 | and random(100) <= 60 then | |
2207 | 2689 | |
2208 | 2690 | self.timer = 0 |
2209 | set_animation(self, "shoot") | |
2691 | self:set_animation("shoot") | |
2210 | 2692 | |
2211 | 2693 | -- play shoot attack sound |
2212 | mob_sound(self, self.sounds.shoot_attack) | |
2694 | self:mob_sound(self.sounds.shoot_attack) | |
2213 | 2695 | |
2214 | 2696 | local p = self.object:get_pos() |
2215 | 2697 | |
2220 | 2702 | local obj = minetest.add_entity(p, self.arrow) |
2221 | 2703 | local ent = obj:get_luaentity() |
2222 | 2704 | local amount = (vec.x * vec.x + vec.y * vec.y + vec.z * vec.z) ^ 0.5 |
2705 | ||
2706 | -- check for custom override for arrow | |
2707 | if self.arrow_override then | |
2708 | self.arrow_override(ent) | |
2709 | end | |
2710 | ||
2223 | 2711 | local v = ent.velocity or 1 -- or set to default |
2224 | 2712 | |
2225 | 2713 | ent.switch = 1 |
2240 | 2728 | |
2241 | 2729 | |
2242 | 2730 | -- falling and fall damage |
2243 | local falling = function(self, pos) | |
2244 | ||
2245 | if self.fly then | |
2731 | function mob_class:falling(pos) | |
2732 | ||
2733 | if self.fly or self.disable_falling then | |
2246 | 2734 | return |
2247 | 2735 | end |
2248 | 2736 | |
2249 | 2737 | -- floating in water (or falling) |
2250 | 2738 | local v = self.object:get_velocity() |
2251 | 2739 | |
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 | |
2740 | -- sanity check | |
2741 | if not v then return end | |
2742 | ||
2743 | local fall_speed = self.fall_speed | |
2744 | ||
2745 | -- in water then use liquid viscosity for float/sink speed | |
2746 | if self.floats == 1 and self.standing_in | |
2747 | and minetest.registered_nodes[self.standing_in].groups.liquid then | |
2748 | ||
2749 | local visc = min( | |
2750 | minetest.registered_nodes[self.standing_in].liquid_viscosity, 7) + 1 | |
2751 | ||
2752 | self.object:set_velocity({x = v.x, y = 0.6, z = v.z}) | |
2753 | fall_speed = -1.2 / visc | |
2286 | 2754 | else |
2287 | 2755 | |
2288 | 2756 | -- fall damage onto solid ground |
2297 | 2765 | |
2298 | 2766 | effect(pos, 5, "tnt_smoke.png", 1, 2, 2, nil) |
2299 | 2767 | |
2300 | if check_for_death(self, {type = "fall"}) then | |
2301 | return | |
2768 | if self:check_for_death({type = "fall"}) then | |
2769 | return true | |
2302 | 2770 | end |
2303 | 2771 | end |
2304 | 2772 | |
2305 | 2773 | self.old_y = self.object:get_pos().y |
2306 | 2774 | end |
2307 | 2775 | end |
2776 | ||
2777 | -- fall at set speed | |
2778 | self.object:set_acceleration({x = 0, y = fall_speed, z = 0}) | |
2308 | 2779 | end |
2309 | 2780 | |
2310 | 2781 | |
2312 | 2783 | local tr = minetest.get_modpath("toolranks") |
2313 | 2784 | |
2314 | 2785 | -- deal damage and effects when mob punched |
2315 | local mob_punch = function(self, hitter, tflp, tool_capabilities, dir) | |
2786 | function mob_class:on_punch(hitter, tflp, tool_capabilities, dir, damage) | |
2316 | 2787 | |
2317 | 2788 | -- mob health check |
2318 | 2789 | if self.health <= 0 then |
2319 | return | |
2790 | return true | |
2320 | 2791 | end |
2321 | 2792 | |
2322 | 2793 | -- custom punch function |
2323 | 2794 | if self.do_punch |
2324 | and self.do_punch(self, hitter, tflp, tool_capabilities, dir) == false then | |
2325 | return | |
2795 | and self:do_punch(hitter, tflp, tool_capabilities, dir) == false then | |
2796 | return true | |
2326 | 2797 | end |
2327 | 2798 | |
2328 | 2799 | -- error checking when mod profiling is enabled |
2329 | 2800 | 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 | |
2801 | ||
2802 | minetest.log("warning", "[mobs] Mod profiling enabled, damage not enabled") | |
2803 | ||
2804 | return true | |
2805 | end | |
2806 | ||
2807 | -- is mob protected | |
2808 | if self.protected then | |
2809 | ||
2810 | -- did player hit mob and if so is it in protected area | |
2811 | if hitter:is_player() then | |
2812 | ||
2813 | local player_name = hitter:get_player_name() | |
2814 | ||
2815 | if player_name ~= self.owner | |
2816 | and minetest.is_protected(self.object:get_pos(), player_name) then | |
2817 | ||
2818 | minetest.chat_send_player(hitter:get_player_name(), | |
2819 | S("Mob has been protected!")) | |
2820 | ||
2821 | return true | |
2822 | end | |
2823 | ||
2824 | -- if protection is on level 2 then dont let arrows harm mobs | |
2825 | elseif self.protected == 2 then | |
2826 | ||
2827 | local ent = hitter and hitter:get_luaentity() | |
2828 | ||
2829 | if ent and ent._is_arrow then | |
2830 | ||
2831 | return true -- arrow entity | |
2832 | ||
2833 | elseif not ent then | |
2834 | ||
2835 | return true -- non entity | |
2836 | end | |
2837 | end | |
2339 | 2838 | end |
2340 | 2839 | |
2341 | 2840 | local weapon = hitter:get_wielded_item() |
2367 | 2866 | end |
2368 | 2867 | |
2369 | 2868 | damage = damage + (tool_capabilities.damage_groups[group] or 0) |
2370 | * tmp * ((armor[group] or 0) / 100.0) | |
2869 | * tmp * ((armor[group] or 0) / 100.0) | |
2371 | 2870 | end |
2372 | 2871 | end |
2373 | 2872 | |
2377 | 2876 | if self.immune_to[n][1] == weapon_def.name then |
2378 | 2877 | |
2379 | 2878 | damage = self.immune_to[n][2] or 0 |
2879 | ||
2380 | 2880 | break |
2381 | 2881 | |
2382 | -- if "all" then no tool does damage unless it's specified in list | |
2882 | -- if "all" then no tools deal damage unless it's specified in list | |
2383 | 2883 | elseif self.immune_to[n][1] == "all" then |
2384 | 2884 | damage = self.immune_to[n][2] or 0 |
2385 | 2885 | end |
2386 | 2886 | end |
2387 | 2887 | |
2888 | --print("Mob Damage is", damage) | |
2889 | ||
2388 | 2890 | -- healing |
2389 | 2891 | if damage <= -1 then |
2892 | ||
2390 | 2893 | self.health = self.health - floor(damage) |
2391 | return | |
2392 | end | |
2393 | ||
2394 | -- print ("Mob Damage is", damage) | |
2894 | ||
2895 | return true | |
2896 | end | |
2395 | 2897 | |
2396 | 2898 | if use_cmi |
2397 | 2899 | and cmi.notify_punch(self.object, hitter, tflp, tool_capabilities, dir, damage) then |
2398 | return | |
2900 | return true | |
2399 | 2901 | end |
2400 | 2902 | |
2401 | 2903 | -- add weapon wear |
2413 | 2915 | end |
2414 | 2916 | end |
2415 | 2917 | |
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 | |
2918 | if tr and weapon_def.original_description then | |
2919 | toolranks.new_afteruse(weapon, hitter, nil, {wear = wear}) | |
2420 | 2920 | else |
2421 | 2921 | weapon:add_wear(wear) |
2422 | 2922 | end |
2426 | 2926 | -- only play hit sound and show blood effects if damage is 1 or over |
2427 | 2927 | if damage >= 1 then |
2428 | 2928 | |
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 | |
2929 | -- select tool use sound if found, or fallback to default | |
2930 | local snd = weapon_def.sound and weapon_def.sound.use | |
2931 | or "default_punch" | |
2932 | ||
2933 | minetest.sound_play(snd, {object = self.object, max_hear_distance = 8}, true) | |
2444 | 2934 | |
2445 | 2935 | -- blood_particles |
2446 | 2936 | if not disable_blood and self.blood_amount > 0 then |
2447 | 2937 | |
2448 | 2938 | local pos = self.object:get_pos() |
2939 | local blood = self.blood_texture | |
2940 | local amount = self.blood_amount | |
2449 | 2941 | |
2450 | 2942 | pos.y = pos.y + (-self.collisionbox[2] + self.collisionbox[5]) * .5 |
2943 | ||
2944 | -- lots of damage = more blood :) | |
2945 | if damage > 10 then | |
2946 | amount = self.blood_amount * 2 | |
2947 | end | |
2451 | 2948 | |
2452 | 2949 | -- do we have a single blood texture or multiple? |
2453 | 2950 | 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 | |
2951 | blood = self.blood_texture[random(#self.blood_texture)] | |
2952 | end | |
2953 | ||
2954 | effect(pos, amount, blood, 1, 2, 1.75, nil, nil, true) | |
2461 | 2955 | end |
2462 | 2956 | |
2463 | 2957 | -- do damage |
2467 | 2961 | local hot = tool_capabilities and tool_capabilities.damage_groups |
2468 | 2962 | and tool_capabilities.damage_groups.fire |
2469 | 2963 | |
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 | ||
2964 | if self:check_for_death({type = "punch", puncher = hitter, hot = hot}) then | |
2965 | return true | |
2966 | end | |
2487 | 2967 | end -- END if damage |
2488 | 2968 | |
2489 | 2969 | -- knock back effect (only on full punch) |
2490 | if self.knock_back | |
2491 | and tflp >= punch_interval then | |
2970 | if self.knock_back and tflp >= punch_interval then | |
2492 | 2971 | |
2493 | 2972 | local v = self.object:get_velocity() |
2973 | ||
2974 | -- sanity check | |
2975 | if not v then return true end | |
2976 | ||
2494 | 2977 | local kb = damage or 1 |
2495 | 2978 | local up = 2 |
2496 | 2979 | |
2504 | 2987 | dir = dir or {x = 0, y = 0, z = 0} |
2505 | 2988 | |
2506 | 2989 | -- 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 | }) | |
2990 | kb = tool_capabilities.damage_groups["knockback"] or kb | |
2991 | ||
2992 | self.object:set_velocity({x = dir.x * kb, y = up, z = dir.z * kb}) | |
2514 | 2993 | |
2515 | 2994 | self.pause_timer = 0.25 |
2516 | 2995 | end |
2520 | 2999 | and self.order ~= "stand" then |
2521 | 3000 | |
2522 | 3001 | 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) | |
3002 | local yaw = yaw_to_pos(self, lp, 3) | |
3003 | ||
2537 | 3004 | self.state = "runaway" |
2538 | 3005 | self.runaway_timer = 0 |
2539 | 3006 | self.following = nil |
2547 | 3014 | and self.child == false |
2548 | 3015 | and self.attack_players == true |
2549 | 3016 | and hitter:get_player_name() ~= self.owner |
2550 | and not mobs.invis[ name ] then | |
3017 | and not is_invisible(self, name) | |
3018 | and self.object ~= hitter then | |
2551 | 3019 | |
2552 | 3020 | -- attack whoever punched mob |
2553 | 3021 | self.state = "" |
2554 | do_attack(self, hitter) | |
3022 | self:do_attack(hitter) | |
2555 | 3023 | |
2556 | 3024 | -- alert others to the attack |
2557 | local objs = minetest.get_objects_inside_radius(hitter:get_pos(), self.view_range) | |
2558 | local obj = nil | |
3025 | local objs = minetest.get_objects_inside_radius( | |
3026 | hitter:get_pos(), self.view_range) | |
3027 | local obj | |
2559 | 3028 | |
2560 | 3029 | for n = 1, #objs do |
2561 | 3030 | |
2563 | 3032 | |
2564 | 3033 | if obj and obj._cmi_is_mob then |
2565 | 3034 | |
2566 | -- only alert members of same mob | |
3035 | -- only alert members of same mob and assigned helper | |
2567 | 3036 | if obj.group_attack == true |
2568 | 3037 | and obj.state ~= "attack" |
2569 | 3038 | and obj.owner ~= name |
2570 | and obj.name == self.name then | |
2571 | do_attack(obj, hitter) | |
3039 | and (obj.name == self.name | |
3040 | or obj.name == self.group_helper) then | |
3041 | ||
3042 | obj:do_attack(hitter) | |
2572 | 3043 | end |
2573 | 3044 | |
2574 | 3045 | -- have owned mobs attack player threat |
2575 | 3046 | if obj.owner == name and obj.owner_loyal then |
2576 | do_attack(obj, self.object) | |
3047 | obj:do_attack(self.object) | |
2577 | 3048 | end |
2578 | 3049 | end |
2579 | 3050 | end |
2582 | 3053 | |
2583 | 3054 | |
2584 | 3055 | -- get entity staticdata |
2585 | local mob_staticdata = function(self) | |
3056 | function mob_class:mob_staticdata() | |
3057 | ||
3058 | -- this handles mob count for mobs activated, unloaded, reloaded | |
3059 | if active_limit > 0 and self.active_toggle then | |
3060 | active_mobs = active_mobs + self.active_toggle | |
3061 | self.active_toggle = -self.active_toggle | |
3062 | --print("-- staticdata", active_mobs, active_limit, self.active_toggle) | |
3063 | end | |
2586 | 3064 | |
2587 | 3065 | -- remove mob when out of range unless tamed |
2588 | 3066 | if remove_far |
2592 | 3070 | and not self.tamed |
2593 | 3071 | and self.lifetimer < 20000 then |
2594 | 3072 | |
2595 | --print ("REMOVED " .. self.name) | |
2596 | ||
2597 | self.object:remove() | |
2598 | ||
2599 | return ""-- nil | |
3073 | --print("REMOVED " .. self.name) | |
3074 | ||
3075 | remove_mob(self, true) | |
3076 | ||
3077 | return minetest.serialize({remove_ok = true, static_save = true}) | |
2600 | 3078 | end |
2601 | 3079 | |
2602 | 3080 | self.remove_ok = true |
2605 | 3083 | self.state = "stand" |
2606 | 3084 | |
2607 | 3085 | -- used to rotate older mobs |
2608 | if self.drawtype | |
2609 | and self.drawtype == "side" then | |
2610 | self.rotate = math.rad(90) | |
3086 | if self.drawtype and self.drawtype == "side" then | |
3087 | self.rotate = rad(90) | |
2611 | 3088 | end |
2612 | 3089 | |
2613 | 3090 | if use_cmi then |
2614 | self.serialized_cmi_components = cmi.serialize_components(self._cmi_components) | |
2615 | end | |
2616 | ||
2617 | local tmp = {} | |
3091 | self.serialized_cmi_components = cmi.serialize_components( | |
3092 | self._cmi_components) | |
3093 | end | |
3094 | ||
3095 | local tmp, t = {} | |
2618 | 3096 | |
2619 | 3097 | for _,stat in pairs(self) do |
2620 | 3098 | |
2621 | local t = type(stat) | |
3099 | t = type(stat) | |
2622 | 3100 | |
2623 | 3101 | if t ~= "function" |
2624 | 3102 | and t ~= "nil" |
2625 | 3103 | and t ~= "userdata" |
3104 | and _ ~= "object" | |
2626 | 3105 | and _ ~= "_cmi_components" then |
2627 | 3106 | tmp[_] = self[_] |
2628 | 3107 | end |
2629 | 3108 | end |
2630 | 3109 | |
2631 | --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n') | |
3110 | --print('===== '..self.name..'\n'.. dump(tmp)..'\n=====\n') | |
3111 | ||
2632 | 3112 | return minetest.serialize(tmp) |
2633 | 3113 | end |
2634 | 3114 | |
2635 | 3115 | |
2636 | 3116 | -- activate mob and reload settings |
2637 |