areas/internal.lua

345 lines
8.9 KiB
Lua
Raw Normal View History

2020-03-08 22:15:00 +01:00
local S = minetest.get_translator("areas")
2013-09-03 01:16:14 +02:00
function areas:player_exists(name)
return minetest.get_auth_handler().get_auth(name) ~= nil
2013-09-03 01:16:14 +02:00
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
2013-09-03 01:16:14 +02:00
-- Save the areas table to a file
function areas:save()
local datastr = minetest.write_json(self.areas)
if not datastr then
minetest.log("error", "[areas] Failed to serialize area data!")
return
end
return safe_file_write(self.config.filename, datastr)
2013-09-03 01:16:14 +02:00
end
-- Load the areas table from the save file
function areas:load()
local file, err = io.open(self.config.filename, "r")
2013-09-03 01:16:14 +02:00
if err then
self.areas = self.areas or {}
return err
end
local data = file:read("*a")
if data:sub(1, 1) == "[" then
self.areas, err = minetest.parse_json(data)
else
self.areas, err = minetest.deserialize(data)
end
if type(self.areas) ~= "table" then
self.areas = {}
end
if err and #data > 10 then
minetest.log("error", "[areas] Failed to load area data: " ..
tostring(err))
end
2013-09-03 01:16:14 +02:00
file:close()
2015-07-09 11:42:13 +02:00
self:populateStore()
end
--- Checks an AreaStore ID.
-- Deletes the AreaStore (falling back to the iterative method)
-- and prints an error message if the ID is invalid.
-- @return Whether the ID was valid.
function areas:checkAreaStoreId(sid)
if not sid then
minetest.log("error", "AreaStore failed to find an ID for an "
2020-03-22 21:25:05 +01:00
.."area! Falling back to iterative area checking.")
2015-07-09 11:42:13 +02:00
self.store = nil
self.store_ids = nil
end
return sid and true or false
end
-- Populates the AreaStore after loading, if needed.
function areas:populateStore()
if not rawget(_G, "AreaStore") then
return
end
local store = AreaStore()
local store_ids = {}
for id, area in pairs(areas.areas) do
local sid = store:insert_area(area.pos1,
area.pos2, tostring(id))
if not self:checkAreaStoreId(sid) then
return
end
store_ids[id] = sid
end
self.store = store
self.store_ids = store_ids
2013-09-03 01:16:14 +02:00
end
-- Finds the first usable index in a table
-- Eg: {[1]=false,[4]=true} -> 2
local function findFirstUnusedIndex(t)
local i = 0
repeat i = i + 1
until t[i] == nil
return i
end
2015-07-09 11:42:13 +02:00
--- Add a area.
-- @return The new area's ID.
2013-09-03 01:16:14 +02:00
function areas:add(owner, name, pos1, pos2, parent)
local id = findFirstUnusedIndex(self.areas)
2015-07-09 11:42:13 +02:00
self.areas[id] = {
name = name,
pos1 = pos1,
pos2 = pos2,
owner = owner,
parent = parent
}
2019-04-02 05:28:50 +02:00
for i=1, #areas.registered_on_adds do
areas.registered_on_adds[i](id, self.areas[id])
end
2015-07-09 11:42:13 +02:00
-- Add to AreaStore
if self.store then
local sid = self.store:insert_area(pos1, pos2, tostring(id))
if self:checkAreaStoreId(sid) then
self.store_ids[id] = sid
end
end
return id
2013-09-03 01:16:14 +02:00
end
2023-08-26 11:40:59 +02:00
--- Remove a area, and optionally its children recursively.
2013-09-03 01:16:14 +02:00
-- If a area is deleted non-recursively the children will
-- have the removed area's parent as their new parent.
function areas:remove(id, recurse)
2013-09-03 01:16:14 +02:00
if recurse then
-- Recursively find child entries and remove them
local cids = self:getChildren(id)
for _, cid in pairs(cids) do
self:remove(cid, true)
2013-09-03 01:16:14 +02:00
end
else
-- Update parents
local parent = self.areas[id].parent
2013-09-03 01:16:14 +02:00
local children = self:getChildren(id)
for _, cid in pairs(children) do
2013-09-03 07:33:08 +02:00
-- The subarea parent will be niled out if the
-- removed area does not have a parent
self.areas[cid].parent = parent
2013-09-03 01:16:14 +02:00
end
end
2019-04-02 05:28:50 +02:00
for i=1, #areas.registered_on_removes do
areas.registered_on_removes[i](id)
end
2013-09-03 01:16:14 +02:00
-- Remove main entry
self.areas[id] = nil
2015-07-09 11:42:13 +02:00
-- Remove from AreaStore
if self.store then
self.store:remove_area(self.store_ids[id])
self.store_ids[id] = nil
end
2013-09-03 01:16:14 +02:00
end
2016-06-19 19:12:16 +02:00
--- Move an area.
function areas:move(id, area, pos1, pos2)
area.pos1 = pos1
area.pos2 = pos2
2019-04-02 05:28:50 +02:00
for i=1, #areas.registered_on_moves do
areas.registered_on_moves[i](id, area, pos1, pos2)
end
2016-06-19 19:12:16 +02:00
if self.store then
self.store:remove_area(areas.store_ids[id])
local sid = self.store:insert_area(pos1, pos2, tostring(id))
if self:checkAreaStoreId(sid) then
self.store_ids[id] = sid
end
end
end
2015-10-31 01:31:24 +01:00
-- Checks if a area between two points is entirely contained by another area.
-- Positions must be sorted.
2013-09-03 01:16:14 +02:00
function areas:isSubarea(pos1, pos2, id)
local area = self.areas[id]
2013-09-03 07:33:08 +02:00
if not area then
return false
end
2015-10-31 01:31:24 +01:00
local ap1, ap2 = area.pos1, area.pos2
local ap1x, ap1y, ap1z = ap1.x, ap1.y, ap1.z
local ap2x, ap2y, ap2z = ap2.x, ap2.y, ap2.z
local p1x, p1y, p1z = pos1.x, pos1.y, pos1.z
local p2x, p2y, p2z = pos2.x, pos2.y, pos2.z
if
(p1x >= ap1x and p1x <= ap2x) and
(p2x >= ap1x and p2x <= ap2x) and
(p1y >= ap1y and p1y <= ap2y) and
(p2y >= ap1y and p2y <= ap2y) and
(p1z >= ap1z and p1z <= ap2z) and
(p2z >= ap1z and p2z <= ap2z) then
2013-09-03 07:33:08 +02:00
return true
2013-09-03 01:16:14 +02:00
end
end
2023-08-26 11:40:59 +02:00
-- Returns a table (list) of children of an area given its identifier
2013-09-03 01:16:14 +02:00
function areas:getChildren(id)
local children = {}
for cid, area in pairs(self.areas) do
2013-09-03 01:16:14 +02:00
if area.parent and area.parent == id then
table.insert(children, cid)
2013-09-03 01:16:14 +02:00
end
end
return children
end
-- checks all possible restrictions registered with
-- areas:registerProtectionCondition
-- builtin callbacks below
2013-09-03 01:16:14 +02:00
function areas:canPlayerAddArea(pos1, pos2, name)
local allowed = true
local errMsg
for i=1, #areas.registered_protection_conditions do
local res, msg = areas.registered_protection_conditions[i](pos1, pos2, name)
if res == true then
-- always allow to protect, no matter of other conditions
return true
elseif res == false then
-- there might be another callback that returns true, so we can't break here
allowed = false
-- save the first error that occurred
errMsg = errMsg or msg
elseif res ~= nil then
local origin = areas.callback_origins[areas.registered_protection_conditions[i]]
error("\n[Mod] areas: Invalid api usage from mod '" ..
origin.mod .. "' in callback registerProtectionCondition() at " ..
origin.source .. ":" .. origin.line)
end
end
return allowed, errMsg
end
-- Checks if the user has sufficient privileges.
areas:registerProtectionCondition(function(pos1, pos2, name)
local privs = minetest.get_player_privs(name)
if privs.areas then
-- always allow administrators to create areas
2013-09-03 01:16:14 +02:00
return true
2013-09-03 07:33:08 +02:00
end
2013-09-03 01:16:14 +02:00
-- Check self protection privilege
if not areas.config.self_protection or
not privs[areas.config.self_protection_privilege] then
2020-03-08 22:15:00 +01:00
return false, S("Self protection is disabled or you do not have"
.." the necessary privilege.")
2013-09-03 01:16:14 +02:00
end
end)
2013-09-03 01:16:14 +02:00
-- check if the area is too big
areas:registerProtectionCondition(function(pos1, pos2, name)
local privs = minetest.get_player_privs(name)
local max_size = privs.areas_high_limit and
areas.config.self_protection_max_size_high or
areas.config.self_protection_max_size
if
(pos2.x - pos1.x + 1) > max_size.x or
(pos2.y - pos1.y + 1) > max_size.y or
(pos2.z - pos1.z + 1) > max_size.z then
2020-03-08 22:15:00 +01:00
return false, S("Area is too big.")
2013-09-03 01:16:14 +02:00
end
end)
2013-09-03 01:16:14 +02:00
-- Check number of areas the user has and make sure it not above the max
areas:registerProtectionCondition(function(pos1, pos2, name)
local privs = minetest.get_player_privs(name)
2014-01-16 02:13:47 +01:00
local count = 0
for _, area in pairs(areas.areas) do
2014-01-16 02:13:47 +01:00
if area.owner == name then
count = count + 1
2013-09-03 01:16:14 +02:00
end
end
local max_areas = privs.areas_high_limit and
areas.config.self_protection_max_areas_high or
areas.config.self_protection_max_areas
if count >= max_areas then
2020-03-08 22:15:00 +01:00
return false, S("You have reached the maximum amount of"
.." areas that you are allowed to protect.")
2014-01-16 02:13:47 +01:00
end
end)
2013-09-03 01:16:14 +02:00
-- checks if the area intersects other areas that the player do not own.
areas:registerProtectionCondition(function(pos1, pos2, name)
local can, id = areas:canInteractInArea(pos1, pos2, name)
2014-07-12 22:37:54 +02:00
if not can then
local area = areas.areas[id]
2020-03-08 22:15:00 +01:00
return false, S("The area intersects with @1 [@2] (@3).",
area.name, id, area.owner)
2013-09-03 01:16:14 +02:00
end
end)
2013-09-03 01:16:14 +02:00
-- Given a id returns a string in the format:
2013-09-03 07:33:08 +02:00
-- "name [id]: owner (x1, y1, z1) (x2, y2, z2) -> children"
function areas:toString(id)
local area = self.areas[id]
local message = ("%s [%d]: %s %s %s"):format(
area.name, id, area.owner,
minetest.pos_to_string(area.pos1),
minetest.pos_to_string(area.pos2))
2013-09-03 01:16:14 +02:00
local children = areas:getChildren(id)
if #children > 0 then
message = message.." -> "..table.concat(children, ", ")
2013-09-03 01:16:14 +02:00
end
return message
2013-09-03 01:16:14 +02:00
end
-- Re-order areas in table by their identifiers
function areas:sort()
local sa = {}
2013-09-03 01:16:14 +02:00
for k, area in pairs(self.areas) do
if not area.parent then
table.insert(sa, area)
local newid = #sa
2013-09-03 01:16:14 +02:00
for _, subarea in pairs(self.areas) do
if subarea.parent == k then
subarea.parent = newid
table.insert(sa, subarea)
2013-09-03 01:16:14 +02:00
end
end
end
end
self.areas = sa
2013-09-03 01:16:14 +02:00
end
-- Checks if a player owns an area or a parent of it
function areas:isAreaOwner(id, name)
local cur = self.areas[id]
2014-07-13 01:10:26 +02:00
if cur and minetest.check_player_privs(name, self.adminPrivs) then
2013-09-03 01:16:14 +02:00
return true
end
while cur do
if cur.owner == name then
return true
elseif cur.parent then
cur = self.areas[cur.parent]
2013-09-03 01:16:14 +02:00
else
return false
end
end
return false
end