Initial checkin

master
sapier 2013-12-06 23:42:19 +01:00
commit 08ceacc31d
13 changed files with 1454 additions and 0 deletions

161
README.txt Normal file
View File

@ -0,0 +1,161 @@
********************************************************************************
* *
* Advanced spawning mod (adv_spawning) 0.0.0 *
* *
* URL: http://github.com/sapier/adv_spawning *
* Author: sapier *
* *
********************************************************************************
Description:
--------------------
Advances spawning mod is designed to provide a feature rich yet easy to use
spawner for entites. It's purpose is to support spawning within large numbers
of different environments. While adv_spawning supports a wide configurable range
of spawning situations, it's performance impact is clamped at minimal level.
To achiev this performance goal adv_spawning is intended for low frequency
entity spawning only. Tyyical spawn rate will be a low single digit count of
entities per second throughout whole world.
API:
--------------------
adv_spawning.register(spawner_name,spawning_def) --> successfull true/false
^ register a spawn to adv_spawning mechanisms
^ spawner_name a unique spawner name
^ spawning_def is configuration of spawner
adv_spawning.statistics() --> statistics data about spawning
Spawning definition:
--------------------
{
spawnee = "some_mod:entity_name", -- name of entity to spawn OR function to be called
absolute_height = -- absolute y value to check
{
min = 1, -- minimum height to spawn at
max = 5 -- maximum height to spawn at
}
relative_height = --relative y value to next non environment node
{
min = 1, -- minimum height above non environment node
max = 5 -- maximum height above non environment node
}
spawn_inside, -- [MANDATORY] list of nodes to to spawn within (see spawn inside example)
surfaces, -- list of nodes to spawn uppon (same format as spawn_inside)
entities_around = -- list of surrounding entity definitions
{
entity_def_1,
entity_def_2,
...
},
nodes_around = -- list of surrounding node definitions
{
node_def_1,
node_def_2,
...
},
light_around = -- list of light around definitions
{
light_around_def_1,
light_around_def_2,
...
}
humidity_around = -- list of humidity around definitions
{
humidity_around_def_1,
humidity_around_def_2,
...
}
temperature_around = -- list of temperature around definitions
{
temperature_around_def_1,
temperature_around_def_2,
...
}
collisionbox = {}, -- collisionbox of entity to spawn (usually same as used for entiy itself)
spawn_interval = 200, -- [MANDATORY] interval to try to spawn a entity
custom_check = fct(pos) -- a custom check to be called return true for pass, false for not pass
}
Light around definition:
{
type = "TIMED_MIN", -- type of light around check
-- TIMED_MIN/TIMED_MAX at least this light level at specified time within whole distance
-- OVERALL_MAX,OVERALL_MIN at least this light level at any time
-- CURRENT_MIN,CURRENT_MAX at least this light level now
distance = 2, -- distance to check (be carefull high distances may cause lag)
-- WARNING: light check is a very very heavy operation don't use large distances
threashold = 2, -- value to match at max/at least to pass this check
time = 6000 -- time to do check at (TIMED_MIN/TIMED_MAX only)
}
Surrounding node definition:
{
type = "MIN", -- type of surround check valid types are MIN and MAX
name = { "default:tree" },-- name(s) of node(s) to check
distance = 7, -- distance to look for node
threshold = 1 -- number to match at max/at least to pass this check
}
Surrounding entity definition:
{
type = "MIN", -- type of surround check valid types are MIN and MAX
entityname = "mod:entity", -- name of entity to check (nil to match all)
distance = 3, -- distance to look for this entity
threshold = 2 -- number to match at max/at least to pass this check
}
Surrounding temperature definition:
{
type = "MIN", -- type of surround check valid types are MIN and MAX
distance = 3, -- distance to look for this temperature
threshold = 2 -- number to match at max/at least to pass this check
}
Surrounding humidity definition:
{
type = "MIN", -- type of surround check valid types are MIN and MAX
distance = 3, -- distance to look for this humidity
threshold = 2 -- number to match at max/at least to pass this check
}
spawn_inside definition (list of nodenames):
{
"air",
"default:water_source",
"default:water_flowing"
}
Statistics:
{
session =
{
spawners_created = 0, -- number of spawners created this session
entities_created = 0, -- number of spawns done
steps = 0, -- number of steps
},
step =
{
min = 0, -- minimum time required for a single step
max = 0, -- maximum time required for a single step
last = 0, -- last steps time
},
load =
{
min = 0, -- minimum load caused
max = 0, -- maximum load caused
cur = 0, -- load caused in last step
avg = 0 -- average load caused
}
}

