local spawners = {} spawner_count = 0 natspawner.on_mob_die = function(self, pos) if self.spawn and self.spawn.pos then local meta = minetest.get_meta(self.spawn.pos) meta:set_int("entity_killed_count", meta:get_int("entity_killed_count") + 1) if self.variation then meta:set_int("spawned_variation_count", meta:get_int("spawned_variation_count") - 1) end end end local function spawn(pos, force) local meta = minetest.get_meta(pos) local spawner_name = meta:get_string("spawner_name") if spawner_name then local spawner = spawners[spawner_name] local replacement_node_name = meta:get_string("replaced_node_name") local timer = minetest.get_node_timer(pos) local interval = math.random(spawner.min_spawn_interval, spawner.max_spawn_interval) if replacement_node_name and replacement_node_name ~= "" then -- Check if variations are forced local force_variation = false --minetest.log("Spawner data: "..dump(spawners[spawner_name].replacement_nodes[replacement_node_name])) if spawners[spawner_name].replacement_nodes[replacement_node_name].variations_only == true then force_variation = true end -- Entity names for which we are going to search local search_names = {} local variation_names = {} local raw_var_names = spawners[spawner_name].replacement_nodes[replacement_node_name].variations if raw_var_names then for name,_ in pairs(raw_var_names) do variation_names[#variation_names + 1] = name end end -- If variations are forced, only take the variation names if force_variation == true then search_names = variation_names else search_names = spawners[spawner_name].entities.all_entity_names end -- Search for nearby players and entities local objects = minetest.get_objects_inside_radius(pos, 10) local entity_count = 0 for _,object in pairs(objects) do if object then if object:is_player() and not force then minetest.log("Player too close") -- Re-schedule timer:start(interval) minetest.log("Next spawning scheduled in "..interval.." seconds") return elseif object:get_luaentity() and object:get_luaentity().entity_name then -- Check if the found entity has the name of one the entities -- that this spawner cares about for i = 1, #search_names do if object:get_luaentity().entity_name == search_names[i] then entity_count = entity_count + 1 break end end end end end -- Create a list of names we are to spawn local spawn_names = {} if force_variation then spawn_names = variation_names elseif #variation_names > 0 then spawn_names[#spawn_names + 1] = spawners[spawner_name].entities.default for i = 1, #variation_names do spawn_names[#spawn_names + 1] = variation_names[i] end else spawn_names[#spawn_names + 1] = spawners[spawner_name].entities.default end -- Choose an entity name. local is_variation = false if force_variation == true then entity_name = spawn_names[math.random(1, #spawn_names)] --minetest.log("New entity name: "..dump(entity_name)) is_variation = true minetest.log("Spawning variation "..entity_name.." (forced)") else local variation_chance = math.random(1, 10) if variation_chance > 7 then entity_name = spawn_names[math.random(1, #spawn_names)] -- Increase variation count meta:set_int("spawned_variation_count", meta:get_int("spawned_variation_count") + 1) is_variation = true minetest.log("Spawned variation count: "..meta:get_int("spawned_variation_count")) minetest.log("Spawning variation "..entity_name.." (not forced)") else entity_name = spawners[spawner_name].entities.default end end -- Validation: enforce entity max spawn count local max_mob_count = spawner.max_mob_count if force_variation then --minetest.log("Entity name: "..dump(entity_name)) --minetest.log("Mob data: "..dump(spawners[spawner_name] -- .replacement_nodes[replacement_node_name] -- .variations[entity_name])) max_mob_count = spawners[spawner_name] .replacement_nodes[replacement_node_name] .variations[entity_name] .max_count or spawner.max_mob_count entity_count = meta:get_int("spawned_variation_count") end minetest.log("Entity count: "..dump(entity_count)) minetest.log("Max mob count: "..dump(max_mob_count)) if force or (entity_count <= max_mob_count) then -- Spawn local spawn_pos = { x=pos.x + math.random(0, spawner.spawn_radius), y=pos.y+3, z=pos.z + math.random(0, spawner.spawn_radius) } -- Check spawn position - if not air, then spawn just above the spawner local spawn_node = minetest.get_node_or_nil(spawn_pos) if spawn_node and spawn_node.name ~= "air" then spawn_pos = pos end minetest.log("Spawning "..entity_name.." at pos "..minetest.pos_to_string(spawn_pos)) local entity = minetest.add_entity(spawn_pos, entity_name) if entity then entity:get_luaentity().entity_name = entity_name entity:get_luaentity().spawn = { pos = pos } if is_variation == true then entity:get_luaentity().variation = true end end minetest.log("Next spawning scheduled in "..interval.." seconds") else minetest.log("Max spawn limit reached") -- Re-calulate interval using deactivation times interval = math.random(spawner.min_deactivation_time, spawner.max_deactivation_time) minetest.log("Deactivating spawner for "..interval.." seconds") end end -- Re-schedule timer:start(interval) end end natspawner.register_spawner = function(spawner_name, def) spawners[spawner_name] = { entities = def.entities, replacement_nodes = def.node_replacement_map, min_player_distance = def.min_player_distance or 10, max_spawn_interval = def.max_spawn_interval or 300, --300 min_spawn_interval = def.min_spawn_interval or 120, --120 spawn_radius = def.spawn_radius or 15, min_kill_count = def.min_kill_count or 20, max_kill_count = def.max_kill_count or 35, min_deactivation_time = def.min_deactivation_time or 60, max_deactivation_time = def.max_deactivation_time or 120, max_mob_count = def.max_mob_count or 15, spawn_on_dig = def.spawn_on_dig } -- Process all names local all_names = {} all_names[#all_names + 1] = def.entities.default for i = 1, #def.entities.others do all_names[#all_names + 1] = def.entities.others[i] end spawners[spawner_name].entities.all_entity_names = all_names spawner_count = spawner_count + 1 -- Register all spawners for _,val in pairs(def.node_replacement_map) do minetest.register_node("natspawner:"..spawner_name..val.name, { description = "Spawner", drop = "natspawner:"..spawner_name.."_"..val.name, tiles = val.tiles, groups = {crumbly=2, soil = 2}, sounds = default.node_sound_sand_defaults(), on_construct = function(pos) local meta = minetest.get_meta(pos) meta:set_string("spawner_name", spawner_name) meta:set_int("entity_spawn_count", 0) meta:set_int("entity_killed_count", 0) meta:set_int("spawned_variation_count", 0) meta:set_int("max_variation_count", math.random(def.min_variation_count or 0, def.max_variation_count or 0)) meta:set_int("next_deactivation_count", math.random(def.min_kill_count, def.max_kill_count)) meta:set_int("next_deactivation_time", math.random(def.min_deactivation_time, def.max_deactivation_time)) local timer = minetest.get_node_timer(pos) timer:start(def.min_spawn_interval) end, on_timer = function(pos) spawn(pos) end, on_dig = function(pos, node, digger) local meta = minetest.get_meta(pos) local entity_killed_count = meta:get_int("entity_killed_count") local next_deactivation_count = meta:get_int("next_deactivation_count") if (entity_killed_count < next_deactivation_count) then if def.spawn_on_dig then spawn(pos, true) end minetest.chat_send_player(digger:get_player_name(), "You have killed "..entity_killed_count.." enemies!") return false else minetest.node_dig(pos, node, digger) end end }) end end natspawner.register_spawner("zombie", { entities = { default = "natspawner:zombie", others = { "natspawner:giant_zombie", "natspawner:ice_zombie", "natspawner:sand_zombie", "natspawner:sand_zombie_small" }, }, node_replacement_map = { ["default:stone"] = { name = "stone_spawner", tiles = {"default_stone.png^[cracko:1:2", "default_stone.png", {name = "default_stone.png"}} }, ["default:sand"] = { name = "sand_spawner", tiles = {"default_sand.png^[cracko:1:2", "default_sand.png", {name = "default_sand.png"}}, variations = { ["natspawner:sand_zombie"] = {}, ["natspawner:sand_zombie_small"] = { max_count = 5 } }, variations_only = true }, ["default:desert_sand"] = { name = "desert_sand_spawner", tiles = {"default_desert_sand.png^[cracko:1:2", "default_desert_sand.png", {name = "default_desert_sand.png"}}, variations = { ["natspawner:sand_zombie"] = {}, ["natspawner:sand_zombie_small"] = { max_count = 5 } }, variations_only = true }, ["default:silver_sand"] = { name = "silver_sand_spawner", tiles = {"default_silver_sand.png^[cracko:1:2", "default_silver_sand.png", {name = "default_silver_sand.png"}}, variations = { ["natspawner:sand_zombie"] = {}, ["natspawner:sand_zombie_small"] = { max_count = 5 } }, variations_only = true }, ["default:ice"] = { name = "ice_spawner", tiles = {"default_ice.png^[cracko:1:2", "default_ice.png", {name = "default_ice.png"}}, variations = { ["natspawner:ice_zombie"] = { max_count = 15, min_count = 10 } }, variations_only = true }, ["default:snowblock"] = { name = "snowblock_spawner", tiles = {"default_snow.png^[cracko:1:2", "default_snow.png", {name = "default_snow.png"}}, variations = { ["natspawner:ice_zombie"] = { max_count = 15, min_count = 10 } }, variations_only = true }, ["default:dirt_with_grass"] = { name = "dirt_with_grass_spawner", tiles = {"default_grass.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_grass_side.png"}}, variations = { ["natspawner:giant_zombie"] = { max_count = 1 } } }, ["default:dirt_with_dry_grass"] = { name = "dirt_with_dry_grass_spawner", tiles = {"default_dry_grass.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_dry_grass_side.png"}} }, ["default:dirt_with_snow"] = { name = "dirt_with_snow_spawner", tiles = {"default_snow.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_snow_side.png"}}, variations = { ["natspawner:ice_zombie"] = { max_count = 15, min_count = 10 } }, variations_only = true }, ["default:dirt_with_rainforest_litter"] = { name = "dirt_with_rainforest_litter_spawner", tiles = {"default_rainforest_litter.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_rainforest_litter_side.png"}}, variations = { ["natspawner:giant_zombie"] = { max_count = 1 } } }, ["default:dirt_with_coniferous_litter"] = { name = "dirt_with_coniferous_litter_spawner", tiles = {"default_coniferous_litter.png^[cracko:1:2", "default_dirt.png", {name = "default_dirt.png^default_coniferous_litter_side.png"}} }, ["default:permafrost"] = { name = "permafrost_spawner", tiles = {"default_permafrost.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png"}}, variations = { ["natspawner:ice_zombie"] = {} } }, ["default:permafrost_with_stones"] = { name = "permafrost_with_stones_spawner", tiles = {"default_permafrost.png^default_stones.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png"}}, variations = { ["natspawner:ice_zombie"] = {} } }, ["default:permafrost_with_moss"] = { name = "permafrost_with_moss_spawner", tiles = {"default_moss.png^[cracko:1:2", "default_permafrost.png", {name = "default_permafrost.png^default_moss_side.png"}}, variations = { ["natspawner:ice_zombie"] = {} } } }, min_player_distance = 5, --20 max_mob_count = 5, --15 max_spawn_interval = 10, --300 min_spawn_interval = 5, --120 spawn_radius = 5, min_kill_count = 5, max_kill_count = 10, min_deactivation_time = 5, max_deactivation_time = 5, spawn_on_dig = true }) local perl1 = {SEED1 = 9130, OCTA1 = 3, PERS1 = 0.5, SCAL1 = 250} -- Values should match minetest mapgen V6 desert noise. local function hlp_fnct(pos, name) local n = minetest.get_node_or_nil(pos) if n and n.name and n.name == name then return true else return false end end local function ground(pos, old) local p2 = pos while hlp_fnct(p2, "air") do p2.y = p2.y -1 end if p2.y < old.y then return p2 else return old end end local function is_air_or_buildable(node) return node and node.name and (node.name == "air" or minetest.registered_nodes[node.name].buildable_to == true) end minetest.register_on_generated(function(minp, maxp, seed) if spawner_count == 0 then return end --minetest.log("Max pos: "..minetest.pos_to_string(maxp)) --minetest.log("Min pos: "..minetest.pos_to_string(minp)) local middle_pos = { x=(maxp.x + minp.x)/2, y=(maxp.y + minp.y)/2 , z=(maxp.z + minp.z)/2 } --minetest.log("Mid pos: "..minetest.pos_to_string(middle_pos)) local spawn_pos = nil spawn_pos = minetest.find_node_near(middle_pos, 40, { "default:dirt_with_grass", "default:dirt_with_snow", "default:dirt_with_coniferous_litter", "default:dirt_with_rainforest_litter", "default:permafrost", "default:permafrost_with_moss", "default:permafrost_with_stones", "default:sand", "default:desert_sand", "default:silver_sand" }) if (spawn_pos ~= nil) then minetest.log("Candidate pos: "..minetest.pos_to_string(spawn_pos)) -- Check pos above local node_above = minetest.get_node_or_nil({x=spawn_pos.x, y=spawn_pos.y+1, z=spawn_pos.z}) if node_above and node_above.name then if (is_air_or_buildable(node_above)) then minetest.log("Found a good pos at: "..minetest.pos_to_string(spawn_pos)) -- Choose a spawner for i = 1, spawner_count do local spawner_name = next(spawners) local chance = math.random(1, spawner_count) if chance == i then -- Create spawner minetest.after(0.8, function(spawner_name, pos) local node_map = spawners[spawner_name].replacement_nodes local node_to_replace = minetest.get_node(pos) if node_map[node_to_replace.name] then minetest.log("Replacing "..node_to_replace.name.." with "..spawner_name) minetest.log("Creating advanced spawner at "..minetest.pos_to_string(pos)) minetest.set_node(pos, {name="natspawner:"..spawner_name..node_map[node_to_replace.name].name}) local meta = minetest.get_meta(pos) meta:set_string("replaced_node_name", node_to_replace.name) else minetest.log("Unable to find replacement for node '"..dump(node_to_replace.name).."'") end end, spawner_name, spawn_pos) end end end end end end) --mobs:register_egg("natspawner:zombie", "Zombie", "zombie_head.png", 0) minetest.register_craftitem("natspawner:rotten_flesh", { description = "Rotten Flesh", inventory_image = "mobs_rotten_flesh.png", on_use = minetest.item_eat(-5), })