A rewrite of the node_ownership Minetest mod with many new features. https://content.minetest.net/packages/ShadowNinja/areas/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
7.6KB

  1. local S = minetest.get_translator("areas")
  2. function areas:player_exists(name)
  3. return minetest.get_auth_handler().get_auth(name) ~= nil
  4. end
  5. local safe_file_write = minetest.safe_file_write
  6. if safe_file_write == nil then
  7. function safe_file_write(path, content)
  8. local file, err = io.open(path, "w")
  9. if err then
  10. return err
  11. end
  12. file:write(content)
  13. file:close()
  14. end
  15. end
  16. -- Save the areas table to a file
  17. function areas:save()
  18. local datastr = minetest.serialize(self.areas)
  19. if not datastr then
  20. minetest.log("error", "[areas] Failed to serialize area data!")
  21. return
  22. end
  23. return safe_file_write(self.config.filename, datastr)
  24. end
  25. -- Load the areas table from the save file
  26. function areas:load()
  27. local file, err = io.open(self.config.filename, "r")
  28. if err then
  29. self.areas = self.areas or {}
  30. return err
  31. end
  32. self.areas = minetest.deserialize(file:read("*a"))
  33. if type(self.areas) ~= "table" then
  34. self.areas = {}
  35. end
  36. file:close()
  37. self:populateStore()
  38. end
  39. --- Checks an AreaStore ID.
  40. -- Deletes the AreaStore (falling back to the iterative method)
  41. -- and prints an error message if the ID is invalid.
  42. -- @return Whether the ID was valid.
  43. function areas:checkAreaStoreId(sid)
  44. if not sid then
  45. minetest.log("error", "AreaStore failed to find an ID for an "
  46. .."area! Falling back to iterative area checking.")
  47. self.store = nil
  48. self.store_ids = nil
  49. end
  50. return sid and true or false
  51. end
  52. -- Populates the AreaStore after loading, if needed.
  53. function areas:populateStore()
  54. if not rawget(_G, "AreaStore") then
  55. return
  56. end
  57. local store = AreaStore()
  58. local store_ids = {}
  59. for id, area in pairs(areas.areas) do
  60. local sid = store:insert_area(area.pos1,
  61. area.pos2, tostring(id))
  62. if not self:checkAreaStoreId(sid) then
  63. return
  64. end
  65. store_ids[id] = sid
  66. end
  67. self.store = store
  68. self.store_ids = store_ids
  69. end
  70. -- Finds the first usable index in a table
  71. -- Eg: {[1]=false,[4]=true} -> 2
  72. local function findFirstUnusedIndex(t)
  73. local i = 0
  74. repeat i = i + 1
  75. until t[i] == nil
  76. return i
  77. end
  78. --- Add a area.
  79. -- @return The new area's ID.
  80. function areas:add(owner, name, pos1, pos2, parent)
  81. local id = findFirstUnusedIndex(self.areas)
  82. self.areas[id] = {
  83. name = name,
  84. pos1 = pos1,
  85. pos2 = pos2,
  86. owner = owner,
  87. parent = parent
  88. }
  89. for i=1, #areas.registered_on_adds do
  90. areas.registered_on_adds[i](id, self.areas[id])
  91. end
  92. -- Add to AreaStore
  93. if self.store then
  94. local sid = self.store:insert_area(pos1, pos2, tostring(id))
  95. if self:checkAreaStoreId(sid) then
  96. self.store_ids[id] = sid
  97. end
  98. end
  99. return id
  100. end
  101. --- Remove a area, and optionally it's children recursively.
  102. -- If a area is deleted non-recursively the children will
  103. -- have the removed area's parent as their new parent.
  104. function areas:remove(id, recurse)
  105. if recurse then
  106. -- Recursively find child entries and remove them
  107. local cids = self:getChildren(id)
  108. for _, cid in pairs(cids) do
  109. self:remove(cid, true)
  110. end
  111. else
  112. -- Update parents
  113. local parent = self.areas[id].parent
  114. local children = self:getChildren(id)
  115. for _, cid in pairs(children) do
  116. -- The subarea parent will be niled out if the
  117. -- removed area does not have a parent
  118. self.areas[cid].parent = parent
  119. end
  120. end
  121. for i=1, #areas.registered_on_removes do
  122. areas.registered_on_removes[i](id)
  123. end
  124. -- Remove main entry
  125. self.areas[id] = nil
  126. -- Remove from AreaStore
  127. if self.store then
  128. self.store:remove_area(self.store_ids[id])
  129. self.store_ids[id] = nil
  130. end
  131. end
  132. --- Move an area.
  133. function areas:move(id, area, pos1, pos2)
  134. area.pos1 = pos1
  135. area.pos2 = pos2
  136. for i=1, #areas.registered_on_moves do
  137. areas.registered_on_moves[i](id, area, pos1, pos2)
  138. end
  139. if self.store then
  140. self.store:remove_area(areas.store_ids[id])
  141. local sid = self.store:insert_area(pos1, pos2, tostring(id))
  142. if self:checkAreaStoreId(sid) then
  143. self.store_ids[id] = sid
  144. end
  145. end
  146. end
  147. -- Checks if a area between two points is entirely contained by another area.
  148. -- Positions must be sorted.
  149. function areas:isSubarea(pos1, pos2, id)
  150. local area = self.areas[id]
  151. if not area then
  152. return false
  153. end
  154. local ap1, ap2 = area.pos1, area.pos2
  155. local ap1x, ap1y, ap1z = ap1.x, ap1.y, ap1.z
  156. local ap2x, ap2y, ap2z = ap2.x, ap2.y, ap2.z
  157. local p1x, p1y, p1z = pos1.x, pos1.y, pos1.z
  158. local p2x, p2y, p2z = pos2.x, pos2.y, pos2.z
  159. if
  160. (p1x >= ap1x and p1x <= ap2x) and
  161. (p2x >= ap1x and p2x <= ap2x) and
  162. (p1y >= ap1y and p1y <= ap2y) and
  163. (p2y >= ap1y and p2y <= ap2y) and
  164. (p1z >= ap1z and p1z <= ap2z) and
  165. (p2z >= ap1z and p2z <= ap2z) then
  166. return true
  167. end
  168. end
  169. -- Returns a table (list) of children of an area given it's identifier
  170. function areas:getChildren(id)
  171. local children = {}
  172. for cid, area in pairs(self.areas) do
  173. if area.parent and area.parent == id then
  174. table.insert(children, cid)
  175. end
  176. end
  177. return children
  178. end
  179. -- Checks if the user has sufficient privileges.
  180. -- If the player is not a administrator it also checks
  181. -- if the area intersects other areas that they do not own.
  182. -- Also checks the size of the area and if the user already
  183. -- has more than max_areas.
  184. function areas:canPlayerAddArea(pos1, pos2, name)
  185. local privs = minetest.get_player_privs(name)
  186. if privs.areas then
  187. return true
  188. end
  189. -- Check self protection privilege, if it is enabled,
  190. -- and if the area is too big.
  191. if not self.config.self_protection or
  192. not privs[areas.config.self_protection_privilege] then
  193. return false, S("Self protection is disabled or you do not have"
  194. .." the necessary privilege.")
  195. end
  196. local max_size = privs.areas_high_limit and
  197. self.config.self_protection_max_size_high or
  198. self.config.self_protection_max_size
  199. if
  200. (pos2.x - pos1.x) > max_size.x or
  201. (pos2.y - pos1.y) > max_size.y or
  202. (pos2.z - pos1.z) > max_size.z then
  203. return false, S("Area is too big.")
  204. end
  205. -- Check number of areas the user has and make sure it not above the max
  206. local count = 0
  207. for _, area in pairs(self.areas) do
  208. if area.owner == name then
  209. count = count + 1
  210. end
  211. end
  212. local max_areas = privs.areas_high_limit and
  213. self.config.self_protection_max_areas_high or
  214. self.config.self_protection_max_areas
  215. if count >= max_areas then
  216. return false, S("You have reached the maximum amount of"
  217. .." areas that you are allowed to protect.")
  218. end
  219. -- Check intersecting areas
  220. local can, id = self:canInteractInArea(pos1, pos2, name)
  221. if not can then
  222. local area = self.areas[id]
  223. return false, S("The area intersects with @1 [@2] (@3).",
  224. area.name, id, area.owner)
  225. end
  226. return true
  227. end
  228. -- Given a id returns a string in the format:
  229. -- "name [id]: owner (x1, y1, z1) (x2, y2, z2) -> children"
  230. function areas:toString(id)
  231. local area = self.areas[id]
  232. local message = ("%s [%d]: %s %s %s"):format(
  233. area.name, id, area.owner,
  234. minetest.pos_to_string(area.pos1),
  235. minetest.pos_to_string(area.pos2))
  236. local children = areas:getChildren(id)
  237. if #children > 0 then
  238. message = message.." -> "..table.concat(children, ", ")
  239. end
  240. return message
  241. end
  242. -- Re-order areas in table by their identifiers
  243. function areas:sort()
  244. local sa = {}
  245. for k, area in pairs(self.areas) do
  246. if not area.parent then
  247. table.insert(sa, area)
  248. local newid = #sa
  249. for _, subarea in pairs(self.areas) do
  250. if subarea.parent == k then
  251. subarea.parent = newid
  252. table.insert(sa, subarea)
  253. end
  254. end
  255. end
  256. end
  257. self.areas = sa
  258. end
  259. -- Checks if a player owns an area or a parent of it
  260. function areas:isAreaOwner(id, name)
  261. local cur = self.areas[id]
  262. if cur and minetest.check_player_privs(name, self.adminPrivs) then
  263. return true
  264. end
  265. while cur do
  266. if cur.owner == name then
  267. return true
  268. elseif cur.parent then
  269. cur = self.areas[cur.parent]
  270. else
  271. return false
  272. end
  273. end
  274. return false
  275. end