2021-05-29 16:12:33 +02:00
local S = minetest.get_translator ( minetest.get_current_modname ( ) )
2017-01-16 17:40:08 +01:00
2021-05-22 23:19:31 +02:00
local math = math
local table = table
2017-05-25 06:09:03 +02:00
mcl_mobspawners = { }
2017-05-25 02:17:24 +02:00
2017-05-25 04:23:21 +02:00
local default_mob = " mobs_mc:pig "
2017-01-16 17:40:08 +01:00
2018-01-07 16:58:44 +01:00
-- Mob spawner
2021-05-22 23:19:31 +02:00
--local spawner_default = default_mob.." 0 15 4 15"
2017-05-25 00:59:41 +02:00
local function get_mob_textures ( mob )
2018-01-07 17:50:50 +01:00
local list = minetest.registered_entities [ mob ] . texture_list
if type ( list [ 1 ] ) == " table " then
return list [ 1 ]
else
return list
end
2017-05-25 00:59:41 +02:00
end
local function find_doll ( pos )
2021-03-16 17:39:06 +01:00
for _ , obj in pairs ( minetest.get_objects_inside_radius ( pos , 0.5 ) ) do
2017-05-25 00:59:41 +02:00
if not obj : is_player ( ) then
2021-05-29 16:12:33 +02:00
if obj and obj : get_luaentity ( ) . name == " mcl_mobspawners:doll " then
2017-05-25 00:59:41 +02:00
return obj
end
end
end
return nil
end
2017-01-16 17:40:08 +01:00
2017-05-25 07:34:53 +02:00
local function spawn_doll ( pos )
return minetest.add_entity ( { x = pos.x , y = pos.y - 0.3 , z = pos.z } , " mcl_mobspawners:doll " )
end
2018-01-07 17:29:56 +01:00
-- Manually set the doll sizes for large mobs
-- TODO: Relocate this code to mobs_mc
local doll_size_overrides = {
[ " mobs_mc:guardian " ] = { x = 0.6 , y = 0.6 } ,
[ " mobs_mc:guardian_elder " ] = { x = 0.72 , y = 0.72 } ,
2024-05-05 18:22:19 +02:00
[ " mobs_mc:rover " ] = { x = 0.8 , y = 0.8 } ,
2018-01-07 17:29:56 +01:00
[ " mobs_mc:iron_golem " ] = { x = 0.9 , y = 0.9 } ,
[ " mobs_mc:ghast " ] = { x = 1.05 , y = 1.05 } ,
[ " mobs_mc:wither " ] = { x = 1.2 , y = 1.2 } ,
[ " mobs_mc:enderdragon " ] = { x = 0.16 , y = 0.16 } ,
2018-01-07 17:54:40 +01:00
[ " mobs_mc:witch " ] = { x = 0.95 , y = 0.95 } ,
2018-01-07 17:29:56 +01:00
}
2019-03-08 23:26:54 +01:00
local spawn_count_overrides = {
[ " mobs_mc:enderdragon " ] = 1 ,
[ " mobs_mc:wither " ] = 1 ,
[ " mobs_mc:ghast " ] = 1 ,
[ " mobs_mc:guardian_elder " ] = 1 ,
[ " mobs_mc:guardian " ] = 2 ,
[ " mobs_mc:iron_golem " ] = 2 ,
}
2018-01-07 17:29:56 +01:00
2017-05-25 01:14:18 +02:00
local function set_doll_properties ( doll , mob )
local mobinfo = minetest.registered_entities [ mob ]
2021-05-08 12:09:16 +02:00
if not mobinfo then return end
2018-01-07 17:29:56 +01:00
local xs , ys
if doll_size_overrides [ mob ] then
xs = doll_size_overrides [ mob ] . x
ys = doll_size_overrides [ mob ] . y
else
2024-05-30 10:29:12 +02:00
xs = ( mobinfo.visual_size . x or 0 ) * 0.33333
ys = ( mobinfo.visual_size . y or 0 ) * 0.33333
2018-01-07 17:29:56 +01:00
end
2017-05-25 01:14:18 +02:00
local prop = {
mesh = mobinfo.mesh ,
textures = get_mob_textures ( mob ) ,
visual_size = {
2018-01-07 17:29:56 +01:00
x = xs ,
y = ys ,
2017-05-25 01:14:18 +02:00
}
}
doll : set_properties ( prop )
2017-05-25 03:09:02 +02:00
doll : get_luaentity ( ) . _mob = mob
2017-05-25 01:14:18 +02:00
end
2019-02-19 00:06:14 +01:00
local function respawn_doll ( pos )
local meta = minetest.get_meta ( pos )
local mob = meta : get_string ( " Mob " )
local doll
if mob and mob ~= " " then
2024-06-15 01:43:10 +02:00
-- Handle conversion of mob spawners
local convert_to = ( minetest.registered_entities [ mob ] or { } ) . _convert_to
if convert_to then
mob = convert_to
meta : set_string ( " Mob " , mob )
end
2019-02-19 00:06:14 +01:00
doll = find_doll ( pos )
if not doll then
doll = spawn_doll ( pos )
set_doll_properties ( doll , mob )
end
end
return doll
end
2017-05-25 01:59:10 +02:00
--[[ Public function: Setup the spawner at pos.
This function blindly assumes there ' s actually a spawner at pos.
If not , then the results are undefined .
2017-05-25 05:45:45 +02:00
All the arguments are optional !
2017-05-25 01:59:10 +02:00
2017-05-25 05:45:45 +02:00
* Mob : ID of mob to spawn ( default : mobs_mc : pig )
2017-05-25 01:59:10 +02:00
* MinLight : Minimum light to spawn ( default : 0 )
2021-05-08 12:12:13 +02:00
* MaxLight : Maximum light to spawn ( default : 15 )
2017-05-25 04:47:08 +02:00
* MaxMobsInArea : How many mobs are allowed in the area around the spawner ( default : 4 )
* PlayerDistance : Spawn mobs only if a player is within this distance ; 0 to disable ( default : 15 )
2017-05-25 01:59:10 +02:00
* YOffset : Y offset to spawn mobs ; 0 to disable ( default : 0 )
] ]
2017-05-25 06:09:03 +02:00
function mcl_mobspawners . setup_spawner ( pos , Mob , MinLight , MaxLight , MaxMobsInArea , PlayerDistance , YOffset )
2018-01-07 16:58:44 +01:00
-- Activate mob spawner and disable editing functionality
2017-05-25 05:45:45 +02:00
if Mob == nil then Mob = default_mob end
2017-05-25 01:59:10 +02:00
if MinLight == nil then MinLight = 0 end
2017-05-25 04:47:08 +02:00
if MaxLight == nil then MaxLight = 15 end
if MaxMobsInArea == nil then MaxMobsInArea = 4 end
if PlayerDistance == nil then PlayerDistance = 15 end
2017-05-25 01:59:10 +02:00
if YOffset == nil then YOffset = 0 end
local meta = minetest.get_meta ( pos )
meta : set_string ( " Mob " , Mob )
meta : set_int ( " MinLight " , MinLight )
meta : set_int ( " MaxLight " , MaxLight )
meta : set_int ( " MaxMobsInArea " , MaxMobsInArea )
meta : set_int ( " PlayerDistance " , PlayerDistance )
meta : set_int ( " YOffset " , YOffset )
2018-01-07 16:37:41 +01:00
-- Create doll or replace existing doll
local doll = find_doll ( pos )
if not doll then
doll = spawn_doll ( pos )
end
2017-05-25 01:59:10 +02:00
set_doll_properties ( doll , Mob )
2017-05-25 05:37:13 +02:00
-- Start spawning very soon
local t = minetest.get_node_timer ( pos )
t : start ( 2 )
end
2018-01-07 16:58:44 +01:00
-- Spawn mobs around pos
2017-05-25 05:37:13 +02:00
-- NOTE: The node is timer-based, rather than ABM-based.
2021-05-29 16:12:33 +02:00
local function spawn_mobs ( pos , elapsed )
2017-05-25 05:37:13 +02:00
-- get meta
local meta = minetest.get_meta ( pos )
-- if amount is 0 then do nothing
2024-09-14 00:10:05 +02:00
local num = meta : get_int ( " MaxMobsInArea " )
2017-05-25 05:37:13 +02:00
if num == 0 then
return
end
-- are we spawning a registered mob?
2024-09-14 00:10:05 +02:00
local mob = meta : get_string ( " Mob " )
2022-05-25 14:44:49 +02:00
if not mcl_mobs.spawning_mobs [ mob ] then
2018-01-07 16:58:44 +01:00
minetest.log ( " error " , " [mcl_mobspawners] Mob Spawner: Mob doesn't exist: " .. mob )
2017-05-25 05:37:13 +02:00
return
end
-- check objects inside 8× 8 area around spawner
local objs = minetest.get_objects_inside_radius ( pos , 8 )
local count = 0
2021-05-22 23:19:31 +02:00
local ent
2017-05-25 05:37:13 +02:00
local timer = minetest.get_node_timer ( pos )
-- spawn mob if player detected and in range
2024-09-14 00:10:05 +02:00
local pla = meta : get_int ( " PlayerDistance " )
2017-05-25 05:37:13 +02:00
if pla > 0 then
local in_range = 0
local objs = minetest.get_objects_inside_radius ( pos , pla )
for _ , oir in pairs ( objs ) do
if oir : is_player ( ) then
in_range = 1
break
end
end
-- player not found
if in_range == 0 then
-- Try again quickly
timer : start ( 2 )
return
end
end
2017-05-25 07:34:53 +02:00
--[[ HACK!
2018-01-07 16:58:44 +01:00
The doll may not stay spawned if the mob spawner is placed far away from
2017-05-25 07:34:53 +02:00
players , so we will check for its existance periodically when a player is nearby .
2018-01-07 16:58:44 +01:00
This would happen almost always when the mob spawner is placed by the mapgen .
2024-11-28 20:55:48 +01:00
This is probably caused by a Luanti bug :
2017-08-14 17:23:43 +02:00
https : // github.com / minetest / minetest / issues / 4759
FIXME : Fix this horrible hack .
2017-05-25 07:34:53 +02:00
] ]
local doll = find_doll ( pos )
if not doll then
doll = spawn_doll ( pos )
set_doll_properties ( doll , mob )
end
2017-05-25 05:37:13 +02:00
-- count mob objects of same type in area
for k , obj in ipairs ( objs ) do
ent = obj : get_luaentity ( )
if ent and ent.name and ent.name == mob then
count = count + 1
end
end
-- Are there too many of same type? then fail
if count >= num then
timer : start ( math.random ( 5 , 20 ) )
return
end
-- find air blocks within 8× 3× 8 nodes of spawner
2024-09-14 00:10:05 +02:00
local yof = meta : get_int ( " YOffset " )
2017-05-25 05:37:13 +02:00
local air = minetest.find_nodes_in_area (
{ x = pos.x - 4 , y = pos.y - 1 + yof , z = pos.z - 4 } ,
{ x = pos.x + 4 , y = pos.y + 1 + yof , z = pos.z + 4 } ,
{ " air " } )
2024-09-14 00:10:05 +02:00
-- spawn mobs in random air blocks. Default max of 4
2017-05-25 05:37:13 +02:00
if air then
2024-09-14 00:10:05 +02:00
local num_to_spawn = spawn_count_overrides [ mob ] or 4
local mlig = meta : get_int ( " MinLight " )
local xlig = meta : get_int ( " MaxLight " )
2017-05-25 05:37:13 +02:00
2024-09-14 03:24:34 +02:00
while # air > 0 do
local pos2 = table.remove_random_element ( air )
2017-05-25 05:37:13 +02:00
-- only if light levels are within range
2024-09-14 00:10:05 +02:00
local lig = minetest.get_node_light ( pos2 ) or 0
2017-05-25 05:37:13 +02:00
if lig >= mlig and lig <= xlig then
2024-09-14 00:10:05 +02:00
if mcl_mobs.spawn ( pos2 , mob ) then
num_to_spawn = num_to_spawn - 1
if num_to_spawn == 0 then break end
end
2017-05-25 05:37:13 +02:00
end
end
end
-- Spawn attempt done. Next spawn attempt much later
2024-09-07 14:58:12 +02:00
timer : start ( math.random ( ) * 29.95 + 10 )
2017-05-25 01:59:10 +02:00
end
2018-01-07 16:58:44 +01:00
-- The mob spawner node.
2017-08-15 14:19:44 +02:00
-- PLACEMENT INSTRUCTIONS:
-- If this node is placed by a player, minetest.item_place, etc. default settings are applied
-- automatially.
-- IF this node is placed by ANY other method (e.g. minetest.set_node, LuaVoxelManip), you
-- MUST call mcl_mobspawners.setup_spawner right after the spawner has been placed.
2017-05-25 06:09:03 +02:00
minetest.register_node ( " mcl_mobspawners:spawner " , {
2017-01-16 17:40:08 +01:00
tiles = { " mob_spawner.png " } ,
drawtype = " glasslike " ,
paramtype = " light " ,
walkable = true ,
2018-01-07 16:58:44 +01:00
description = S ( " Mob Spawner " ) ,
2020-02-19 04:54:17 +01:00
_tt_help = S ( " Makes mobs appear " ) ,
2018-01-08 02:19:00 +01:00
_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. " ) ,
2019-03-15 09:50:32 +01:00
_doc_items_usagehelp = S ( " If you have a spawn egg, you can 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. " ) ,
2017-12-13 00:30:23 +01:00
groups = { pickaxey = 1 , material_stone = 1 , deco_block = 1 } ,
2017-03-11 16:36:05 +01:00
is_ground_content = false ,
2017-02-01 21:09:24 +01:00
drop = " " ,
2017-01-16 17:40:08 +01:00
2017-08-15 14:19:44 +02:00
-- If placed by player, setup spawner with default settings
2017-08-15 14:16:40 +02:00
on_place = function ( itemstack , placer , pointed_thing )
2017-12-13 00:30:23 +01:00
if pointed_thing.type ~= " node " then
return itemstack
end
-- Use pointed node's on_rightclick function first, if present
local node = minetest.get_node ( pointed_thing.under )
if placer and not placer : get_player_control ( ) . sneak then
if minetest.registered_nodes [ node.name ] and minetest.registered_nodes [ node.name ] . on_rightclick then
return minetest.registered_nodes [ node.name ] . on_rightclick ( pointed_thing.under , node , placer , itemstack ) or itemstack
end
end
local name = placer : get_player_name ( )
local privs = minetest.get_player_privs ( name )
if not privs.maphack then
2018-01-07 16:58:44 +01:00
minetest.chat_send_player ( name , " Placement denied. You need the “maphack” privilege to place mob spawners. " )
2017-12-13 00:30:23 +01:00
return itemstack
end
2017-08-15 14:16:40 +02:00
local node_under = minetest.get_node ( pointed_thing.under )
local new_itemstack , success = minetest.item_place ( itemstack , placer , pointed_thing )
if success then
local placepos
2023-09-29 20:47:07 +02:00
local def = minetest.registered_nodes [ node_under.name ]
if def and def.buildable_to then
2017-08-15 14:16:40 +02:00
placepos = pointed_thing.under
else
placepos = pointed_thing.above
end
mcl_mobspawners.setup_spawner ( placepos )
end
return new_itemstack
end ,
2017-05-25 00:59:41 +02:00
on_destruct = function ( pos )
2018-01-07 16:37:41 +01:00
-- Remove doll (if any)
2017-05-25 05:45:45 +02:00
local obj = find_doll ( pos )
if obj then
obj : remove ( )
2017-05-25 01:59:10 +02:00
end
2023-03-20 00:46:35 +01:00
if not minetest.is_creative_enabled ( " " ) then
2023-02-06 15:19:56 +01:00
mcl_experience.throw_xp ( pos , math.random ( 15 , 43 ) )
end
2017-01-16 17:40:08 +01:00
end ,
2019-02-19 00:06:14 +01:00
on_punch = function ( pos )
respawn_doll ( pos )
end ,
2018-01-07 16:58:44 +01:00
on_timer = spawn_mobs ,
2017-05-25 05:37:13 +02:00
2017-02-11 18:46:23 +01:00
sounds = mcl_sounds.node_sound_metal_defaults ( ) ,
2018-01-07 16:37:41 +01:00
2020-04-17 21:40:13 +02:00
_mcl_blast_resistance = 5 ,
2017-02-27 18:32:35 +01:00
_mcl_hardness = 5 ,
2017-01-16 17:40:08 +01:00
} )
2017-05-25 00:59:41 +02:00
-- Mob spawner doll (rotating icon inside cage)
2017-05-25 02:06:36 +02:00
local doll_def = {
2017-05-25 00:59:41 +02:00
hp_max = 1 ,
2019-03-07 03:53:06 +01:00
physical = false ,
pointable = false ,
2017-05-25 00:59:41 +02:00
visual = " mesh " ,
makes_footstep_sound = false ,
timer = 0 ,
automatic_rotate = math.pi * 2.9 ,
_mob = default_mob , -- name of the mob this doll represents
}
2017-05-25 02:06:36 +02:00
doll_def.get_staticdata = function ( self )
2017-05-25 00:59:41 +02:00
return self._mob
end
2017-05-25 02:06:36 +02:00
doll_def.on_activate = function ( self , staticdata , dtime_s )
2017-05-25 00:59:41 +02:00
local mob = staticdata
2017-05-25 01:14:18 +02:00
if mob == " " or mob == nil then
mob = default_mob
2017-05-25 00:59:41 +02:00
end
2024-05-30 10:29:12 +02:00
-- Handle conversion of mob spawners
local convert_to = ( minetest.registered_entities [ mob ] or { } ) . _convert_to
if convert_to then mob = convert_to end
2017-05-25 01:14:18 +02:00
set_doll_properties ( self.object , mob )
2019-03-06 04:38:57 +01:00
self.object : set_velocity ( { x = 0 , y = 0 , z = 0 } )
self.object : set_acceleration ( { x = 0 , y = 0 , z = 0 } )
2017-05-25 00:59:41 +02:00
self.object : set_armor_groups ( { immortal = 1 } )
end
2017-05-25 02:06:36 +02:00
doll_def.on_step = function ( self , dtime )
2017-05-25 00:59:41 +02:00
-- Check if spawner is still present. If not, delete the entity
2019-02-19 00:06:14 +01:00
self.timer = self.timer + dtime
2019-02-01 06:33:07 +01:00
local n = minetest.get_node_or_nil ( self.object : get_pos ( ) )
2017-05-25 00:59:41 +02:00
if self.timer > 1 then
2017-05-25 06:09:03 +02:00
if n and n.name and n.name ~= " mcl_mobspawners:spawner " then
2017-05-25 00:59:41 +02:00
self.object : remove ( )
end
end
end
2017-05-25 02:06:36 +02:00
doll_def.on_punch = function ( self , hitter ) end
2017-05-25 00:59:41 +02:00
2017-05-25 06:09:03 +02:00
minetest.register_entity ( " mcl_mobspawners:doll " , doll_def )
2017-05-25 00:59:41 +02:00
2019-02-19 00:06:14 +01:00
-- FIXME: Doll can get destroyed by /clearobjects
minetest.register_lbm ( {
label = " Respawn mob spawner dolls " ,
name = " mcl_mobspawners:respawn_entities " ,
nodenames = { " mcl_mobspawners:spawner " } ,
run_at_every_load = true ,
action = function ( pos , node )
respawn_doll ( pos )
end ,
} )
2024-05-30 10:29:12 +02:00
minetest.register_on_mods_loaded ( function ( )
for name , mobinfo in pairs ( minetest.registered_entities ) do
2024-09-15 23:08:37 +02:00
if mobinfo.is_mob and not ( mobinfo.visual_size or mobinfo._convert_to ) then
2024-05-30 10:29:12 +02:00
minetest.log ( " warning " , " Definition for " .. tostring ( name ) .. " is missing field 'visual_size', mob spawners will not work properly " )
end
end
end )