Added daytime check

Fixed collisionbox check
Fixed relative/absolute height check
Added (partial) spawndef check
Added support for spawning multiple entities at once
master
sapier 2013-12-08 01:07:34 +01:00
parent 00989dd1c6
commit 3c9262a1ac
6 changed files with 255 additions and 78 deletions

View File

@ -97,8 +97,16 @@ Spawning definition:
deviation = 2, -- maximum number of nodes not matching flat check deviation = 2, -- maximum number of nodes not matching flat check
}, },
daytimes = -- do only spawn within these daytimes
{
daytime_def_1,
daytime_def_2,
...
}
collisionbox = {}, -- collisionbox of entity to spawn (usually same as used for entiy itself) collisionbox = {}, -- collisionbox of entity to spawn (usually same as used for entiy itself)
spawn_interval = 200, -- [MANDATORY] interval to try to spawn a entity spawn_interval = 200, -- [MANDATORY] interval to try to spawn a entity
spawns_per_interval = 1, -- try to spawn multiple mobs (if time available)
custom_check = fct(pos), -- a custom check to be called return true for pass, false for not pass custom_check = fct(pos), -- a custom check to be called return true for pass, false for not pass
cyclic_spawning = true -- spawn per spawner step (defaults to true) cyclic_spawning = true -- spawn per spawner step (defaults to true)
} }
@ -152,6 +160,12 @@ spawn_inside definition (list of nodenames):
"default:water_flowing" "default:water_flowing"
} }
Daytime definition:
{
begin = 0.0, --minimum daytime
stop = 0.25, --maximum daytime
}
Statistics: Statistics:
{ {
session = session =

View File

@ -15,7 +15,14 @@
function adv_spawning.register(spawner_name,spawning_def) function adv_spawning.register(spawner_name,spawning_def)
if adv_spawning.spawner_definitions[spawner_name] == nil then if adv_spawning.spawner_definitions[spawner_name] == nil then
--TODO validate spawning definition
if not adv_spawning.verify_check_entities_around(spawning_def.entities_around) then
return false
end
if not adv_spawning.verify_check_nodes_around(spawning_def.nodes_around) then
return false
end
adv_spawning.spawner_definitions[spawner_name] = spawning_def adv_spawning.spawner_definitions[spawner_name] = spawning_def
print("ADV_SPAWNING: registering spawner \"" .. spawner_name .. "\"") print("ADV_SPAWNING: registering spawner \"" .. spawner_name .. "\"")

View File

@ -8,7 +8,7 @@
-- --
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local version = "0.0.2" local version = "0.0.3"
if adv_spawning ~= nil then if adv_spawning ~= nil then
minetest.log("error","MOD: adv_spawning requires adv_spawning variable to be available") minetest.log("error","MOD: adv_spawning requires adv_spawning variable to be available")
@ -22,6 +22,7 @@ adv_spawning = {}
local adv_modpath = minetest.get_modpath("adv_spawning") local adv_modpath = minetest.get_modpath("adv_spawning")
dofile (adv_modpath .. "/internal.lua") dofile (adv_modpath .. "/internal.lua")
dofile (adv_modpath .. "/spawndef_checks.lua")
dofile (adv_modpath .. "/api.lua") dofile (adv_modpath .. "/api.lua")
dofile (adv_modpath .. "/spawn_seed.lua") dofile (adv_modpath .. "/spawn_seed.lua")

View File

@ -15,6 +15,12 @@
-- @return maximum of a and b -- @return maximum of a and b
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function MAX(a,b) function MAX(a,b)
if a == nil then
return b
end
if b == nil then
return a
end
if a > b then if a > b then
return a return a
else else
@ -29,6 +35,12 @@ end
-- @return minimum of a and b -- @return minimum of a and b
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function MIN(a,b) function MIN(a,b)
if a == nil then
return b
end
if b == nil then
return a
end
if a > b then if a > b then
return b return b
else else
@ -272,6 +284,11 @@ function adv_spawning.handlespawner(spawnername,spawnerpos,minp,maxp)
local permanent_error = false local permanent_error = false
local spawndef = adv_spawning.spawner_definitions[spawnername] local spawndef = adv_spawning.spawner_definitions[spawnername]
if not adv_spawning.check_daytime(spawndef.daytimes) then
adv_spawning.log("info","didn't meet daytime check")
return false,nil
end
local max_x = spawnerpos.x + adv_spawning.spawner_distance/2 local max_x = spawnerpos.x + adv_spawning.spawner_distance/2
local min_x = spawnerpos.x - adv_spawning.spawner_distance/2 local min_x = spawnerpos.x - adv_spawning.spawner_distance/2
@ -298,7 +315,7 @@ function adv_spawning.handlespawner(spawnername,spawnerpos,minp,maxp)
new_pos.x = math.random(min_x,max_x) new_pos.x = math.random(min_x,max_x)
new_pos.z = math.random(min_z,max_z) new_pos.z = math.random(min_z,max_z)
local continue = false
--check if entity is configured to spawn at surface --check if entity is configured to spawn at surface
if spawndef.relative_height == nil or if spawndef.relative_height == nil or
@ -308,7 +325,9 @@ function adv_spawning.handlespawner(spawnername,spawnerpos,minp,maxp)
spawndef.spawn_inside) spawndef.spawn_inside)
else else
new_pos.y = adv_spawning.get_relative_pos(lower_y,upper_y,new_pos, new_pos.y = adv_spawning.get_relative_pos(lower_y,upper_y,new_pos,
spawndef.spawn_inside,spawndef.relative_height) spawndef.spawn_inside,
spawndef.relative_height,
spawndef.absolute_height)
end end
--check if we did found a position within relative range --check if we did found a position within relative range
@ -317,126 +336,112 @@ function adv_spawning.handlespawner(spawnername,spawnerpos,minp,maxp)
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't find a suitable y pos " minetest.pos_to_string(new_pos) .. " didn't find a suitable y pos "
.. lower_y .. "<-->" .. upper_y ) .. lower_y .. "<-->" .. upper_y )
continue = true return false,nil
end end
--check absolute height --check absolute height
if not continue and if not adv_spawning.check_absolute_height(new_pos,spawndef.absolute_height) then
not adv_spawning.check_absolute_height(new_pos,spawndef.absolute_height) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet absolute height check") minetest.pos_to_string(new_pos) .. " didn't meet absolute height check")
continue = true return false,true
permanent_error = true
end end
--check surface --check surface
--NOTE needs to be done before collision box check as y pos may be modified there --NOTE needs to be done before collision box check as y pos may be modified there
if not continue and if not adv_spawning.check_surface(new_pos,
not adv_spawning.check_surface(new_pos,
spawndef.surfaces, spawndef.surfaces,
spawndef.relative_height, spawndef.relative_height,
spawndef.spawn_inside) then spawndef.spawn_inside) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet surface check") minetest.pos_to_string(new_pos) .. " didn't meet surface check")
continue = true return false,nil
end end
--flat area check --flat area check
--NOTE needs to be done before collision box check as y pos may be modified there --NOTE needs to be done before collision box check as y pos may be modified there
if not continue and if not adv_spawning.check_flat_area(new_pos,
not adv_spawning.check_flat_area(new_pos,
spawndef.flat_area, spawndef.flat_area,
spawndef.spawn_inside, spawndef.spawn_inside,
spawndef.surfaces) then spawndef.surfaces) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet flat area check") minetest.pos_to_string(new_pos) .. " didn't meet flat area check")
continue = true return false,nil
end end
--check collisionbox --check collisionbox
if not continue then local checkresult,y_pos =
local checkresult,y_pos = adv_spawning.check_collisionbox(new_pos,
adv_spawning.check_collisionbox(new_pos, spawndef.collisionbox,spawndef.spawn_inside)
spawndef.collisionbox,spawndef.spawn_inside)
if checkresult and y_pos ~= nil then if checkresult and y_pos ~= nil then
new_pos.y = y_pos new_pos.y = y_pos
end end
if not checkresult then if not checkresult then
continue = true adv_spawning.log("info",
end minetest.pos_to_string(new_pos) .. " didn't meet collisionbox check")
return false,nil
end end
--check entities around --check entities around
if not continue and if not adv_spawning.check_entities_around(new_pos,spawndef.entities_around) then
not adv_spawning.check_entities_around(new_pos,spawndef.entities_around) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet entities check") minetest.pos_to_string(new_pos) .. " didn't meet entities check")
continue = true return false,nil
end end
--check nodes around --check nodes around
if not continue and if not adv_spawning.check_nodes_around(new_pos,spawndef.nodes_around) then
not adv_spawning.check_nodes_around(new_pos,spawndef.nodes_around) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet nodes check") minetest.pos_to_string(new_pos) .. " didn't meet nodes check")
continue = true return false,nil
end end
--check light around --check light around
if not continue and if not adv_spawning.check_light_around(new_pos,spawndef.light_around) then
not adv_spawning.check_light_around(new_pos,spawndef.light_around) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet light check") minetest.pos_to_string(new_pos) .. " didn't meet light check")
continue = true return false,nil
end end
--check humidity --check humidity
if not continue and if not adv_spawning.check_humidity_around(new_pos,spawndef.humidity_around) then
not adv_spawning.check_humidity_around(new_pos,spawndef.humidity_around) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet humidity check") minetest.pos_to_string(new_pos) .. " didn't meet humidity check")
continue = true return false,nil
end end
--check temperature --check temperature
if not continue and if not adv_spawning.check_temperature_around(new_pos,spawndef.temperature_around) then
not adv_spawning.check_temperature_around(new_pos,spawndef.temperature_around) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet temperature check") minetest.pos_to_string(new_pos) .. " didn't meet temperature check")
continue = true return false,nil
end end
--custom check --custom check
if not continue and if (spawndef.custom_check ~= nil and
(spawndef.custom_check ~= nil and
type(spawndef.custom_check) == "function") then type(spawndef.custom_check) == "function") then
if not spawndef.custom_check(new_pos) then if not spawndef.custom_check(new_pos) then
adv_spawning.log("info", adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet custom check") minetest.pos_to_string(new_pos) .. " didn't meet custom check")
continue = true return false,nil
end end
end end
--do spawn --do spawn
if not continue then --print("Now spawning: " .. spawndef.spawnee .. " at " ..
print("Now spawning: " .. spawndef.spawnee .. " at " .. -- minetest.pos_to_string(new_pos))
minetest.pos_to_string(new_pos))
if type(spawndef.spawnee) == "function" then if type(spawndef.spawnee) == "function" then
spawndef.spawnee(new_pos) spawndef.spawnee(new_pos)
else else
minetest.add_entity(new_pos,spawndef.spawnee) minetest.add_entity(new_pos,spawndef.spawnee)
end
adv_spawning.statistics.session.entities_created =
adv_spawning.statistics.session.entities_created +1
return true
end end
return false,permanent_error adv_spawning.statistics.session.entities_created =
adv_spawning.statistics.session.entities_created +1
return true
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -455,14 +460,23 @@ function adv_spawning.get_surface(y_min,y_max,new_pos,spawn_inside)
local spawnable_nodes = local spawnable_nodes =
minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside) minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside)
if #spawnable_nodes == 0 then
return nil
end
local spawnable_node_passed = false
for i=y_max, y_min, -1 do for i=y_max, y_min, -1 do
local pos = { x=new_pos.x,z=new_pos.z,y=i} local pos = { x=new_pos.x,z=new_pos.z,y=i}
if not adv_spawning.contains_pos(spawnable_nodes,pos) then if not adv_spawning.contains_pos(spawnable_nodes,pos) then
local node = minetest.get_node(pos) local node = minetest.get_node(pos)
if node.name ~= "ignore" then if node.name ~= "ignore" and
spawnable_node_passed then
return i+1 return i+1
end end
else
spawnable_node_passed = true
end end
end end
@ -476,13 +490,19 @@ end
-- @param new_pos position to spawn at -- @param new_pos position to spawn at
-- @param spawn_inside nodes to spawn at -- @param spawn_inside nodes to spawn at
-- @param relative_height -- @param relative_height
-- @param absolute_height
-- @return y-value of last spawnable node -- @return y-value of last spawnable node
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function adv_spawning.get_relative_pos(y_min,y_max,new_pos,spawn_inside,relative_height) function adv_spawning.get_relative_pos(y_min,y_max,new_pos,spawn_inside,relative_height,absolute_height)
local y_val = adv_spawning.get_surface(y_min,y_max,new_pos,spawn_inside) local y_val = adv_spawning.get_surface(y_min,y_max,new_pos,spawn_inside)
if y_val == nil then if y_val == nil then
return nil if (relative_height.min ~= nil or
relative_height.max ~= nil) then
return nil
else
y_val = y_min
end
end end
local top_pos = { x=new_pos.x, z=new_pos.z, y=y_max} local top_pos = { x=new_pos.x, z=new_pos.z, y=y_max}
@ -498,12 +518,21 @@ function adv_spawning.get_relative_pos(y_min,y_max,new_pos,spawn_inside,relative
end end
end end
top_pos.y = MIN(absolute_height.max,top_pos.y)
bottom_pos.y = MAX(absolute_height.min,bottom_pos.y)
if top_pos.y < bottom_pos.y then
--print("Invalid interval: " .. bottom_pos.y .. "<-->" .. top_pos.y)
return nil
end
local spawnable_nodes = local spawnable_nodes =
minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside) minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside)
if #spawnable_nodes > 0 then if #spawnable_nodes > 0 then
return spawnable_nodes[math.random(1,#spawnable_nodes)].y return spawnable_nodes[math.random(1,#spawnable_nodes)].y
else else
--print("no suitable nodes" .. bottom_pos.y .. "<-->" .. top_pos.y)
return nil return nil
end end
end end
@ -609,6 +638,46 @@ function adv_spawning.contains(table_to_check,value)
return false return false
end end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_daytimes
-- @param table_to_check
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_daytime(daytimedefs)
if daytimedefs == nil then
return true
end
local current_time = minetest.get_timeofday()
local match = false
for i=1,#daytimedefs,1 do
if daytimedefs[i].begin ~= nil and
daytimedefs[i].stop ~= nil then
if current_time < daytimedefs[i].stop and
current_time > daytimedefs[i].begin then
match = true
break
end
end
if daytimedefs[i].begin ~= nil and
current_time > daytimedefs[i].begin then
match = true
break
end
if daytimedefs[i].stop ~= nil and
current_time < daytimedefs[i].stop then
match = true
break
end
end
return match
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_nodes_around -- @function [parent=#adv_spawning] check_nodes_around
-- @param pos position to validate -- @param pos position to validate
@ -632,12 +701,12 @@ function adv_spawning.check_nodes_around(pos,nodes_around)
if nodes_around[i].type == "MIN" then if nodes_around[i].type == "MIN" then
if found == nil then if found == nil then
print("not enough: " .. dump(nodes_around[i].name) .. " around") --print("not enough: " .. dump(nodes_around[i].name) .. " around")
return false return false
end end
else else
if found ~= nil then if found ~= nil then
print("to many: " .. dump(nodes_around[i].name) .. " around " .. dump(found)) --print("to many: " .. dump(nodes_around[i].name) .. " around " .. dump(found))
return false return false
end end
end end
@ -654,11 +723,16 @@ function adv_spawning.check_nodes_around(pos,nodes_around)
if nodes_around[i].type == "MIN" and if nodes_around[i].type == "MIN" and
#found_nodes < nodes_around[i].threshold then #found_nodes < nodes_around[i].threshold then
--print("Found MIN: " .. dump(nodes_around[i].name) ..
-- "\n at locations: " .. dump(found_nodes))
--print ("Only " .. #found_nodes .. "/" .. nodes_around[i].threshold)
return false return false
end end
if nodes_around[i].type == "MAX" and if nodes_around[i].type == "MAX" and
#found_nodes > nodes_around[i].threshold then #found_nodes > nodes_around[i].threshold then
--print("Found MAX: " .. dump(nodes_around[i].name) ..
-- "\n at locations: " .. dump(found_nodes))
return false return false
end end
end end
@ -682,7 +756,6 @@ function adv_spawning.check_entities_around(pos,entities_around)
local entity_in_range = local entity_in_range =
minetest.get_objects_inside_radius(pos, entities_around[i].distance) minetest.get_objects_inside_radius(pos, entities_around[i].distance)
if entities_around[i].entityname == nil then if entities_around[i].entityname == nil then
if entities_around[i].type == "MIN" and if entities_around[i].type == "MIN" and
#entity_in_range < entities_around[i].threshold then #entity_in_range < entities_around[i].threshold then
@ -942,8 +1015,7 @@ function adv_spawning.check_collisionbox(pos,collisionbox,spawn_inside)
for z=minp.z,maxp.z,1 do for z=minp.z,maxp.z,1 do
for x=minp.x,maxp.x,1 do for x=minp.x,maxp.x,1 do
local checkpos = {x=x,y=y,z=z} local checkpos = {x=x,y=y,z=z}
if not adv_spawning.is_same_pos(checkpos,lastpos) then
if adv_spawning.is_same_pos(checkpos,lastpos) then
local node = minetest.get_node(checkpos) local node = minetest.get_node(checkpos)
if not adv_spawning.contains(spawn_inside,node.name) then if not adv_spawning.contains(spawn_inside,node.name) then

View File

@ -46,28 +46,38 @@ function adv_spawning.seed_step(self,dtime)
local rand_spawner = math.random(1,#self.pending_spawners) local rand_spawner = math.random(1,#self.pending_spawners)
local key = self.pending_spawners[rand_spawner] local key = self.pending_spawners[rand_spawner]
local tries = 1
if adv_spawning.handlespawner(key,self.object:getpos()) then if adv_spawning.spawner_definitions[key].spawns_per_interval ~= nil then
self.spawning_data[key] = tries = adv_spawning.spawner_definitions[key].spawns_per_interval
adv_spawning.spawner_definitions[key].spawn_interval
else
self.spawning_data[key] =
adv_spawning.spawner_definitions[key].spawn_interval/4
end end
--check quota again while tries > 0 do
adv_spawning.quota_leave()
if not adv_spawning.quota_enter() then if adv_spawning.handlespawner(key,self.object:getpos()) then
return self.spawning_data[key] =
adv_spawning.spawner_definitions[key].spawn_interval
else
self.spawning_data[key] =
adv_spawning.spawner_definitions[key].spawn_interval/4
end
--check quota again
adv_spawning.quota_leave()
if not adv_spawning.quota_enter() then
return
end
tries = tries -1
end end
table.remove(self.pending_spawners,rand_spawner) table.remove(self.pending_spawners,rand_spawner)
per_step_count = per_step_count +1 per_step_count = per_step_count +1
end end
if (#self.pending_spawners > 0) then -- if (#self.pending_spawners > 0) then
print("Handled " .. per_step_count .. " spawners, spawners left: " .. #self.pending_spawners) -- print("Handled " .. per_step_count .. " spawners, spawners left: " .. #self.pending_spawners)
end -- end
adv_spawning.quota_leave() adv_spawning.quota_leave()
end end
end end
@ -221,7 +231,7 @@ function adv_spawning.seed_scan_for_applyable_spawners(self)
end end
if not continue then if not continue then
self.spawning_data[key] = value.spawn_interval self.spawning_data[key] = value.spawn_interval * math.random()
else else
self.spawning_data[key] = nil self.spawning_data[key] = nil
end end

73
spawndef_checks.lua Normal file
View File

@ -0,0 +1,73 @@
-------------------------------------------------------------------------------
-- advanced spawning mod
--
--@license WTFP
--@copyright Sapier
--@author Sapier
--@date 2013-12-05
--
-------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] verify_check_entities_around
-- @param entities_around a spawndef entities_around config
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.verify_check_entities_around(entities_around)
if entities_around ~= nil then
for i=1,#entities_around,1 do
if type(entities_around[i].distance) ~= "number" then
print("ADV_SPAWNING: missing distance in entities_around definition")
return false
end
if entities_around[i].type ~= "MIN" and
entities_around[i].type ~= "MAX" then
print("ADV_SPAWNING: invalid type \"" ..
dump(entities_around[i].type) ..
"\" in entities_around definition")
return false
end
end
end
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] verify_check_nodes_around
-- @param nodes_around a spawndef entities_around config
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.verify_check_nodes_around(nodes_around)
if nodes_around ~= nil then
for i=1,#nodes_around,1 do
if type(nodes_around[i].distance) ~= "number" then
print("ADV_SPAWNING: missing distance in entities_around definition")
return false
end
if nodes_around[i].type ~= "MIN" and
nodes_around[i].type ~= "MAX" then
print("ADV_SPAWNING: invalid type \"" ..
dump(nodes_around[i].type) ..
"\" in entities_around definition")
return false
end
if nodes_around[i].name == nil or
type(nodes_around[i].name) ~= "table" then
print("ADV_SPAWNING: invalid type of name \"" ..
type(nodes_around[i].name) .. "\"" .. " Data: " ..
dump(nodes_around[i].name) ..
" in nodes_around definition")
return false
end
end
end
return true
end