local S = minetest.get_translator("mobs") local max_per_block = tonumber(minetest.settings:get("max_objects_per_block") or 99) -- helper functions local function is_player(player) if player and type(player) == "userdata" and minetest.is_player(player) then return true end end local square = math.sqrt local get_distance = function(a, b) if not a or not b then return 50 end -- nil check and default distance local x, y, z = a.x - b.x, a.y - b.y, a.z - b.z return square(x * x + y * y + z * z) end -- mob spawner local spawner_default = "mobs_animal:pumba 10 15 0 0 0" minetest.register_node("mobs:spawner", { tiles = {"mob_spawner.png"}, drawtype = "glasslike", paramtype = "light", walkable = true, description = S("Mob Spawner"), groups = {cracky = 1, pickaxey = 3}, is_ground_content = false, _mcl_hardness = 1, _mcl_blast_resistance = 5, sounds = mobs.node_sound_stone_defaults(), on_construct = function(pos) local meta = minetest.get_meta(pos) -- setup formspec local head = S("(mob name) (min light) (max light) (amount)" .. " (player distance) (Y offset)") -- text entry formspec meta:set_string("formspec", "size[10,3.5]" .. "label[0.15,0.5;" .. minetest.formspec_escape(head) .. "]" .. "field[1,2.5;8.5,0.8;text;" .. S("Command:") .. ";${command}]") meta:set_string("infotext", S("Spawner Not Active (enter settings)")) meta:set_string("command", spawner_default) end, on_right_click = function(pos, placer) if minetest.is_protected(pos, placer:get_player_name()) then return end end, on_receive_fields = function(pos, formname, fields, sender) if not fields.text or fields.text == "" then return end local meta = minetest.get_meta(pos) local comm = fields.text:split(" ") local name = sender:get_player_name() if minetest.is_protected(pos, name) then minetest.record_protection_violation(pos, name) return end local mob = comm[1] or "" -- mob to spawn local mlig = tonumber(comm[2]) -- min light local xlig = tonumber(comm[3]) -- max light local num = tonumber(comm[4]) -- total mobs in area local pla = tonumber(comm[5]) -- player distance (0 to disable) local yof = tonumber(comm[6]) or 0 -- Y offset to spawn mob if mob ~= "" and mobs.spawning_mobs[mob] and num and num >= 0 and num <= 10 and mlig and mlig >= 0 and mlig <= 15 and xlig and xlig >= 0 and xlig <= 15 and pla and pla >= 0 and pla <= 20 and yof and yof > -10 and yof < 10 then meta:set_string("command", fields.text) meta:set_string("infotext", S("Spawner Active (@1)", mob)) else minetest.chat_send_player(name, S("Mob Spawner settings failed!")) minetest.chat_send_player(name, S("Syntax: “name min_light[0-14] max_light[0-14] " .. "max_mobs_in_area[0 to disable] player_distance[1-20] " .. "y_offset[-10 to 10]”")) end end }) -- spawner abm minetest.register_abm({ label = "Mob spawner node", nodenames = {"mobs:spawner"}, interval = 10, chance = 4, catch_up = false, action = function(pos, node, active_object_count, active_object_count_wider) -- return if too many entities already if active_object_count_wider >= max_per_block then return end -- get meta and command local meta = minetest.get_meta(pos) local comm = meta:get_string("command"):split(" ") -- get settings from command local mob = comm[1] local mlig = tonumber(comm[2]) local xlig = tonumber(comm[3]) local num = tonumber(comm[4]) local pla = tonumber(comm[5]) or 0 local yof = tonumber(comm[6]) or 0 -- 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 --print ("--- mob doesn't exist", mob) return end -- check objects inside 9x9 area around spawner local objs = minetest.get_objects_inside_radius(pos, 9) local count = 0 local ent -- count mob objects of same type in area for _, obj in ipairs(objs) do ent = obj:get_luaentity() if ent and ent.name and ent.name == mob then count = count + 1 end end -- is there too many of same type? if count >= num then return end -- when player distance above 0, spawn mob if player detected and in range if pla > 0 then local in_range, player local players = minetest.get_connected_players() for i = 1, #players do player = players[i] if get_distance(player:get_pos(), pos) <= pla then in_range = true break end end -- player not found if not in_range then return end end -- set medium mob usually spawns in (defaults to air) local reg = minetest.registered_entities[mob].fly_in if not reg or type(reg) == "string" then reg = {(reg or "air")} end -- find air blocks within 5 nodes of spawner local air = minetest.find_nodes_in_area( {x = pos.x - 5, y = pos.y + yof, z = pos.z - 5}, {x = pos.x + 5, y = pos.y + yof, z = pos.z + 5}, reg) -- spawn in random air block if air and #air > 0 then local pos2 = air[math.random(#air)] 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 and minetest.registered_entities[mob] then minetest.add_entity(pos2, mob) end end end })