Compare commits

20 Commits

Author SHA1 Message Date
aca830fd22 Add support for playerfactions mod (#37)
Add faction indicator to HUD
2019-09-21 15:33:42 +02:00
95c1165e28 Add and parse from settingtypes.txt. Update readme 2019-07-11 20:02:32 +02:00
5527dc8945 Replace deprecated functions with newer ones (#36)
This commit replaces a few deprecated calls to getpos with get_pos
2019-07-10 20:26:42 +02:00
6218e5884d Limit areas to -30992,30992 due to MABLOCK_SIZE=16
Internally, when allocating an AreaStore, the limits are required
to be within the last full block, and so, you cannot create one "on"
the edge, as it will trigger an exception. When limited to the last
full mapblock, it all works fine.
2019-04-03 11:10:59 -07:00
cfd4bb2423 Last of the luacheck cleanups. 2019-04-03 10:26:20 -07:00
6e2b9a0a51 Needs to be defined earlier. 2019-04-03 10:24:02 -07:00
024424ee8b Also limit chatcommand area_pos[12] positions. 2019-04-03 10:17:47 -07:00
f70600db30 oops, commas would be nice. 2019-04-02 16:06:19 -07:00
9508a004d0 fix posLimit(pos) to process and return a position table 2019-04-02 14:53:54 -07:00
630bdefd98 Don't allow areas to cross outside mapgen world limits.
Limit any area to within [-31000,31000].
2019-04-01 21:28:03 -07:00
a303abe51b Add template .luacheckrc 2019-04-01 21:22:31 -07:00
7b51f84404 Limit recalculations.
Setting based - limit area recalculation and allow tuning of the
interval.
2019-04-01 21:21:31 -07:00
1bbb997c7a Add callbacks for area operations 2019-04-01 20:28:50 -07:00
09c030352f Use the new minetest.safe_file_write API if possible when saving database. 2019-04-01 20:27:17 -07:00
2637876555 Fix pos big (#1)
* fix pos being to big

* improve

* make the change instead in hud.lua
2019-04-01 20:24:59 -07:00
289d0e623c Update usage of settings API 2017-06-09 13:00:12 -04:00
d3d43d9511 Reference 'settings.lua' instead of 'config.lua' (#21) 2017-05-06 13:41:45 -04:00
6080ff065e Add API for adding areas to HUD 2016-12-19 16:36:37 +00:00
23f81f6278 Add mod.conf 2016-12-04 04:12:27 +00:00
7cb8787beb Use get_auth_handler().get_auth() instead of auth_table
minetest.auth_table is an implementation detail of the default auth handler.
No guarantee is made that it even exists and using this table directly is incompatible
with custom auth handlers.  Instead, use the proper auth handler API.
2016-09-03 21:05:52 -04:00
13 changed files with 308 additions and 70 deletions

19
.luacheckrc Normal file
View File

@ -0,0 +1,19 @@
unused_args = false
allow_defined_top = true
read_globals = {
"DIR_DELIM",
"core",
"dump",
"vector", "nodeupdate",
"VoxelManip", "VoxelArea",
"PseudoRandom", "ItemStack",
"AreaStore",
"intllib",
"default",
table = { fields = { "copy", "getn" } }
}
globals = {
"minetest"
}

View File

@ -1,21 +1,29 @@
Areas mod for Minetest 0.4.8+ Areas mod for Minetest
============================= ======================
Dependencies
------------
Minetest 5.0.0+ is recommended, but 0.4.16+ should work as well.
Configuration Configuration
------------- -------------
If you wish to specify configuration options, such as whether players are Open the tab `Settings -> All Settings -> Mods -> areas` to get a list of all
allowed to protect their own areas with the `protect` command (disabled by possible settings.
default), you should check config.lua and set the appropriate settings in your
server's configuration file (probably `minetest.conf`). For server owners: Check `settingtypes.txt` and modify your `minetest.conf`
according to the wanted setting changes.
Tutorial Tutorial
-------- --------
To protect an area you must first set the corner positions of the area. 1) Specify the corner positions of the area you would like to protect.
In order to set the corner positions you can run: Use one of the following commands:
* `/area_pos set` and punch the two corner nodes to set them. * `/area_pos set` and punch the two corner nodes to set them.
* `/area_pos set1/set2` and punch only the first or second corner node to * `/area_pos set1/set2` and punch only the first or second corner node to
set them one at a time. set them one at a time.
@ -23,25 +31,25 @@ In order to set the corner positions you can run:
* `/area_pos1/2 X Y Z` to set one of the positions to the specified * `/area_pos1/2 X Y Z` to set one of the positions to the specified
coordinates. coordinates.
Once you have set the border positions you can protect the area by running one 2) Protect the selected area by running one of the following commands:
of the following commands:
* `/set_owner <OwnerName> <AreaName>` -- If you have the `areas` privilege. * `/set_owner <OwnerName> <AreaName>` -- If you have the `areas` privilege.
* `/protect <AreaName>` -- If you have the `areas` privilege or the server * `/protect <AreaName>` -- If you have the `areas` privilege or the server
administrator has enabled area self-protection. administrator has enabled area self-protection.
The area name is used only for informational purposes (so that you know what The area name is used only for informational purposes and has no functional
an area is for). It is not used for any other purpose. importance.
For example: `/set_owner SomePlayer Mese city` For example: `/set_owner SomePlayer Mese city`
Now that you own an area you may want to add sub-owners to it. You can do this 3) You now own an area. You may now add sub-owners to it if you want to (see command `/add_owner`). Before using the `/add_owner` command you have to
with the `add_owner` command. Anyone with an area can use the `add_owner` select the corners of the sub-area as you did in step 1.
command on their areas. Before using the `add_owner` command you have to
select the corners of the sub-area as you did for `set_owner`. If your markers If your markers are still around your original area and you want to grant
are still around your original area and you want to grant access to your access to your entire area you will not have to re-set them. Use `/select_area` to place the markers at the corners of an existing area if you've reset your
entire area you will not have to re-set them. You can also use `select_area` to
place the markers at the corners of an existing area if you've reset your
markers and want to grant access to a full area. markers and want to grant access to a full area.
The `add_owner` command expects three arguments:
The `/add_owner` command expects three arguments:
1. The ID number of the parent area (the area that you want to add a 1. The ID number of the parent area (the area that you want to add a
sub-area to). sub-area to).
2. The name of the player that will own the sub-area. 2. The name of the player that will own the sub-area.

43
api.lua
View File

@ -1,7 +1,41 @@
local hudHandlers = {}
areas.registered_on_adds = {}
areas.registered_on_removes = {}
areas.registered_on_moves = {}
function areas:registerOnAdd(func)
table.insert(areas.registered_on_adds, func)
end
function areas:registerOnRemove(func)
table.insert(areas.registered_on_removes, func)
end
function areas:registerOnMove(func)
table.insert(areas.registered_on_moves, func)
end
--- Adds a function as a HUD handler, it will be able to add items to the Areas HUD element.
function areas:registerHudHandler(handler)
table.insert(hudHandlers, handler)
end
function areas:getExternalHudEntries(pos)
local areas = {}
for _, func in pairs(hudHandlers) do
func(pos, areas)
end
return areas
end
--- Returns a list of areas that include the provided position. --- Returns a list of areas that include the provided position.
function areas:getAreasAtPos(pos) function areas:getAreasAtPos(pos)
local res = {} local res = {}
if self.store then if self.store then
local a = self.store:get_areas_for_pos(pos, false, true) local a = self.store:get_areas_for_pos(pos, false, true)
for store_id, store_area in pairs(a) do for store_id, store_area in pairs(a) do
@ -60,10 +94,14 @@ function areas:canInteract(pos, name)
for _, area in pairs(self:getAreasAtPos(pos)) do for _, area in pairs(self:getAreasAtPos(pos)) do
if area.owner == name or area.open then if area.owner == name or area.open then
return true return true
else elseif areas.factions_available and area.faction_open then
owned = true local faction_name = factions.get_player_faction(area.owner)
if faction_name ~= nil and faction_name == factions.get_player_faction(name) then
return true
end end
end end
owned = true
end
return not owned return not owned
end end
@ -125,4 +163,3 @@ function areas:canInteractInArea(pos1, pos2, name, allow_open)
-- intersecting areas and they are all owned by the player. -- intersecting areas and they are all owned by the player.
return true return true
end end

48
api.md Normal file
View File

@ -0,0 +1,48 @@
Areas mod API
===
API list
---
* `areas:registerHudHandler(handler)` - Registers a handler to add items to the Areas HUD. See [HUD](#hud).
* `areas:registerOnAdd(func(id, area))`
* `areas:registerOnRemove(func(id))`
* `areas:registerOnMove(func(id, area, pos1, pos2))`
HUD
---
If you are making a protection mod or a similar mod that adds invisible regions
to the world, and you would like then to show up in the areas HUD element, you
can register a callback to show your areas.
HUD handler specification:
* `handler(pos, list)`
* `pos` - The position to check.
* `list` - The list of area HUD elements, this should be modified in-place.
The area list item is a table containing a list of tables with the following fields:
* `id` - An identifier for the area. This should be a unique string in the format `mod:id`.
* `name` - The name of the area.
* `owner` - The player name of the region owner, if any.
All of the fields are optional but at least one of them must be set.
### Example
local function areas_hud_handler(pos, areas)
local val = find_my_protection(pos)
if val then
table.insert(areas, {
id = "mod:"..val.id,
name = val.name,
owner = val.owner,
})
end
end
areas:registerHudHandler(areas_hud_handler)

View File

@ -286,6 +286,30 @@ minetest.register_chatcommand("area_open", {
}) })
if areas.factions_available then
minetest.register_chatcommand("area_faction_open", {
params = "<ID>",
description = "Toggle an area open/closed for members in your faction.",
func = function(name, param)
local id = tonumber(param)
if not id then
return false, "Invalid usage, see /help area_faction_open."
end
if not areas:isAreaOwner(id, name) then
return false, "Area "..id.." does not exist"
.." or is not owned by you."
end
local open = not areas.areas[id].faction_open
-- Save false as nil to avoid inflating the DB.
areas.areas[id].faction_open = open or nil
areas:save()
return true, ("Area %s for faction members."):format(open and "opened" or "closed")
end
})
end
minetest.register_chatcommand("move_area", { minetest.register_chatcommand("move_area", {
params = "<ID>", params = "<ID>",
description = "Move (or resize) an area to the current positions.", description = "Move (or resize) an area to the current positions.",
@ -383,10 +407,10 @@ minetest.register_chatcommand("area_info", {
table.insert(lines, ("%s spanning up to %dx%dx%d.") table.insert(lines, ("%s spanning up to %dx%dx%d.")
:format(str, size.x, size.y, size.z)) :format(str, size.x, size.y, size.z))
end end
local function priv_limit_info(priv, max_count, max_size) local function priv_limit_info(lpriv, lmax_count, lmax_size)
size_info(("Players with the %q privilege".. size_info(("Players with the %q privilege"..
" can protect up to %d areas"):format( " can protect up to %d areas"):format(
priv, max_count), max_size) lpriv, lmax_count), lmax_size)
end end
if self_prot then if self_prot then
if privs.areas then if privs.areas then

32
hud.lua
View File

@ -1,17 +1,43 @@
-- This is inspired by the landrush mod by Bremaweb -- This is inspired by the landrush mod by Bremaweb
areas.hud = {} areas.hud = {}
areas.hud.refresh = 0
minetest.register_globalstep(function(dtime) minetest.register_globalstep(function(dtime)
areas.hud.refresh = areas.hud.refresh + dtime
if areas.hud.refresh > areas.config["tick"] then
areas.hud.refresh = 0
else
return
end
for _, player in pairs(minetest.get_connected_players()) do for _, player in pairs(minetest.get_connected_players()) do
local name = player:get_player_name() local name = player:get_player_name()
local pos = vector.round(player:getpos()) local pos = vector.round(player:get_pos())
pos = vector.apply(pos, function(p)
return math.max(math.min(p, 2147483), -2147483)
end)
local areaStrings = {} local areaStrings = {}
for id, area in pairs(areas:getAreasAtPos(pos)) do for id, area in pairs(areas:getAreasAtPos(pos)) do
table.insert(areaStrings, ("%s [%u] (%s%s)") local faction_info = area.faction_open and areas.factions_available and
factions.get_player_faction(area.owner)
area.faction_open = faction_info
table.insert(areaStrings, ("%s [%u] (%s%s%s)")
:format(area.name, id, area.owner, :format(area.name, id, area.owner,
area.open and ":open" or "")) area.open and ":open" or "",
faction_info and ":"..faction_info or ""))
end end
for i, area in pairs(areas:getExternalHudEntries(pos)) do
local str = ""
if area.name then str = area.name .. " " end
if area.id then str = str.."["..area.id.."] " end
if area.owner then str = str.."("..area.owner..")" end
table.insert(areaStrings, str)
end
local areaString = "Areas:" local areaString = "Areas:"
if #areaStrings > 0 then if #areaStrings > 0 then
areaString = areaString.."\n".. areaString = areaString.."\n"..

View File

@ -4,6 +4,8 @@
areas = {} areas = {}
areas.factions_available = minetest.global_exists("factions")
areas.adminPrivs = {areas=true} areas.adminPrivs = {areas=true}
areas.startTime = os.clock() areas.startTime = os.clock()
@ -32,7 +34,7 @@ if not minetest.registered_privileges[areas.config.self_protection_privilege] th
}) })
end end
if minetest.setting_getbool("log_mod") then if minetest.settings:get_bool("log_mods") then
local diffTime = os.clock() - areas.startTime local diffTime = os.clock() - areas.startTime
minetest.log("action", "areas loaded in "..diffTime.."s.") minetest.log("action", "areas loaded in "..diffTime.."s.")
end end

View File

@ -1,6 +1,18 @@
function areas:player_exists(name) function areas:player_exists(name)
return minetest.auth_table[name] ~= nil return minetest.get_auth_handler().get_auth(name) ~= nil
end
local safe_file_write = minetest.safe_file_write
if safe_file_write == nil then
function safe_file_write(path, content)
local file, err = io.open(path, "w")
if err then
return err
end
file:write(content)
file:close()
end
end end
-- Save the areas table to a file -- Save the areas table to a file
@ -10,12 +22,7 @@ function areas:save()
minetest.log("error", "[areas] Failed to serialize area data!") minetest.log("error", "[areas] Failed to serialize area data!")
return return
end end
local file, err = io.open(self.config.filename, "w") return safe_file_write(self.config.filename, datastr)
if err then
return err
end
file:write(datastr)
file:close()
end end
-- Load the areas table from the save file -- Load the areas table from the save file
@ -86,6 +93,11 @@ function areas:add(owner, name, pos1, pos2, parent)
owner = owner, owner = owner,
parent = parent parent = parent
} }
for i=1, #areas.registered_on_adds do
areas.registered_on_adds[i](id, self.areas[id])
end
-- Add to AreaStore -- Add to AreaStore
if self.store then if self.store then
local sid = self.store:insert_area(pos1, pos2, tostring(id)) local sid = self.store:insert_area(pos1, pos2, tostring(id))
@ -118,6 +130,10 @@ function areas:remove(id, recurse)
end end
end end
for i=1, #areas.registered_on_removes do
areas.registered_on_removes[i](id)
end
-- Remove main entry -- Remove main entry
self.areas[id] = nil self.areas[id] = nil
@ -133,6 +149,11 @@ function areas:move(id, area, pos1, pos2)
area.pos1 = pos1 area.pos1 = pos1
area.pos2 = pos2 area.pos2 = pos2
for i=1, #areas.registered_on_moves do
areas.registered_on_moves[i](id, area, pos1, pos2)
end
if self.store then if self.store then
self.store:remove_area(areas.store_ids[id]) self.store:remove_area(areas.store_ids[id])
local sid = self.store:insert_area(pos1, pos2, tostring(id)) local sid = self.store:insert_area(pos1, pos2, tostring(id))
@ -282,4 +303,3 @@ function areas:isAreaOwner(id, name)
end end
return false return false
end end

View File

@ -10,7 +10,7 @@ minetest.register_chatcommand("legacy_load_areas", {
minetest.chat_send_player(name, "Converting areas...") minetest.chat_send_player(name, "Converting areas...")
local version = tonumber(param) local version = tonumber(param)
if version == 0 then if version == 0 then
err = areas:node_ownership_load() local err = areas:node_ownership_load()
if err then if err then
minetest.chat_send_player(name, "Error loading legacy file: "..err) minetest.chat_send_player(name, "Error loading legacy file: "..err)
return return
@ -48,6 +48,7 @@ minetest.register_chatcommand("legacy_load_areas", {
function areas:node_ownership_load() function areas:node_ownership_load()
local filename = minetest.get_worldpath().."/owners.tbl" local filename = minetest.get_worldpath().."/owners.tbl"
local tables, err
tables, err = loadfile(filename) tables, err = loadfile(filename)
if err then if err then
return err return err

2
mod.conf Normal file
View File

@ -0,0 +1,2 @@
name = areas
optional_depends = playerfactions

26
pos.lua
View File

@ -11,6 +11,16 @@ areas.set_pos = {}
areas.pos1 = {} areas.pos1 = {}
areas.pos2 = {} areas.pos2 = {}
local LIMIT = 30992 -- this is due to MAPBLOCK_SIZE=16!
local function posLimit(pos)
return {
x = math.max(math.min(pos.x, LIMIT), -LIMIT),
y = math.max(math.min(pos.y, LIMIT), -LIMIT),
z = math.max(math.min(pos.z, LIMIT), -LIMIT)
}
end
minetest.register_chatcommand("select_area", { minetest.register_chatcommand("select_area", {
params = "<ID>", params = "<ID>",
description = "Select a area by id.", description = "Select a area by id.",
@ -35,7 +45,7 @@ minetest.register_chatcommand("area_pos1", {
.." location or the one specified", .." location or the one specified",
privs = {}, privs = {},
func = function(name, param) func = function(name, param)
local pos = nil local pos
local found, _, x, y, z = param:find( local found, _, x, y, z = param:find(
"^(-?%d+)[, ](-?%d+)[, ](-?%d+)$") "^(-?%d+)[, ](-?%d+)[, ](-?%d+)$")
if found then if found then
@ -43,14 +53,14 @@ minetest.register_chatcommand("area_pos1", {
elseif param == "" then elseif param == "" then
local player = minetest.get_player_by_name(name) local player = minetest.get_player_by_name(name)
if player then if player then
pos = player:getpos() pos = player:get_pos()
else else
return false, "Unable to get position." return false, "Unable to get position."
end end
else else
return false, "Invalid usage, see /help area_pos1." return false, "Invalid usage, see /help area_pos1."
end end
pos = vector.round(pos) pos = posLimit(vector.round(pos))
areas:setPos1(name, pos) areas:setPos1(name, pos)
return true, "Area position 1 set to " return true, "Area position 1 set to "
..minetest.pos_to_string(pos) ..minetest.pos_to_string(pos)
@ -62,7 +72,7 @@ minetest.register_chatcommand("area_pos2", {
description = "Set area protection region position 2 to your" description = "Set area protection region position 2 to your"
.." location or the one specified", .." location or the one specified",
func = function(name, param) func = function(name, param)
local pos = nil local pos
local found, _, x, y, z = param:find( local found, _, x, y, z = param:find(
"^(-?%d+)[, ](-?%d+)[, ](-?%d+)$") "^(-?%d+)[, ](-?%d+)[, ](-?%d+)$")
if found then if found then
@ -70,14 +80,14 @@ minetest.register_chatcommand("area_pos2", {
elseif param == "" then elseif param == "" then
local player = minetest.get_player_by_name(name) local player = minetest.get_player_by_name(name)
if player then if player then
pos = player:getpos() pos = player:get_pos()
else else
return false, "Unable to get position." return false, "Unable to get position."
end end
else else
return false, "Invalid usage, see /help area_pos2." return false, "Invalid usage, see /help area_pos2."
end end
pos = vector.round(pos) pos = posLimit(vector.round(pos))
areas:setPos2(name, pos) areas:setPos2(name, pos)
return true, "Area position 2 set to " return true, "Area position 2 set to "
..minetest.pos_to_string(pos) ..minetest.pos_to_string(pos)
@ -130,12 +140,12 @@ function areas:getPos(playerName)
end end
function areas:setPos1(playerName, pos) function areas:setPos1(playerName, pos)
areas.pos1[playerName] = pos areas.pos1[playerName] = posLimit(pos)
areas.markPos1(playerName) areas.markPos1(playerName)
end end
function areas:setPos2(playerName, pos) function areas:setPos2(playerName, pos)
areas.pos2[playerName] = pos areas.pos2[playerName] = posLimit(pos)
areas.markPos2(playerName) areas.markPos2(playerName)
end end

View File

@ -2,42 +2,45 @@ local world_path = minetest.get_worldpath()
areas.config = {} areas.config = {}
local function setting(tp, name, default) local function setting(name, tp, default)
local full_name = "areas." .. name local full_name = "areas." .. name
local value local value
if tp == "boolean" then if tp == "bool" then
value = minetest.setting_getbool(full_name) value = minetest.settings:get_bool(full_name)
default = value == nil and minetest.is_yes(default)
elseif tp == "string" then elseif tp == "string" then
value = minetest.setting_get(full_name) value = minetest.settings:get(full_name)
elseif tp == "position" then elseif tp == "v3f" then
value = minetest.setting_get_pos(full_name) value = minetest.setting_get_pos(full_name)
elseif tp == "number" then default = value == nil and minetest.string_to_pos(default)
value = tonumber(minetest.setting_get(full_name)) elseif tp == "float" or tp == "int" then
value = tonumber(minetest.settings:get(full_name))
local v, other = default:match("^(%S+) (.+)")
default = value == nil and tonumber(other and v or default)
else else
error("Invalid setting type!") error("Cannot parse setting type " .. tp)
end end
if value == nil then if value == nil then
value = default value = default
assert(default ~= nil, "Cannot parse default for " .. full_name)
end end
--print("add", name, default, value)
areas.config[name] = value areas.config[name] = value
end end
local file = io.open(areas.modpath .. "/settingtypes.txt", "r")
for line in file:lines() do
local name, tp, value = line:match("^areas%.(%S+) %(.*%) (%S+) (.*)")
if value then
setting(name, tp, value)
end
end
file:close()
-------------- --------------
-- Settings -- -- Settings --
-------------- --------------
setting("string", "filename", world_path.."/areas.dat") setting("filename", "string", world_path.."/areas.dat")
-- Allow players with a privilege create their own areas
-- within the maximum size and number.
setting("boolean", "self_protection", false)
setting("string", "self_protection_privilege", "interact")
setting("position", "self_protection_max_size", {x=64, y=128, z=64})
setting("number", "self_protection_max_areas", 4)
-- For players with the areas_high_limit privilege.
setting("position", "self_protection_max_size_high", {x=512, y=512, z=512})
setting("number", "self_protection_max_areas_high", 32)
-- legacy_table (owner_defs) compatibility. Untested and has known issues.
setting("boolean", "legacy_table", false)

38
settingtypes.txt Normal file
View File

@ -0,0 +1,38 @@
# This file is parsed in "settings.lua". Check regex first.
# Static paths do not work well with settings
#areas.filename (Configuration file path) string (world_path)/areas.dat
# Allow players with a privilege create their own areas using /protect
# within the specified size and amount limits.
areas.self_protection (Self protection) bool false
# Self protection: Privilege required to protect an area
areas.self_protection_privilege (Self protection: Required privs) string interact
# Refresh delay for the name displays in the HUD in seconds
areas.tick (HUD update delay) float 0.5 0 100
# Enable the legacy owner_defs metatable mode. Untested and possibly unstable
areas.legacy_table (Legacy owner_defs metatable) bool false
[Self protection (normal)]
# Self protection (normal): Maximal size of the protectable area
# Only enter positive whole numbers for the coordinate values or you'll mess up stuff.
areas.self_protection_max_size (Maximal area size) v3f (64, 128, 64)
# Self protection (normal): Maximal amount of protected areas per player
areas.self_protection_max_areas (Maximal area count) int 4
[Self protection (high)]
# Self protection (normal): Maximal size of the protectable area
# This setting applies for plyaers with the privilege 'areas_high_limit'
areas.self_protection_max_size_high (Maximal area size) v3f (512, 512, 512)
# Self protection (normal): Maximal amount of protected areas per player
# Only enter positive whole numbers for the coordinate values or you'll mess up stuff.
# This setting applies for plyaers with the privilege 'areas_high_limit'
areas.self_protection_max_areas_high (Maximal area count) float 32