5 local default_mob
= "mobs_mc:pig"
8 local spawner_default
= default_mob
.." 0 15 4 15"
10 local function get_mob_textures(mob
)
11 local list
= minetest
.registered_entities
[mob
].texture_list
12 if type(list
[1]) == "table" then
19 local function find_doll(pos
)
20 for _
,obj
in ipairs(minetest
.get_objects_inside_radius(pos
, 0.5)) do
21 if not obj
:is_player() then
22 if obj
~= nil and obj
:get_luaentity().name
== "mcl_mobspawners:doll" then
30 local function spawn_doll(pos
)
31 return minetest
.add_entity({x
=pos
.x
, y
=pos
.y
-0.3, z
=pos
.z
}, "mcl_mobspawners:doll")
34 -- Manually set the doll sizes for large mobs
35 -- TODO: Relocate this code to mobs_mc
36 local doll_size_overrides
= {
37 ["mobs_mc:guardian"] = { x
= 0.6, y
= 0.6 },
38 ["mobs_mc:guardian_elder"] = { x
= 0.72, y
= 0.72 },
39 ["mobs_mc:enderman"] = { x
= 0.8, y
= 0.8 },
40 ["mobs_mc:iron_golem"] = { x
= 0.9, y
= 0.9 },
41 ["mobs_mc:ghast"] = { x
= 1.05, y
= 1.05 },
42 ["mobs_mc:wither"] = { x
= 1.2, y
= 1.2 },
43 ["mobs_mc:enderdragon"] = { x
= 0.16, y
= 0.16 },
44 ["mobs_mc:witch"] = { x
= 0.95, y
= 0.95 },
47 local function set_doll_properties(doll
, mob
)
48 local mobinfo
= minetest
.registered_entities
[mob
]
50 if doll_size_overrides
[mob
] then
51 xs
= doll_size_overrides
[mob
].x
52 ys
= doll_size_overrides
[mob
].y
54 xs
= mobinfo
.visual_size
.x
* 0.33333
55 ys
= mobinfo
.visual_size
.y
* 0.33333
59 textures
= get_mob_textures(mob
),
65 doll
:set_properties(prop
)
66 doll
:get_luaentity()._mob
= mob
69 --[[ Public function: Setup the spawner at pos.
70 This function blindly assumes there's actually a spawner at pos.
71 If not, then the results are undefined.
72 All the arguments are optional!
74 * Mob: ID of mob to spawn (default: mobs_mc:pig)
75 * MinLight: Minimum light to spawn (default: 0)
76 * MaxLight: Maximum light to spawn (default: 15)
77 * MaxMobsInArea: How many mobs are allowed in the area around the spawner (default: 4)
78 * PlayerDistance: Spawn mobs only if a player is within this distance; 0 to disable (default: 15)
79 * YOffset: Y offset to spawn mobs; 0 to disable (default: 0)
82 function mcl_mobspawners
.setup_spawner(pos
, Mob
, MinLight
, MaxLight
, MaxMobsInArea
, PlayerDistance
, YOffset
)
83 -- Activate mob spawner and disable editing functionality
84 if Mob
== nil then Mob
= default_mob
end
85 if MinLight
== nil then MinLight
= 0 end
86 if MaxLight
== nil then MaxLight
= 15 end
87 if MaxMobsInArea
== nil then MaxMobsInArea
= 4 end
88 if PlayerDistance
== nil then PlayerDistance
= 15 end
89 if YOffset
== nil then YOffset
= 0 end
90 local meta
= minetest
.get_meta(pos
)
91 meta
:set_string("Mob", Mob
)
92 meta
:set_int("MinLight", MinLight
)
93 meta
:set_int("MaxLight", MaxLight
)
94 meta
:set_int("MaxMobsInArea", MaxMobsInArea
)
95 meta
:set_int("PlayerDistance", PlayerDistance
)
96 meta
:set_int("YOffset", YOffset
)
98 -- Create doll or replace existing doll
99 local doll
= find_doll(pos
)
101 doll
= spawn_doll(pos
)
103 set_doll_properties(doll
, Mob
)
106 -- Start spawning very soon
107 local t
= minetest
.get_node_timer(pos
)
111 -- Spawn mobs around pos
112 -- NOTE: The node is timer-based, rather than ABM-based.
113 local spawn_mobs
= function(pos
, elapsed
)
116 local meta
= minetest
.get_meta(pos
)
119 local mob
= meta
:get_string("Mob")
120 local mlig
= meta
:get_int("MinLight")
121 local xlig
= meta
:get_int("MaxLight")
122 local num
= meta
:get_int("MaxMobsInArea")
123 local pla
= meta
:get_int("PlayerDistance")
124 local yof
= meta
:get_int("YOffset")
126 -- if amount is 0 then do nothing
131 -- are we spawning a registered mob?
132 if not mobs
.spawning_mobs
[mob
] then
133 minetest
.log("error", "[mcl_mobspawners] Mob Spawner: Mob doesn't exist: "..mob
)
137 -- check objects inside 8×8 area around spawner
138 local objs
= minetest
.get_objects_inside_radius(pos
, 8)
143 local timer
= minetest
.get_node_timer(pos
)
145 -- spawn mob if player detected and in range
149 local objs
= minetest
.get_objects_inside_radius(pos
, pla
)
151 for _
,oir
in pairs(objs
) do
153 if oir
:is_player() then
162 if in_range
== 0 then
170 The doll may not stay spawned if the mob spawner is placed far away from
171 players, so we will check for its existance periodically when a player is nearby.
172 This would happen almost always when the mob spawner is placed by the mapgen.
173 This is probably caused by a Minetest bug:
174 https://github.com/minetest/minetest/issues/4759
175 FIXME: Fix this horrible hack.
177 local doll
= find_doll(pos
)
179 doll
= spawn_doll(pos
)
180 set_doll_properties(doll
, mob
)
183 -- count mob objects of same type in area
184 for k
, obj
in ipairs(objs
) do
186 ent
= obj
:get_luaentity()
188 if ent
and ent
.name
and ent
.name
== mob
then
193 -- Are there too many of same type? then fail
195 timer
:start(math
.random(5, 20))
199 -- find air blocks within 8×3×8 nodes of spawner
200 local air
= minetest
.find_nodes_in_area(
201 {x
= pos
.x
- 4, y
= pos
.y
- 1 + yof
, z
= pos
.z
- 4},
202 {x
= pos
.x
+ 4, y
= pos
.y
+ 1 + yof
, z
= pos
.z
+ 4},
205 -- spawn up to 4 mobs in random air blocks
209 -- We're out of space! Stop spawning
212 local air_index
= math
.random(#air
)
213 local pos2
= air
[air_index
]
214 local lig
= minetest
.get_node_light(pos2
) or 0
216 pos2
.y
= pos2
.y
+ 0.5
218 -- only if light levels are within range
219 if lig
>= mlig
and lig
<= xlig
then
220 minetest
.add_entity(pos2
, mob
)
222 table.remove(air
, air_index
)
226 -- Spawn attempt done. Next spawn attempt much later
227 timer
:start(math
.random(10, 39.95))
231 -- The mob spawner node.
232 -- PLACEMENT INSTRUCTIONS:
233 -- If this node is placed by a player, minetest.item_place, etc. default settings are applied
235 -- IF this node is placed by ANY other method (e.g. minetest.set_node, LuaVoxelManip), you
236 -- MUST call mcl_mobspawners.setup_spawner right after the spawner has been placed.
237 minetest
.register_node("mcl_mobspawners:spawner", {
238 tiles
= {"mob_spawner.png"},
239 drawtype
= "glasslike",
242 description
= S("Mob Spawner"),
243 _doc_items_longdesc
= S("A mob spawner regularily causes mobs to appear around it while a player is nearby. Some mob spawners are disabled while in light."),
244 _doc_items_usagehelp
= S("If you have a spawn egg, you use it to change the mob to spawn. Just place the item on the mob spawner. Player-set mob spawners always spawn mobs regardless of the light level."),
245 groups
= {pickaxey
=1, material_stone
=1, deco_block
=1},
246 is_ground_content
= false,
249 -- If placed by player, setup spawner with default settings
250 on_place
= function(itemstack
, placer
, pointed_thing
)
251 if pointed_thing
.type ~= "node" then
255 -- Use pointed node's on_rightclick function first, if present
256 local node
= minetest
.get_node(pointed_thing
.under
)
257 if placer
and not placer
:get_player_control().sneak
then
258 if minetest
.registered_nodes
[node
.name
] and minetest
.registered_nodes
[node
.name
].on_rightclick
then
259 return minetest
.registered_nodes
[node
.name
].on_rightclick(pointed_thing
.under
, node
, placer
, itemstack
) or itemstack
263 local name
= placer
:get_player_name()
264 local privs
= minetest
.get_player_privs(name
)
265 if not privs
.maphack
then
266 minetest
.chat_send_player(name
, "Placement denied. You need the “maphack” privilege to place mob spawners.")
269 local node_under
= minetest
.get_node(pointed_thing
.under
)
270 local new_itemstack
, success
= minetest
.item_place(itemstack
, placer
, pointed_thing
)
273 if minetest
.registered_nodes
[node_under
.name
].buildable_to
then
274 placepos
= pointed_thing
.under
276 placepos
= pointed_thing
.above
278 mcl_mobspawners
.setup_spawner(placepos
)
283 on_destruct
= function(pos
)
284 -- Remove doll (if any)
285 local obj
= find_doll(pos
)
291 on_timer
= spawn_mobs
,
293 on_receive_fields
= function(pos
, formname
, fields
, sender
)
295 if not fields
.text
or fields
.text
== "" then
299 local meta
= minetest
.get_meta(pos
)
300 local comm
= fields
.text
:split(" ")
301 local name
= sender
:get_player_name()
303 if minetest
.is_protected(pos
, name
) then
304 minetest
.record_protection_violation(pos
, name
)
308 local mob
= comm
[1] -- mob to spawn
309 local mlig
= tonumber(comm
[2]) -- min light
310 local xlig
= tonumber(comm
[3]) -- max light
311 local num
= tonumber(comm
[4]) -- total mobs in area
312 local pla
= tonumber(comm
[5]) -- player distance (0 to disable)
313 local yof
= tonumber(comm
[6]) or 0 -- Y offset to spawn mob
315 if mob
and mob
~= "" and mobs
.spawning_mobs
[mob
] == true
316 and num
and num
>= 0 and num
<= 10
317 and mlig
and mlig
>= 0 and mlig
<= 15
318 and xlig
and xlig
>= 0 and xlig
<= 15
319 and pla
and pla
>=0 and pla
<= 20
320 and yof
and yof
> -10 and yof
< 10 then
322 mcl_mobspawners
.setup_spawner(pos
, mob
, mlig
, xlig
, num
, pla
, yof
)
324 minetest
.chat_send_player(name
, S("Mob Spawner settings failed!"))
325 minetest
.chat_send_player(name
,
326 S("Syntax: name min_light[0-14] max_light[0-14] max_mobs_in_area[0 to disable] distance[1-20] y_offset[-10 to 10]"))
329 sounds
= mcl_sounds
.node_sound_metal_defaults(),
331 _mcl_blast_resistance
= 25,
335 -- Mob spawner doll (rotating icon inside cage)
340 collisionbox
= {0,0,0,0,0,0},
342 makes_footstep_sound
= false,
344 automatic_rotate
= math
.pi
* 2.9,
346 _mob
= default_mob
, -- name of the mob this doll represents
349 doll_def
.get_staticdata
= function(self
)
353 doll_def
.on_activate
= function(self
, staticdata
, dtime_s
)
354 local mob
= staticdata
355 if mob
== "" or mob
== nil then
358 set_doll_properties(self
.object
, mob
)
359 self
.object
:setvelocity({x
=0, y
=0, z
=0})
360 self
.object
:setacceleration({x
=0, y
=0, z
=0})
361 self
.object
:set_armor_groups({immortal
=1})
365 doll_def
.on_step
= function(self
, dtime
)
366 -- Check if spawner is still present. If not, delete the entity
367 self
.timer
= self
.timer
+ 0.01
368 local n
= minetest
.get_node_or_nil(self
.object
:getpos())
369 if self
.timer
> 1 then
370 if n
and n
.name
and n
.name
~= "mcl_mobspawners:spawner" then
376 doll_def
.on_punch
= function(self
, hitter
) end
378 minetest
.register_entity("mcl_mobspawners:doll", doll_def
)