summaryrefslogtreecommitdiff
path: root/areas/internal.lua
diff options
context:
space:
mode:
Diffstat (limited to 'areas/internal.lua')
-rw-r--r--areas/internal.lua265
1 files changed, 265 insertions, 0 deletions
diff --git a/areas/internal.lua b/areas/internal.lua
new file mode 100644
index 0000000..ea94a27
--- /dev/null
+++ b/areas/internal.lua
@@ -0,0 +1,265 @@
+
+function areas:player_exists(name)
+ return minetest.auth_table[name] ~= nil
+end
+
+-- Save the areas table to a file
+function areas:save()
+ local datastr = minetest.serialize(self.areas)
+ if not datastr then
+ minetest.log("error", "[areas] Failed to serialize area data!")
+ return
+ end
+ local file, err = io.open(self.config.filename, "w")
+ if err then
+ return err
+ end
+ file:write(datastr)
+ file:close()
+end
+
+-- Load the areas table from the save file
+function areas:load()
+ local file, err = io.open(self.config.filename, "r")
+ if err then
+ self.areas = self.areas or {}
+ return err
+ end
+ self.areas = minetest.deserialize(file:read("*a"))
+ if type(self.areas) ~= "table" then
+ self.areas = {}
+ end
+ file:close()
+ 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 "
+ .."area! Falling back to iterative area checking.")
+ 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
+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
+
+--- Add a area.
+-- @return The new area's ID.
+function areas:add(owner, name, pos1, pos2, parent)
+ local id = findFirstUnusedIndex(self.areas)
+ self.areas[id] = {
+ name = name,
+ pos1 = pos1,
+ pos2 = pos2,
+ owner = owner,
+ parent = parent
+ }
+ -- 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
+end
+
+--- Remove a area, and optionally it's children recursively.
+-- 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)
+ 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)
+ end
+ else
+ -- Update parents
+ local parent = self.areas[id].parent
+ local children = self:getChildren(id)
+ for _, cid in pairs(children) do
+ -- The subarea parent will be niled out if the
+ -- removed area does not have a parent
+ self.areas[cid].parent = parent
+
+ end
+ end
+
+ -- Remove main entry
+ self.areas[id] = nil
+
+ -- Remove from AreaStore
+ if self.store then
+ self.store:remove_area(self.store_ids[id])
+ self.store_ids[id] = nil
+ end
+end
+
+-- Checks if a area between two points is entirely contained by another area
+function areas:isSubarea(pos1, pos2, id)
+ local area = self.areas[id]
+ if not area then
+ return false
+ end
+ local p1, p2 = area.pos1, area.pos2
+ if (pos1.x >= p1.x and pos1.x <= p2.x) and
+ (pos2.x >= p1.x and pos2.x <= p2.x) and
+ (pos1.y >= p1.y and pos1.y <= p2.y) and
+ (pos2.y >= p1.y and pos2.y <= p2.y) and
+ (pos1.z >= p1.z and pos1.z <= p2.z) and
+ (pos2.z >= p1.z and pos2.z <= p2.z) then
+ return true
+ end
+end
+
+-- Returns a table (list) of children of an area given it's identifier
+function areas:getChildren(id)
+ local children = {}
+ for cid, area in pairs(self.areas) do
+ if area.parent and area.parent == id then
+ table.insert(children, cid)
+ end
+ end
+ return children
+end
+
+-- Checks if the user has sufficient privileges.
+-- If the player is not a administrator it also checks
+-- if the area intersects other areas that they do not own.
+-- Also checks the size of the area and if the user already
+-- has more than max_areas.
+function areas:canPlayerAddArea(pos1, pos2, name)
+ local privs = minetest.get_player_privs(name)
+ if privs.areas then
+ return true
+ end
+
+ -- Check self protection privilege, if it is enabled,
+ -- and if the area is too big.
+ if not self.config.self_protection or
+ not privs[areas.config.self_protection_privilege] then
+ return false, "Self protection is disabled or you do not have"
+ .." the necessary privilege."
+ end
+
+ local max_size = privs.areas_high_limit and
+ self.config.self_protection_max_size_high or
+ self.config.self_protection_max_size
+ if
+ (pos2.x - pos1.x) > max_size.x or
+ (pos2.y - pos1.y) > max_size.y or
+ (pos2.z - pos1.z) > max_size.z then
+ return false, "Area is too big."
+ end
+
+ -- Check number of areas the user has and make sure it not above the max
+ local count = 0
+ for _, area in pairs(self.areas) do
+ if area.owner == name then
+ count = count + 1
+ end
+ end
+ local max_areas = privs.areas_high_limit and
+ self.config.self_protection_max_areas_high or
+ self.config.self_protection_max_areas
+ if count >= max_areas then
+ return false, "You have reached the maximum amount of"
+ .." areas that you are allowed to protect."
+ end
+
+ -- Check intersecting areas
+ local can, id = self:canInteractInArea(pos1, pos2, name)
+ if not can then
+ local area = self.areas[id]
+ return false, ("The area intersects with %s [%u] (%s).")
+ :format(area.name, id, area.owner)
+ end
+
+ return true
+end
+
+-- Given a id returns a string in the format:
+-- "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))
+
+ local children = areas:getChildren(id)
+ if #children > 0 then
+ message = message.." -> "..table.concat(children, ", ")
+ end
+ return message
+end
+
+-- Re-order areas in table by their identifiers
+function areas:sort()
+ local sa = {}
+ for k, area in pairs(self.areas) do
+ if not area.parent then
+ table.insert(sa, area)
+ local newid = #sa
+ for _, subarea in pairs(self.areas) do
+ if subarea.parent == k then
+ subarea.parent = newid
+ table.insert(sa, subarea)
+ end
+ end
+ end
+ end
+ self.areas = sa
+end
+
+-- Checks if a player owns an area or a parent of it
+function areas:isAreaOwner(id, name)
+ local cur = self.areas[id]
+ if cur and minetest.check_player_privs(name, self.adminPrivs) then
+ return true
+ end
+ while cur do
+ if cur.owner == name then
+ return true
+ elseif cur.parent then
+ cur = self.areas[cur.parent]
+ else
+ return false
+ end
+ end
+ return false
+end
+