518 lines
17 KiB
Lua
518 lines
17 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: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)
|
|
|
|
-- No spawners registered
|
|
if spawner_count == 0 then return end
|
|
|
|
if maxp.y < 0 then return end
|
|
math.randomseed(seed)
|
|
local cnt = 0
|
|
|
|
local perlin1 = minetest.env:get_perlin(perl1.SEED1, perl1.OCTA1, perl1.PERS1, perl1.SCAL1)
|
|
local noise1 = perlin1:get2d({x=minp.x,y=minp.y})--,z=minp.z})
|
|
|
|
if noise1 > 0.25 or noise1 < -0.26 then
|
|
local mpos = {x=math.random(minp.x,maxp.x), y=math.random(minp.y,maxp.y), z=math.random(minp.z,maxp.z)}
|
|
|
|
local p2 = minetest.find_node_near(mpos, 25, {
|
|
"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"
|
|
})
|
|
while p2 == nil and cnt < 10 do
|
|
cnt = cnt+1
|
|
mpos = {x=math.random(minp.x,maxp.x), y=math.random(minp.y,maxp.y), z=math.random(minp.z,maxp.z)}
|
|
p2 = minetest.find_node_near(mpos, 25, {
|
|
"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"
|
|
})
|
|
end
|
|
if p2 == nil then return end
|
|
if p2.y < 0 then return end
|
|
|
|
local off = 0
|
|
|
|
-- Simpler finding routine - check if node immediately above is air,
|
|
-- and if node 16 blocks above is air
|
|
minetest.log("Checking pos to spawn: "..minetest.pos_to_string(p2))
|
|
local next_node_above = minetest.get_node_or_nil({x=p2.x, y=p2.y+1, z=p2.z})
|
|
minetest.log("Found next node above: "..dump(next_node_above))
|
|
local next_mapblock_above = minetest.get_node_or_nil({x=p2.x, y=p2.y+16, z=p2.z})
|
|
minetest.log("Found next node above mapblock: "..dump(next_mapblock_above))
|
|
|
|
if is_air_or_buildable(next_node_above) and is_air_or_buildable(next_mapblock_above) then
|
|
|
|
-- 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, p2)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- local opos1 = {x=p2.x+22,y=p2.y-1,z=p2.z+22}
|
|
-- local opos2 = {x=p2.x+22,y=p2.y-1,z=p2.z}
|
|
-- local opos3 = {x=p2.x,y=p2.y-1,z=p2.z+22}
|
|
-- local opos1_n = minetest.get_node_or_nil(opos1)
|
|
-- local opos2_n = minetest.get_node_or_nil(opos2)
|
|
-- local opos3_n = minetest.get_node_or_nil(opos3)
|
|
-- if opos1_n and opos1_n.name and opos1_n.name == "air" then
|
|
-- p2 = ground(opos1, p2)
|
|
-- end
|
|
-- if opos2_n and opos2_n.name and opos2_n.name == "air" then
|
|
-- p2 = ground(opos2, p2)
|
|
-- end
|
|
-- if opos3_n and opos3_n.name and opos3_n.name == "air" then
|
|
-- p2 = ground(opos3, p2)
|
|
-- end
|
|
-- p2.y = p2.y - 3
|
|
-- if p2.y < 0 then p2.y = 0 end
|
|
--if minetest.find_node_near(p2, 25, {"default:water_source"}) ~= nil or minetest.find_node_near(p2, 22, {"default:dirt_with_grass"}) ~= nil or minetest.find_node_near(p2, 52, {"default:sandstonebrick"}) ~= nil then return end
|
|
|
|
--minetest.after(0.8,make,p2)
|
|
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),
|
|
})
|