3 --made for MC like Survival game
4 --License for code WTFPL and otherwise stated in readmes
6 -- ENDERMAN BEHAVIOUR (OLD):
7 -- In this game, endermen attack the player on sight, like other monsters do.
8 -- However, they have a reduced viewing range to make them less dangerous.
9 -- This differs from MC, in which endermen only become hostile when provoked,
10 -- and they are provoked by looking directly at them.
11 -- TODO: Implement MC behaviour.
14 -----------------------------
15 -- implemented ability to detect when seen / break eye contact and aggressive response
16 -- implemented teleport to avoid arrows.
17 -- implemented teleport to avoid rain.
18 -- implemented teleport to chase.
19 -- added enderman particles.
20 -- drew mcl_portal_particle1.png
21 -- drew mcl_portal_particle2.png
22 -- drew mcl_portal_particle3.png
23 -- drew mcl_portal_particle4.png
24 -- drew mcl_portal_particle5.png
26 -- fixed the grass_with_dirt issue.
28 local S
= minetest
.get_translator("mobs_mc")
31 --################### ENDERMAN
34 local pr
= PseudoRandom(os
.time()*(-334))
36 -- How freqeuntly to take and place blocks, in seconds
37 local take_frequency_min
= 25
38 local take_frequency_max
= 90
39 local place_frequency_min
= 10
40 local place_frequency_max
= 30
42 -- Create the textures table for the enderman, depending on which kind of block
43 -- the enderman holds (if any).
44 local create_enderman_textures
= function(block_type
, itemstring
)
45 local base
= "mobs_mc_enderman.png^mobs_mc_enderman_eyes.png"
47 --[[ Order of the textures in the texture table:
56 Enderman texture (base)
59 if block_type
== "cube" then
60 local tiles
= minetest
.registered_nodes
[itemstring
].tiles
63 if mobs_mc
.enderman_block_texture_overrides
[itemstring
] then
64 -- Texture override available? Use these instead!
65 textures
= mobs_mc
.enderman_block_texture_overrides
[itemstring
]
67 -- Extract the texture names
69 if type(tiles
[i
]) == "string" then
71 elseif type(tiles
[i
]) == "table" then
76 table.insert(textures
, last
)
88 base
, -- Enderman texture
90 -- Node of plantlike drawtype, 45° (recommended)
91 elseif block_type
== "plantlike45" then
92 local textures
= minetest
.registered_nodes
[itemstring
].tiles
104 -- Node of plantlike drawtype, 90°
105 elseif block_type
== "plantlike90" then
106 local textures
= minetest
.registered_nodes
[itemstring
].tiles
118 elseif block_type
== "unknown" then
128 base
, -- Enderman texture
130 -- No block held (for initial texture)
131 elseif block_type
== "nothing" or block_type
== nil then
141 base
, -- Enderman texture
146 -- Select a new animation definition.
147 local select_enderman_animation
= function(animation_type
)
148 -- Enderman holds a block
149 if animation_type
== "block" then
163 -- Enderman doesn't hold a block
164 elseif animation_type
== "normal" or animation_type
== nil then
181 local mobs_griefing
= minetest
.settings
:get_bool("mobs_griefing") ~= false
183 mobs
:register_mob("mobs_mc:enderman", {
184 -- TODO: Endermen should be classified as passive
186 spawn_class
= "passive",
191 collisionbox
= {-0.3, -0.01, -0.3, 0.3, 2.89, 0.3},
193 mesh
= "mobs_mc_enderman.b3d",
194 textures
= create_enderman_textures(),
195 visual_size
= {x
=3, y
=3},
196 makes_footstep_sound
= true,
198 war_cry
= "mobs_sandmonster",
199 death
= "green_slime_death",
200 -- TODO: damage, random
208 {name
= mobs_mc
.items
.ender_pearl
,
213 animation
= select_enderman_animation("normal"),
215 -- TODO: Teleport enderman on damage, etc.
216 do_custom
= function(self
, dtime
)
217 -- PARTICLE BEHAVIOUR HERE.
218 local enderpos
= self
.object
:get_pos()
219 local chanceOfParticle
= math
.random(0, 1)
220 if chanceOfParticle
== 1 then
221 minetest
.add_particle({
222 pos
= {x
=enderpos
.x
+math
.random(-1,1)*math
.random()/2,y
=enderpos
.y
+math
.random(0,3),z
=enderpos
.z
+math
.random(-1,1)*math
.random()/2},
223 velocity
= {x
=math
.random(-.25,.25), y
=math
.random(-.25,.25), z
=math
.random(-.25,.25)},
224 acceleration
= {x
=math
.random(-.5,.5), y
=math
.random(-.5,.5), z
=math
.random(-.5,.5)},
225 expirationtime
= math
.random(),
226 size
= math
.random(),
227 collisiondetection
= true,
229 texture
= "mcl_portals_particle"..math
.random(1, 5)..".png",
232 -- RAIN DAMAGE / EVASIVE WARP BEHAVIOUR HERE.
233 if mcl_weather
.state
== "rain" or mcl_weather
.state
== "lightning" then
235 local enderpos
= self
.object
:get_pos()
236 enderpos
.y
= enderpos
.y
+2.89
237 local height
= {x
=enderpos
.x
, y
=enderpos
.y
+512,z
=enderpos
.z
}
238 local ray
= minetest
.raycast(enderpos
, height
, true)
239 -- Check for blocks above enderman.
240 for pointed_thing
in ray
do
241 if pointed_thing
.type == "node" then
242 local nn
= minetest
.get_node(minetest
.get_pointed_thing_position(pointed_thing
)).name
243 local def
= minetest
.registered_nodes
[nn
]
244 if (not def
) or def
.walkable
then
245 -- There's a node in the way. Delete arrow without damage
252 if damage
== true then
254 --rain hurts enderman
255 self
.object
:punch(self
.object
, 1.0, {
256 full_punch_interval
=1.0,
257 damage_groups
={fleshy
=self
._damage
},
259 --randomly teleport hopefully under something.
263 -- AGRESSIVELY WARP/CHASE PLAYER BEHAVIOUR HERE.
264 if self
.state
== "attack" then
265 if (minetest
.get_timeofday() * 24000) > 5001 and (minetest
.get_timeofday() * 24000) < 19000 then
270 local target
= self
.attack
271 local pos
= target
:get_pos()
273 if vector
.distance(self
.object
:get_pos(), target
:get_pos()) > 10 then
274 self
:teleport(target
)
280 -- ARROW / DAYTIME PEOPLE AVOIDANCE BEHAVIOUR HERE.
281 -- Check for arrows and people nearby.
282 local enderpos
= self
.object
:get_pos()
283 local objs
= minetest
.get_objects_inside_radius(enderpos
, 4)
287 if minetest
.is_player(obj
) then
288 -- Warp from players during day.
289 if (minetest
.get_timeofday() * 24000) > 5001 and (minetest
.get_timeofday() * 24000) < 19000 then
293 local lua
= obj
:get_luaentity()
295 if lua
.name
== "mcl_bows:arrow_entity" then
302 -- PROVOKED BEHAVIOUR HERE.
303 local enderpos
= self
.object
:get_pos()
304 if self
.provoked
== "broke_contact" then
305 self
.provoked
= "false"
306 if (minetest
.get_timeofday() * 24000) > 5001 and (minetest
.get_timeofday() * 24000) < 19000 then
310 if self
.attack
~= nil then
311 self
.state
= 'attack'
315 -- Check to see if people are near by enough to look at us.
316 local objs
= minetest
.get_objects_inside_radius(enderpos
, 64)
321 if minetest
.is_player(obj
) then
322 -- Check if they are looking at us.
323 local player_pos
= obj
:get_pos()
324 local look_dir_not_normalized
= obj
:get_look_dir()
325 local look_dir
= vector
.normalize(look_dir_not_normalized
)
326 local look_pos
= vector
.new({x
= look_dir
.x
+player_pos
.x
, y
= look_dir
.y
+player_pos
.y
+ 1.5, z
= look_dir
.z
+player_pos
.z
}) -- Arbitrary value (1.5) is head level according to player info mod.
327 -- Cast up to 64 to see if player is looking at enderman.
329 local node
= minetest
.get_node(look_pos
)
330 if node
.name
~= "air" then
333 if look_pos
.x
-1<enderpos
.x
and look_pos
.x
+1>enderpos
.x
and look_pos
.y
-2.89<enderpos
.y
and look_pos
.y
-2>enderpos
.y
and look_pos
.z
-1<enderpos
.z
and look_pos
.z
+1>enderpos
.z
then
334 self
.provoked
= "staring"
335 self
.attack
= minetest
.get_player_by_name(obj
:get_player_name())
338 if self
.provoked
== "staring" then
339 self
.provoked
= "broke_contact"
342 look_pos
.x
= look_pos
.x
+ (.25 * look_dir
.x
)
343 look_pos
.y
= look_pos
.y
+ (.25 * look_dir
.y
)
344 look_pos
.z
= look_pos
.z
+ (.25 * look_dir
.z
)
349 -- TAKE AND PLACE STUFF BEHAVIOUR BELOW.
350 if not mobs_griefing
then
353 -- Take and put nodes
354 if not self
._take_place_timer
or not self
._next_take_place_time
then
355 self
._take_place_timer
= 0
356 self
._next_take_place_time
= math
.random(take_frequency_min
, take_frequency_max
)
359 self
._take_place_timer
= self
._take_place_timer
+ dtime
360 if (self
._taken_node
== nil or self
._taken_node
== "") and self
._take_place_timer
>= self
._next_take_place_time
then
362 self
._take_place_timer
= 0
363 self
._next_take_place_time
= math
.random(place_frequency_min
, place_frequency_max
)
364 local pos
= self
.object
:get_pos()
365 local takable_nodes
= minetest
.find_nodes_in_area({x
=pos
.x
-2, y
=pos
.y
-1, z
=pos
.z
-2}, {x
=pos
.x
+2, y
=pos
.y
+1, z
=pos
.z
+2}, mobs_mc
.enderman_takable
)
366 if #takable_nodes
>= 1 then
367 local r
= pr
:next(1, #takable_nodes
)
368 local take_pos
= takable_nodes
[r
]
369 local node
= minetest
.get_node(take_pos
)
370 -- Don't destroy protected stuff.
371 if not minetest
.is_protected(take_pos
, "") then
372 local dug
= minetest
.dig_node(take_pos
)
374 if mobs_mc
.enderman_replace_on_take
[node
.name
] then
375 self
._taken_node
= mobs_mc
.enderman_replace_on_take
[node
.name
]
377 self
._taken_node
= node
.name
379 local def
= minetest
.registered_nodes
[self
._taken_node
]
380 -- Update animation and texture accordingly (adds visibly carried block)
383 if def
.drawtype
== "normal" or
384 def
.drawtype
== "nodebox" or
385 def
.drawtype
== "liquid" or
386 def
.drawtype
== "flowingliquid" or
387 def
.drawtype
== "glasslike" or
388 def
.drawtype
== "glasslike_framed" or
389 def
.drawtype
== "glasslike_framed_optional" or
390 def
.drawtype
== "allfaces" or
391 def
.drawtype
== "allfaces_optional" or
392 def
.drawtype
== nil then
394 elseif def
.drawtype
== "plantlike" then
396 block_type
= "plantlike45"
397 elseif def
.drawtype
== "airlike" then
401 -- Fallback for complex drawtypes
402 block_type
= "unknown"
404 self
.base_texture
= create_enderman_textures(block_type
, self
._taken_node
)
405 self
.object
:set_properties({ textures
= self
.base_texture
})
406 self
.animation
= select_enderman_animation("block")
407 mobs
:set_animation(self
, self
.animation
.current
)
408 if def
.sounds
and def
.sounds
.dug
then
409 minetest
.sound_play(def
.sounds
.dug
, {pos
= take_pos
, max_hear_distance
= 16}, true)
414 elseif self
._taken_node
~= nil and self
._taken_node
~= "" and self
._take_place_timer
>= self
._next_take_place_time
then
416 self
._take_place_timer
= 0
417 self
._next_take_place_time
= math
.random(take_frequency_min
, take_frequency_max
)
418 local pos
= self
.object
:get_pos()
419 local yaw
= self
.object
:get_yaw()
420 -- Place node at looking direction
421 local place_pos
= vector
.subtract(pos
, minetest
.facedir_to_dir(minetest
.dir_to_facedir(minetest
.yaw_to_dir(yaw
))))
422 -- Also check to see if protected.
423 if minetest
.get_node(place_pos
).name
== "air" and not minetest
.is_protected(place_pos
, "") then
424 -- ... but only if there's a free space
425 local success
= minetest
.place_node(place_pos
, {name
= self
._taken_node
})
427 local def
= minetest
.registered_nodes
[self
._taken_node
]
428 -- Update animation accordingly (removes visible block)
429 self
.animation
= select_enderman_animation("normal")
430 mobs
:set_animation(self
, self
.animation
.current
)
431 if def
.sounds
and def
.sounds
.place
then
432 minetest
.sound_play(def
.sounds
.place
, {pos
= place_pos
, max_hear_distance
= 16}, true)
434 self
._taken_node
= ""
439 do_teleport
= function(self
, target
)
440 if target
~= nil then
441 local target_pos
= target
:get_pos()
442 -- Find all solid nodes below air in a 10×10×10 cuboid centered on the target
443 local nodes
= minetest
.find_nodes_in_area_under_air(vector
.subtract(target_pos
, 5), vector
.add(target_pos
, 5), {"group:solid", "group:cracky", "group:crumbly"})
447 -- Up to 64 attempts to teleport
448 for n
=1, math
.min(64, #nodes
) do
449 local r
= pr
:next(1, #nodes
)
450 local nodepos
= nodes
[r
]
452 -- Selected node needs to have 3 nodes of free space above
454 local node
= minetest
.get_node({x
=nodepos
.x
, y
=nodepos
.y
+u
, z
=nodepos
.z
})
455 if minetest
.registered_nodes
[node
.name
].walkable
then
461 telepos
= {x
=nodepos
.x
, y
=nodepos
.y
+1, z
=nodepos
.z
}
465 self
.object
:set_pos(telepos
)
470 -- Attempt to randomly teleport enderman
471 local pos
= self
.object
:get_pos()
472 -- Up to 8 top-level attempts to teleport
474 local node_ok
= false
475 -- We need to add (or subtract) different random numbers to each vector component, so it couldn't be done with a nice single vector.add() or .subtract():
476 local randomCube
= vector
.new( pos
.x
+ 8*(pr
:next(0,16)-8), pos
.y
+ 8*(pr
:next(0,16)-8), pos
.z
+ 8*(pr
:next(0,16)-8) )
477 local nodes
= minetest
.find_nodes_in_area_under_air(vector
.subtract(randomCube
, 4), vector
.add(randomCube
, 4), {"group:solid", "group:cracky", "group:crumbly"})
480 -- Up to 8 low-level (in total up to 8*8 = 64) attempts to teleport
481 for n
=1, math
.min(8, #nodes
) do
482 local r
= pr
:next(1, #nodes
)
483 local nodepos
= nodes
[r
]
486 local node
= minetest
.get_node({x
=nodepos
.x
, y
=nodepos
.y
+u
, z
=nodepos
.z
})
487 if minetest
.registered_nodes
[node
.name
].walkable
then
493 self
.object
:set_pos({x
=nodepos
.x
, y
=nodepos
.y
+1, z
=nodepos
.z
})
505 on_die
= function(self
, pos
)
506 -- Drop carried node on death
507 if self
._taken_node
~= nil and self
._taken_node
~= "" then
508 minetest
.add_item(pos
, self
._taken_node
)
510 mobs
.death_effect(pos
, self
.collisionbox
)
512 do_punch
= function(self
, hitter
, tflp
, tool_caps
, dir
)
513 -- damage from rain caused by itself so we don't want it to attack itself.
514 if hitter
~= self
.object
and hitter
~= nil then
515 if (minetest
.get_timeofday() * 24000) > 5001 and (minetest
.get_timeofday() * 24000) < 19000 then
518 self
:teleport(hitter
)
524 armor
= { fleshy
= 100, water_vulnerable
= 100 },
528 attack_type
= "dogfight",
533 mobs
:spawn_specific("mobs_mc:enderman", mobs_mc
.spawn
.solid
, {"air"}, 0, minetest
.LIGHT_MAX
+1, 30, 3000, 12, mobs_mc
.spawn_height
.end_min
, mobs_mc
.spawn_height
.end_max
)
535 mobs
:spawn_specific("mobs_mc:enderman", mobs_mc
.spawn
.solid
, {"air"}, 0, 7, 30, 19000, 2, mobs_mc
.spawn_height
.overworld_min
, mobs_mc
.spawn_height
.overworld_max
)
536 -- Nether spawn (rare)
537 mobs
:spawn_specific("mobs_mc:enderman", mobs_mc
.spawn
.solid
, {"air"}, 0, 7, 30, 27500, 4, mobs_mc
.spawn_height
.nether_min
, mobs_mc
.spawn_height
.nether_max
)
540 mobs
:register_egg("mobs_mc:enderman", S("Enderman"), "mobs_mc_spawn_icon_enderman.png", 0)