internal.lua 7.5 KB

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