1692 lines
49 KiB
Lua
1692 lines
49 KiB
Lua
|
-------------------------------------------------------------------------------
|
||
|
-- advanced spawning mod
|
||
|
--
|
||
|
--@license WTFP
|
||
|
--@copyright Sapier
|
||
|
--@author Sapier
|
||
|
--@date 2013-12-05
|
||
|
--
|
||
|
--------------------------------------------------------------------------------
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] MAX
|
||
|
-- @param a first value to compare
|
||
|
-- @param b second value to compare
|
||
|
-- @return maximum of a and b
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.MAX(a,b)
|
||
|
if a == nil then
|
||
|
return (b or 0)
|
||
|
end
|
||
|
if b == nil then
|
||
|
return (a or 0)
|
||
|
end
|
||
|
if a > b then
|
||
|
return (a or 0)
|
||
|
else
|
||
|
return (b or 0)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] MIN
|
||
|
-- @param a first value to compare
|
||
|
-- @param b second value to compare
|
||
|
-- @return minimum of a and b
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.MIN(a,b)
|
||
|
if a == nil then
|
||
|
return (b or 0)
|
||
|
end
|
||
|
if b == nil then
|
||
|
return (a or 0)
|
||
|
end
|
||
|
if a > b then
|
||
|
return (b or 0)
|
||
|
else
|
||
|
return (a or 0)
|
||
|
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 = 70
|
||
|
adv_spawning.spawner_y_offset = 20
|
||
|
adv_spawning.max_spawning_frequency_hz = 5
|
||
|
adv_spawning.max_mapgen_tries_per_step = 3
|
||
|
adv_spawning.spawner_warned = {}
|
||
|
adv_spawning.loglevel = 0
|
||
|
adv_spawning.spawner_validation_delta = 0
|
||
|
adv_spawning.spawner_validation_interval = 30
|
||
|
|
||
|
adv_spawning.active_range = minetest.setting_get("active_block_range")
|
||
|
|
||
|
if (adv_spawning.active_range == nil) then
|
||
|
adv_spawning.log("info", "No \"active_block_range\" set, defaulting to 5")
|
||
|
adv_spawning.active_range = 5
|
||
|
else
|
||
|
adv_spawning.active_range = adv_spawning.active_range * 16
|
||
|
end
|
||
|
|
||
|
adv_spawning.spawner_definitions = {}
|
||
|
adv_spawning.mapgen_jobqueue = {}
|
||
|
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.log("action", "Using minetest.get_us_time() for quota calc")
|
||
|
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.log("action", "Using socket.gettime() for quota calc")
|
||
|
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)
|
||
|
if adv_spawning.quota_enter(true) then
|
||
|
--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)
|
||
|
+ adv_spawning.spawner_y_offset
|
||
|
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
|
||
|
if not adv_spawning.quota_leave() then
|
||
|
adv_spawning.dbg_log(2, "mapgen_hook did use way too much time 1")
|
||
|
end
|
||
|
minetest.add_entity({x=x,y=y,z=z},"adv_spawning:spawn_seed")
|
||
|
adv_spawning.quota_enter(true)
|
||
|
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
|
||
|
|
||
|
adv_spawning.queue_mapgen_jobs(minp,maxp)
|
||
|
if not adv_spawning.quota_leave() then
|
||
|
adv_spawning.dbg_log(2, "mapgen_hook did use way too much time 2")
|
||
|
end
|
||
|
else
|
||
|
assert("Mapgen hook could not be executed" == nil)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] global_onstep
|
||
|
-- @param dtime time since last call
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.global_onstep(dtime)
|
||
|
|
||
|
if dtime == 0 then
|
||
|
-- don't try to calc differences for no time
|
||
|
return
|
||
|
end
|
||
|
|
||
|
adv_spawning.statistics.step.last =
|
||
|
math.floor(adv_spawning.quota_reload - adv_spawning.quota_left + 0.5)
|
||
|
|
||
|
adv_spawning.statistics.step.max = adv_spawning.MAX(adv_spawning.statistics.step.last,
|
||
|
adv_spawning.statistics.step.max)
|
||
|
|
||
|
adv_spawning.statistics.step.min = adv_spawning.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 = adv_spawning.MAX(adv_spawning.statistics.load.cur,
|
||
|
adv_spawning.statistics.load.max)
|
||
|
|
||
|
adv_spawning.statistics.load.min = adv_spawning.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
|
||
|
|
||
|
if core.is_yes(
|
||
|
core.setting_get("adv_spawning_validate_spawners")) then
|
||
|
|
||
|
adv_spawning.spawner_validation_delta =
|
||
|
adv_spawning.spawner_validation_delta + dtime
|
||
|
|
||
|
if adv_spawning.spawner_validation_delta >
|
||
|
adv_spawning.spawner_validation_interval then
|
||
|
|
||
|
if adv_spawning.quota_enter() then
|
||
|
local playerlist = core.get_connected_players()
|
||
|
|
||
|
for k,v in ipairs(playerlist) do
|
||
|
if not adv_spawning.time_over(10) then
|
||
|
adv_spawning.refresh_spawners(v:getpos())
|
||
|
else
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
adv_spawning.spawner_validation_delta = 0
|
||
|
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--reduce following quota by overtime from last step
|
||
|
if adv_spawning.quota_left < 0 then
|
||
|
adv_spawning.quota_left =
|
||
|
adv_spawning.MAX(0,adv_spawning.quota_left + adv_spawning.quota_reload)
|
||
|
else
|
||
|
adv_spawning.quota_left = adv_spawning.quota_reload
|
||
|
end
|
||
|
|
||
|
if adv_spawning.quota_enter() then
|
||
|
adv_spawning.handle_mapgen_spawning()
|
||
|
if not adv_spawning.quota_leave() then
|
||
|
adv_spawning.dbg_log(2, "globalstep took to long")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] quota_enter
|
||
|
-- @param force ignore quota but start calculation
|
||
|
-- @return true/false
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.quota_enter(force)
|
||
|
--ONLY enable this one if you're quite sure there aren't bugs in
|
||
|
--assert(adv_spawning.quota_starttime == nil)
|
||
|
local retval = false
|
||
|
|
||
|
if adv_spawning.quota_left <= 0 then
|
||
|
|
||
|
if force == true then
|
||
|
if adv_spawning.quota_left < -10 then
|
||
|
adv_spawning.dbg_log(1, "Quota: task is too important to skip do it anyway," ..
|
||
|
" quota already passed by: " ..
|
||
|
string.format("%.2f ms",adv_spawning.quota_left))
|
||
|
end
|
||
|
retval = true
|
||
|
else
|
||
|
if adv_spawning.quota_left * -2 > adv_spawning.quota_reload then
|
||
|
adv_spawning.dbg_log(1, "Quota: no time left: " ..
|
||
|
string.format("%.2f ms",adv_spawning.quota_left))
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
retval = true
|
||
|
end
|
||
|
-- print("+++++++++++++++++Quota enter+++++++++++++++++++++")
|
||
|
-- print(debug.traceback())
|
||
|
-- print("+++++++++++++++++++++++++++++++++++++++++++++++++")
|
||
|
if retval then
|
||
|
adv_spawning.quota_starttime = adv_spawning.gettime()
|
||
|
end
|
||
|
|
||
|
return retval
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] time_over
|
||
|
-- @param minimum minimal value required to be left
|
||
|
-- @return true/false
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.time_over(minimum)
|
||
|
assert(adv_spawning.quota_starttime ~= nil)
|
||
|
-- if adv_spawning.quota_starttime == nil then
|
||
|
-- return
|
||
|
-- end
|
||
|
|
||
|
if minimum == nil then
|
||
|
minimum = 0
|
||
|
end
|
||
|
|
||
|
local now = adv_spawning.gettime()
|
||
|
|
||
|
local time_passed = now - adv_spawning.quota_starttime
|
||
|
|
||
|
if (time_passed < 0) then
|
||
|
if adv_spawning.timebackwardwarning ~= true then
|
||
|
core.log("error", "ADV_SPAWNING: Error either there's a bug in time"
|
||
|
.." calculation\n or your time just went backwards: old timestamp: "
|
||
|
.. adv_spawning.quota_starttime .. " current_time: " .. now .. "\n")
|
||
|
adv_spawning.timebackwardwarning = true
|
||
|
|
||
|
end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
return (adv_spawning.quota_left - time_passed) < minimum
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] quota_leave
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.quota_leave()
|
||
|
--assert(adv_spawning.quota_starttime ~= nil)
|
||
|
if adv_spawning.quota_starttime == nil then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local now = adv_spawning.gettime()
|
||
|
|
||
|
local time_passed = now - adv_spawning.quota_starttime
|
||
|
|
||
|
if (time_passed < 0) then
|
||
|
if adv_spawning.timebackwardwarning ~= true then
|
||
|
core.log("error", "ADV_SPAWNING: Error either there's a bug in time"
|
||
|
.." calculation\n or your time just went backwards: old timestamp: "
|
||
|
.. adv_spawning.quota_starttime .. " current_time: " .. now .. "\n")
|
||
|
adv_spawning.timebackwardwarning = true
|
||
|
end
|
||
|
else
|
||
|
adv_spawning.quota_left = adv_spawning.quota_left - time_passed
|
||
|
end
|
||
|
|
||
|
if (adv_spawning.quota_left < -adv_spawning.quota_reload) then
|
||
|
adv_spawning.dbg_log(1, "excessive overtime, quota remaining: " .. adv_spawning.quota_left)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
adv_spawning.quota_starttime = nil
|
||
|
--print("-----------------Quota leave----------------------")
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] handlespawner
|
||
|
-- @param spawnername unique name of spawner
|
||
|
-- @param spawnerpos position of spawner
|
||
|
-- @param minp (OPTIONAL) override spawner defaults
|
||
|
-- @param maxp (OPTIONAL) override spawner defaults
|
||
|
-- @param ignore_active_area set to true for mapgen spawning
|
||
|
-- @return successfull true/false, permanent_error true,false, reason_string
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.handlespawner(spawnername,spawnerpos,minp,maxp,ignore_active_area)
|
||
|
|
||
|
local permanent_error = false
|
||
|
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, "daytimecheck failed"
|
||
|
end
|
||
|
|
||
|
local max_x = spawnerpos.x + adv_spawning.spawner_distance/2
|
||
|
local min_x = spawnerpos.x - adv_spawning.spawner_distance/2
|
||
|
|
||
|
local max_z = spawnerpos.z + adv_spawning.spawner_distance/2
|
||
|
local min_z = 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
|
||
|
|
||
|
if minp ~= nil then
|
||
|
min_x = minp.x
|
||
|
min_z = minp.z
|
||
|
lower_y = minp.y
|
||
|
end
|
||
|
|
||
|
if maxp ~= nil then
|
||
|
max_x = maxp.x
|
||
|
max_z = maxp.z
|
||
|
upper_y = maxp.y
|
||
|
end
|
||
|
|
||
|
--get random pos
|
||
|
local new_pos = {}
|
||
|
|
||
|
new_pos.x = math.random(min_x,max_x)
|
||
|
new_pos.z = math.random(min_z,max_z)
|
||
|
|
||
|
local yreason = "ukn"
|
||
|
|
||
|
--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, yreason = adv_spawning.get_surface(lower_y,upper_y,new_pos,
|
||
|
spawndef.spawn_inside)
|
||
|
else
|
||
|
if spawndef.spawn_inside == nil then
|
||
|
print("ERROR: " .. spawnername .. " tries to spawn within nil")
|
||
|
assert(false)
|
||
|
end
|
||
|
new_pos.y, yreason = adv_spawning.get_relative_pos(lower_y,upper_y,new_pos,
|
||
|
spawndef.spawn_inside,
|
||
|
spawndef.relative_height,
|
||
|
spawndef.absolute_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 )
|
||
|
return false, nil, "didn't find a valid ypos at " .. minetest.pos_to_string(new_pos)
|
||
|
.. " " .. lower_y .. "<-->" .. upper_y .. " rsn: " .. yreason
|
||
|
end
|
||
|
|
||
|
--check absolute height
|
||
|
local abs_height_retval, abs_height_rsn = adv_spawning.check_absolute_height(new_pos,spawndef.absolute_height)
|
||
|
if not abs_height_retval then
|
||
|
adv_spawning.log("info",
|
||
|
minetest.pos_to_string(new_pos) .. " didn't meet absolute height check")
|
||
|
return false, true, "absolute height check failed rsn: " .. abs_height_rsn
|
||
|
end
|
||
|
|
||
|
--check active area
|
||
|
if not ignore_active_area and not adv_spawning.check_active_block(new_pos) then
|
||
|
adv_spawning.log("info",
|
||
|
minetest.pos_to_string(new_pos) .. " didn't meet active area check")
|
||
|
return false, nil , "area check failed"
|
||
|
end
|
||
|
|
||
|
--check surface
|
||
|
--NOTE needs to be done before collision box check as y pos may be modified there
|
||
|
if 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, is: " ..
|
||
|
minetest.get_node({x=new_pos.x,z=new_pos.z,y=new_pos.y-1}).name)
|
||
|
return false, nil, "surface check failed"
|
||
|
end
|
||
|
|
||
|
--flat area check
|
||
|
--NOTE needs to be done before collision box check as y pos may be modified there
|
||
|
if not adv_spawning.check_flat_area(new_pos,
|
||
|
spawndef.flat_area,
|
||
|
spawndef.spawn_inside,
|
||
|
spawndef.surfaces) then
|
||
|
adv_spawning.log("info",
|
||
|
minetest.pos_to_string(new_pos) .. " didn't meet flat area check")
|
||
|
return false, nil, "flat area check failed"
|
||
|
end
|
||
|
|
||
|
--check collisionbox
|
||
|
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
|
||
|
adv_spawning.log("info",
|
||
|
minetest.pos_to_string(new_pos) .. " didn't meet collisionbox check")
|
||
|
return false, nil, "collision box check failed"
|
||
|
end
|
||
|
|
||
|
--check entities around
|
||
|
if 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")
|
||
|
return false, nil, "entitie around check failed"
|
||
|
end
|
||
|
|
||
|
--check nodes around
|
||
|
if 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")
|
||
|
return false, nil, "nodes around check failed"
|
||
|
end
|
||
|
|
||
|
--check light around
|
||
|
if 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")
|
||
|
return false, nil, "light check failed"
|
||
|
end
|
||
|
|
||
|
-- ONLY use this if you have luajit
|
||
|
-- --check light around
|
||
|
-- if not adv_spawning.check_light_around_voxel(new_pos,spawndef.light_around) then
|
||
|
-- adv_spawning.log("info",
|
||
|
-- minetest.pos_to_string(new_pos) .. " didn't meet light check")
|
||
|
-- return false, nil, "luajit light check failed"
|
||
|
-- end
|
||
|
|
||
|
--check humidity
|
||
|
if 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")
|
||
|
return false, nil, "humidity check failed"
|
||
|
end
|
||
|
|
||
|
--check temperature
|
||
|
if 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")
|
||
|
return false, nil, "temperature check failed"
|
||
|
end
|
||
|
|
||
|
--custom check
|
||
|
if (spawndef.custom_check ~= nil and
|
||
|
type(spawndef.custom_check) == "function") then
|
||
|
|
||
|
local retval, reason = spawndef.custom_check(new_pos,spawndef)
|
||
|
|
||
|
if not reason then
|
||
|
reason = "custom check failed"
|
||
|
end
|
||
|
|
||
|
if not retval then
|
||
|
adv_spawning.log("info",
|
||
|
minetest.pos_to_string(new_pos) .. " didn't meet custom check")
|
||
|
return false, nil, reason
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--do spawn
|
||
|
--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
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @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}
|
||
|
|
||
|
-- get list of all nodes within our y-range we could spawn within
|
||
|
local spawnable_nodes =
|
||
|
minetest.find_nodes_in_area(bottom_pos, top_pos, spawn_inside)
|
||
|
|
||
|
-- if there ain't a single node to spawn within get out of here
|
||
|
if #spawnable_nodes == 0 then
|
||
|
return nil, "no spawnable nodes at all"
|
||
|
end
|
||
|
|
||
|
local spawnable_node_passed = false
|
||
|
|
||
|
-- loop from topmost position to bottom
|
||
|
for i=y_max, y_min, -1 do
|
||
|
-- get current position
|
||
|
local pos = { x=new_pos.x,z=new_pos.z,y=i}
|
||
|
|
||
|
-- if the node at current position ain't one of those we can spawn within
|
||
|
if not adv_spawning.contains_pos(spawnable_nodes,pos) then
|
||
|
|
||
|
-- get more information about this node
|
||
|
local node = minetest.get_node(pos)
|
||
|
|
||
|
local text = "false"
|
||
|
|
||
|
if spawnable_node_passed then
|
||
|
text = "true"
|
||
|
end
|
||
|
|
||
|
-- if node ain't unloaded and we did already see a spawnable node above
|
||
|
-- return position above as pos to spawn
|
||
|
if node.name ~= "ignore" and
|
||
|
spawnable_node_passed then
|
||
|
return i+1, "pos found"
|
||
|
end
|
||
|
else
|
||
|
-- set marker about having seen a spawnable node above
|
||
|
spawnable_node_passed = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return nil, "no matching node, nodecnt: " .. #spawnable_nodes
|
||
|
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
|
||
|
-- @param absolute_height
|
||
|
-- @return y-value of last spawnable node
|
||
|
--------------------------------------------------------------------------------
|
||
|
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)
|
||
|
|
||
|
if y_val == nil then
|
||
|
if (relative_height.min ~= nil or
|
||
|
relative_height.max ~= nil) then
|
||
|
return nil, "y_pos not witing range of "
|
||
|
.. relative_height.min .. "<-->" .. relative_height.max
|
||
|
else
|
||
|
y_val = y_min
|
||
|
end
|
||
|
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
|
||
|
|
||
|
top_pos.y = adv_spawning.MIN(absolute_height.max,top_pos.y)
|
||
|
bottom_pos.y = adv_spawning.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, "invalid interval: " .. bottom_pos.y .. "<-->" .. top_pos.y
|
||
|
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, "rpos found"
|
||
|
else
|
||
|
--print("no suitable nodes" .. bottom_pos.y .. "<-->" .. top_pos.y)
|
||
|
return nil, "no spawnable nodes found around"
|
||
|
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.remove(pos_list,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, "no height limit"
|
||
|
end
|
||
|
|
||
|
if absolute_height.min ~= nil and
|
||
|
pos.y < absolute_height.min then
|
||
|
return false, pos.y .. " < " .. absolute_height.min
|
||
|
end
|
||
|
|
||
|
if absolute_height.max ~= nil and
|
||
|
pos.y > absolute_height.max then
|
||
|
return false, pos.y .. " > " .. absolute_height.max
|
||
|
end
|
||
|
|
||
|
return true, "height ok"
|
||
|
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.max ~= nil 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_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
|
||
|
end
|
||
|
break
|
||
|
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
|
||
|
-- @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
|
||
|
--print("not enough: " .. dump(nodes_around[i].name) .. " around")
|
||
|
return false
|
||
|
end
|
||
|
else
|
||
|
if found ~= nil then
|
||
|
--print("to many: " .. dump(nodes_around[i].name) .. " around " .. dump(found))
|
||
|
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
|
||
|
--print("Found MIN: " .. dump(nodes_around[i].name) ..
|
||
|
-- "\n at locations: " .. dump(found_nodes))
|
||
|
--print ("Only " .. #found_nodes .. "/" .. nodes_around[i].threshold)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if nodes_around[i].type == "MAX" and
|
||
|
#found_nodes > nodes_around[i].threshold then
|
||
|
--print("Found MAX: " .. dump(nodes_around[i].name) ..
|
||
|
-- "\n at locations: " .. dump(found_nodes))
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return true
|
||
|
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 j=0,24000,1000 do
|
||
|
local light_level = minetest.get_node_light(checkpos, j)
|
||
|
|
||
|
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_light_around_voxel
|
||
|
-- @param pos position to validate
|
||
|
-- @param light_around light around definitions
|
||
|
-- @return true/false
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.check_light_around_voxel(pos,light_around)
|
||
|
if light_around == nil then
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
local maxdistance = 0
|
||
|
|
||
|
for i=1,#light_around,1 do
|
||
|
maxdistance = adv_spawning.MAX(maxdistance,light_around[i].distance)
|
||
|
end
|
||
|
|
||
|
-- voxelmanip is of no use for low counts of nodes
|
||
|
if maxdistance <=10 then
|
||
|
return adv_spawning.check_light_around(pos,light_around)
|
||
|
end
|
||
|
|
||
|
local minp = { x=math.floor(pos.x - maxdistance),
|
||
|
y=math.floor(pos.y - maxdistance),
|
||
|
z=math.floor(pos.z - maxdistance)}
|
||
|
local maxp = { x=math.ceil(pos.x + maxdistance),
|
||
|
y=math.ceil(pos.y + maxdistance),
|
||
|
z=math.ceil(pos.z + maxdistance)}
|
||
|
|
||
|
local voxeldata = minetest.get_voxel_manip()
|
||
|
local got_minp,got_maxp = voxeldata:read_from_map(minp,maxp)
|
||
|
|
||
|
local voxel_light_data = voxeldata:get_light_data()
|
||
|
local node_data = voxeldata:get_data()
|
||
|
local voxelhelper = VoxelArea:new({MinEdge=got_minp,MaxEdge=got_maxp})
|
||
|
|
||
|
|
||
|
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 z=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 j=0,24000,1000 do
|
||
|
local light_level =
|
||
|
adv_spawning.voxelmaniplight(node_data,
|
||
|
voxel_light_data,
|
||
|
voxelhelper,
|
||
|
checkpos,j)
|
||
|
|
||
|
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 =
|
||
|
adv_spawning.voxelmaniplight(node_data,
|
||
|
voxel_light_data,
|
||
|
voxelhelper,
|
||
|
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
|
||
|
|
||
|
local light_lookup = {
|
||
|
{4250+125, 150},
|
||
|
{4500+125, 150},
|
||
|
{4750+125, 250},
|
||
|
{5000+125, 350},
|
||
|
{5250+125, 500},
|
||
|
{5500+125, 675},
|
||
|
{5750+125, 875},
|
||
|
{6000+125, 1000},
|
||
|
{6250+125, 1000}
|
||
|
}
|
||
|
|
||
|
function adv_spawning.day_night_ratio(time)
|
||
|
|
||
|
--make sure time is between 0 and 240000
|
||
|
if time < 0 then
|
||
|
time = time - (((time*-1)/24000)*24000)
|
||
|
end
|
||
|
if time > 24000 then
|
||
|
time = time + ((time/24000)*24000)
|
||
|
end
|
||
|
|
||
|
--invert time for sunset
|
||
|
if time > 12000 then
|
||
|
time = 24000 - time
|
||
|
end
|
||
|
|
||
|
local dnr = 1000
|
||
|
|
||
|
for i=1,#light_lookup,1 do
|
||
|
if time < light_lookup[i][1] then
|
||
|
dnr = light_lookup[i][2]
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return dnr
|
||
|
end
|
||
|
|
||
|
function adv_spawning.voxelmaniplight(node_data,light_data,area,pos,time)
|
||
|
|
||
|
if not area:containsp(pos) then
|
||
|
return minetest.get_node_light(pos, time)
|
||
|
end
|
||
|
|
||
|
pos = vector.round(pos)
|
||
|
local index = area:indexp(pos)
|
||
|
|
||
|
local raw_light_value = light_data[index]
|
||
|
|
||
|
if raw_light_value == nil then
|
||
|
return nil
|
||
|
end
|
||
|
|
||
|
local light_day = nil
|
||
|
local light_night = nil
|
||
|
|
||
|
--read node information
|
||
|
local content_id = node_data[index]
|
||
|
local nodename = minetest.get_name_from_content_id(content_id)
|
||
|
local nodedef = minetest.registered_nodes[nodename]
|
||
|
|
||
|
-- check for solid node
|
||
|
if nodedef.paramtype ~= "light" then
|
||
|
light_day = 0
|
||
|
light_night = 0
|
||
|
else
|
||
|
light_day = raw_light_value % 16
|
||
|
light_night = (raw_light_value - light_day)/16
|
||
|
end
|
||
|
|
||
|
--check lightsource
|
||
|
if nodedef.light_source ~= nil then
|
||
|
light_day = adv_spawning.MAX(nodedef.light_source,light_day)
|
||
|
light_night = adv_spawning.MAX(nodedef.light_source,light_night)
|
||
|
end
|
||
|
|
||
|
time = time *24000
|
||
|
time = time %24000
|
||
|
|
||
|
local dnr = adv_spawning.day_night_ratio(time)
|
||
|
|
||
|
local c = 1000
|
||
|
local current_light = ((dnr * light_day + (c-dnr) * light_night))/c
|
||
|
if(current_light > LIGHT_MAX+1) then
|
||
|
current_light = LIGHT_MAX+1
|
||
|
end
|
||
|
|
||
|
return math.floor(current_light)
|
||
|
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] check_flat_area
|
||
|
-- @param pos position to validate
|
||
|
-- @param range to check for same height
|
||
|
-- @param deviation maximum nmber of nodes not matching flat check
|
||
|
-- @param spawn_inside nodes to spawn inside
|
||
|
-- @return true/false
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.check_flat_area(new_pos,flat_area,spawn_inside,surfaces)
|
||
|
|
||
|
if flat_area == nil then
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
local range = flat_area.range
|
||
|
|
||
|
local back_left = {x=new_pos.x-range,y=new_pos.y-1,z=new_pos.z-range}
|
||
|
local front_right = { x=new_pos.x+range,y=new_pos.y-1,z=new_pos.z+range}
|
||
|
|
||
|
local current_deviation = 0
|
||
|
|
||
|
if flat_area.deviation ~= nil then
|
||
|
current_deviation = flat_area.deviation
|
||
|
end
|
||
|
|
||
|
local required_nodes = (range*2+1)*(range*2+1) - current_deviation
|
||
|
|
||
|
if surfaces == nil then
|
||
|
local ground_nodes =
|
||
|
minetest.find_nodes_in_area(back_left, front_right, spawn_inside)
|
||
|
|
||
|
if #ground_nodes > current_deviation then
|
||
|
adv_spawning.log("info","check_flat_area: " .. range .. " "
|
||
|
..dump(current_deviation).. " " .. #ground_nodes )
|
||
|
--adv_spawning.dump_area({x=back_left.x,y=new_pos.y-1,z=back_left.z},
|
||
|
-- front_right)
|
||
|
return false
|
||
|
end
|
||
|
else
|
||
|
local ground_nodes =
|
||
|
minetest.find_nodes_in_area(back_left, front_right, surfaces)
|
||
|
|
||
|
if #ground_nodes < required_nodes then
|
||
|
adv_spawning.log("info","check_flat_area: " .. range .. " " ..
|
||
|
dump(current_deviation).. " " .. #ground_nodes )
|
||
|
--adv_spawning.dump_area({x=back_left.x,y=new_pos.y-1,z=back_left.z},
|
||
|
-- front_right)
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
back_left.y = new_pos.y
|
||
|
front_right.y = new_pos.y
|
||
|
|
||
|
local inside_nodes =
|
||
|
minetest.find_nodes_in_area(back_left, front_right, spawn_inside)
|
||
|
|
||
|
if #inside_nodes < required_nodes then
|
||
|
adv_spawning.log("info","check_flat_area: " .. range .. " " ..
|
||
|
dump(current_deviation) .. " "
|
||
|
.. #inside_nodes .. "/" .. required_nodes)
|
||
|
--adv_spawning.dump_area({x=back_left.x,y=new_pos.y-1,z=back_left.z},
|
||
|
-- front_right)
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
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
|
||
|
print("ADV_SPAWNING:" .. text)
|
||
|
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[1] >= -0.5 and collisionbox[2] >= -0.5 and collisionbox[3] >= -0.5 and
|
||
|
collisionbox[4] <= 0.5 and collisionbox[5] <= 0.5 and collisionbox[6] <= 0.5 then
|
||
|
return true,nil
|
||
|
end
|
||
|
|
||
|
--lets do the more complex checks
|
||
|
--first check if we need to move up
|
||
|
if collisionbox[2] < -0.5 then
|
||
|
pos.y = pos.y + (collisionbox[2]*-1) - 0.45
|
||
|
end
|
||
|
|
||
|
local minp = {
|
||
|
x=pos.x+collisionbox[1],
|
||
|
y=pos.y+collisionbox[2],
|
||
|
z=pos.z+collisionbox[3]
|
||
|
}
|
||
|
local maxp = {
|
||
|
x=pos.x+collisionbox[4],
|
||
|
y=pos.y+collisionbox[5],
|
||
|
z=pos.z+collisionbox[6]
|
||
|
}
|
||
|
|
||
|
local lastpos = nil
|
||
|
|
||
|
for y=minp.y,maxp.y,1 do
|
||
|
for z=minp.z,maxp.z,1 do
|
||
|
for x=minp.x,maxp.x,1 do
|
||
|
local checkpos = {x=x,y=y,z=z}
|
||
|
if not adv_spawning.is_same_pos(checkpos,lastpos) then
|
||
|
local node = minetest.get_node(checkpos)
|
||
|
|
||
|
if not adv_spawning.contains(spawn_inside,node.name) then
|
||
|
adv_spawning.log("info","Failed collision box check: " ..
|
||
|
minetest.pos_to_string(pos) .. " "
|
||
|
.. dump(node.name) .. " at ".. minetest.pos_to_string(checkpos))
|
||
|
--adv_spawning.dump_area()
|
||
|
return false,nil
|
||
|
end
|
||
|
|
||
|
lastpos = checkpos
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return true,pos.y
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] check_active_block
|
||
|
-- @param pos position to check
|
||
|
-- @param collisionbox collisionbox to use
|
||
|
-- @param spawn_inside nodes to spawn inside
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.check_active_block(pos)
|
||
|
local players = minetest.get_connected_players()
|
||
|
|
||
|
for i=1,#players,1 do
|
||
|
if vector.distance(pos,players[i]:getpos()) < adv_spawning.active_range then
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] is_same_pos
|
||
|
-- @param pos1 first for comparison
|
||
|
-- @param pos2 second position for comparison
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.is_same_pos(pos1,pos2)
|
||
|
if pos1 == nil or pos2 == nil then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
if pos1.x ~= pos2.x or
|
||
|
pos1.y ~= pos2.y or
|
||
|
pos1.z ~= pos2.z then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] handle_mapgen_spawning
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.handle_mapgen_spawning()
|
||
|
local continue = false
|
||
|
while(not continue and #adv_spawning.mapgen_jobqueue > 0) do
|
||
|
local toprocess = adv_spawning.mapgen_jobqueue[1]
|
||
|
table.remove(adv_spawning.mapgen_jobqueue,1)
|
||
|
|
||
|
--print("Processing job: " .. dump(toprocess) .. " no_quota: " ..
|
||
|
-- dump(adv_spawning.time_over(10)))
|
||
|
|
||
|
local tries = 0
|
||
|
|
||
|
while ( toprocess.retries > 0 and
|
||
|
toprocess.spawntotal > 0 and
|
||
|
tries < adv_spawning.max_mapgen_tries_per_step and
|
||
|
(not adv_spawning.time_over(10)) )do
|
||
|
|
||
|
local single_spawn_check = adv_spawning.gettime()
|
||
|
|
||
|
local retval,permanent_error = adv_spawning.handlespawner(toprocess.spawner,
|
||
|
{x=0,y=0,z=0},
|
||
|
toprocess.minp,
|
||
|
toprocess.maxp,
|
||
|
true)
|
||
|
|
||
|
local delta = adv_spawning.gettime() - adv_spawning.quota_starttime
|
||
|
|
||
|
if retval then
|
||
|
toprocess.spawntotal = toprocess.spawntotal -1
|
||
|
end
|
||
|
|
||
|
if permanent_error then
|
||
|
toprocess.retries = 0
|
||
|
end
|
||
|
|
||
|
toprocess.retries = toprocess.retries -1
|
||
|
tries = tries +1
|
||
|
end
|
||
|
|
||
|
if toprocess.retries > 0 then
|
||
|
if toprocess.spawntotal > 0 then
|
||
|
table.insert(adv_spawning.mapgen_jobqueue,toprocess)
|
||
|
end
|
||
|
continue = true
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] queue_mapgen_jobs
|
||
|
-- @param minp
|
||
|
-- @param maxp
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.queue_mapgen_jobs(minp,maxp)
|
||
|
for key,value in pairs(adv_spawning.spawner_definitions) do
|
||
|
local continue = false
|
||
|
|
||
|
--check if cyclic spawning is enabled
|
||
|
if not continue and
|
||
|
(value.mapgen == nil or
|
||
|
value.mapgen.enabled == false) then
|
||
|
continue = true
|
||
|
end
|
||
|
|
||
|
|
||
|
if not continue then
|
||
|
table.insert(adv_spawning.mapgen_jobqueue,
|
||
|
{
|
||
|
minp = minp,
|
||
|
maxp = maxp,
|
||
|
spawner = key,
|
||
|
retries = value.mapgen.retries,
|
||
|
spawntotal = value.mapgen.spawntotal
|
||
|
})
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] dump_area
|
||
|
-- @param minp
|
||
|
-- @param maxp
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.dump_area(minp,maxp)
|
||
|
print("Dumping: " .. dump(minp) .. "<-->" .. dump(maxp))
|
||
|
for y=minp.y,maxp.y,1 do
|
||
|
print("--- ypos: " .. y .. " --------------------------------------------------------------")
|
||
|
for z=minp.z,maxp.z,1 do
|
||
|
local line = ""
|
||
|
for x=minp.x,maxp.x,1 do
|
||
|
local node = minetest.get_node({x=x,y=y,z=z})
|
||
|
|
||
|
local toprint = node.name
|
||
|
|
||
|
if toprint:find(":") ~= nil then
|
||
|
toprint = toprint:sub(toprint:find(":")+1)
|
||
|
end
|
||
|
|
||
|
line = line .. string.format(" %15s |",toprint)
|
||
|
end
|
||
|
print(line)
|
||
|
end
|
||
|
print("")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] check_time
|
||
|
-- @param starttime time since when to check
|
||
|
-- @param checkid name of this check
|
||
|
--
|
||
|
-- @return current time for next check
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.check_time(starttime, checkid)
|
||
|
local currenttime = adv_spawning.gettime()
|
||
|
local delta = currenttime - starttime
|
||
|
|
||
|
if (delta > adv_spawning.quota_reload) then
|
||
|
if adv_spawning.spawner_warned[checkid] ~= true then
|
||
|
adv_spawning.dbg_log(1, "spawner " .. checkid ..
|
||
|
"\n\texceeded more then full reload time on init (" .. delta .. " ms)." ..
|
||
|
"\n\tFix it as it will cause major lag on mapgen!")
|
||
|
adv_spawning.spawner_warned[checkid] = true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return currenttime
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] dbg_log
|
||
|
-- @param loglevel level print it
|
||
|
-- @param message message to print
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.dbg_log(loglevel, message)
|
||
|
if (adv_spawning.loglevel >= loglevel ) then
|
||
|
core.log("action", "ADV_SPAWNING: " .. message)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] refresh_spawners
|
||
|
-- @param pos to refresh spawners around
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.refresh_spawners(pos)
|
||
|
|
||
|
local min = {x=pos.x-32, y=pos.y-32, z=pos.z-32}
|
||
|
local max = {x=pos.x+32, y=pos.y+32, z=pos.z+32}
|
||
|
|
||
|
local start_x =
|
||
|
math.floor(min.x/adv_spawning.spawner_distance)
|
||
|
* adv_spawning.spawner_distance
|
||
|
local start_y =
|
||
|
(math.floor(min.y/adv_spawning.spawner_distance)
|
||
|
* adv_spawning.spawner_distance)
|
||
|
+ adv_spawning.spawner_y_offset
|
||
|
local start_z =
|
||
|
math.floor(min.z/adv_spawning.spawner_distance)
|
||
|
* adv_spawning.spawner_distance
|
||
|
|
||
|
for x=start_x,max.x,adv_spawning.spawner_distance do
|
||
|
for y=start_y,max.y,adv_spawning.spawner_distance do
|
||
|
for z=start_z,max.z,adv_spawning.spawner_distance do
|
||
|
if x > min.x and
|
||
|
y > min.y and
|
||
|
z > min.z then
|
||
|
if not adv_spawning.quota_leave() then
|
||
|
adv_spawning.dbg_log(2,
|
||
|
"adv_spawning: refresh_spawners did use way too much time 1")
|
||
|
end
|
||
|
minetest.add_entity({x=x,y=y,z=z},"adv_spawning:spawn_seed")
|
||
|
adv_spawning.quota_enter(true)
|
||
|
adv_spawning.log("info", "adv_spawning: adding spawner entity at "
|
||
|
.. core.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] table_count
|
||
|
-- @param tocount table to get number of elements from
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.table_count(tocount)
|
||
|
local retval = 0
|
||
|
for k,v in pairs(tocount) do
|
||
|
retval = retval +1
|
||
|
end
|
||
|
|
||
|
return retval
|
||
|
end
|
||
|
|
||
|
function adv_spawning.build_shell(pos, d)
|
||
|
local retval = {}
|
||
|
|
||
|
-- build top face
|
||
|
for x = -d , d , 1 do
|
||
|
for z = -d, d, 1 do
|
||
|
retval[#retval+1] = { x = pos.x + x, y = pos.y + d, z = pos.z + z}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- build bottom face
|
||
|
for x = -d , d , 1 do
|
||
|
for z = -d, d, 1 do
|
||
|
retval[#retval+1] = { x = pos.x + x, y = pos.y -d, z = pos.z + z}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- build x- face
|
||
|
for z = -d , d , 1 do
|
||
|
for y = - (d -1) , (d -1), 1 do
|
||
|
retval[#retval+1] = { x = pos.x -d, y = pos.y + y, z = pos.z + z}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- build x+ face
|
||
|
for z = -d , d , 1 do
|
||
|
for y = - (d -1) , (d -1), 1 do
|
||
|
retval[#retval+1] = { x = pos.x + d, y = pos.y + y, z = pos.z + z}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- build z- face
|
||
|
for x = - (d -1) , (d -1) , 1 do
|
||
|
for y = - (d -1) , (d -1), 1 do
|
||
|
retval[#retval+1] = { x = pos.x + x, y = pos.y + y, z = pos.z - d}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- build z+ face
|
||
|
for x = -(d -1) , (d -1) , 1 do
|
||
|
for y = - (d -1) , (d -1), 1 do
|
||
|
retval[#retval+1] = { x = pos.x + x, y = pos.y + y, z = pos.z + d}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return retval;
|
||
|
end
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
-- @function [parent=#adv_spawning] table_count
|
||
|
-- @param tocount table to get number of elements from
|
||
|
--------------------------------------------------------------------------------
|
||
|
function adv_spawning.find_nodes_in(pos, min_range, max_range, nodetypes)
|
||
|
|
||
|
local nodetypes_to_use = nodetypes
|
||
|
|
||
|
if type(nodetypes) == "string" then
|
||
|
nodetypes_to_use = { }
|
||
|
table.insert(nodetypes_to_use, nodetypes)
|
||
|
end
|
||
|
|
||
|
for i = min_range, max_range, 1 do
|
||
|
local positions = adv_spawning.build_shell(pos, i)
|
||
|
|
||
|
for i = 1, #positions, 1 do
|
||
|
local node = minetest.get_node_or_nil(positions[i])
|
||
|
|
||
|
if node ~= nil then
|
||
|
for i = 1, #nodetypes_to_use, 1 do
|
||
|
if node.name == nodetypes_to_use[i] then
|
||
|
return positions[i]
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return nil
|
||
|
end
|