33
api.lua Normal file
View File

@ -0,0 +1,33 @@
-------------------------------------------------------------------------------
-- advanced spawning mod
--
--@license WTFP
--@copyright Sapier
--@author Sapier
--@date 2013-12-05
--
-------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] register
-- @param spawn_definition a definition to use for spawning
--------------------------------------------------------------------------------
function adv_spawning.register(spawner_name,spawning_def)
if adv_spawning.spawner_definitions[spawner_name] == nil then
--TODO validate spawning definition
adv_spawning.spawner_definitions[spawner_name] = spawning_def
return true
else
return false
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] get_statistics
-- @return get snapshot of statistics
--------------------------------------------------------------------------------
function adv_spawning.get_statistics()
return minetest.deserialize(minetest.serialize(adv_spawning.statistics))
end

31
init.lua Normal file
View File

@ -0,0 +1,31 @@
-------------------------------------------------------------------------------
-- advanced spawning mod
--
--@license WTFP
--@copyright Sapier
--@author Sapier
--@date 2013-12-05
--
-------------------------------------------------------------------------------
local version = "0.0.1"
if adv_spawning ~= nil then
minetest.log("error","MOD: adv_spawning requires adv_spawning variable to be available")
end
--------------------------------------------------------------------------------
-- @type adv_spawning base element for usage of adv_spawning
-- -----------------------------------------------------------------------------
adv_spawning = {}
local adv_modpath = minetest.get_modpath("adv_spawning")
dofile (adv_modpath .. "/internal.lua")
dofile (adv_modpath .. "/api.lua")
dofile (adv_modpath .. "/spawn_seed.lua")
adv_spawning.initialize()
print("Advanced spawning mod version " .. version .. " loaded")

854
internal.lua Normal file
View File

