484 lines
15 KiB
Lua
484 lines
15 KiB
Lua
|
|
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),
|
|
})
|