Merge branch 'open-towns'

master
Tai Kedzierski 2019-01-30 21:15:40 +00:00
commit 4b7df3c860
8 changed files with 336 additions and 74 deletions

View File

@ -1,16 +1,17 @@
# `[rspawn]` Randomized Spawning for Minetest
Causes players to receive a spawn point anywhere on the map. Players will likely spawn veeery far from eachother into prisitine areas.
Causes players to receive a spawn point anywhere on the map. Players will likely spawn very far from eachother into prisitine areas.
## Features
* Player is assigned randomized spawnpoint on joining
* New players will not spawn into protected areas
* Player will respawn at their spawnpoint if they die.
* 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
* If `beds` spawning is active, then beds can be used to set players' re-spawn point (they still go to their main spawnpoint on invoking `/spawn`).
* Commands
* Players can return to their spawn point with the `/spawn` command if they have `spawn` privilege.
* Players can invite other players to join their spawn - see "Spawn invites" below
* Players can invite other players to join their spawn - see "Spawn guests" below
* Players can allow any other player to visit their spawn - see "Town hosting" below
* 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.
* Moderator players can assign a new random spawn for another player using `/playerspawn` if they have the `spawnadmin` privilege.
@ -23,11 +24,29 @@ Randomized spawning typically causes players to spawn far from eachother. If pla
The player issuing the invite (host) must typically pay a levvy when adding another player.
* `/spawn add <player>` - allow another player to visit your spawn directly, or lift their exile
* `/spawn kick <player>` - revoke rights to visit you, and if they are in your space, returns them to their own spawn
* `/spawn add <player>` - allow another player to visit your spawn directly (levvy must be paid), or lift their exile (no levvy to pay)
* `/spawn kick <targetplayer>`
* revoke rights to visit you
* if the exiled player gets close to your spawn, they are kicked back to their own spawn
* `/spawn visit <player>` - visit a player's spawn
* `/spawn guests` - see who you have added to your spawn
* `/spawn hosts` - see who has added you to their spawn
* `/spawn hosts` - see whose spawns you may visit
Guests can help the spawn owner manage bans on their town.
### Town hosting
You can host a town from your spawn if you wish. Hosting a town means that any player who connects to the server will be able to visit your spawn. You can still `/spawn kick <playername>` individually in this mode. If you switch off town hosting, only allowed guests in your normal guestlist can visit.
There is no levvy on hosting a town.
* `/spawn town { open | close }` - switch town hosting on or off.
* `/spawn town { ban | unban } <playername> [<town>]` - ban or unban a player from a town
* Town owners can use this, as well as unexiled guests of the town owner
Explicit guests can ban/unban other players from a town.
Town owner can forcibly ban a player by first adding the player to their guest list, and then exiling them. Guests cannot override this.
## Settings
@ -35,7 +54,7 @@ Note that the spawn generation is performed in the background on a timer, allowi
*Generic settings used*
* `name` - on servers, sets the name of the admin, players can spawn in areas protected by the admin.
* `name` - used for knowing the server admin's name
* `water_level` - Spawns are always set above water level, default `1`
* `static_spawnpoint` - main position 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
@ -43,42 +62,46 @@ Note that the spawn generation is performed in the background on a timer, allowi
*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.max_pregen` - maximum number of spawn points to pre-generate, default `20`
* `rspawn.search_radius` - lateral radius around random point, within which a spawn point will be sought, default `32`
* `rspawn.gen_frequency` - how frequently (in seconds) to generate a new spawn point, default `30`, increase this on slower servers
* `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.cooldown_time` - how many seconds between two uses of `/newspawn`, per player
* `rspawn.levvy_name` - name of the block to use as levvy charge on the player issuing an invitation, default `default:cobble`
* `rspawn.levvy_qtty` - number of blocks to levvy from the player who issued the invitation, default `10`
* `rspawn.kick_on_fail` - whether to kick the player if a randomized spawn cannot be set, default `false`
* `rspawn.spawn_block` - place this custom block under the user's spawn point
* Guestlist and town related settings
* `rspawn.levvy_name` - name of the block to use as levvy charge on the player issuing an invitation, default `default:cobble`
* `rspawn.levvy_qtty` - number of blocks to levvy from the player who issued the invitation, default `10`
* `rspawn.kick_period` - how frequently to check if exiled players are too near their locus of exile, default `3` (seconds)
* `rspawn.exile_distance` - distance from exile locus at which player gets bounced back to their own spawn, default `64` (nodes)
* `rspawn.debug` - whether to print debugging messages, default `false`
* Bounds limiting - you can limit the random spawning to a given area if you wish:
* Bounds limiting - you can limit the random spawning search area to a given subsection of the global map if you wish:
* `rspawn.min_x`, `rspawn.max_x`, `rspawn.min_z`, `rspawn.max_z` as expected
## Troubleshooting
As admin, you will receive notifications of inability to generate spawns when players join without being set a spawn.
You can turn on `rspawn.debug = true` to see debug in logs.
If the generation log shows `0 air nodes found within <x>` on more than 2-3 consecutive tries, you may want to check the max number of forceloaded blocks configured - see `max_forceloaded_blocks`.
Spawn generation uses a temporary forceload to read the blocks in the area ; it then releases the forceload after operating, so should not depend on the `max_forceloaded_blocks` setting.
This should be at least `2*(rspawn.search_radius^3) / (16^3)`, so with the default `rspawn.search_radius = 32`, you should have at least `max_forceloaded_blocks = 8`
If the generation log shows `0 air nodes found within <x>` on more than 2-3 consecutive tries, you may want to check that another mod is not forceloading blocks and then not subsequently clearing them.
Also check that another mod is not forceloading blocks and not clearing them.
You may also find some mods (rarely) do permanent forceloads. In your world folder `~/.minetest/worlds/<yourworld>` there should eb a `force_loaded.txt` - see that its contents are simply `return {}`; if there is data in the table, then something else is forceloading blocks.
You may also find some mods do permanent forceloads by design (though this should be rare). In your world folder `~/.minetest/worlds/<yourworld>` there should eb a `force_loaded.txt` - see that its contents are simply `return {}`; if there is data in the table, then something else is forceloading blocks with permanence.
Resolutions in order of best to worst:
* identify the mod and have it clear them properly (ideal)
* on UNIX/Linux you should be able to run `grep -rl forceload ~/.minetest/mods/` to see all mod files where forceloading is being done
* increase the max number of forceloaded blocks
* (not great - you will effectively be simply mitigating a forceloaded-blocks-related memory leak)
* Stop minetest, delete the `force_loaded.txt` file, and start it again
* (bad - some things in the mods using the forceload mechanism may break)
## Singple Player Mode
## Single Player Mode
This mod is mainly intended for use on servers with multiple players.

View File

@ -3,14 +3,6 @@ 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", "0") )
local radial_step = 16
@ -24,8 +16,8 @@ rspawn.bedspawn = minetest.setting_getbool("enable_bed_respawn") ~= false -- fro
-- 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.spawnanywhere = minetest.settings:get_bool("rspawn.spawn_anywhere") ~= false
rspawn.kick_on_fail = minetest.settings:get_bool("rspawn.kick_on_fail") == true
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)

View File

@ -7,7 +7,7 @@ local cooldown_time = tonumber(minetest.settings:get("rspawn.cooldown_time")) or
minetest.register_privilege("spawn", "Can teleport to a spawn position and manage shared spawns.")
minetest.register_privilege("setspawn", "Can manually set a spawn point.")
minetest.register_privilege("newspawn", "Can get a new randomized spawn position.")
minetest.register_privilege("spawnadmin", "Can clean up timers and set new spawns for players.")
minetest.register_privilege("spawnadmin", "Can set new spawns for players.")
-- Support functions
@ -35,11 +35,11 @@ end
minetest.register_chatcommand("spawn", {
description = "Teleport to your spawn, or manage guests in your spawn.",
params = "[ add <player> | visit <player> | kick <player> | guests | hosts ]",
params = "[ add <player> | visit <player> | kick <player> | guests | hosts | town { open | close | ban <player> [<town>] | unban <player> [<town>] } ]",
privs = "spawn",
func = function(playername, args)
local target = rspawn.playerspawns[playername]
local args = args:split(" ")
local args = args:split(" ", false, 1)
if #args == 0 then
if target then
@ -50,13 +50,14 @@ minetest.register_chatcommand("spawn", {
minetest.chat_send_player(playername, "You have no spawn position!")
return
end
elseif #args < 3 then
elseif #args < 4 then
for command,action in pairs({
["guests"] = function() rspawn.guestlists:listguests(playername) end,
["hosts"] = function() rspawn.guestlists:listhosts(playername) end,
["add"] = function(commandername,targetname) rspawn.guestlists:addplayer(commandername,targetname) end,
["visit"] = function(commandername,targetname) rspawn.guestlists:visitplayer(targetname, commandername) end,
["kick"] = function(commandername,targetname) rspawn.guestlists:exileplayer(commandername, targetname) end,
["kick"] = function(commandername, params) rspawn.guestlists:kickplayer(commandername, params) end,
["town"] = function(commandername,mode) rspawn.guestlists:townset(commandername, mode) end,
}) do
if args[1] == command then
@ -65,14 +66,14 @@ minetest.register_chatcommand("spawn", {
return
elseif #args == 1 then
action()
action(playername)
return
end
end
end
end
minetest.chat_send_player(playername, "Please check '/help spawn'")
minetest.chat_send_player(playername, "Bad command. Please check '/help spawn'")
end
})

View File

@ -88,6 +88,9 @@ function rspawn:spawnload()
local pregens = rspawn.playerspawns["pre gen"] or {}
rspawn.playerspawns["pre gen"] = pregens
local towns = rspawn.playerspawns["town lists"] or {}
rspawn.playerspawns["town lists"] = towns
reconcile_original_spawns()
reconcile_guestlist_spawns()

View File

@ -1,4 +1,10 @@
function rspawn:d(stuff)
-- Quick debugging
minetest.debug(dump(stuff))
end
function rspawn:debug(message, data)
-- Debugging from setting
if not rspawn.debug_on then
return
end
@ -8,7 +14,7 @@ function rspawn:debug(message, data)
if data ~= nil then
debug_data = " :: "..dump(data)
end
local debug_string = "rspawn : "..message..debug_data
local debug_string = "[rspawn] DEBUG : "..message..debug_data
minetest.debug(debug_string)
end

View File

@ -1,13 +1,20 @@
-- API holder object
rspawn.guestlists = {}
-- invitations[guest] = host
rspawn.invitations = {}
local kick_step = 0
local invite_charge = {}
local kick_period = tonumber(minetest.settings:get("rspawn.kick_period")) or 3
local exile_distance = tonumber(minetest.settings:get("rspawn.exile_distance")) or 64
levvy_name = minetest.settings:get("rspawn.levvy_name") or "default:cobble"
levvy_qtty = tonumber(minetest.settings:get("rspawn.levvy_qtty")) or 10
levvy_nicename = "cobblestone"
local GUEST_BAN = 0
local GUEST_ALLOW = 1
-- Levvy helpers
-- FIXME Minetest API might actually be able to do this cross-stacks with a single call at inventory level.
local levvy_name = minetest.settings:get("rspawn.levvy_name") or "default:cobble"
local levvy_qtty = tonumber(minetest.settings:get("rspawn.levvy_qtty")) or 10
local levvy_nicename = "cobblestone"
minetest.after(0,function()
if minetest.registered_items[levvy_name] then
@ -19,18 +26,13 @@ minetest.after(0,function()
end
end)
local function canvisit(hostname, guestname)
local glist = rspawn.playerspawns["guest lists"][hostname] or {}
return glist[guestname] == 1
end
local function find_levvy(player)
-- return itemstack index, and stack itself, with qtty removed
-- or none if not found/not enough found
local i
if not player then
minetest.log("action", "Tried to access undefined player")
minetest.log("error", "[rspawn] Levvy : Tried to access undefined player")
return false
end
@ -39,7 +41,7 @@ local function find_levvy(player)
local total_count = 0
if not player_inv then
minetest.log("action", "Could not access inventory for "..pname)
minetest.log("error", "[rspawn] Levvy : Could not access inventory for "..pname)
return false
end
@ -65,7 +67,7 @@ end
function rspawn:consume_levvy(player)
if not player then
minetest.log("action", "Tried to access undefined player")
minetest.log("error", "[rspawn] Levvy : Tried to access undefined player")
return false
end
@ -101,18 +103,43 @@ function rspawn:consume_levvy(player)
return false
end
-- Visitation rights check
local function canvisit(hostname, guestname)
local host_glist = rspawn.playerspawns["guest lists"][hostname] or {}
local town_lists = rspawn.playerspawns["town lists"] or {}
local explicitly_banned = host_glist[guestname] == GUEST_BAN
local explicitly_banned_from_town = town_lists[hostname] and
town_lists[hostname][guestname] == GUEST_BAN
local open_town = town_lists[hostname] and town_lists[hostname]["town status"] == "on"
if explicitly_banned or explicitly_banned_from_town then
return false
elseif open_town then
return true
end
return false
end
-- Operational functions (to be invoked by /command)
function rspawn.guestlists:addplayer(hostname, guestname)
local guestlist = rspawn.playerspawns["guest lists"][hostname] or {}
if guestlist[guestname] ~= nil then
if guestlist[guestname] == 0 then
if guestlist[guestname] == GUEST_BAN then
minetest.chat_send_player(guestname, hostname.." let you back into their spawn.")
minetest.log("action", "[rspawn] "..hostname.." lifted exile on "..guestname)
end
guestlist[guestname] = 1
guestlist[guestname] = GUEST_ALLOW
elseif rspawn:consume_levvy(minetest.get_player_by_name(hostname) ) then -- Automatically notifies host if they don't have enough
guestlist[guestname] = 1
guestlist[guestname] = GUEST_ALLOW
minetest.chat_send_player(guestname, hostname.." added you to their spawn! You can now visit them with /spawn visit "..hostname)
minetest.log("action", "[rspawn] "..hostname.." added "..guestname.." to their spawn")
else
return
end
@ -123,30 +150,31 @@ function rspawn.guestlists:addplayer(hostname, guestname)
end
function rspawn.guestlists:exileplayer(hostname, guestname)
if hostname == guestname then
minetest.chat_send_player(hostname, "Cannot ban yourself!")
return false
end
local guestlist = rspawn.playerspawns["guest lists"][hostname] or {}
if guestlist[guestname] == 1 then
guestlist[guestname] = 0
if guestlist[guestname] == GUEST_ALLOW then
guestlist[guestname] = GUEST_BAN
rspawn.playerspawns["guest lists"][hostname] = guestlist
else
minetest.chat_send_player(hostname, guestname.." is not in your accepted guests list.")
return
minetest.chat_send_player(hostname, guestname.." is not in accepted guests list for "..hostname)
return false
end
minetest.chat_send_player(guestname, hostname.." banishes you!")
rspawn.guestlists:kick(hostname, guestname)
minetest.chat_send_player(guestname, "You may no longer visit "..hostname)
minetest.log("action", "rspawn - "..hostname.." exiles "..guestname)
rspawn:spawnsave()
return true
end
function rspawn.guestlists:kick(hostname, guestname)
local guest = minetest.get_player_by_name(guestname)
local guestpos = guest:getpos()
local hostspawnpos = rspawn.playerspawns[hostname]
local guestspawnpos = rspawn.playerspawns[guestname]
if vector.distance(guestpos, hostspawnpos) < 32 then
guest:setpos(guestspawnpos)
function rspawn.guestlists:kickplayer(hostname, guestname)
if rspawn.guestlists:exileplayer(hostname, guestname) then
minetest.chat_send_player(hostname, "Evicted "..guestname.." from your spawn")
minetest.log("action", "rspawn - "..hostname.." evicts "..guestname)
end
end
@ -154,12 +182,31 @@ function rspawn.guestlists:listguests(hostname)
local guests = ""
local guestlist = rspawn.playerspawns["guest lists"][hostname] or {}
local global_hosts = rspawn.playerspawns["town lists"] or {}
if global_hosts[hostname] then
guests = ", You are an active town host."
end
-- Explicit guests
for guestname,status in pairs(guestlist) do
if status == 1 then status = "" else status = " (exiled)" end
if status == GUEST_ALLOW then status = "" else status = " (exiled guest)" end
guests = guests..", "..guestname..status
end
-- Town bans - always list so this can be maanged even when town is closed
for guestname,status in pairs(global_hosts[hostname] or {}) do
if guestname ~= "town status" then
if status == GUEST_ALLOW then status = "" else status = " (banned from town)" end
guests = guests..", "..guestname..status
end
end
if guests == "" then
guests = ", No guests, not hosting a town."
end
minetest.chat_send_player(hostname, guests:sub(3))
end
@ -169,17 +216,32 @@ function rspawn.guestlists:listhosts(guestname)
for hostname,hostguestlist in pairs(rspawn.playerspawns["guest lists"]) do
for gname,status in pairs(hostguestlist) do
if guestname == gname then
if status == 1 then status = "" else status = " (exiled)" end
hosts = hosts..", "..hostname..status
if status == GUEST_ALLOWED then
hosts = hosts..", "..hostname
end
end
end
end
local global_hostlist = rspawn.playerspawns["town lists"] or {}
for hostname,host_banlist in pairs(global_hostlist) do
if host_banlist["town status"] == "on" and
host_banlist[guestname] ~= GUEST_BAN
then
hosts = hosts..", "..hostname.." (town)"
end
end
if hosts == "" then
hosts = ", (no visitable hosts)"
end
minetest.chat_send_player(guestname, hosts:sub(3))
end
function rspawn.guestlists:visitplayer(hostname, guestname)
if not (hostname and guestname) then return end
local guest = minetest.get_player_by_name(guestname)
local hostpos = rspawn.playerspawns[hostname]
@ -189,8 +251,175 @@ function rspawn.guestlists:visitplayer(hostname, guestname)
end
if guest and canvisit(hostname, guestname) then
minetest.log("action", "[rspawn] "..guestname.." visits "..hostname.." (/spawn visit)")
guest:setpos(hostpos)
else
minetest.chat_send_player(guestname, "Could not visit "..hostname)
end
end
local function act_on_behalf(hostname, callername)
return hostname == callername or -- caller is the town owner, always allow
( -- caller can act on behalf of town owner
rspawn.playerspawns["guest lists"][hostname] and
rspawn.playerspawns["guest lists"][hostname][callername] == GUEST_ALLOW
)
end
local function townban(callername, guestname, hostname)
if not (callername and guestname) then return end
hostname = hostname or callername
if act_on_behalf(hostname, callername) then
if not rspawn.playerspawns["town lists"][hostname] then
minetest.chat_send_player(callername, "No such town "..hostname)
return
end
rspawn.playerspawns["town lists"][hostname][guestname] = GUEST_BAN
minetest.chat_send_player(callername, "Evicted "..guestname.." from "..hostname.."'s spawn")
minetest.log("action", "[rspawn] - "..callername.." evicts "..guestname.." on behalf of "..hostname)
else
minetest.chat_send_player(callername, "You are not permitted to act on behalf of "..hostname)
end
rspawn:spawnsave()
end
local function townunban(callername, guestname, hostname)
if not (callername and guestname) then return end
hostname = hostname or callername
if act_on_behalf(hostname, callername) then
if not rspawn.playerspawns["town lists"][hostname] then
minetest.chat_send_player(callername, "No such town "..hostname)
return
end
rspawn.playerspawns["town lists"][hostname][guestname] = nil
minetest.chat_send_player(callername, "Allowed "..guestname.." back to town "..hostname)
minetest.log("action", "[rspawn] - "..callername.." lifts eviction on "..guestname.." on behalf of "..hostname)
else
minetest.chat_send_player(callername, "You are not permitted to act on behalf of "..hostname)
end
rspawn:spawnsave()
end
local function listtowns()
local town_lists = rspawn.playerspawns["town lists"] or {}
local open_towns = ""
for townname,banlist in pairs(town_lists) do
if banlist["town status"] == "on" then
open_towns = open_towns..", "..townname
end
end
if open_towns ~= "" then
return open_towns:sub(3)
end
end
function rspawn.guestlists:townset(hostname, params)
if not hostname then return end
params = params or ""
params = params:split(" ")
local mode = params[1]
local guestname = params[2]
local town_lists = rspawn.playerspawns["town lists"] or {}
local town_banlist = town_lists[hostname] or {}
if mode == "open" then
town_banlist["town status"] = "on"
minetest.chat_send_all(hostname.." is opens access to all!")
minetest.log("action", "[rspawn] town: "..hostname.." sets their spawn to open")
elseif mode == "close" then
town_banlist["town status"] = "off"
minetest.chat_send_all(hostname.." closes town access - only guests may directly visit.")
minetest.log("action", "[rspawn] town: "..hostname.." sets their spawn to closed")
elseif mode == "status" then
minetest.chat_send_player(hostname, "Town mode is: "..town_banlist["town status"])
return
elseif mode == "ban" and guestname and guestname ~= hostname then
townban(hostname, guestname, params[3])
elseif mode == "unban" and guestname then
townunban(hostname, guestname, params[3])
elseif mode == nil or mode == "" then
local open_towns = listtowns()
if not open_towns then
open_towns = "(none yet)"
end
minetest.chat_send_player(hostname, open_towns)
else
minetest.chat_send_player(hostname, "Unknown parameterless town operation: "..tostring(mode) )
return
end
town_lists[hostname] = town_banlist
rspawn.playerspawns["town lists"] = town_lists
rspawn:spawnsave()
end
-- Exile check
minetest.register_globalstep(function(dtime)
if kick_step < kick_period then
kick_step = kick_step + dtime
return
else
kick_step = 0
end
for _x,guest in ipairs(minetest.get_connected_players()) do
local guestname = guest:get_player_name()
local playerprivs = minetest.get_player_privs(guestname)
if not (playerprivs.basic_privs or playerprivs.server) then
local guestpos = guest:getpos()
for _y,player_list_name in ipairs({"guest lists", "town lists"}) do
for hostname,host_guestlist in pairs(rspawn.playerspawns[player_list_name] or {}) do
if host_guestlist[guestname] == GUEST_BAN then
-- Check distance of guest from banned pos
local vdist = vector.distance(guestpos, rspawn.playerspawns[hostname])
-- Check distance of guest from their own pos
-- If their spawn is very close to one they are banned from,
-- and they are close to their own, kick should not occur
local sdist = vector.distance(guestpos, rspawn.playerspawns[guestname])
if vdist < exile_distance and sdist > exile_distance then
guest:setpos(rspawn.playerspawns[guestname])
minetest.chat_send_player(guestname, "You got too close to "..hostname.."'s turf.")
minetest.log("action", "[rspawn] Auto-kicked "..guestname.." for being too close to "..hostname.."'s spawn")
elseif vdist < exile_distance*1.5 and sdist > exile_distance then
minetest.chat_send_player(guestname, "You are getting too close to "..hostname.."'s turf.")
end
end
end
end
end
end
end)
-- Announce towns!
minetest.register_on_joinplayer(function(player)
local open_towns = listtowns()
if open_towns then
minetest.chat_send_player(player:get_player_name(), "Currently open towns: "..open_towns..". Visit with '/spawn visit <townname>' !")
end
end)

View File

@ -64,8 +64,14 @@ function rspawn:get_next_spawn()
if len_pgen() > 0 then
nspawn = get_pgen(len_pgen() )
rspawn:debug("Returning pregenerated spawn",nspawn)
set_pgen(len_pgen(), nil)
-- Someone might have claimed the area since.
if minetest.is_protected(nspawn, "") then
return rspawn:get_next_spawn()
else
rspawn:debug("Returning pregenerated spawn",nspawn)
end
end
return nspawn

View File

@ -7,6 +7,8 @@ rspawn.gen_frequency (Spawnpoint generation frequency [seconds]) string 60
rspawn.spawn_block (Node to place under new spawn point) string
rspawn.levvy_name (Levvy itemstring) string "default:cobble"
rspawn.levvy_qtty (Levvy quantity) string 10
rspawn.kick_period (Exile kick check period) string 1
rspawn.exile_distance (Exile distance) string 64
rspawn.cooldown_time (Cooldown between /newspawn uses) string 300
rspawn.min_x (Westmost bounds) string -31000
rspawn.max_x (Eastmost bounds) string 31000