2019-03-08 02:22:28 +03:00
local S = minetest.get_translator ( " mcl_mobspawners " )
2017-01-16 19:40:08 +03:00
2017-05-25 07:09:03 +03:00
mcl_mobspawners = { }
2017-05-25 03:17:24 +03:00
2017-05-25 05:23:21 +03:00
local default_mob = " mobs_mc:pig "
2017-01-16 19:40:08 +03:00
2018-01-07 18:58:44 +03:00
-- Mob spawner
2017-05-25 05:47:08 +03:00
local spawner_default = default_mob .. " 0 15 4 15 "
2017-05-25 01:59:41 +03:00
local function get_mob_textures ( mob )
2018-01-07 19:50:50 +03: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 01:59:41 +03:00
end
local function find_doll ( pos )
2018-01-08 18:10:42 +03:00
for _ , obj in ipairs ( minetest.get_objects_inside_radius ( pos , 0.5 ) ) do
2017-05-25 01:59:41 +03:00
if not obj : is_player ( ) then
2017-05-25 07:09:03 +03:00
if obj ~= nil and obj : get_luaentity ( ) . name == " mcl_mobspawners:doll " then
2017-05-25 01:59:41 +03:00
return obj
end
end
end
return nil
end
2017-01-16 19:40:08 +03:00
2017-05-25 08:34:53 +03: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 19:29:56 +03: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 } ,
[ " mobs_mc:enderman " ] = { x = 0.8 , y = 0.8 } ,
[ " 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 19:54:40 +03:00
[ " mobs_mc:witch " ] = { x = 0.95 , y = 0.95 } ,
2018-01-07 19:29:56 +03:00
}
2019-03-09 01:26:54 +03: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 19:29:56 +03:00
2017-05-25 02:14:18 +03:00
local function set_doll_properties ( doll , mob )
local mobinfo = minetest.registered_entities [ mob ]
2018-01-07 19:29:56 +03:00
local xs , ys
if doll_size_overrides [ mob ] then
xs = doll_size_overrides [ mob ] . x
ys = doll_size_overrides [ mob ] . y
else
xs = mobinfo.visual_size . x * 0.33333
ys = mobinfo.visual_size . y * 0.33333
end
2017-05-25 02:14:18 +03:00
local prop = {
mesh = mobinfo.mesh ,
textures = get_mob_textures ( mob ) ,
visual_size = {
2018-01-07 19:29:56 +03:00
x = xs ,
y = ys ,
2017-05-25 02:14:18 +03:00
}
}
doll : set_properties ( prop )
2017-05-25 04:09:02 +03:00
doll : get_luaentity ( ) . _mob = mob
2017-05-25 02:14:18 +03:00
end
2019-02-19 02:06:14 +03: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
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 02:59:10 +03: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 06:45:45 +03:00
All the arguments are optional !
2017-05-25 02:59:10 +03:00
2017-05-25 06:45:45 +03:00
* Mob : ID of mob to spawn ( default : mobs_mc : pig )
2017-05-25 02:59:10 +03:00
* MinLight : Minimum light to spawn ( default : 0 )
* MaxLight : Maximum light to spawn ( default : 15 )
2017-05-25 05:47:08 +03: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 02:59:10 +03:00
* YOffset : Y offset to spawn mobs ; 0 to disable ( default : 0 )
] ]
2017-05-25 07:09:03 +03:00
function mcl_mobspawners . setup_spawner ( pos , Mob , MinLight , MaxLight , MaxMobsInArea , PlayerDistance , YOffset )
2018-01-07 18:58:44 +03:00
-- Activate mob spawner and disable editing functionality
2017-05-25 06:45:45 +03:00
if Mob == nil then Mob = default_mob end
2017-05-25 02:59:10 +03:00
if MinLight == nil then MinLight = 0 end
2017-05-25 05:47:08 +03: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 02:59:10 +03: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 18:37:41 +03:00
-- Create doll or replace existing doll
local doll = find_doll ( pos )
if not doll then
doll = spawn_doll ( pos )
end
2017-05-25 02:59:10 +03:00
set_doll_properties ( doll , Mob )
2017-05-25 06:37:13 +03:00
2017-05-25 08:34:53 +03:00
2017-05-25 06:37:13 +03:00
-- Start spawning very soon
local t = minetest.get_node_timer ( pos )
t : start ( 2 )
end
2018-01-07 18:58:44 +03:00
-- Spawn mobs around pos
2017-05-25 06:37:13 +03:00
-- NOTE: The node is timer-based, rather than ABM-based.
2018-01-07 18:58:44 +03:00
local spawn_mobs = function ( pos , elapsed )
2017-05-25 06:37:13 +03:00
-- get meta
local meta = minetest.get_meta ( pos )
-- get settings
local mob = meta : get_string ( " Mob " )
local mlig = meta : get_int ( " MinLight " )
local xlig = meta : get_int ( " MaxLight " )
local num = meta : get_int ( " MaxMobsInArea " )
local pla = meta : get_int ( " PlayerDistance " )
local yof = meta : get_int ( " YOffset " )
-- if amount is 0 then do nothing
if num == 0 then
return
end
-- are we spawning a registered mob?
if not mobs.spawning_mobs [ mob ] then
2018-01-07 18:58:44 +03:00
minetest.log ( " error " , " [mcl_mobspawners] Mob Spawner: Mob doesn't exist: " .. mob )
2017-05-25 06:37:13 +03:00
return
end
-- check objects inside 8× 8 area around spawner
local objs = minetest.get_objects_inside_radius ( pos , 8 )
local count = 0
local ent = nil
local timer = minetest.get_node_timer ( pos )
-- spawn mob if player detected and in range
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 08:34:53 +03:00
--[[ HACK!
2018-01-07 18:58:44 +03:00
The doll may not stay spawned if the mob spawner is placed far away from
2017-05-25 08:34:53 +03:00
players , so we will check for its existance periodically when a player is nearby .
2018-01-07 18:58:44 +03:00
This would happen almost always when the mob spawner is placed by the mapgen .
2017-08-14 18:23:43 +03:00
This is probably caused by a Minetest bug :
https : // github.com / minetest / minetest / issues / 4759
FIXME : Fix this horrible hack .
2017-05-25 08:34:53 +03:00
] ]
local doll = find_doll ( pos )
if not doll then
doll = spawn_doll ( pos )
set_doll_properties ( doll , mob )
end
2017-05-25 06:37:13 +03: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
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 " } )
-- spawn up to 4 mobs in random air blocks
if air then
2019-03-09 01:26:54 +03:00
local max = 4
if spawn_count_overrides [ mob ] then
max = spawn_count_overrides [ mob ]
end
for a = 1 , max do
2017-05-25 06:37:13 +03:00
if # air <= 0 then
-- We're out of space! Stop spawning
break
end
local air_index = math.random ( # air )
local pos2 = air [ air_index ]
local lig = minetest.get_node_light ( pos2 ) or 0
pos2.y = pos2.y + 0.5
-- only if light levels are within range
if lig >= mlig and lig <= xlig then
minetest.add_entity ( pos2 , mob )
end
table.remove ( air , air_index )
end
end
-- Spawn attempt done. Next spawn attempt much later
timer : start ( math.random ( 10 , 39.95 ) )
2017-05-25 02:59:10 +03:00
end
2018-01-07 18:58:44 +03:00
-- The mob spawner node.
2017-08-15 15:19:44 +03: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 07:09:03 +03:00
minetest.register_node ( " mcl_mobspawners:spawner " , {
2017-01-16 19:40:08 +03:00
tiles = { " mob_spawner.png " } ,
drawtype = " glasslike " ,
paramtype = " light " ,
walkable = true ,
2018-01-07 18:58:44 +03:00
description = S ( " Mob Spawner " ) ,
2020-02-19 06:54:17 +03:00
_tt_help = S ( " Makes mobs appear " ) ,
2018-01-08 04:19:00 +03: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 11:50:32 +03: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 02:30:23 +03:00
groups = { pickaxey = 1 , material_stone = 1 , deco_block = 1 } ,
2017-03-11 18:36:05 +03:00
is_ground_content = false ,
2017-02-01 23:09:24 +03:00
drop = " " ,
2017-01-16 19:40:08 +03:00
2017-08-15 15:19:44 +03:00
-- If placed by player, setup spawner with default settings
2017-08-15 15:16:40 +03:00
on_place = function ( itemstack , placer , pointed_thing )
2017-12-13 02:30:23 +03: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 18:58:44 +03:00
minetest.chat_send_player ( name , " Placement denied. You need the “maphack” privilege to place mob spawners. " )
2017-12-13 02:30:23 +03:00
return itemstack
end
2017-08-15 15:16:40 +03: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
if minetest.registered_nodes [ node_under.name ] . buildable_to then
placepos = pointed_thing.under
else
placepos = pointed_thing.above
end
mcl_mobspawners.setup_spawner ( placepos )
end
return new_itemstack
end ,
2017-05-25 01:59:41 +03:00
on_destruct = function ( pos )
2018-01-07 18:37:41 +03:00
-- Remove doll (if any)
2017-05-25 06:45:45 +03:00
local obj = find_doll ( pos )
if obj then
obj : remove ( )
2017-05-25 02:59:10 +03:00
end
2017-01-16 19:40:08 +03:00
end ,
2019-02-19 02:06:14 +03:00
on_punch = function ( pos )
respawn_doll ( pos )
end ,
2018-01-07 18:58:44 +03:00
on_timer = spawn_mobs ,
2017-05-25 06:37:13 +03:00
2017-02-11 20:46:23 +03:00
sounds = mcl_sounds.node_sound_metal_defaults ( ) ,
2018-01-07 18:37:41 +03:00
2020-04-17 22:40:13 +03:00
_mcl_blast_resistance = 5 ,
2017-02-27 20:32:35 +03:00
_mcl_hardness = 5 ,
2017-01-16 19:40:08 +03:00
} )
2017-05-25 01:59:41 +03:00
-- Mob spawner doll (rotating icon inside cage)
2017-05-25 03:06:36 +03:00
local doll_def = {
2017-05-25 01:59:41 +03:00
hp_max = 1 ,
2019-03-07 05:53:06 +03:00
physical = false ,
pointable = false ,
2017-05-25 01:59:41 +03: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 03:06:36 +03:00
doll_def.get_staticdata = function ( self )
2017-05-25 01:59:41 +03:00
return self._mob
end
2017-05-25 03:06:36 +03:00
doll_def.on_activate = function ( self , staticdata , dtime_s )
2017-05-25 01:59:41 +03:00
local mob = staticdata
2017-05-25 02:14:18 +03:00
if mob == " " or mob == nil then
mob = default_mob
2017-05-25 01:59:41 +03:00
end
2017-05-25 02:14:18 +03:00
set_doll_properties ( self.object , mob )
2019-03-06 06:38:57 +03:00
self.object : set_velocity ( { x = 0 , y = 0 , z = 0 } )
self.object : set_acceleration ( { x = 0 , y = 0 , z = 0 } )
2017-05-25 01:59:41 +03:00
self.object : set_armor_groups ( { immortal = 1 } )
end
2017-05-25 03:06:36 +03:00
doll_def.on_step = function ( self , dtime )
2017-05-25 01:59:41 +03:00
-- Check if spawner is still present. If not, delete the entity
2019-02-19 02:06:14 +03:00
self.timer = self.timer + dtime
2019-02-01 08:33:07 +03:00
local n = minetest.get_node_or_nil ( self.object : get_pos ( ) )
2017-05-25 01:59:41 +03:00
if self.timer > 1 then
2017-05-25 07:09:03 +03:00
if n and n.name and n.name ~= " mcl_mobspawners:spawner " then
2017-05-25 01:59:41 +03:00
self.object : remove ( )
end
end
end
2017-05-25 03:06:36 +03:00
doll_def.on_punch = function ( self , hitter ) end
2017-05-25 01:59:41 +03:00
2017-05-25 07:09:03 +03:00
minetest.register_entity ( " mcl_mobspawners:doll " , doll_def )
2017-05-25 01:59:41 +03:00
2019-02-19 02:06:14 +03: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 ,
} )
2017-05-25 00:12:13 +03:00