Pregenerate spawn points
Overhaul of mod to pre-generate static spawnpoints at intervals and save between server reboots. * The spawn points are generated in the background on a timer, up to a maximum count * Players requesting a new spawn point will receive it immediately, if there is one available
This commit is contained in:
parent
f4f6d6ce43
commit
4b1c6bac85
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.swp
|
44
README.md
44
README.md
@ -1,33 +1,49 @@
|
||||
# r-Spawn for Minetest
|
||||
|
||||
A spawn command for Minetest without needing a fixed point -- `singleplayer` rejoice!
|
||||
A spawn engine for Minetest without needing a fixed point.
|
||||
|
||||
Players are each given their own randomized spawn point near the spawn origin on first joining. If no `static_spawnpoint` is defined in `minetest.conf`, the origin is 0,0,0. If static spawn point is defined, that point is used as origin instead.
|
||||
Players are each given their own randomized spawn point near the spawn origin on first joining.
|
||||
|
||||
If `static_spawnpoint` is defined in `minetest.conf`, that point is used as spawn point instead (compatibility)
|
||||
|
||||
## Features
|
||||
|
||||
* A normal game in singleplayer mode will still alow the player access to a spawn location
|
||||
* Player is assigned randomized spawnpoint on joining
|
||||
* Player will respawn at their spawnpoint if they die.
|
||||
* Players will respawn at their bed if this option is active (default `bedspawn = true`)
|
||||
* Their `/spawn` location will still be the randomized location.
|
||||
* If `beds` spawning is active, then beds can be used to reset the players' spawn point.
|
||||
* Players will not spawn in spaces that are protected by any other player than the Server Admin.
|
||||
* Additional commands
|
||||
* Commands
|
||||
* Players can return to their spawn point with the `/spawn` command if they have `spawn` privilege.
|
||||
* Players can request a new spawn point by typing `/newspawn` if they have the `newspawn` privilege.
|
||||
* Players can set their spawn point by typing `/setspawn` if they have the `setspawn` privelege.
|
||||
* Secondary mode: `spawn_anywhere`
|
||||
* Players can assign a new random spawn for another player using `/playerspawn` if they have the `spawnadmin` privilege.
|
||||
|
||||
### Spawn Anywhere
|
||||
In the case of a server, players can be given spawns very far from eachother, and maybe not meet anybody for a long time ...!
|
||||
|
||||
If `spawn_anywhere` is set in minetest.conf, any *new* player will be given a spawn point anywhere in the world. In the case of a server, players can be given spawns very far from eachother, and maybe not meet anybody for a long time ...!
|
||||
KNOWN ISSUE - Any player not yet registered with a spawn point will be given a spawn point anywhere in the world. If applying retroactively to a server, this will cause existing players to be re-spawned once.
|
||||
|
||||
## Considerations for a server
|
||||
## Settings
|
||||
|
||||
If running on a server consider the following
|
||||
Note that the spawn generation is performed in the background on a timer, allowing storing a collection of random spawn points to be generated ahead of time.
|
||||
|
||||
* make sure the space around the origin is clear of ownership, or is owned by the server admin
|
||||
* make sure there is sufficient space (try for 32 nodes radius around and above origin) and walkable nodes in the area
|
||||
*Generic settings used*
|
||||
|
||||
Failure to take these into consideration will often mean that the calculation of a new spawn point will take longer and be more processor-intense.
|
||||
* `name` - on servers, sets the name of the admin, players can spawn in areas protected by the admin.
|
||||
* `water_level` - Spawns are always set above water level, default `1`
|
||||
* `static_spawnpoint` - main plce the player will start at, default `{0,0,0}`
|
||||
* `enable_bed_respawn` - from `beds` mod - if active, then respawning will happen at beds, instead of randomized spawnpoint
|
||||
|
||||
*rSpawn-specific settings*
|
||||
|
||||
* Settings related to spawn generation
|
||||
* `rspawn.max_pregen` - maximum number of spawn points to pre-generate, default `5`
|
||||
* `rspawn.search_radius` - lateral radius around random point, within which a spawn poitn will be sought, default `32`
|
||||
* `rspawn.gen_frequency` - how frequently (in seconds) to generate a new spawn point, default `30`
|
||||
* `rspawn.spawn_anywhere` - whether to spawn anywhere in the world at sea level
|
||||
* default `true`
|
||||
* if `false`, will randomize around the static spawn point
|
||||
* `rspawn.kick_on_fail` - whether to kick the player if a randomized spawn cannot be set, default `false`
|
||||
* `rspawn.debug` - whether to print debugging messages, default `false`
|
||||
|
||||
## License
|
||||
|
||||
|
196
init.lua
196
init.lua
@ -3,78 +3,74 @@ rspawn.playerspawns = {}
|
||||
|
||||
local mpath = minetest.get_modpath("rspawn")
|
||||
|
||||
local function notnil_or(d, v)
|
||||
if v == nil then
|
||||
return d
|
||||
else
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
-- Water level, plus one to ensure we are above the sea.
|
||||
local water_level = tonumber(minetest.settings:get("water_level", "1") )+1
|
||||
local radial_step = 16
|
||||
|
||||
local static_spawnpoint = minetest.setting_get_pos("static_spawnpoint") or {x=0, y=50, z=0}
|
||||
-- Setting with no namespace for interoperability
|
||||
local static_spawnpoint = minetest.setting_get_pos("static_spawnpoint") or {x=0, y=0, z=0}
|
||||
|
||||
-- Setting from beds mod
|
||||
rspawn.bedspawn = minetest.setting_getbool("enable_bed_respawn", true) -- from beds mod
|
||||
|
||||
-- Detect server mode, of sorts
|
||||
rspawn.adminname = minetest.settings:get("name", "singleplayer")
|
||||
rspawn.spawnanywhere = minetest.settings:get_bool("spawn_anywhere", true)
|
||||
rspawn.bedspawn = minetest.setting_getbool("enable_bed_respawn", true)
|
||||
|
||||
-- rSpawn specific settings
|
||||
rspawn.debug_on = minetest.settings:get_bool("rspawn.debug")
|
||||
rspawn.spawnanywhere = notnil_or(true, minetest.settings:get_bool("rspawn.spawn_anywhere") )
|
||||
rspawn.kick_on_fail = notnil_or(false, minetest.settings:get_bool("rspawn.kick_on_fail"))
|
||||
rspawn.max_pregen_spawns = tonumber(minetest.settings:get("rspawn.max_pregen") or 5)
|
||||
rspawn.search_radius = tonumber(minetest.settings:get("rspawn.search_radius") or 32)
|
||||
rspawn.gen_frequency = tonumber(minetest.settings:get("rspawn.gen_frequency") or 30)
|
||||
|
||||
dofile(mpath.."/src/data.lua")
|
||||
dofile(mpath.."/src/commands.lua")
|
||||
dofile(mpath.."/src/forceload.lua")
|
||||
dofile(mpath.."/src/debugging.lua")
|
||||
|
||||
|
||||
|
||||
local dbg = dofile(mpath.."/src/debugging.lua")
|
||||
|
||||
rspawn:spawnload()
|
||||
|
||||
local function forceload_operate(pos1, pos2, handler)
|
||||
local i,j,k
|
||||
|
||||
for i=pos1.x,pos2.x,16 do
|
||||
for j=pos1.y,pos2.y,16 do
|
||||
for k=pos1.z,pos2.z,16 do
|
||||
handler({x=i,y=j,z=k})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function forceload_blocks_in(pos1, pos2)
|
||||
forceload_operate(pos1, pos2, minetest.forceload_block)
|
||||
end
|
||||
|
||||
local function forceload_free_blocks_in(pos1, pos2)
|
||||
forceload_operate(pos1, pos2, minetest.forceload_free_block)
|
||||
end
|
||||
|
||||
local function daylight_above(min_daylight, pos)
|
||||
local level = minetest.get_node_light(pos, 0.5)
|
||||
return min_daylight <= level
|
||||
end
|
||||
|
||||
function rspawn:newspawn(pos, radius)
|
||||
-- Given a seed position and a radius, find an exact spawn location
|
||||
-- that is walkable and with 2 air nodes above it
|
||||
|
||||
if not radius then
|
||||
radius = radial_step
|
||||
end
|
||||
|
||||
if radius > 4*radial_step then
|
||||
dbg("__ No valid spawnable location around "..minetest.pos_to_string(pos))
|
||||
return
|
||||
end
|
||||
|
||||
dbg("Trying somewhere around "..minetest.pos_to_string(pos))
|
||||
|
||||
local breadth = radius/2
|
||||
function rspawn:get_positions_for(pos, radius)
|
||||
local breadth = radius
|
||||
local altitude = radius*2
|
||||
|
||||
local pos1 = {x=pos.x-breadth, y=pos.y, z=pos.z-breadth}
|
||||
local pos2 = {x=pos.x+breadth, y=pos.y+altitude, z=pos.z+breadth}
|
||||
|
||||
dbg("Searching "..minetest.pos_to_string(pos1).." to "..minetest.pos_to_string(pos2))
|
||||
return pos1,pos2
|
||||
end
|
||||
|
||||
minetest.emerge_area(pos1, pos2)
|
||||
forceload_blocks_in(pos1, pos2)
|
||||
function rspawn:newspawn(pos, radius)
|
||||
-- Given a seed position and a radius, find an exact spawn location
|
||||
-- that is walkable and with 2 air nodes above it
|
||||
|
||||
rspawn:debug("Trying somewhere around "..minetest.pos_to_string(pos))
|
||||
|
||||
local pos1,pos2 = rspawn:get_positions_for(pos, radius)
|
||||
|
||||
rspawn:debug("Searching "..minetest.pos_to_string(pos1).." to "..minetest.pos_to_string(pos2))
|
||||
|
||||
local airnodes = minetest.find_nodes_in_area(pos1, pos2, {"air"})
|
||||
local validnodes = {}
|
||||
|
||||
dbg("Found "..tostring(#airnodes).." air nodes within "..tostring(radius))
|
||||
rspawn:debug("Found "..tostring(#airnodes).." air nodes within "..tostring(radius))
|
||||
for _,anode in pairs(airnodes) do
|
||||
local under = minetest.get_node( {x=anode.x, y=anode.y-1, z=anode.z} ).name
|
||||
local over = minetest.get_node( {x=anode.x, y=anode.y+1, z=anode.z} ).name
|
||||
@ -90,17 +86,11 @@ function rspawn:newspawn(pos, radius)
|
||||
end
|
||||
|
||||
if #validnodes > 0 then
|
||||
minetest.log("info", "New spawn point found with radius "..tostring(radius))
|
||||
forceload_free_blocks_in(pos1, pos2)
|
||||
rspawn:debug("Valid spawn points found with radius "..tostring(radius))
|
||||
return validnodes[math.random(1,#validnodes)]
|
||||
else
|
||||
rspawn:debug("No valid air nodes")
|
||||
end
|
||||
|
||||
local pos = rspawn:newspawn(pos, radius+radial_step)
|
||||
if not pos then
|
||||
-- Nothing found, do cleanup with this largest forceloaded area
|
||||
forceload_free_blocks_in(pos1, pos2)
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
function rspawn:genpos()
|
||||
@ -110,7 +100,8 @@ function rspawn:genpos()
|
||||
if rspawn.spawnanywhere then
|
||||
pos = {
|
||||
x = math.random(-30000,30000),
|
||||
y = math.random(water_level, water_level+10),
|
||||
--y = math.random(water_level, water_level+20),
|
||||
y = water_level, -- always at waterlevel
|
||||
z = math.random(-30000,30000),
|
||||
}
|
||||
end
|
||||
@ -118,75 +109,62 @@ function rspawn:genpos()
|
||||
return pos
|
||||
end
|
||||
|
||||
function rspawn:set_new_playerspawn(player, args)
|
||||
local newpos
|
||||
if args == "here" then
|
||||
newpos = player:get_pos()
|
||||
elseif args then
|
||||
newpos = minetest.string_to_pos(args)
|
||||
end
|
||||
|
||||
if not newpos then
|
||||
newpos = rspawn:genpos()
|
||||
end
|
||||
|
||||
local spawnpos = rspawn:newspawn(newpos)
|
||||
local name = player:get_player_name()
|
||||
|
||||
if spawnpos then
|
||||
rspawn.playerspawns[name] = spawnpos
|
||||
rspawn:spawnsave()
|
||||
return spawnpos
|
||||
end
|
||||
end
|
||||
|
||||
local function confirm_new_spawn(name, newpos)
|
||||
minetest.chat_send_player(name, "New spawn set at "..minetest.pos_to_string(newpos))
|
||||
local spos = minetest.pos_to_string(newpos)
|
||||
|
||||
rspawn.debug("Saving spawn for "..name, spos)
|
||||
rspawn.playerspawns[name] = newpos
|
||||
rspawn:spawnsave()
|
||||
|
||||
minetest.chat_send_player(name, "New spawn set at "..spos)
|
||||
|
||||
minetest.get_player_by_name(name):setpos(rspawn.playerspawns[name])
|
||||
end
|
||||
|
||||
function rspawn:double_set_new_playerspawn(player, attempts)
|
||||
local cpos = minetest.pos_to_string(rspawn:genpos())
|
||||
local name = player:get_player_name()
|
||||
attempts = attempts or 1
|
||||
function rspawn:set_newplayer_spawn(player)
|
||||
local playername = player:get_player_name()
|
||||
|
||||
minetest.chat_send_player(name, tostring(attempts)..": Searching for a suitable spawn around "..cpos)
|
||||
if not rspawn.playerspawns[playername] then
|
||||
local newpos = rspawn:get_next_spawn()
|
||||
|
||||
dbg("Primary check on "..cpos)
|
||||
local newpos = rspawn:set_new_playerspawn(player, cpos)
|
||||
if newpos then
|
||||
confirm_new_spawn(playername, newpos)
|
||||
|
||||
if not newpos then
|
||||
-- Repeat only after some time: give the server time to get through previous emerge calls
|
||||
minetest.after(4,function()
|
||||
-- Second attempt at the same location - emerge calls should have yielded
|
||||
-- map data to work with
|
||||
dbg("Secondary check on "..cpos)
|
||||
newpos = rspawn:set_new_playerspawn(player, cpos)
|
||||
else
|
||||
if rspawn.adminname ~= "singleplayer" or playername ~= rspawn.adminname then
|
||||
minetest.chat_send_player(playername, "Please wait until a spawn point is available ...")
|
||||
minetest.after(15, function()
|
||||
rspawn:set_newplayer_spawn(player)
|
||||
end)
|
||||
elseif rspawn.kick_on_fail then
|
||||
minetest.kick_player(playername, "No personalized spawn points available - please try again later.")
|
||||
|
||||
if not newpos then
|
||||
if attempts > 0 then
|
||||
-- Repeat the process at a new location
|
||||
rspawn:double_set_new_playerspawn(player, attempts - 1)
|
||||
else
|
||||
minetest.chat_send_player(name, "! Could not identify suitable spawn location (try again?)")
|
||||
end
|
||||
else
|
||||
confirm_new_spawn(name, newpos)
|
||||
minetest.chat_send_player(playername, "Could not get custom spawn! Retrying in "..rspawn.gen_frequency.." seconds")
|
||||
|
||||
minetest.after(gen_frequency, function()
|
||||
rspawn:set_newplayer_spawn(player)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function rspawn:renew_player_spawn(playername)
|
||||
local player = minetest.get_player_by_name(playername)
|
||||
|
||||
local newpos = rspawn:get_next_spawn()
|
||||
|
||||
if newpos then
|
||||
confirm_new_spawn(playername, newpos)
|
||||
|
||||
else
|
||||
confirm_new_spawn(name, newpos)
|
||||
minetest.chat_send_player(playername, "Could not get custom spawn!")
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
-- Use the recursive mode - it is not acceptable for a player
|
||||
-- not to receive a randomized spawn
|
||||
minetest.after(1,function()
|
||||
if not rspawn.playerspawns[player:get_player_name()] then
|
||||
rspawn:double_set_new_playerspawn(player, 10)
|
||||
end
|
||||
end)
|
||||
rspawn:set_newplayer_spawn(player)
|
||||
end)
|
||||
|
||||
minetest.register_on_respawnplayer(function(player)
|
||||
@ -203,3 +181,5 @@ minetest.register_on_respawnplayer(function(player)
|
||||
player:setpos(rspawn.playerspawns[name])
|
||||
return true
|
||||
end)
|
||||
|
||||
dofile(mpath.."/src/pregeneration.lua")
|
||||
|
@ -35,18 +35,27 @@ minetest.register_chatcommand("setspawn", {
|
||||
end
|
||||
})
|
||||
|
||||
local function request_new_spawn(username, targetname)
|
||||
local timername = username
|
||||
if targetname ~= username then
|
||||
timername = username.." "..targetname
|
||||
end
|
||||
|
||||
if not newspawn_cooldown[timername] then
|
||||
rspawn:renew_player_spawn(targetname)
|
||||
newspawn_cooldown[timername] = 300
|
||||
else
|
||||
minetest.chat_send_player(username, tostring(math.ceil(newspawn_cooldown[timername])).."sec until you can randomize a new spawn for "..targetname)
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("newspawn", {
|
||||
description = "Randomly select a new spawn position.",
|
||||
params = "",
|
||||
privs = "newspawn",
|
||||
func = function(name, args)
|
||||
if not newspawn_cooldown[name] then
|
||||
rspawn:double_set_new_playerspawn(minetest.get_player_by_name(name), 2)
|
||||
newspawn_cooldown[name] = 300
|
||||
else
|
||||
minetest.chat_send_player(name, tostring(math.ceil(newspawn_cooldown[name])).."sec until you can randomize a new spawn.")
|
||||
end
|
||||
end
|
||||
request_new_spawn(name, name)
|
||||
end
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("playerspawn", {
|
||||
@ -54,13 +63,7 @@ minetest.register_chatcommand("playerspawn", {
|
||||
params = "playername",
|
||||
privs = "spawnadmin",
|
||||
func = function(adminname, playername)
|
||||
local jointname = adminname.."--"..playername
|
||||
if not newspawn_cooldown[jointname] then
|
||||
rspawn:double_set_new_playerspawn(minetest.get_player_by_name(playername), 2)
|
||||
newspawn_cooldown[jointname] = 60
|
||||
else
|
||||
minetest.chat_send_player(adminname, tostring(math.ceil(newspawn_cooldown[jointname])).."sec until you can randomize a new spawn for "..playername)
|
||||
end
|
||||
request_new_spawn(adminname, playername)
|
||||
end
|
||||
})
|
||||
|
||||
|
21
src/data.lua
21
src/data.lua
@ -12,15 +12,24 @@ function rspawn:spawnsave()
|
||||
end
|
||||
file:write(serdata)
|
||||
file:close()
|
||||
|
||||
pregens = rspawn.playerspawns["pre gen"] or {}
|
||||
minetest.debug("Wrote rspawn data with "..tostring(#pregens).." pregen nodes")
|
||||
end
|
||||
|
||||
function rspawn:spawnload()
|
||||
local file, err = io.open(spawnsfile, "r")
|
||||
if err then
|
||||
minetest.log("error", "[spawn] Data read failed")
|
||||
return
|
||||
end
|
||||
rspawn.playerspawns = minetest.deserialize(file:read("*a"))
|
||||
file:close()
|
||||
if not err then
|
||||
rspawn.playerspawns = minetest.deserialize(file:read("*a"))
|
||||
file:close()
|
||||
else
|
||||
minetest.log("error", "[spawn] Data read failed - initializing")
|
||||
rspawn.playerspawns = {}
|
||||
end
|
||||
|
||||
pregens = rspawn.playerspawns["pre gen"] or {}
|
||||
rspawn.playerspawns["pre gen"] = pregens
|
||||
|
||||
minetest.debug("Loaded rspawn data with "..tostring(#pregens).." pregen nodes")
|
||||
end
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
local debug_on = minetest.settings:get_bool("rspawn.debug")
|
||||
|
||||
local function debug(message, data)
|
||||
if not debug_on then
|
||||
function rspawn:debug(message, data)
|
||||
if not rspawn.debug_on then
|
||||
return
|
||||
end
|
||||
|
||||
@ -14,5 +12,3 @@ local function debug(message, data)
|
||||
|
||||
minetest.debug(debug_string)
|
||||
end
|
||||
|
||||
return debug
|
||||
|
23
src/forceload.lua
Normal file
23
src/forceload.lua
Normal file
@ -0,0 +1,23 @@
|
||||
local function forceload_operate(pos1, pos2, handler)
|
||||
local i,j,k
|
||||
|
||||
for i=pos1.x,pos2.x,16 do
|
||||
for j=pos1.y,pos2.y,16 do
|
||||
for k=pos1.z,pos2.z,16 do
|
||||
handler({x=i,y=j,z=k})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function rspawn:forceload_blocks_in(pos1, pos2)
|
||||
rspawn:debug("Forceloading blocks -----------¬", {pos1=minetest.pos_to_string(pos1),pos2=minetest.pos_to_string(pos2)})
|
||||
minetest.emerge_area(pos1, pos2)
|
||||
forceload_operate(pos1, pos2, minetest.forceload_block)
|
||||
end
|
||||
|
||||
function rspawn:forceload_free_blocks_in(pos1, pos2)
|
||||
rspawn:debug("Freeing forceloaded blocks ____/", {pos1=minetest.pos_to_string(pos1),pos2=minetest.pos_to_string(pos2)})
|
||||
forceload_operate(pos1, pos2, minetest.forceload_free_block)
|
||||
end
|
||||
|
71
src/pregeneration.lua
Normal file
71
src/pregeneration.lua
Normal file
@ -0,0 +1,71 @@
|
||||
local steptime = 0
|
||||
|
||||
-- Ensure pregen data is stored and saved properly
|
||||
|
||||
local function len_pgen()
|
||||
return #rspawn.playerspawns["pre gen"]
|
||||
end
|
||||
|
||||
local function set_pgen(idx, v)
|
||||
rspawn.playerspawns["pre gen"][idx] = v
|
||||
rspawn:spawnsave()
|
||||
end
|
||||
|
||||
local function get_pgen(idx)
|
||||
return rspawn.playerspawns["pre gen"][idx]
|
||||
end
|
||||
|
||||
-- Spawn generation
|
||||
|
||||
local function push_new_spawn()
|
||||
if len_pgen() >= rspawn.max_pregen_spawns then
|
||||
rspawn:debug("Max pregenerated spawns ("..rspawn.max_pregen_spawns..") reached : "..len_pgen())
|
||||
return
|
||||
end
|
||||
|
||||
local random_pos = rspawn:genpos()
|
||||
local pos1,pos2 = rspawn:get_positions_for(random_pos, rspawn.search_radius)
|
||||
|
||||
rspawn:forceload_blocks_in(pos1, pos2)
|
||||
|
||||
minetest.after(10, function()
|
||||
-- Let the forceload do its thing, then act
|
||||
|
||||
local newpos = rspawn:newspawn(random_pos, rspawn.search_radius)
|
||||
if newpos then
|
||||
rspawn:debug("Generated "..minetest.pos_to_string(newpos))
|
||||
set_pgen(len_pgen()+1, newpos )
|
||||
else
|
||||
rspawn:debug("Failed to generate new spawn point to push")
|
||||
end
|
||||
|
||||
rspawn:forceload_free_blocks_in(pos1, pos2)
|
||||
end)
|
||||
end
|
||||
|
||||
minetest.register_globalstep(function(dtime)
|
||||
steptime = steptime + dtime
|
||||
if steptime > rspawn.gen_frequency then
|
||||
steptime = 0
|
||||
else
|
||||
return
|
||||
end
|
||||
|
||||
push_new_spawn()
|
||||
end)
|
||||
|
||||
-- Access pregenrated spawns
|
||||
|
||||
function rspawn:get_next_spawn()
|
||||
local nspawn
|
||||
|
||||
if len_pgen() > 0 then
|
||||
nspawn = get_pgen(len_pgen() )
|
||||
rspawn:debug("Returning pregenerated spawn",nspawn)
|
||||
set_pgen(len_pgen(), nil)
|
||||
else
|
||||
push_new_spawn()
|
||||
end
|
||||
|
||||
return nspawn
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user