pax_global_header00006660000000000000000000000064126402025700014510gustar00rootroot0000000000000052 comment=ec41aa93f6c026181717909c9445199f95643dba adv_spawning-0.0.13/000077500000000000000000000000001264020257000142515ustar00rootroot00000000000000adv_spawning-0.0.13/README.txt000066400000000000000000000164761264020257000157650ustar00rootroot00000000000000******************************************************************************** * * * Advanced spawning mod (adv_spawning) 0.0.6 * * * * 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 achieve this performance goal adv_spawning is intended for low frequency entity spawning only. Typical 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 e.g. func(pos) 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, ... }, mapgen = -- configuration for initial mapgen spawning { enabled = true, -- mapgen spawning enabled or not retries = 5, -- number of tries to spawn a entity prior giving up spawntotal = 3, -- number of entities to try on mapgen }, flat_area = -- check for amount of flat area around, -- (only usefull for ground bound mobs) { range = 3, -- range to be checked for flattness deviation = 2, -- maximum number of nodes not matching flat check }, daytimes = -- do only spawn within these daytimes { daytime_def_1, daytime_def_2, ... } collisionbox = {}, -- collisionbox of entity to spawn (usually same as used for entiy itself) spawn_interval = 200, -- [MANDATORY] interval to try to spawn a entity spawns_per_interval = 1, -- try to spawn multiple mobs (if time available) custom_check = fct(pos,spawndef), -- a custom check to be called return true for pass, false for not pass cyclic_spawning = true -- spawn per spawner step (defaults to true) } 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" } Daytime definition: { begin = 0.0, --minimum daytime stop = 0.25, --maximum daytime } 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 } } Settings: adv_spawning_validate_spawners = false ^ make advanced_spawning check area around active players for lost spawner seeds Changelog: 0.0.9 -Fix broken spawner initialization -Add support for regenerating spawner seeds (only around active players) set >>adv_spawning_validate_spawners<< to true if you want advanced spawning to do this. Note: this might need some additional cpu time 0.0.8 -Fix large steps caused by uninterruptable spawn seed initialization within activation 0.0.7 -handle time steps backward without assertion 0.0.6 -add configuration option adv_spawing.debug to show or hide spawner entities -fix quota overflow calculation -add rightclick function to show debug info 0.0.5 -fix MIN/MAX to always return a number value -fix default activity range to use a initial value if not manually configured -don't calculate statistics with zero dtime -fix invalid debug log using old variable nameadv_spawning-0.0.13/api.lua000066400000000000000000000036121264020257000155270ustar00rootroot00000000000000------------------------------------------------------------------------------- -- 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 if not adv_spawning.verify_check_entities_around(spawning_def.entities_around) then return false end if not adv_spawning.verify_check_nodes_around(spawning_def.nodes_around) then return false end adv_spawning.spawner_definitions[spawner_name] = spawning_def adv_spawning.dbg_log(0, "registering spawner \"" .. spawner_name .. "\"") adv_spawning.dbg_log(0, "now handling: " .. adv_spawning.table_count(adv_spawning.spawner_definitions) .. " spawner definitions") 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 -------------------------------------------------------------------------------- -- @function [parent=#adv_spawning] get_spawner_density -- @return get snapshot of statistics -------------------------------------------------------------------------------- function adv_spawning.get_spawner_density() return adv_spawning.spawner_distance,adv_spawning.spawner_y_offset endadv_spawning-0.0.13/init.lua000066400000000000000000000020151264020257000157150ustar00rootroot00000000000000------------------------------------------------------------------------------- -- advanced spawning mod -- --@license WTFP --@copyright Sapier --@author Sapier --@date 2013-12-05 -- ------------------------------------------------------------------------------- local version = "0.0.13" if adv_spawning ~= nil then core.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 = {} adv_spawning.debug = core.setting_get("adv_spawning.debug") local adv_modpath = core.get_modpath("adv_spawning") dofile (adv_modpath .. "/internal.lua") dofile (adv_modpath .. "/spawndef_checks.lua") dofile (adv_modpath .. "/api.lua") dofile (adv_modpath .. "/spawn_seed.lua") adv_spawning.initialize() core.log("action", "Advanced spawning mod version " .. version .. " loaded") adv_spawning-0.0.13/internal.lua000066400000000000000000001412571264020257000166020ustar00rootroot00000000000000------------------------------------------------------------------------------- -- 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 adv_spawning-0.0.13/spawn_seed.lua000066400000000000000000000270171264020257000171130ustar00rootroot00000000000000------------------------------------------------------------------------------- -- 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 local starttime = adv_spawning.gettime() adv_spawning.seed_activate(self) adv_spawning.check_time(starttime, "Initializing spawner on_step took way too much time") return end self.mydtime = self.mydtime + dtime if (self.mydtime < 1/adv_spawning.max_spawning_frequency_hz) then return end --check if we did finish initialization of our spawner list by now if not adv_spawning.seed_scan_for_applyable_spawners(self) 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 local key = nil local starttime = adv_spawning.gettime() while #self.pending_spawners > 0 and per_step_count < adv_spawning.max_spawns_per_spawner and (not adv_spawning.time_over(10)) do local rand_spawner = math.random(1,#self.pending_spawners) key = self.pending_spawners[rand_spawner] local tries = 1 if adv_spawning.spawner_definitions[key].spawns_per_interval ~= nil then tries = adv_spawning.spawner_definitions[key].spawns_per_interval end while tries > 0 do local successfull, permanent_error, reason = adv_spawning.handlespawner(key,self.object:getpos()) if successfull then self.spawning_data[key] = adv_spawning.spawner_definitions[key].spawn_interval self.spawn_fail_reasons[key] = "successfull spawned" else self.spawning_data[key] = adv_spawning.spawner_definitions[key].spawn_interval/4 self.spawn_fail_reasons[key] = reason end --check quota again if not adv_spawning.quota_leave() then adv_spawning.dbg_log(2, "spawner " .. key .. " did use way too much time") end if not adv_spawning.quota_enter() then return end tries = tries -1 end starttime = adv_spawning.check_time(starttime, key .. " for " .. adv_spawning.spawner_definitions[key].spawnee .. " did use way too much time") table.remove(self.pending_spawners,rand_spawner) per_step_count = per_step_count +1 end -- if (#self.pending_spawners > 0) then -- adv_spawning.dbg_log(3, "Handled " .. per_step_count .. " spawners, spawners left: " .. #self.pending_spawners) -- end if not adv_spawning.quota_leave() then adv_spawning.dbg_log(2, "spawner " .. key .. " did use way too much time") end 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) self.pending_spawners = {} self.spawn_fail_reasons = {} self.initialized_spawners = 0 self.activated = true -- fix unaligned own pos local pos = self.object:getpos() pos.x = math.floor(pos.x + 0.5) pos.y = math.floor(pos.y + 0.5) pos.z = math.floor(pos.z + 0.5) self.object:setpos(pos) if not adv_spawning.quota_leave() then adv_spawning.dbg_log(2, "on activate " .. self.name .. " did use way too much time") end end end -------------------------------------------------------------------------------- -- @function [parent=#adv_spawning] on_rightclick -- @param self spawner entity -- @param clicker (unused) -------------------------------------------------------------------------------- function adv_spawning.on_rightclick(self, clicker) if adv_spawning.debug then print("ADV_SPAWNING: time till next spawn: " .. self.mydtime) print("ADV_SPAWNING: pending spawners: " .. #self.pending_spawners) print("ADV_SPAWNING: Spawner may spawn " .. adv_spawning.table_count(self.spawning_data) .. " mobs:") local index = 1 for key,value in pairs(self.spawning_data) do local reason = "unknown" if self.spawn_fail_reasons[key] then reason = self.spawn_fail_reasons[key] end print(string.format("%3d:",index) .. string.format("%30s ",key) .. string.format("%3d s (", value) .. reason .. ")") index = index +1 end 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 adv_spawning.debug then 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 self.object:set_armor_groups({ immortal=100 }) adv_spawning.seed_activate(self) end, on_step = adv_spawning.seed_step, get_staticdata = function(self) return minetest.serialize(self.spawning_data) end, on_rightclick = adv_spawning.on_rightclick } ) 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) assert(self ~= nil) local pos = self.object:getpos() local objects = minetest.get_objects_inside_radius(pos, 0.5) if objects == nil then return false end -- check if any of those found objects is a spawning seed for k,v in ipairs(objects) do local entity = v:get_luaentity() if entity ~= nil then if entity.name == "adv_spawning:spawn_seed" and entity.object ~= self.object then self.object:remove() return true end end end return false end function adv_spawning.init_spawner(self, pos, name, spawnerdef) local starttime = adv_spawning.gettime() if self.spawner_init_state ~= nil then self.spawner_init_state = "initial" end local starttime = adv_spawning.gettime() if self.spawner_init_state == "initial" then --check if cyclic spawning is enabled if spawnerdef.cyclic_spawning ~= nil and spawnerdef.cyclic_spawning == false then self.spawning_data[name] = nil return true end self.spawner_init_state = "abs_height" end starttime = adv_spawning.check_time(starttime, name .. "cyclic check") if self.spawner_init_state == "abs_height" then --if spawner is far away from spawn area don't even try to spawn if spawnerdef.absolute_height ~= nil then if spawnerdef.absolute_height.min ~= nil and spawnerdef.absolute_height.min > pos.y + (adv_spawning.spawner_distance/2) then self.spawning_data[name] = nil return true end if spawnerdef.absolute_height.max ~= nil and spawnerdef.absolute_height.max < pos.y - (adv_spawning.spawner_distance/2) then self.spawning_data[name] = nil return true end end self.spawner_init_state = "environment" end starttime = adv_spawning.check_time(starttime, name .. "height check") if self.spawner_init_state == "environment" then local runidx = 1 local radius = adv_spawning.spawner_distance / 2 if self.spawnerinit_env_radius ~= nil then runidx = self.spawnerinit_env_radius end local found = false for i = runidx , radius, 1 do adv_spawning.quota_leave() if not adv_spawning.quota_enter() then self.spawnerinit_env_radius = runidx return false end if spawnerdef.spawn_inside == nil then print(name .. " tries to spawn within nil") assert(false) end local resultpos = adv_spawning.find_nodes_in(pos, runidx, runidx, spawnerdef.spawn_inside) if (resultpos ~= nil) then local node = minetest.get_node_or_nil(resultpos) found = true break end end starttime = adv_spawning.check_time(starttime, name .. " at environment check radius was: " .. radius .. " env: " .. dump(spawnerdef.spawn_inside)) if not found then self.spawning_data[name] = nil end end self.spawner_init_state = "initial" self.spawning_data[name] = spawnerdef.spawn_interval * math.random() return true 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) if self.initialized_spawners >= adv_spawning.table_count(adv_spawning.spawner_definitions) then return true end local runindex = 0 if self.spawner_init_idx ~= nil then runindex = self.spawner_init_idx end local pos = self.object:getpos() for key,value in pairs(adv_spawning.spawner_definitions) do if not adv_spawning.quota_enter() then return false end local continue = false if runindex >= self.initialized_spawners then self.initialized_spawners = self.initialized_spawners + 1 else continue = true end if not continue then runindex = runindex + 1 if not adv_spawning.init_spawner(self, pos, key, value) then return false end end adv_spawning.quota_leave() end return self.initialized_spawners == #adv_spawning.spawner_definitions endadv_spawning-0.0.13/spawndef_checks.lua000066400000000000000000000043301264020257000201030ustar00rootroot00000000000000------------------------------------------------------------------------------- -- advanced spawning mod -- --@license WTFP --@copyright Sapier --@author Sapier --@date 2013-12-05 -- ------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- @function [parent=#adv_spawning] verify_check_entities_around -- @param entities_around a spawndef entities_around config -- @return true/false -------------------------------------------------------------------------------- function adv_spawning.verify_check_entities_around(entities_around) if entities_around ~= nil then for i=1,#entities_around,1 do if type(entities_around[i].distance) ~= "number" then adv_spawning.dbg_log(0, "missing distance in entities_around definition") return false end if entities_around[i].type ~= "MIN" and entities_around[i].type ~= "MAX" then adv_spawning.dbg_log(0, "invalid type \"" .. dump(entities_around[i].type) .. "\" in entities_around definition") return false end end end return true end -------------------------------------------------------------------------------- -- @function [parent=#adv_spawning] verify_check_nodes_around -- @param nodes_around a spawndef entities_around config -- @return true/false -------------------------------------------------------------------------------- function adv_spawning.verify_check_nodes_around(nodes_around) if nodes_around ~= nil then for i=1,#nodes_around,1 do if type(nodes_around[i].distance) ~= "number" then adv_spawning.dbg_log(0, "missing distance in entities_around definition") return false end if nodes_around[i].type ~= "MIN" and nodes_around[i].type ~= "MAX" then adv_spawning.dbg_log(0, "invalid type \"" .. dump(nodes_around[i].type) .. "\" in entities_around definition") return false end if nodes_around[i].name == nil or type(nodes_around[i].name) ~= "table" then adv_spawning.dbg_log(0, "invalid type of name \"" .. type(nodes_around[i].name) .. "\"" .. " Data: " .. dump(nodes_around[i].name) .. " in nodes_around definition") return false end end end return true endadv_spawning-0.0.13/testmod/000077500000000000000000000000001264020257000157305ustar00rootroot00000000000000adv_spawning-0.0.13/testmod/depends.txt000066400000000000000000000000141264020257000201060ustar00rootroot00000000000000adv_spawningadv_spawning-0.0.13/testmod/init.lua000066400000000000000000000060331264020257000174000ustar00rootroot00000000000000 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("adv_stats", { params = "", description = "print advanced spawning satistics to logfile" , func = function() local stats = adv_spawning.get_statistics() adv_spawning.dbg_log(0, "Adv. Spawning stats:") adv_spawning.dbg_log(0, "----------------------------------------") adv_spawning.dbg_log(0, "Spawners added: " .. stats.session.spawners_created) adv_spawning.dbg_log(0, "Spawnees added: " .. stats.session.entities_created) adv_spawning.dbg_log(0, "") adv_spawning.dbg_log(0, "Longest step: " .. stats.step.max) adv_spawning.dbg_log(0, "") adv_spawning.dbg_log(0, "Current load: " .. stats.load.cur) adv_spawning.dbg_log(0, "Average load: " .. stats.load.avg) adv_spawning.dbg_log(0, "Maximum load: " .. stats.load.max) end })adv_spawning-0.0.13/testmod/textures/000077500000000000000000000000001264020257000176135ustar00rootroot00000000000000adv_spawning-0.0.13/testmod/textures/testmod_num1.png000066400000000000000000000005071264020257000227420ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME )`tEXtCommentCreated with GIMPWIDATxٱ @V66nK@#G&x ϛ;q$$I*5c8Z {}0`G~qOrN2$!INIn?A`~?~Zs{$I[eMIENDB`adv_spawning-0.0.13/testmod/textures/testmod_num2.png000066400000000000000000000012751264020257000227460ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME  4)}tEXtCommentCreated with GIMPW%IDATx͋QLIB"D SK# eGJ݌_rd' /;1^&1}Vrθyn=yC&d2L&$Ӧ˰=EYg#<uj3Їsxjeg7,6)*q;!$ @/;j>xO#E7NT 1MxEj+PA?&x`yS%`o*.< pUh7.oLi:@cєX.vHx"+ ? +V]4nRJ5)(Y ?0;v&I7]~ejb_%'c_@~ vUxS">vŢ&$7zDZ,ҚJ䯫-FV|-m[mM_1 ߉]~ڷIW]̌Y~~!ݛlVq#vʏbO4c2՗-J d2L& |YR;^IENDB`adv_spawning-0.0.13/testmod/textures/testmod_num3.png000066400000000000000000000014261264020257000227450ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME !atEXtCommentCreated with GIMPW~IDATxϋQbb$ Y6ccg#IXl(D cs,8_康|ٜ=s|Ԑ*?6afa&&чxN-F=]wr W^bChq.< z|:`քVz-.VL,p`<硸Pau*5JKNTZhq1g?`rd;%"k3:"9`s*EB%8g X|l*pH;[nme,laEUء75 `TuJ7G-)6z0> t`x 0lԚ_t&cSЊWSh#NGWK Nd4!CJFD|K%֗luS{S)FԔnbm++Z۱|(ě0g% =ٿx!{SiUin!c av.fw@5P-'j:B.k2w:DZZWMXضf%d/{X^*< ~±ȞOIENDB`adv_spawning-0.0.13/testmod/textures/testmod_num4.png000066400000000000000000000010371264020257000227440ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME "=tEXtCommentCreated with GIMPWIDATx?(a?O@QJJYl&Ve0XoU6F$eedUe(2( 2(+ zswr;}A!B!G/P*5٨S>iS@٫n2(IQtՋ 5(yty dv>ymYu>iƋ{Nrd,vuI JA{`Ћc7kh:>)#SPzphY' F3Ã!!(}AQb{P0  0w9#-A@pc/41+JF&gE'`&{U~=WFfF%`{q!=?b:-u9 \ʪG@$@$@$@B! B!9IENDB`adv_spawning-0.0.13/textures/000077500000000000000000000000001264020257000161345ustar00rootroot00000000000000adv_spawning-0.0.13/textures/adv_spawning_invisible.png000066400000000000000000000002641264020257000233700ustar00rootroot00000000000000PNG  IHDRabKGD pHYs  tIME 54tEXtCommentCreated with GIMPWIDAT8c?%B0j c=AIENDB`adv_spawning-0.0.13/textures/adv_spawning_spawner.png000066400000000000000000000030071264020257000230610ustar00rootroot00000000000000PNG  IHDR@@iqbKGD pHYs  tIME  9NftEXtCommentCreated with GIMPWoIDATxOHOFЪ$?-(UDAPFlIz׃XT<ب R`-AZ-Tddsx!3;ﳳ޼HfPr?NMN Ӓrhx$ApZ7>\\4. mKER .Z߾QEb pn|0sԷhbbfggn&n7%&&RRRP^^lǏk+@@.//ىL8h4uuux=-JJJ$D@ (_ё4:r!:V?~_tӧOhhhÇp]`C4߿26 UUUa$ggg ,˭zF>B_l6GM^gVVV7o0@ 5} 3 Nj` r=33SM}}=^ SF???}苋`40|@aa!wuuiA- X,gjll$׫jZrr[2xv;|> S4pDq 9 f3ۋ尡(E͆qxrr2V+qzz(&*ԄI$`ll kXRߎAoo/hGDx^lmmahhYp]4gqiyy-..F///We;EHfffYMtީ;-899+|L;o߾}9,'+o~2 D锞NWWWt||L@t:ϟ?I'~9KUUn" ;Nd2 fJII I 㡅 \.x