1 local S
= minetest
.get_translator("tsm_pyramids")
3 local mod_cmi
= minetest
.get_modpath("cmi") ~= nil
5 local mummy_walk_limit
= 1
6 local mummy_chillaxin_speed
= 1
7 local mummy_animation_speed
= 10
8 -- Note: This is currently broken due to a bug in Irrlicht, leave at 0
9 local mummy_animation_blend
= 0
11 -- Default player appearance
12 local mummy_mesh
= "tsm_pyramids_mummy.x"
13 local mummy_texture
= {"tsm_pyramids_mummy.png"}
15 local mummy_drop
= "default:papyrus"
17 local spawner_entity_offset
= -0.28
19 local sound_normal
= "mummy"
20 local sound_hit
= "mummy_hurt"
21 local sound_dead
= "mummy_death"
23 local spawner_check_range
= 17
24 local spawner_max_mobs
= 6
26 local function get_animations()
50 local ANIM_WALK_MINE
= 5
53 local function hit(self
)
54 self
.object
:set_texture_mod("^tsm_pyramids_hit.png")
55 minetest
.after(0.4, function(self
)
56 local prop
= {textures
= mummy_texture
,}
57 if self
~= nil and self
.object
~= nil then
58 self
.object
:set_texture_mod("")
63 local function mummy_update_visuals_def(self
)
64 npc_anim
= 0 -- Animation will be set further below immediately
66 textures
= mummy_texture
,
68 self
.object
:set_properties(prop
)
74 collisionbox
= {-0.4, -0.01, -0.4, 0.4, 1.9, 0.4},
76 visual_size
= {x
=8,y
=8},
78 textures
= mummy_texture
,
79 makes_footstep_sound
= true,
95 -- Track last cause of damage for cmi.notify_die
96 last_damage_cause
= { type = "unknown" },
98 description
= S("Mummy"),
101 -- Returns true if a mummy spawner entity was found at pos.
102 -- If self is provided, this object does not count.
103 local function check_if_mummy_spawner_entity_exists(pos
, self
)
104 local ents
= minetest
.get_objects_inside_radius(pos
, 0.5)
106 if (not self
) or (ents
[e
] ~= ents
[e
]) then
107 local lua
= ents
[e
]:get_luaentity()
109 if lua
.name
== "tsm_pyramids:mummy_spawner" then
119 local spawner_DEF
= {
124 visual_size
= {x
=3.3,y
=3.3},
126 textures
= mummy_texture
,
127 makes_footstep_sound
= false,
129 automatic_rotate
= math
.pi
* 2.9,
132 spawner_DEF
.on_activate
= function(self
)
133 local pos
= self
.object
:get_pos()
134 local spos
= vector
.new(pos
.x
, pos
.y
+ spawner_entity_offset
, pos
.z
)
135 if check_if_mummy_spawner_entity_exists(spos
, self
) then
136 -- Remove possible duplicate entity
140 mummy_update_visuals_def(self
)
141 self
.object
:set_velocity({x
=0, y
=0, z
=0})
142 self
.object
:set_acceleration({x
=0, y
=0, z
=0})
143 self
.object
:set_armor_groups({immortal
=1})
147 -- Regularily check if entity is still inside spawner
148 spawner_DEF
.on_step
= function(self
, dtime
)
149 self
.timer
= self
.timer
+ dtime
150 local pos
= self
.object
:get_pos()
151 pos
.y
= pos
.y
- spawner_entity_offset
152 local n
= minetest
.get_node_or_nil(pos
)
153 if self
.timer
> 50 then
154 if n
and n
.name
and n
.name
~= "tsm_pyramids:spawner_mummy" then
162 spawner_DEF
.on_punch
= function(self
, hitter
)
166 MUMMY_DEF
.on_activate
= function(self
, staticdata
, dtime_s
)
168 cmi
.notify_activate(self
, dtime_s
)
170 mummy_update_visuals_def(self
)
171 self
.anim
= get_animations()
172 self
.object
:set_animation({x
=self
.anim
.stand_START
,y
=self
.anim
.stand_END
}, mummy_animation_speed
, mummy_animation_blend
)
173 self
.npc_anim
= ANIM_STAND
174 self
.object
:set_acceleration({x
=0,y
=-20,z
=0})--20
176 self
.object
:set_armor_groups({fleshy
=130})
179 MUMMY_DEF
.on_punch
= function(self
, puncher
, time_from_last_punch
, tool_capabilities
, dir
, damage
)
181 cmi
.notify_punch(self
, puncher
, time_from_last_punch
, tool_capabilities
, dir
, damage
)
183 self
.attacker
= puncher
185 if damage
and damage
> 0 then
191 if puncher
~= nil then
192 minetest
.sound_play(sound_hit
, {pos
= self
.object
:get_pos(), loop
= false, max_hear_distance
= 10, gain
= 0.4}, true)
193 if time_from_last_punch
>= 0.45 then
195 self
.direction
= {x
=self
.object
:get_velocity().x
, y
=self
.object
:get_velocity().y
, z
=self
.object
:get_velocity().z
}
197 self
.object
:set_velocity({x
=dir
.x
*mummy_chillaxin_speed
,y
=5,z
=dir
.z
*mummy_chillaxin_speed
})
198 if self
.state
== 1 then
200 elseif self
.state
>= 2 then
207 MUMMY_DEF
.on_death
= function(self
, killer
)
208 minetest
.sound_play(sound_dead
, {pos
= self
.object
:get_pos(), max_hear_distance
= 10 , gain
= 0.3}, true)
209 -- Drop item on death
210 local count
= math
.random(0,3)
212 local pos
= self
.object
:get_pos()
214 minetest
.add_item(pos
, mummy_drop
.. " " .. count
)
217 cmi
.notify_die(self
, self
.last_damage
)
221 MUMMY_DEF
.on_step
= function(self
, dtime
)
223 cmi
.notify_step(self
, dtime
)
225 self
.timer
= self
.timer
+ 0.01
226 self
.turn_timer
= self
.turn_timer
+ 0.01
227 self
.jump_timer
= self
.jump_timer
+ 0.01
228 self
.punch_timer
= self
.punch_timer
+ 0.01
229 self
.attacking_timer
= self
.attacking_timer
+ 0.01
230 self
.sound_timer
= self
.sound_timer
+ dtime
+ 0.01
232 local current_pos
= self
.object
:get_pos()
233 local current_node
= minetest
.get_node(current_pos
)
234 if self
.time_passed
== nil then
238 -- Environment damage
239 local def
= minetest
.registered_nodes
[current_node
.name
]
240 local dps
= def
.damage_per_second
242 local dmg_node
, dmg_pos
243 if dps
~= nil and dps
> 0 then
247 if dmg
< 4 and (minetest
.get_item_group(current_node
.name
, "water") ~= 0 or minetest
.get_item_group(current_node
.name
, "lava") ~= 0) then
250 -- Damage by suffocation
251 if (def
.walkable
== nil or def
.walkable
== true)
252 and (def
.drowning
== nil or def
.drowning
== 0)
253 and (def
.damage_per_second
== nil or def
.damage_per_second
<= 0)
254 and (def
.collision_box
== nil or def
.collision_box
.type == "regular")
255 and (def
.node_box
== nil or def
.node_box
.type == "regular")
256 and (def
.groups
and def
.groups
.disable_suffocation
~= 1) then
259 self
.envdmg_timer
= self
.envdmg_timer
+ dtime
261 if self
.envdmg_timer
>= 1 then
262 local new_hp
= self
.object
:get_hp() - dmg
264 if self
.on_death
then
270 self
.envdmg_timer
= 0
271 self
.object
:set_hp(new_hp
)
273 type = "environment",
279 minetest
.sound_play(sound_hit
, {pos
= current_pos
, max_hear_distance
= 10, gain
= 0.4}, true)
286 --update moving state every 1 or 2 seconds
287 if self
.state
< 3 then
288 if self
.timer
> math
.random(1,2) then
289 if self
.attacker
== "" then
290 self
.state
= math
.random(1,2)
291 else self
.state
= 1 end
297 if self
.sound_timer
> math
.random(5,35) then
298 minetest
.sound_play(sound_normal
, {pos
= current_pos
, max_hear_distance
= 10, gain
= 0.2}, true)
303 if self
.state
>= 8 then
304 if self
.punch_timer
> 0.15 then
305 if self
.state
== 9 then
306 self
.object
:set_velocity({x
=self
.direction
.x
*mummy_chillaxin_speed
,y
=-20,z
=self
.direction
.z
*mummy_chillaxin_speed
})
308 elseif self
.state
== 8 then
309 self
.object
:set_velocity({x
=0,y
=-20,z
=0})
316 if self
.state
== 1 then
319 for _
,object
in ipairs(minetest
.get_objects_inside_radius(self
.object
:get_pos(), 4)) do
320 if object
:is_player() then
322 local NPC
= self
.object
:get_pos()
323 local PLAYER
= object
:get_pos()
324 self
.vec
= {x
=PLAYER
.x
-NPC
.x
, y
=PLAYER
.y
-NPC
.y
, z
=PLAYER
.z
-NPC
.z
}
325 self
.yaw
= math
.atan(self
.vec
.z
/self
.vec
.x
)+math
.pi^
2
326 if PLAYER
.x
> NPC
.x
then
327 self
.yaw
= self
.yaw
+ math
.pi
329 self
.yaw
= self
.yaw
- 2
330 self
.object
:set_yaw(self
.yaw
)
331 self
.attacker
= object
335 if self
.attacker
== "" and self
.turn_timer
> math
.random(1,4) then
336 self
.yaw
= 360 * math
.random()
337 self
.object
:set_yaw(self
.yaw
)
339 self
.direction
= {x
= math
.sin(self
.yaw
)*-1, y
= -20, z
= math
.cos(self
.yaw
)}
341 self
.object
:set_velocity({x
=0,y
=self
.object
:get_velocity().y
,z
=0})
342 if self
.npc_anim
~= ANIM_STAND
then
343 self
.anim
= get_animations()
344 self
.object
:set_animation({x
=self
.anim
.stand_START
,y
=self
.anim
.stand_END
}, mummy_animation_speed
, mummy_animation_blend
)
345 self
.npc_anim
= ANIM_STAND
347 if self
.attacker
~= "" then
348 self
.direction
= {x
= math
.sin(self
.yaw
)*-1, y
= -20, z
= math
.cos(self
.yaw
)}
353 if self
.state
== 2 then
355 if self
.direction
~= nil then
356 self
.object
:set_velocity({x
=self
.direction
.x
*mummy_chillaxin_speed
,y
=self
.object
:get_velocity().y
,z
=self
.direction
.z
*mummy_chillaxin_speed
})
358 if self
.turn_timer
> math
.random(1,4) and not self
.attacker
then
359 self
.yaw
= 360 * math
.random()
360 self
.object
:set_yaw(self
.yaw
)
362 self
.direction
= {x
= math
.sin(self
.yaw
)*-1, y
= -20, z
= math
.cos(self
.yaw
)}
364 if self
.npc_anim
~= ANIM_WALK
then
365 self
.anim
= get_animations()
366 self
.object
:set_animation({x
=self
.anim
.walk_START
,y
=self
.anim
.walk_END
}, mummy_animation_speed
, mummy_animation_blend
)
367 self
.npc_anim
= ANIM_WALK
370 if self
.attacker
~= "" and minetest
.settings
:get_bool("enable_damage") then
371 local s
= self
.object
:get_pos()
372 local p
= self
.attacker
:get_pos()
373 if (s
~= nil and p
~= nil) then
374 local dist
= ((p
.x
-s
.x
)^
2 + (p
.y
-s
.y
)^
2 + (p
.z
-s
.z
)^
2)^
0.5
376 if dist
< 2 and self
.attacking_timer
> 0.6 then
377 self
.attacker
:punch(self
.object
, 1.0, {
378 full_punch_interval
=1.0,
379 damage_groups
= {fleshy
=1}
381 self
.attacking_timer
= 0
388 minetest
.register_entity("tsm_pyramids:mummy", MUMMY_DEF
)
389 minetest
.register_entity("tsm_pyramids:mummy_spawner", spawner_DEF
)
394 minetest
.register_craftitem("tsm_pyramids:spawn_egg", {
395 description
= S("Mummy Spawn Egg"),
396 _doc_items_longdesc
= S("Can be used to create a hostile mummy."),
397 _doc_items_usagehelp
= S("Place the egg to create a mummy on this spot. Careful, it will probably attack immediately!"),
398 inventory_image
= "tsm_pyramids_mummy_egg.png",
399 liquids_pointable
= false,
401 on_place
= function(itemstack
, placer
, pointed_thing
)
402 if pointed_thing
.type ~= "node" then
406 -- am I clicking on something with existing on_rightclick function?
407 local node
= minetest
.get_node(pointed_thing
.under
)
408 if placer
and not placer
:get_player_control().sneak
then
409 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
410 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
414 minetest
.add_entity(pointed_thing
.above
,"tsm_pyramids:mummy")
415 if not minetest
.settings
:get_bool("creative_mode") then
416 itemstack
:take_item()
423 -- Spawn a mummy at position
424 function tsm_pyramids
.spawn_mummy_at(pos
, number)
425 local node
= minetest
.get_node(pos
)
426 if node
.name
~= "air" then
430 minetest
.add_entity(pos
,"tsm_pyramids:mummy")
435 if default
.node_sound_metal_defaults
then
436 spawnersounds
= default
.node_sound_metal_defaults()
438 spawnersounds
= default
.node_sound_stone_defaults()
441 local spawn_mummy_spawner_entity
= function(pos
)
442 local spos
= vector
.new(pos
.x
, pos
.y
+spawner_entity_offset
, pos
.z
)
443 minetest
.add_entity(spos
, "tsm_pyramids:mummy_spawner")
446 -- Respawn mummy spawner entity at pos if none exists
447 local respawn_mummy_spawner_entity
= function(pos
)
448 local spos
= vector
.new(pos
.x
, pos
.y
+ spawner_entity_offset
, pos
.z
)
449 if check_if_mummy_spawner_entity_exists(spos
) then
452 spawn_mummy_spawner_entity(pos
)
455 minetest
.register_node("tsm_pyramids:spawner_mummy", {
456 description
= S("Mummy Spawner"),
457 _doc_items_longdesc
= S("A mummy spawner causes hostile mummies to appear in its vicinity as long it exists."),
459 tiles
= {"tsm_pyramids_spawner.png"},
460 is_ground_content
= false,
461 drawtype
= "allfaces",
462 groups
= {cracky
=1,level
=1},
464 on_construct
= function(pos
)
465 spawn_mummy_spawner_entity(pos
)
467 on_punch
= function(pos
)
468 respawn_mummy_spawner_entity(pos
)
470 on_destruct
= function(pos
)
471 for _
,obj
in ipairs(minetest
.get_objects_inside_radius(pos
, 0.5)) do
472 if obj
~= nil and not obj
:is_player() then
473 if obj
:get_luaentity().name
== "tsm_pyramids:mummy_spawner" then
479 sounds
= spawnersounds
,
482 -- Neccessary in case the spawner entity got lost due to /clearobjects
483 minetest
.register_lbm({
484 label
= "Respawn mummy spawner entity",
485 name
= "tsm_pyramids:respawn_mummy_spawner_entity",
486 nodenames
= { "tsm_pyramids:spawner_mummy" },
487 run_at_every_load
= true,
488 action
= function(pos
, node
)
489 respawn_mummy_spawner_entity(pos
)
493 -- Attempt to spawn a mummy at a random appropriate position around pos.
495 -- * Must be close to pos
497 -- * Must be air on top of a non-air block
498 -- * No more than 6 mummies in area
499 -- * Player must be near is player_near_required is true
500 function tsm_pyramids
.attempt_mummy_spawn(pos
, player_near_required
)
501 local player_near
= false
503 for _
,obj
in ipairs(minetest
.get_objects_inside_radius(pos
, spawner_check_range
)) do
504 if obj
:is_player() then
507 if obj
:get_luaentity() and obj
:get_luaentity().name
== "tsm_pyramids:mummy" then
512 if player_near
or (not player_near_required
) then
513 if mobs
< spawner_max_mobs
then
514 local offset
= {x
=5,y
=2,z
=5}
515 local nposses
= minetest
.find_nodes_in_area(vector
.subtract(pos
, offset
), vector
.add(pos
,offset
), "air")
516 local tries
= math
.min(6, #nposses
)
518 local r
= math
.random(1, #nposses
)
519 local npos
= nposses
[r
]
520 -- Check if mummy has 2 nodes of free space
521 local two_space
= false
522 -- Check if mummy has something to walk on
523 local footing
= false
524 -- Find the lowest node
527 local below
= minetest
.get_node(npos
)
528 if minetest
.registered_items
[below
.name
].liquidtype
~= "none" then
531 if below
.name
~= "air" then
540 local light
= minetest
.get_node_light(npos
, 0.5)
541 if not two_space
then
542 local above
= minetest
.get_node({x
=npos
.x
, y
=npos
.y
+1, z
=npos
.z
})
543 if above
.name
== "air" then
547 if footing
and two_space
and light
< 15 then
548 tsm_pyramids
.spawn_mummy_at(npos
, 1)
551 table.remove(nposses
, r
)
558 if not minetest
.settings
:get_bool("only_peaceful_mobs") then
559 minetest
.register_abm({
560 nodenames
= {"tsm_pyramids:spawner_mummy"},
563 action
= function(pos
, node
, active_object_count
, active_object_count_wider
)
564 tsm_pyramids
.attempt_mummy_spawn(pos
, true)
569 if minetest
.get_modpath("awards") then
570 awards
.register_achievement("tsm_pyramids_no_mummy_spawner", {
571 title
= S("No more mummies!"),
572 description
= S("Destroy a mummy spawner by digging."),
574 icon
= "tsm_pyramids_spawner.png",
577 node
= "tsm_pyramids:spawner_mummy",