Update helptext of obsidian
[MineClone/MineClone2.git] / mods / ENTITIES / mobs_mc / enderman.lua
blob3f62bdb2a7f53933b81b9a7ce432f89a7416c5f7
1 --MCmobs v0.4
2 --maikerumine
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.
13 -- Rootyjr
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
25 -- added rain damage.
26 -- fixed the grass_with_dirt issue.
28 local S = minetest.get_translator("mobs_mc")
30 --###################
31 --################### ENDERMAN
32 --###################
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:
48 Flower, 90 degrees
49 Flower, 45 degrees
50 Held block, backside
51 Held block, bottom
52 Held block, front
53 Held block, left
54 Held block, right
55 Held block, top
56 Enderman texture (base)
58 -- Regular cube
59 if block_type == "cube" then
60 local tiles = minetest.registered_nodes[itemstring].tiles
61 local textures = {}
62 local last
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]
66 else
67 -- Extract the texture names
68 for i = 1, 6 do
69 if type(tiles[i]) == "string" then
70 last = tiles[i]
71 elseif type(tiles[i]) == "table" then
72 if tiles[i].name then
73 last = tiles[i].name
74 end
75 end
76 table.insert(textures, last)
77 end
78 end
79 return {
80 "blank.png",
81 "blank.png",
82 textures[5],
83 textures[2],
84 textures[6],
85 textures[3],
86 textures[4],
87 textures[1],
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
93 return {
94 "blank.png",
95 textures[1],
96 "blank.png",
97 "blank.png",
98 "blank.png",
99 "blank.png",
100 "blank.png",
101 "blank.png",
102 base,
104 -- Node of plantlike drawtype, 90°
105 elseif block_type == "plantlike90" then
106 local textures = minetest.registered_nodes[itemstring].tiles
107 return {
108 textures[1],
109 "blank.png",
110 "blank.png",
111 "blank.png",
112 "blank.png",
113 "blank.png",
114 "blank.png",
115 "blank.png",
116 base,
118 elseif block_type == "unknown" then
119 return {
120 "blank.png",
121 "blank.png",
122 "unknown_node.png",
123 "unknown_node.png",
124 "unknown_node.png",
125 "unknown_node.png",
126 "unknown_node.png",
127 "unknown_node.png",
128 base, -- Enderman texture
130 -- No block held (for initial texture)
131 elseif block_type == "nothing" or block_type == nil then
132 return {
133 "blank.png",
134 "blank.png",
135 "blank.png",
136 "blank.png",
137 "blank.png",
138 "blank.png",
139 "blank.png",
140 "blank.png",
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
150 return {
151 walk_speed = 25,
152 run_speed = 50,
153 stand_speed = 25,
154 stand_start = 200,
155 stand_end = 200,
156 walk_start = 161,
157 walk_end = 200,
158 run_start = 161,
159 run_end = 200,
160 punch_start = 121,
161 punch_end = 160,
163 -- Enderman doesn't hold a block
164 elseif animation_type == "normal" or animation_type == nil then
165 return {
166 walk_speed = 25,
167 run_speed = 50,
168 stand_speed = 25,
169 stand_start = 40,
170 stand_end = 80,
171 walk_start = 0,
172 walk_end = 40,
173 run_start = 0,
174 run_end = 40,
175 punch_start = 81,
176 punch_end = 120,
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
185 type = "monster",
186 spawn_class = "passive",
187 passive = true,
188 pathfinding = 1,
189 hp_min = 40,
190 hp_max = 40,
191 collisionbox = {-0.3, -0.01, -0.3, 0.3, 2.89, 0.3},
192 visual = "mesh",
193 mesh = "mobs_mc_enderman.b3d",
194 textures = create_enderman_textures(),
195 visual_size = {x=3, y=3},
196 makes_footstep_sound = true,
197 sounds = {
198 war_cry = "mobs_sandmonster",
199 death = "green_slime_death",
200 -- TODO: damage, random
201 distance = 16,
203 walk_velocity = 0.2,
204 run_velocity = 3.4,
205 damage = 7,
206 reach = 2,
207 drops = {
208 {name = mobs_mc.items.ender_pearl,
209 chance = 1,
210 min = 0,
211 max = 1,},
213 animation = select_enderman_animation("normal"),
214 _taken_node = "",
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,
228 vertical = false,
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
234 local damage = true
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
246 damage = false
247 break
252 if damage == true then
253 self.state = ""
254 --rain hurts enderman
255 self.object:punch(self.object, 1.0, {
256 full_punch_interval=1.0,
257 damage_groups={fleshy=self._damage},
258 }, nil)
259 --randomly teleport hopefully under something.
260 self:teleport(nil)
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
266 self:teleport(nil)
267 self.state = ""
268 else
269 if self.attack then
270 local target = self.attack
271 local pos = target:get_pos()
272 if pos ~= nil then
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)
284 for n = 1, #objs do
285 local obj = objs[n]
286 if obj then
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
290 self:teleport(nil)
292 else
293 local lua = obj:get_luaentity()
294 if lua then
295 if lua.name == "mcl_bows:arrow_entity" then
296 self:teleport(nil)
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
307 self:teleport(nil)
308 self.state = ""
309 else
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)
317 local obj
318 for n = 1, #objs do
319 obj = objs[n]
320 if obj then
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.
328 for n = 1,64,.25 do
329 local node = minetest.get_node(look_pos)
330 if node.name ~= "air" then
331 break
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())
336 break
337 else
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
351 return
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)
357 return
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
361 -- Take random node
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)
373 if dug then
374 if mobs_mc.enderman_replace_on_take[node.name] then
375 self._taken_node = mobs_mc.enderman_replace_on_take[node.name]
376 else
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)
381 local block_type
382 -- Cube-shaped
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
393 block_type = "cube"
394 elseif def.drawtype == "plantlike" then
395 -- Flowers and stuff
396 block_type = "plantlike45"
397 elseif def.drawtype == "airlike" then
398 -- Just air
399 block_type = nil
400 else
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
415 -- Place taken node
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})
426 if success then
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 = ""
438 end,
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"})
444 local telepos
445 if nodes ~= nil then
446 if #nodes > 0 then
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]
451 local node_ok = true
452 -- Selected node needs to have 3 nodes of free space above
453 for u=1, 3 do
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
456 node_ok = false
457 break
460 if node_ok then
461 telepos = {x=nodepos.x, y=nodepos.y+1, z=nodepos.z}
464 if telepos then
465 self.object:set_pos(telepos)
469 else
470 -- Attempt to randomly teleport enderman
471 local pos = self.object:get_pos()
472 -- Up to 8 top-level attempts to teleport
473 for n=1, 8 do
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"})
478 if nodes ~= nil then
479 if #nodes > 0 then
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]
484 node_ok = true
485 for u=1, 3 do
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
488 node_ok = false
489 break
492 if node_ok then
493 self.object:set_pos({x=nodepos.x, y=nodepos.y+1, z=nodepos.z})
494 break
499 if node_ok then
500 break
504 end,
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)
511 end,
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
516 self:teleport(nil)
517 else
518 self:teleport(hitter)
519 self.attack=hitter
520 self.state="attack"
523 end,
524 armor = { fleshy = 100, water_vulnerable = 100 },
525 water_damage = 8,
526 view_range = 64,
527 fear_height = 4,
528 attack_type = "dogfight",
532 -- End spawn
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)
534 -- Overworld spawn
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)
539 -- spawn eggs
540 mobs:register_egg("mobs_mc:enderman", S("Enderman"), "mobs_mc_spawn_icon_enderman.png", 0)