@ -0,0 +1,854 @@
-------------------------------------------------------------------------------
-- advanced spawning mod
--
--@license WTFP
--@copyright Sapier
--@author Sapier
--@date 2013-12-05
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- @function MAX
-- @param a first value to compare
-- @param b second value to compare
-- @return maximum of a and b
--------------------------------------------------------------------------------
function MAX(a,b)
if a > b then
return a
else
return b
end
end
--------------------------------------------------------------------------------
-- @function MIN
-- @param a first value to compare
-- @param b second value to compare
-- @return minimum of a and b
--------------------------------------------------------------------------------
function MIN(a,b)
if a > b then
return b
else
return a
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] initialize
--------------------------------------------------------------------------------
function adv_spawning.initialize()
--initialize data
adv_spawning.quota_starttime = nil
adv_spawning.quota_reload = 100
adv_spawning.quota_left = adv_spawning.quota_reload
adv_spawning.max_spawns_per_spawner = 2
adv_spawning.spawner_distance = 100
adv_spawning.max_spawning_frequency_hz = 5
adv_spawning.spawner_definitions = {}
adv_spawning.statistics =
{
session =
{
spawners_created = 0,
entities_created = 0,
steps = 0,
},
step =
{
min = 0,
max = 0,
last = 0,
},
load =
{
min = 0,
max = 0,
cur = 0,
avg = 0
}
}
adv_spawning.gettime = function() return os.clock() * 1000 end
if type(minetest.get_us_time) == "function" then
adv_spawning.gettime = function()
return minetest.get_us_time() / 1000
end
else
if socket == nil then
local status, module = pcall(require, 'socket')
if status and type(module.gettime) == "function" then
adv_spawning.gettime = function()
return socket.gettime()*1000
end
end
end
end
--register global onstep
minetest.register_globalstep(adv_spawning.global_onstep)
--register seed spawner entity
adv_spawning.seed_initialize()
--register mapgen hook
minetest.register_on_generated(adv_spawning.mapgen_hook)
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] mapgen_hook
-- @param minp minimal position of block
-- @param maxp maximal position of block
-- @param blockseed seed for this block
--------------------------------------------------------------------------------
function adv_spawning.mapgen_hook(minp,maxp,blockseed)
--find positions within current block to place a spawner seed
local start_x =
math.floor(minp.x/adv_spawning.spawner_distance)
* adv_spawning.spawner_distance
local start_y =
(math.floor(minp.y/adv_spawning.spawner_distance)
* adv_spawning.spawner_distance) +20
local start_z =
math.floor(minp.z/adv_spawning.spawner_distance)
* adv_spawning.spawner_distance
for x=start_x,maxp.x,adv_spawning.spawner_distance do
for y=start_y,maxp.y,adv_spawning.spawner_distance do
for z=start_z,maxp.z,adv_spawning.spawner_distance do
if x > minp.x and
y > minp.y and
z > minp.z then
minetest.add_entity({x=x,y=y,z=z},"adv_spawning:spawn_seed")
--adv_spawning.log("info", "adv_spawning: adding spawner entity at "
-- .. minetest.pos_to_string({x=x,y=y,z=z}))
adv_spawning.statistics.session.spawners_created =
adv_spawning.statistics.session.spawners_created +1
end
end
end
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] global_onstep
-- @param dtime time since last call
--------------------------------------------------------------------------------
function adv_spawning.global_onstep(dtime)
adv_spawning.statistics.step.last =
adv_spawning.quota_reload - adv_spawning.quota_left
adv_spawning.statistics.step.max = MAX(adv_spawning.statistics.step.last,
adv_spawning.statistics.step.max)
adv_spawning.statistics.step.min = MIN(adv_spawning.statistics.step.last,
adv_spawning.statistics.step.min)
adv_spawning.statistics.session.steps =
adv_spawning.statistics.session.steps + 1
adv_spawning.statistics.load.cur =
adv_spawning.statistics.step.last/(dtime*1000)
adv_spawning.statistics.load.max = MAX(adv_spawning.statistics.load.cur,
adv_spawning.statistics.load.max)
adv_spawning.statistics.load.min = MIN(adv_spawning.statistics.load.cur,
adv_spawning.statistics.load.min)
adv_spawning.statistics.load.avg =
( (adv_spawning.statistics.load.avg *
(adv_spawning.statistics.session.steps-1)) +
adv_spawning.statistics.load.cur) /
adv_spawning.statistics.session.steps
adv_spawning.quota_left = adv_spawning.quota_reload
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] quota_enter
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.quota_enter()
--ONLY enable this one if you're quite sure there aren't bugs in
--assert(adv_spawning.quota_starttime == nil)
if adv_spawning.quota_left <= 0 then
print("Quota: no time left: " .. adv_spawning.quota_left)
return false
end
--print("+++++++++++++++++Quota enter+++++++++++++++++++++")
--print(debug.traceback())
--print("+++++++++++++++++++++++++++++++++++++++++++++++++")
adv_spawning.quota_starttime = adv_spawning.gettime()
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] quota_left
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.time_over()
assert(adv_spawning.quota_starttime ~= nil)
local now = adv_spawning.gettime()
local time_passed = now - adv_spawning.quota_starttime
assert(time_passed >= 0)
return (adv_spawning.quota_left - time_passed) < 0
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] quota_leave
--------------------------------------------------------------------------------
function adv_spawning.quota_leave()
assert(adv_spawning.quota_starttime ~= nil)
local now = adv_spawning.gettime()
local time_passed = now - adv_spawning.quota_starttime
assert(time_passed >= 0)
adv_spawning.quota_left = adv_spawning.quota_left - time_passed
adv_spawning.quota_starttime = nil
--print("-----------------Quota leave----------------------")
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] handlespawner
-- @param spawnername unique name of spawner
-- @param spawnerpos position of spawner
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.handlespawner(spawnername,spawnerpos)
local spawndef = adv_spawning.spawner_definitions[spawnername]
--get random pos
local new_pos = {}
new_pos.x = math.random(spawnerpos.x - adv_spawning.spawner_distance/2,
spawnerpos.x + adv_spawning.spawner_distance/2)
new_pos.z = math.random(spawnerpos.z - adv_spawning.spawner_distance/2,
spawnerpos.z + adv_spawning.spawner_distance/2)
local upper_y = spawnerpos.y + adv_spawning.spawner_distance/2
local lower_y = spawnerpos.y - adv_spawning.spawner_distance/2
local continue = false
--check if entity is configured to spawn at surface
if spawndef.relative_height == nil or
(spawndef.relative_height.max ~= nil and
spawndef.relative_height.max <= 1) then
new_pos.y = adv_spawning.get_surface(lower_y,upper_y,new_pos,
spawndef.spawn_inside)
else
new_pos.y = adv_spawning.get_relative_pos(lower_y,upper_y,new_pos,
spawndef.spawn_inside,spawndef.relative_height)
end
--check if we did found a position within relative range
if new_pos.y == nil then
new_pos.y="?"
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't find a suitable y pos "
.. lower_y .. "<-->" .. upper_y )
continue = true
end
--check absolute height
if not continue and
not adv_spawning.check_absolute_height(new_pos,spawndef.absolute_height) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet absolute height check")
continue = true
end
--check collisionbox
if not continue then
local checkresult,y_pos =
adv_spawning.check_collisionbox(new_pos,
spawndef.collisionbox,spawndef.spawn_inside)
if checkresult and y_pos ~= nil then
new_pos.y = y_pos
end
if not checkresult then
continue = true
end
end
--check surface
if not continue and
not adv_spawning.check_surface(new_pos,
spawndef.surfaces,
spawndef.relative_height,
spawndef.spawn_inside) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet surface check")
continue = true
end
--check entities around
if not continue and
not adv_spawning.check_entities_around(new_pos,spawndef.entities_around) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet entities check")
continue = true
end
--check nodes around
if not continue and
not adv_spawning.check_nodes_around(new_pos,spawndef.nodes_around) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet nodes check")
continue = true
end
--check light around
if not continue and
not adv_spawning.check_light_around(new_pos,spawndef.light_around) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet light check")
continue = true
end
--check humidity
if not continue and
not adv_spawning.check_humidity_around(new_pos,spawndef.humidity_around) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet humidity check")
continue = true
end
--check temperature
if not continue and
not adv_spawning.check_temperature_around(new_pos,spawndef.temperature_around) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet temperature check")
continue = true
end
--custom check
if not continue and
(spawndef.custom_check ~= nil and
type(spawndef.custom_check) == "function") then
if not spawndef.custom_check(new_pos) then
adv_spawning.log("info",
minetest.pos_to_string(new_pos) .. " didn't meet custom check")
continue = true
end
end
--do spawn
if not continue then
print("Now spawning: " .. spawndef.spawnee .. " at " ..
minetest.pos_to_string(new_pos))
if type(spawndef.spawnee) == "function" then
spawndef.spawnee(new_pos)
else
minetest.add_entity(new_pos,spawndef.spawnee)
end
adv_spawning.statistics.session.entities_created =
adv_spawning.statistics.session.entities_created +1
return true
end
return false
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] get_surface
-- @param y_min minumum relevant y pos
-- @param y_max maximum relevant y pos
-- @param new_pos position to spawn at
-- @param spawn_inside nodes to spawn at
-- @return y-value of last spawnable node
--------------------------------------------------------------------------------
function adv_spawning.get_surface(y_min,y_max,new_pos,spawn_inside)
local top_pos = { x=new_pos.x, z=new_pos.z, y=y_max}
local bottom_pos = { x=new_pos.x, z=new_pos.z, y=y_min}
local spawnable_nodes =
minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside)
for i=y_max, y_min, -1 do
local pos = { x=new_pos.x,z=new_pos.z,y=i}
if not adv_spawning.contains_pos(spawnable_nodes,pos) then
local node = minetest.get_node(pos)
if node.name ~= "ignore" then
return i+1
end
end
end
return nil
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] get_relative_pos
-- @param y_min minumum relevant y pos
-- @param y_max maximum relevant y pos
-- @param new_pos position to spawn at
-- @param spawn_inside nodes to spawn at
-- @param relative_height
-- @return y-value of last spawnable node
--------------------------------------------------------------------------------
function adv_spawning.get_relative_pos(y_min,y_max,new_pos,spawn_inside,relative_height)
local y_val = adv_spawning.get_surface(y_min,y_max,new_pos,spawn_inside)
if y_val == nil then
return nil
end
local top_pos = { x=new_pos.x, z=new_pos.z, y=y_max}
local bottom_pos = { x=new_pos.x, z=new_pos.z, y=y_val}
if relative_height ~= nil then
if relative_height.min ~= nil then
bottom_pos.y = y_val + relative_height.min
end
if relative_height.max ~= nil then
top_pos.y = y_val + relative_height.max
end
end
local spawnable_nodes =
minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside)
if #spawnable_nodes > 0 then
return spawnable_nodes[math.random(1,#spawnable_nodes)].y
else
return nil
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] contains_pos
-- @param pos_list table containing positions
-- @param pos a position to search
-- @param remove if this is set to true a position is removed on match
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.contains_pos(pos_list,pos,remove)
for i=1,#pos_list,1 do
if pos_list[i].x == pos.x and
pos_list[i].z == pos.z and
pos_list[i].y == pos.y then
if remove then
table.erase(i)
end
return true
end
end
return false
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_absolute_height
-- @param pos to verify
-- @param absolute_height configuration for absolute height check
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_absolute_height(pos,absolute_height)
if absolute_height == nil then
return true
end
if absolute_height.min ~= nil and
pos.y < absolute_height.min then
return false
end
if absolute_height.max ~= nil and
pos.y > absolute_height.max then
return false
end
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_surface
-- @param pos to verify
-- @param surface configuration
-- @param relative_height required to check for non ground bound spawning
-- @param spawn_inside nodes to spawn inside
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_surface(pos,surfaces,relative_height,spawn_inside)
if surfaces == nil then
return true
end
if relative_height == nil or (
relative_height.min <= 1 and
relative_height.max <= 1) then
local lower_pos = {x=pos.x, y= pos.y-1, z=pos.z}
local node_below = minetest.get_node(lower_pos)
return adv_spawning.contains(surfaces,node_below.name)
else
local ymin = pos.y-relative_height.max-1
local ymax = pos.y+relative_height.max
local surface = adv_spawning.get_surface(ymin, ymax, pos, spawn_inside)
if surface == nil then
return false
else
local lower_pos = {x=pos.x, y= surface-1, z=pos.z}
local node_below = minetest.get_node(lower_pos)
return adv_spawning.contains(surfaces,node_below.name)
end
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] contains
-- @param table_to_check
-- @param value
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.contains(table_to_check,value)
for i=1,#table_to_check,1 do
if table_to_check[i] == value then
return true
end
end
return false
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_nodes_around
-- @param pos position to validate
-- @param nodes_around node around definitions
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_nodes_around(pos,nodes_around)
if nodes_around == nil then
return true
end
for i=1,#nodes_around,1 do
--first handle special cases 0 and 1 in a quick way
if (nodes_around[i].threshold == 1 and nodes_around[i].type == "MIN") or
(nodes_around[i].threshold == 0 and nodes_around[i].type == "MAX")then
local found =
minetest.find_node_near(pos,nodes_around[i].distance,
nodes_around[i].name)
if nodes_around[i].type == "MIN" then
if found == nil then
return false
end
else
if found ~= nil then
return false
end
end
else
--need to do the full blown check
local found_nodes = minetest.find_nodes_in_area(
{ x=pos.x-nodes_around[i].distance,
y=pos.y-nodes_around[i].distance,
z=pos.z-nodes_around[i].distance},
{ x=pos.x+nodes_around[i].distance,
y=pos.y+nodes_around[i].distance,
z=pos.z+nodes_around[i].distance},
nodes_around[i].name)
if nodes_around[i].type == "MIN" and
#found_nodes < nodes_around[i].threshold then
return false
end
if nodes_around[i].type == "MAX" and
#found_nodes > nodes_around[i].threshold then
return false
end
end
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_entities_around
-- @param pos position to validate
-- @param entities_around entity around definitions
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_entities_around(pos,entities_around)
if entities_around == nil then
return true
end
for i=1,#entities_around,1 do
local entity_in_range =
minetest.get_objects_inside_radius(pos, entities_around[i].distance)
if entities_around[i].entityname == nil then
if entities_around[i].type == "MIN" and
#entity_in_range < entities_around[i].threshold then
return false
end
if entities_around[i].type == "MAX" and
#entity_in_range > entities_around[i].threshold then
return false
end
end
local count = 0
for j=1,#entity_in_range,1 do
local entity = entity_in_range[j]:get_luaentity()
if entity ~= nil then
if entity.name == entities_around[i].entityname then
count = count +1
end
if count > entities_around[i].threshold then
break
end
end
end
if entities_around[i].type == "MIN" and
count < entities_around[i].threshold then
return false
end
if entities_around[i].type == "MAX" and
count > entities_around[i].threshold then
return false
end
end
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_light_around
-- @param pos position to validate
-- @param light_around light around definitions
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_light_around(pos,light_around)
if light_around == nil then
return true
end
for i=1,#light_around,1 do
for x=pos.x-light_around[i].distance,pos.x+light_around[i].distance,1 do
for y=pos.y-light_around[i].distance,pos.y+light_around[i].distance,1 do
for x=pos.z-light_around[i].distance,pos.z+light_around[i].distance,1 do
local checkpos = { x=x,y=y,z=z}
local time = minetest.get_timeofday()
if light_around[i].type == "TIMED_MIN" or
light_around[i].type == "TIMED_MAX" then
time = light_around[i].time
end
if light_around[i].type == "OVERALL_MIN" or
light_around[i].type == "OVERALL_MAX" then
for i=0,24000,1000 do
local light_level = minetest.get_node_light(checkpos, i)
if light_level ~= nil then
if light_around[i].type == "OVERALL_MAX" and
light_level > light_around[i].threshold then
return false
end
if light_around[i].type == "OVERALL_MIN" and
light_level < light_around[i].threshold then
return false
end
end
end
else
local light_level = minetest.get_node_light(checkpos, time)
if light_level ~= nil then
if (light_around[i].type == "TIMED_MIN" or
light_around[i].type == "CURRENT_MIN") and
light_level < light_around[i].threshold then
return false
end
if (light_around[i].type == "TIMED_MAX" or
light_around[i].type == "CURRENT_MAX") and
light_level > light_around[i].threshold then
return false
end
end
end
end
end
end
end
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_temperature_around
-- @param pos position to validate
-- @param temperature_around temperature around definitions
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_temperature_around(pos,temperature_around)
if temperature_around == nil then
return true
end
--TODO
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_humidity_around
-- @param pos position to validate
-- @param humidity_around humidity around definitions
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.check_humidity_around(pos,humidity_around)
if humidity_around == nil then
return true
end
--TODO
return true
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] log
-- @param level
-- @param text
--------------------------------------------------------------------------------
function adv_spawning.log(level,text)
local is_debug = false
if not is_debug then
return
end
minetest.log(level,text)
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] check_collisionbox
-- @param pos position to check
-- @param collisionbox collisionbox to use
-- @param spawn_inside nodes to spawn inside
--------------------------------------------------------------------------------
function adv_spawning.check_collisionbox(pos,collisionbox,spawn_inside)
if collisionbox == nil then
return true,nil
end
--skip for collisionboxes smaller then a single node
if collisionbox[0] >= -0.5 and collisionbox[1] >= -0.5 and collisionbox[2] >= -0.5 and
collisionbox[3] <= 0.5 and collisionbox[4] <= 0.5 and collisionbox[5] <= 0.5 then
return true,nil
end
--lets do the more complex checks
--first check if we need to move up
if collisionbox[1] < -0.5 then
pos.y = pos.y + (collisionbox[1]*-1) - 0.5
end
local corners = {}
--centerpos
table.insert(corners, pos)
--top_right_back
table.insert(corners, {x=pos.x+collisionbox[3],
y=pos.y+collisionbox[4],
z=pos.z+collisionbox[2]})
--top_right_front
table.insert(corners, {x=pos.x+collisionbox[3],
y=pos.y+collisionbox[4],
z=pos.z+collisionbox[5]})
--bottom_right_front
table.insert(corners, {x=pos.x+collisionbox[3],
y=pos.y+collisionbox[1],
z=pos.z+collisionbox[5]})
--bottom_right_back
table.insert(corners, {x=pos.x+collisionbox[3],
y=pos.y+collisionbox[1],
z=pos.z+collisionbox[2]})
--top_left_back
table.insert(corners, {x=pos.x+collisionbox[0],
y=pos.y+collisionbox[4],
z=pos.z+collisionbox[2]})
--top_left_front
table.insert(corners, {x=pos.x+collisionbox[0],
y=pos.y+collisionbox[4],
z=pos.z+collisionbox[5]})
--bottom_left_front
table.insert(corners, {x=pos.x+collisionbox[0],
y=pos.y+collisionbox[1],
z=pos.z+collisionbox[5]})
--bottom_left_back
table.insert(corners, {x=pos.x+collisionbox[0],
y=pos.y+collisionbox[1],
z=pos.z+collisionbox[2]})
local last_checked = nil
for i=0,#corners,1 do
if not adv_spawning.is_same_pos(#corners[i],last_checked) then
local node = minetest.get_node(#corners[i])
if not adv_spawning.contains(spawn_inside,node.name) then
return false,nil
end
last_checked = #corners[i]
end
end
return true,pos.y
end

220
spawn_seed.lua Normal file
View File

@ -0,0 +1,220 @@
-------------------------------------------------------------------------------
-- advanced spawning mod
--
--@license WTFP
--@copyright Sapier
--@author Sapier
--@date 2013-12-05
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_step
-- @param self spawner entity
-- @param dtime time since last call
--------------------------------------------------------------------------------
function adv_spawning.seed_step(self,dtime)
if not self.activated then
adv_spawning.seed_activate(self)
return
end
self.mydtime = self.mydtime + dtime
if (self.mydtime < 1/adv_spawning.max_spawning_frequency_hz) then
return
end
if adv_spawning.quota_enter() then
self.pending_spawners = {}
adv_spawning.seed_countdown_spawners(self,self.mydtime)
self.mydtime = 0
--check quota again
adv_spawning.quota_leave()
if not adv_spawning.quota_enter() then
return
end
local per_step_count = 0
while #self.pending_spawners > 0 and
per_step_count < adv_spawning.max_spawns_per_spawner do
local rand_spawner = math.random(1,#self.pending_spawners)
local key = self.pending_spawners[rand_spawner]
if adv_spawning.handlespawner(key,self.object:getpos()) then
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
table.remove(self.pending_spawners,rand_spawner)
per_step_count = per_step_count +1
end
if (#self.pending_spawners > 0) then
print("Handled " .. per_step_count .. " spawners, spawners left: " .. #self.pending_spawners)
end
adv_spawning.quota_leave()
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_activate
-- @param self spawner entity
--------------------------------------------------------------------------------
function adv_spawning.seed_activate(self)
if adv_spawning.quota_enter() then
if adv_spawning.seed_check_for_collision(self) then
adv_spawning.quota_leave()
return
end
if self.serialized_data ~= nil then
self.spawning_data = minetest.deserialize(self.serialized_data)
end
if self.spawning_data == nil then
self.spawning_data = {}
end
adv_spawning.seed_validate_spawndata(self)
adv_spawning.seed_scan_for_applyable_spawners(self)
self.pending_spawners = {}
self.activated = true
adv_spawning.quota_leave()
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_initialize
--------------------------------------------------------------------------------
function adv_spawning.seed_initialize()
local spawner_texture = "adv_spawning_invisible.png^[makealpha:128,0,0^[makealpha:128,128,0"
local spawner_collisionbox = { 0.0,0.0,0.0,0.0,0.0,0.0}
--if debug
spawner_texture = "adv_spawning_spawner.png"
spawner_collisionbox = { -0.5,-0.5,-0.5,0.5,0.5,0.5 }
--end
minetest.register_entity("adv_spawning:spawn_seed",
{
collisionbox = spawner_collisionbox,
visual = "sprite",
textures = { spawner_texture },
physical = false,
groups = { "immortal" },
on_activate = function(self,staticdata,dtime_s)
self.activated = false
self.mydtime = dtime_s
self.serialized_data = staticdata
adv_spawning.seed_activate(self)
end,
on_step = adv_spawning.seed_step,
get_staticdata = function(self)
return minetest.serialize(self.spawning_data)
end
}
)
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_validate_spawndata
-- @param self spawner entity
--------------------------------------------------------------------------------
function adv_spawning.seed_validate_spawndata(self)
for key,value in pairs(self.spawning_data) do
if adv_spawning.spawner_definitions[key] == nil then
self.spawning_data[key] = nil
end
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_countdown_spawners
-- @param self spawner entity
-- @param dtime time to decrement spawners
--------------------------------------------------------------------------------
function adv_spawning.seed_countdown_spawners(self,dtime)
for key,value in pairs(self.spawning_data) do
self.spawning_data[key] = self.spawning_data[key] - dtime
if self.spawning_data[key] < 0 then
table.insert(self.pending_spawners,key)
end
end
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_check_for_collision
-- @param self spawner entity
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.seed_check_for_collision(self)
--TODO check if there already is another spawner at exactly this position
return false
end
--------------------------------------------------------------------------------
-- @function [parent=#adv_spawning] seed_scan_for_applyable_spawners
-- @param self spawner entity
-- @return true/false
--------------------------------------------------------------------------------
function adv_spawning.seed_scan_for_applyable_spawners(self)
local pos = self.object:getpos()
for key,value in pairs(adv_spawning.spawner_definitions) do
local continue = false
--if spawner is far away from spawn area don't even try to spawn
if not continue and
value.absolute_height ~= nil then
if value.absolute_height.min
> pos.y + (adv_spawning.spawner_distance/2) then
continue = true
end
if value.absolute_height.max
< pos.y - (adv_spawning.spawner_distance/2) then
continue = true
end
end
--check for presence of environment
if not continue then
local radius =
math.sqrt(adv_spawning.spawner_distance*
adv_spawning.spawner_distance*2)/2
if minetest.find_node_near(pos,radius,
value.spawn_inside) == nil then
continue = false
end
end
if not continue then
self.spawning_data[key] = value.spawn_interval
else
self.spawning_data[key] = nil
end
end
end

1
testmod/depends.txt Normal file
View File

@ -0,0 +1 @@
adv_spawning

154
testmod/init.lua Normal file
View File

@ -0,0 +1,154 @@
minetest.register_entity("testmod:bogus_1",
{
collisionbox = { -0.5,-0.5,-0.5,0.5,0.5,0.5 },
visual = "sprite",
textures = { "testmod_num1.png" },
physical = false,
groups = { "immortal" },
}
)
minetest.register_entity("testmod:bogus_2",
{
collisionbox = { -0.5,-0.5,-0.5,0.5,0.5,0.5 },
visual = "sprite",
textures = { "testmod_num2.png" },
physical = false,
groups = { "immortal" },
}
)
minetest.register_entity("testmod:bogus_3",
{
collisionbox = { -0.5,-0.5,-0.5,0.5,0.5,0.5 },
visual = "sprite",
textures = { "testmod_num3.png" },
physical = false,
groups = { "immortal" },
}
)
minetest.register_entity("testmod:bogus_4",
{
collisionbox = { -0.5,-0.5,-0.5,0.5,0.5,0.5 },
visual = "sprite",
textures = { "testmod_num4.png" },
physical = false,
groups = { "immortal" },
}
)
--adv_spawning.register("some_bogus_entity_1",
-- {
-- spawnee = "testmod:bogus_1",
-- spawn_interval = 10,
--
-- spawn_inside =
-- {
-- "air"
-- },
--
-- entities_around =
-- {
-- { type="MAX",entityname = "testmod:bogus_1",distance=20,threshold=1 }
-- },
--
-- relative_height =
-- {
-- max = 1
-- }
-- })
--adv_spawning.register("some_bogus_entity_2",
-- {
-- spawnee = "testmod:bogus_2",
-- spawn_interval = 5,
-- spawn_inside =
-- {
-- "air"
-- },
--
-- entities_around =
-- {
-- { type="MAX",distance=20,threshold=1 }
-- },
--
-- relative_height =
-- {
-- max = 1
-- },
--
-- surfaces =
-- {
-- "default:dirt_with_grass"
-- }
-- })
--adv_spawning.register("some_bogus_entity_3",
-- {
-- spawnee = "testmod:bogus_3",
-- spawn_interval = 3,
-- spawn_inside =
-- {
-- "air"
-- },
--
-- entities_around =
-- {
-- { type="MAX",entityname = "testmod:bogus_4",distance=20,threshold=1 }
-- },
--
-- relative_height =
-- {
-- max = 4,
-- min = 4,
-- },
-- })
adv_spawning.register("some_bogus_entity_4",
{
spawnee = "testmod:bogus_4",
spawn_interval = 3,
spawn_inside =
{
"air"
},
entities_around =
{
{ type="MAX",distance=30,threshold=1 }
},
relative_height =
{
max = 4,
min = 4,
},
surfaces =
{
"default:leaves"
}
})
minetest.register_chatcommand("stats",
{
params = "",
description = "show advanced spawning satistics" ,
func = function()
local stats = adv_spawning.get_statistics()
print("Adv. Spawning stats:")
print("----------------------------------------")
print("Spawners added: " .. stats.session.spawners_created)
print("Spawnees added: " .. stats.session.entities_created)
print("")
print("Longest step: " .. stats.step.max)
print("")
print("Current load: " .. stats.load.cur)
print("Average load: " .. stats.load.avg)
print("Maximum load: " .. stats.load.max)
end
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB