commit 4ae050a3ae13d5fc7a01b72fe2576508916e5145 Author: ShadowNinja Date: Mon Sep 2 19:16:14 2013 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5236e1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ + diff --git a/api.lua b/api.lua new file mode 100644 index 0000000..1c471e1 --- /dev/null +++ b/api.lua @@ -0,0 +1,51 @@ +--TODO Less code duplication + +-- Checks if the area is unprotected or owned by you +function areas:canInteract(pos, name) + if minetest.check_player_privs(name, {areas=true}) then + return true + end + local owned = false + for _, area in pairs(self.areas) do + p1, p2 = area.pos1, area.pos2 + if pos.x >= p1.x and pos.x <= p2.x and + pos.y >= p1.y and pos.y <= p2.y and + pos.z >= p1.z and pos.z <= p2.z then + if area.owner == name then + return true + else + owned = true + end + end + end + return not owned +end + +-- Returns a table (list) of all players that own an area +function areas:getNodeOwners(pos) + local owners = {} + for _, area in pairs(self.areas) do + if pos.x >= area.pos1.x and pos.x <= area.pos2.x and + pos.y >= area.pos1.y and pos.y <= area.pos2.y and + pos.z >= area.pos1.z and pos.z <= area.pos2.z then + if area.owner ~= nil then + table.insert(owners, area.owner) + end + end + end + return owners +end + +-- Checks if an area has an owner +function areas.hasOwner(pos) + for _, area in pairs(areas.areas) do + p1, p2 = area.pos1, area.pos2 + if pos.x >= p1.x and pos.x <= p2.x and + pos.y >= p1.y and pos.y <= p2.y and + pos.z >= p1.z and pos.z <= p2.z then + return true + end + end + return false +end + diff --git a/chatcommands.lua b/chatcommands.lua new file mode 100644 index 0000000..22c4d8e --- /dev/null +++ b/chatcommands.lua @@ -0,0 +1,302 @@ +minetest.register_chatcommand("protect", { + params = "", + description = "Protect your own area", + privs = {[areas.self_protection_privilege]=true}, + func = function(name, param) + if param ~= "" then + + local pos1, pos2 = {}, {} + if areas:getPos1(name) and areas:getPos2(name) then + pos1 = areas:getPos1(name) + pos2 = areas:getPos2(name) + pos1, pos2 = areas:sortPos(pos1, pos2) + else + minetest.chat_send_player(name, 'You need to select an area first') + return + end + + minetest.log("action", "/protect invoked, owner="..name.. + " areaname="..param.. + " startpos="..minetest.pos_to_string(pos1).. + " endpos=" ..minetest.pos_to_string(pos2)) + + local canAdd, errMsg = areas:canPlayerAddArea(pos1, pos2, name) + if not canAdd then + minetest.chat_send_player(name, "You can't protect that area: "..errMsg) + return + end + + areas:add(name, param, pos1, pos2, nil) + areas:save() + + minetest.chat_send_player(name, "Area protected") + else + minetest.chat_send_player(name, 'Invalid usage, see /help protect') + end +end}) + + +minetest.register_chatcommand("set_owner", { + params = " ", + description = "Protect an area beetween two positions and give a player access to it without setting the parent of the area to any existing area", + privs = {areas=true}, + func = function(name, param) + if param and param ~= "" then + local found, _, ownername, areaname = param:find('^([^%s]+)%s(.+)$') + + if not found then + minetest.chat_send_player(name, "Incorrect usage, see /help set_owner") + return + end + + local pos1, pos2 = {}, {} + if areas:getPos1(name) and areas:getPos2(name) then + pos1 = areas:getPos1(name) + pos2 = areas:getPos2(name) + pos1, pos2 = areas:sortPos(pos1, pos2) + else + minetest.chat_send_player(name, 'You need to select an area first') + return + end + + if not areas:player_exists(ownername) then + minetest.chat_send_player(name, 'The player "'..ownername..'" does not exist') + return + end + + --local canAdd, errMsg = areas:canPlayerAddArea(pos1, pos2, name) + --if not canAdd then + -- minetest.chat_send_player(name, "You can't protect that area: "..errMsg) + -- return + --end + + minetest.log("action", "/set_owner invoked, Owner="..ownername.. + " AreaName="..areaname.. + " StartPos="..minetest.pos_to_string(pos1).. + " EndPos=" ..minetest.pos_to_string(pos2)) + + areas:add(ownername, areaname, pos1, pos2, nil) + areas:save() + + minetest.chat_send_player(ownername, "A concession has been granted to you! Type /list_areas to show your concessions.") + minetest.chat_send_player(name, "Area protected") + else + minetest.chat_send_player(name, 'Invalid usage, see /help set_owner') + end +end}) + + +minetest.register_chatcommand("add_owner", { + params = " ", + description = "Give a player access to a sub-area beetween two positions that have already been protected, use set_owner if you don't want the parent to be set", + privs = {}, + func = function(name, param) + if param and param ~= "" then + local found, _, pid, ownername, areaname = param:find('^(%d+)%s([^%s]+)%s(.+)$') + + if not found then + minetest.chat_send_player(name, "Incorrect usage, see /help set_owner") + return + end + + local pos1, pos2 = {}, {} + if areas:getPos1(name) and areas:getPos2(name) then + pos1 = areas:getPos1(name) + pos2 = areas:getPos2(name) + pos1, pos2 = areas:sortPos(pos1, pos2) + else + minetest.chat_send_player(name, 'You need to select an area first') + return + end + + if not areas:player_exists(ownername) then + minetest.chat_send_player(name, 'The player "'..ownername..'" does not exist') + return + end + + minetest.log("action", "add_owner invoked, Owner = "..ownername.. + " AreaName = "..areaname.." ParentID = "..pid.. + " StartPos = "..pos1.x..","..pos1.y..","..pos1.z.. + " EndPos = " ..pos2.x..","..pos2.y..","..pos2.z) + + --Look to see if this new area is inside an area owned by the player using this function + pid = tonumber(pid) + if (not areas:isAreaOwner(pid, name)) or + (not areas:isSubarea(pos1, pos2, pid)) then + minetest.chat_send_player(name, "You can't protect that area") + return + end + + areas:add(ownername, areaname, pos1, pos2, pid) + areas:save() + + minetest.chat_send_player(ownername, "A concession has been granted to you! Type /list_areas to show your concessions.") + minetest.chat_send_player(name, "You granted "..ownername.." a concession successfully!") + else + minetest.chat_send_player(name, 'Invalid usage, see /help add_owner') + end +end}) + + +minetest.register_chatcommand("rename_area", { + params = " ", + description = "Rename a area that you own", + privs = {}, + func = function(name, param) + local found, _, id, newName = param:find("^(%d+)%s(.+)$") + + if not found then + minetest.chat_send_player(name, "Invalid usage, see /help rename_area") + return + end + + index = areas:getIndexById(tonumber(id)) + + if not index then + minetest.chat_send_player(name, "That area doesn't exist") + return + end + + if not areas:isAreaOwner(id, name) then + minetest.chat_send_player(name, "You don't own that area") + return + end + + areas.areas[index].name = newName + areas:save() +end}) + + +minetest.register_chatcommand("list_owners", { + params = "", + description = "list the players that can edit the area you are in", + privs = {}, + func = function(name, param) + local owners = areas:getNodeOwners(vector.round(minetest.get_player_by_name(name):getpos())) + if #owners > 0 then + minetest.chat_send_player(name, "Owners: "..table.concat(owners, ", ")) + else + minetest.chat_send_player(name, "Your position is unowned") + end +end}) + + +minetest.register_chatcommand("find_areas", { + params = "", + description = "Find areas using a Lua regular expression", + privs = {}, + func = function(name, param) + if param and param ~= "" then + local found = false + for _, area in pairs(areas.areas) do + if areas:isAreaOwner(area.id, name) and + areas:toString(area):find(param) then + minetest.chat_send_player(name, areas:toString(area)) + found = true + end + end + if not found then + minetest.chat_send_player(name, "No matches found") + end + else + minetest.chat_send_player(name, "Regular expression required") + end +end}) + + +minetest.register_chatcommand("list_areas", { + params = "", + description = "list the areas you own, or all areas if you have privileges", + privs = {}, + func = function(name, param) + admin = minetest.check_player_privs(name, {areas=true}) + if admin then + minetest.chat_send_player(name, "Showing all owner entries.") + else + minetest.chat_send_player(name, "Showing your owner entries (You can only modify these).") + end + for _, area in pairs(areas.areas) do + if admin or area.owner == name then + minetest.chat_send_player(name, areas:toString(area)) + end + end +end}) + + +minetest.register_chatcommand("recursive_remove_areas", { + params = "", + description = "Recursively remove areas using an id", + privs = {}, + func = function(name, param) + local id = tonumber(param) + if not id then + minetest.chat_send_player(name, 'Invalid usage, see /help recursive_remove_areas') + minetest.chat_send_player(name, 'Use /list_areas to see entries') + return + end + + if areas:isAreaOwner(id, name) then + areas:remove(id, true) + areas:sort() + areas:save() + else + minetest.chat_send_player(name, "Area "..id.." does not exist or is not owned by you") + return + end + minetest.chat_send_player(name, 'Removed area '..id..'and sub areas') +end}) + + +minetest.register_chatcommand("remove_area", { + params = "", + description = "Remove an area using an id", + privs = {}, + func = function(name, param) + local id = tonumber(param) + if not id then + minetest.chat_send_player(name, 'Invalid usage, see /help remove_area') + minetest.chat_send_player(name, 'Use /list_areas to see entries') + return + end + + if areas:isAreaOwner(id, name) then + areas:remove(id, false) + areas:sort() + areas:save() + else + minetest.chat_send_player(name, "Area "..id.." does not exist or is not owned by you") + return + end + minetest.chat_send_player(name, 'Removed area '..id) +end}) + + +minetest.register_chatcommand("change_owner", { + params = " ", + description = "change the owner of an area using its id", + privs = {}, + func = function(name, param) + local found, _, id, new_owner = param:find('^(%d+)%s+([^%s]+)$') + + if not found then + minetest.chat_send_player(name, 'Invalid usage, see /help change_area_owner') + minetest.chat_send_player(name, 'Use /list_areas to see entries') + return + end + + if not areas:player_exists(new_owner) then + minetest.chat_send_player(name, 'The player "'..new_owner..'" does not exist') + return + end + + id = tonumber(id) + if areas:isAreaOwner(id, name) then + areas.areas[areas:getIndexById(id)].owner = new_owner + areas:save() + minetest.chat_send_player(name, 'Owner changed succesfully') + minetest.chat_send_player(new_owner, name..'" has granted you a concession!') + else + minetest.chat_send_player(new_owner, "Area "..id.." does not exist or is not owned by you") + end +end}) + diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..e69de29 diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..35737fd --- /dev/null +++ b/init.lua @@ -0,0 +1,31 @@ +-- Areas mod by ShadowNinja +-- Based on node_ownership +-- License: GPLv2+ + +areas = {} + +areas.startTime = os.clock() + +areas.modpath = minetest.get_modpath("areas") +dofile(areas.modpath.."/settings.lua") +dofile(areas.modpath.."/api.lua") +dofile(areas.modpath.."/internal.lua") +dofile(areas.modpath.."/chatcommands.lua") +dofile(areas.modpath.."/pos.lua") +dofile(areas.modpath.."/interact.lua") +dofile(areas.modpath.."/legacy.lua") + +areas:load() + +minetest.register_privilege("areas", {description = "Can administer areas"}) + +if not minetest.registered_privileges[areas.self_protection_privilege] then + minetest.register_privilege(areas.self_protection_privilege, + {description = "Can protect areas"}) +end + +if minetest.setting_getbool("log_mod") then + local diffTime = os.clock() - areas.startTime + print("[areas] loaded in "..diffTime.."s.") +end + diff --git a/interact.lua b/interact.lua new file mode 100644 index 0000000..f6a7cb1 --- /dev/null +++ b/interact.lua @@ -0,0 +1,39 @@ + +-- Gives a player a warning message about a area being protected +local function printWarning(name, pos) + local owners = areas:getNodeOwners(pos) + minetest.chat_send_player(name, ("%s is protected by %s.") + :format(minetest.pos_to_string(pos), table.concat(owners, ", "))) +end + +if minetest.can_interact then + old_can_interact = minetest.can_interact + function minetest.can_interact(pos, name) + if not areas:canInteract(pos, name) then + printWarning(name, pos) + return false + end + return old_can_interact(pos, name) + end +else + local old_node_place = minetest.item_place_node + function minetest.item_place_node(itemstack, placer, pointed_thing) + -- XXX: buildable_to nodes can mess this up + local pos = pointed_thing.above + if not areas:canInteract(pos, placer:get_player_name()) then + printWarning(placer:get_player_name(), pos) + return itemstack -- Abort place. + end + return old_node_place(itemstack, placer, pointed_thing) + end + + local old_node_dig = minetest.node_dig + function minetest.node_dig(pos, node, digger) + if not areas:canInteract(pos, digger:get_player_name()) then + printWarning(digger:get_player_name(), pos) + return -- Abort dig. + end + return old_node_dig(pos, node, digger) + end +end + diff --git a/internal.lua b/internal.lua new file mode 100644 index 0000000..41a0497 --- /dev/null +++ b/internal.lua @@ -0,0 +1,193 @@ +function areas:player_exists(name) + return minetest.auth_table[name] ~= nil +end + +-- Save the areas table to a file +function areas:save() + file, err = io.open(self.filename, "w") + if err then + return err + end + file:write(minetest.serialize(self.areas)) + file:close() +end + +-- Load the areas table from the save file +function areas:load() + file, err = io.open(self.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() +end + +-- Shorter than the table function +function areas:add(owner, name, pos1, pos2, parent) + table.insert(areas.areas, {id=table.maxn(self.areas)+1, name=name, + pos1=pos1, pos2=pos2, owner=owner, parent=parent}) +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:removeArea(cid, true) + end + else + -- Update parents + local parent = self:getAreaById(id).parent + local children = self:getChildren(id) + for _, child in pairs(children) do + -- The subarea parent will be niled out if the removed area does not have a parent + areas.areas[self:getIndexById(child)].parent = parent + + end + end + + -- Remove main entry + table.remove(self.areas, self:getIndexById(id)) +end + +-- Checks if a area between two points is entirely contained by another area +function areas:isSubarea(pos1, pos2, id) + local area = areas:getAreaById(id) + if area then + 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 + return false +end + +-- Returns a table (list) of children of an area given it's identifier +function areas:getChildren(id) + local children = {} + for _, area in pairs(self.areas) do + if area.parent and area.parent == id then + table.insert(children, area.id) + 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) + --[[ + if minetest.check_player_privs(name, {areas=true}) then + return true + end--]] + + -- Check self protection privilege, if it is enabled, and if the area is too big. + if (not self.self_protection) or + (not minetest.check_player_privs(name, {[areas.self_protection_privilege]=true})) then + return false, "Self protection is disabled or you do not have the necessary privilege." + end + + if (pos2.x - pos1.x) > self.self_protection_max_size.x or + (pos2.y - pos1.y) > self.self_protection_max_size.y or + (pos2.z - pos1.z) > self.self_protection_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 + if self.self_protection then + local count = 0 + for _, area in pairs(self.areas) do + if area.owner == name then + count = count + 1 + end + end + if count > self.self_protection_max_areas then + return false, "You have reached the maximum amount of areas that you are allowed to protect." + end + end + + -- Check intersecting areas + for _, area in pairs(self.areas) do + if (area.pos1.x <= pos2.x and area.pos2.x >= pos1.x) and + (area.pos1.y <= pos2.y and area.pos2.y >= pos1.y) and + (area.pos1.z <= pos2.z and area.pos2.z >= pos1.z) then + --Found an area intersecting with the suplied area + if area.owner ~= name then + return false, "The area intersects with a area that you do not own." + end + end + end + + return true, "" +end + +-- Given a area returns a string in the format "name [id]: owner (x1, y1, z1) (x2, y2, z2) -> children" +function areas:toString(area) + local message = area.name.. + "["..area.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 + +-- Returns a area given it's identifier +function areas:getAreaById(id) + for _, area in pairs(self.areas) do + if area.id == id then return area end + end +end + +-- Returns a table index for an area given it's identifier +function areas:getIndexById(id) + for i, area in pairs(self.areas) do + if area.id == id then return i end + end +end + +-- Re-order areas in table by their identifiers +function areas:sort() + for k, area in pairs(self.areas) do + if area.id ~= k then + for _, subarea in pairs(self.areas) do + if subarea.parent == area.id then + subarea.parent = k + end + end + area.id = k + end + end +end + +-- Checks if a player owns an area or a parent of it +function areas:isAreaOwner(id, name) + cur = self:getAreaById(id) + if cur and minetest.check_player_privs(name, {areas=true}) then + return true + end + while cur do + if cur.owner == name then + return true + elseif cur.parent then + cur = self:getAreaById(cur.parent) + else + return false + end + end + return false +end + diff --git a/legacy.lua b/legacy.lua new file mode 100644 index 0000000..9ff7c77 --- /dev/null +++ b/legacy.lua @@ -0,0 +1,145 @@ +-- This file contains functions to convert from +-- the old areas format and other compatability code. + +minetest.register_chatcommand("legacy_load_areas", { + params = "", + description = "Loads, converts, and saves the areas from a legacy save file.", + privs = {areas=true, server=true}, + func = function(name, param) + minetest.chat_send_player(name, "Converting areas...") + local startTime = os.clock() + + err = areas:legacy_load() + if err then + minetest.chat_send_player(name, "Error loading legacy file: "..err) + return + end + minetest.chat_send_player(name, "Legacy file loaded.") + + for k, area in pairs(areas.areas) do + --New position format + areas.areas[k].pos1 = {x=area.x1, y=area.y1, z=area.z1} + areas.areas[k].pos2 = {x=area.x2, y=area.y2, z=area.z2} + + areas.areas[k].x1, areas.areas[k].y1, + areas.areas[k].z1, areas.areas[k].x2, + areas.areas[k].y2, areas.areas[k].z2 = + nil, nil, nil, nil, nil, nil + + --Area positions sorting + areas.areas[k].pos1, areas.areas[k].pos2 = + areas:sortPos(areas.areas[k].pos1, areas.areas[k].pos2) + + --Add name + areas.areas[k].name = "unnamed" + end + minetest.chat_send_player(name, "Table format updated.") + + areas:save() + minetest.chat_send_player(name, "Converted areas saved.") + minetest.chat_send_player(name, "Finished in "..tostring(os.clock() - startTime).."s.") +end}) + +-- The old load function from node_ownership (with minor modifications) +function areas:legacy_load() + local filename = minetest.get_worldpath().."/owners.tbl" + tables, err = loadfile(filename) + if err then + return err + end + + tables = tables() + for idx = 1, #tables do + local tolinkv, tolinki = {}, {} + for i, v in pairs(tables[idx]) do + if type(v) == "table" and tables[v[1]] then + table.insert(tolinkv, {i, tables[v[1]]}) + end + if type(i) == "table" and tables[i[1]] then + table.insert(tolinki, {i, tables[i[1]]}) + end + end + -- link values, first due to possible changes of indices + for _, v in ipairs(tolinkv) do + tables[idx][v[1]] = v[2] + end + -- link indices + for _, v in ipairs(tolinki) do + tables[idx][v[2]], tables[idx][v[1]] = tables[idx][v[1]], nil + end + end + self.areas = tables[1] +end + +-- Returns the name of the first player that owns an area +function areas.getNodeOwnerName(pos) + for _, area in pairs(areas.areas) do + p1, p2 = area.pos1, area.pos2 + if pos.x >= p1.x and pos.x <= p2.x and + pos.y >= p1.y and pos.y <= p2.y and + pos.z >= p1.z and pos.z <= p2.z then + if area.owner ~= nil then + return area.owner + end + end + end + return false +end + +-- Checks if a node is owned by you +function areas.isNodeOwner(pos, name) + if minetest.check_player_privs(name, {areas=true}) then + return true + end + for _, area in pairs(areas.areas) do + p1, p2 = area.pos1, area.pos2 + if pos.x >= p1.x and pos.x <= p2.x and + pos.y >= p1.y and pos.y <= p2.y and + pos.z >= p1.z and pos.z <= p2.z then + if name == area.owner then + return true + end + end + end + return false +end + +IsPlayerNodeOwner = areas.isNodeOwner +GetNodeOwnerName = areas.getNodeOwnerName +HasOwner = areas.hasOwner + +if areas.legacy_table then + owner_defs = {} + setmetatable(owner_defs, { + __index = function(table, key) + local a = rawget(areas.areas, key) + if a then + a.x1 = a.pos1.x + a.y1 = a.pos1.y + a.z1 = a.pos1.z + a.x2 = a.pos2.x + a.y2 = a.pos2.y + a.z2 = a.pos2.z + a.pos1, a.pos2 = nil, nil + end + return a + end, + __newindex = function(table, key, value) + if rawget(areas.areas, key) ~= nil then + local a = value + a.pos1, a.pos2 = {}, {} + a.pos1.x = a.x1 + a.pos1.y = a.y1 + a.pos1.z = a.z1 + a.pos2.x = a.x2 + a.pos2.y = a.y2 + a.pos2.z = a.z2 + a.x1, a.y1, a.z1, a.x2, a.y2, a.z2 + = nil, nil, nil, nil, nil, nil + a.name = a.name or "unnamed" + return rawset(areas.areas, key, a); + end + end + }) +end + diff --git a/pos.lua b/pos.lua new file mode 100644 index 0000000..d89289a --- /dev/null +++ b/pos.lua @@ -0,0 +1,249 @@ + +-- I could depend on WorldEdit for this, but you need to have the 'worldedit' +-- permission to use those commands and you don't have /area_pos{1,2} [x y z|x,y,z] +-- Since this is mostly copied from WorldEdit it is licensed under the AGPL. + +areas.marker1 = {} +areas.marker2 = {} +areas.set_pos = {} +areas.pos1 = {} +areas.pos2 = {} + +minetest.register_chatcommand("select_area", { + params = "", + description = "Select a area by id.", + privs = {}, + func = function(name, param) + local id = tonumber(param) + if not id then + minetest.chat_send_player(name, "Invalid usage, see /help select_area.") + end + + for k, area in pairs(areas.areas) do + if area.id == id then + areas:setPos1(name, area.pos1) + areas:setPos2(name, area.pos2) + minetest.chat_send_player(name, "Area "..tostring(id).." selected.") + return + end + end + minetest.chat_send_player(name, "The area "..tostring(id).." does not exist.") +end}) + +minetest.register_chatcommand("area_pos1", { + params = "[X Y Z|X,Y,Z]", + description = "Set area protection region position 1 to the player's location or the one specified", + privs = {}, + func = function(name, param) + local pos = {} + local found, _, x, y, z = param:find("^(-?%d+)[%s%,]+(-?%d+)[%s%,]+(-?%d+)$") + if found then + pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)} + elseif param == "" then + player = minetest.get_player_by_name(name) + if player then + pos = player:getpos() + else + minetest.chat_send_player(name, "Unable to get position") + return + end + else + minetest.chat_send_player(name, "Invalid usage, see /help no_pos1") + end + pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) + areas:setPos1(name, pos) + minetest.chat_send_player(name, "Area position 1 set to " .. minetest.pos_to_string(pos)) + end, +}) + +minetest.register_chatcommand("area_pos2", { + params = "[X Y Z|X,Y,Z]", + description = "Set area protection region position 2 to the player's location or the one specified", + privs = {}, + func = function(name, param) + local pos = {} + local found, _, x, y, z = param:find("^(-?%d+)[%s%,]+(-?%d+)[%s%,]+(-?%d+)$") + if found then + pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)} + elseif param == "" then + player = minetest.get_player_by_name(name) + if player then + pos = player:getpos() + else + minetest.chat_send_player(name, "Unable to get position") + return + end + else + minetest.chat_send_player(name, "Invalid usage, see /help no_pos2") + end + pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) + areas:setPos2(name, pos) + minetest.chat_send_player(name, "Area position 2 set to " .. minetest.pos_to_string(pos)) + end, +}) + + +minetest.register_chatcommand("area_pos", { + params = "set/set1/set2/get", + description = "Set area protection region, position 1, or position 2 by punching nodes, or display the region", + privs = {}, + func = function(name, param) + if param == "set" then -- Set both area positions + areas.set_pos[name] = "pos1" + minetest.chat_send_player(name, "Select positions by punching two nodes") + elseif param == "set1" then -- Set area position 1 + areas.set_pos[name] = "pos1only" + minetest.chat_send_player(name, "Select position 1 by punching a node") + elseif param == "set2" then -- Set area position 2 + areas.set_pos[name] = "pos2" + minetest.chat_send_player(name, "Select position 2 by punching a node") + elseif param == "get" then -- Display current area positions + if areas.pos1[name] ~= nil then + minetest.chat_send_player(name, "Position 1: " .. minetest.pos_to_string(areas.pos1[name])) + else + minetest.chat_send_player(name, "Position 1 not set") + end + if areas.pos2[name] ~= nil then + minetest.chat_send_player(name, "Position 2: " .. minetest.pos_to_string(areas.pos2[name])) + else + minetest.chat_send_player(name, "Position 2 not set") + end + else + minetest.chat_send_player(name, "Unknown subcommand: " .. param) + end + end, +}) + +function areas:getPos1(playerName) + return areas.pos1[playerName] +end + +function areas:getPos2(playerName) + return areas.pos2[playerName] +end + +function areas:setPos1(playerName, pos) + areas.pos1[playerName] = pos + areas.markPos1(playerName) +end + +function areas:setPos2(playerName, pos) + areas.pos2[playerName] = pos + areas.markPos2(playerName) +end + + +minetest.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if name ~= "" and areas.set_pos[name] ~= nil then --currently setting position + if areas.set_pos[name] == "pos1" then --setting position 1 + areas.pos1[name] = pos + areas.markPos1(name) + areas.set_pos[name] = "pos2" --set position 2 on the next invocation + minetest.chat_send_player(name, "Position 1 set to " .. minetest.pos_to_string(pos)) + elseif areas.set_pos[name] == "pos1only" then --setting position 1 only + areas.pos1[name] = pos + areas.markPos1(name) + areas.set_pos[name] = nil --finished setting positions + minetest.chat_send_player(name, "Position 1 set to " .. minetest.pos_to_string(pos)) + elseif areas.set_pos[name] == "pos2" then --setting position 2 + areas.pos2[name] = pos + areas.markPos2(name) + areas.set_pos[name] = nil --finished setting positions + minetest.chat_send_player(name, "Position 2 set to " .. minetest.pos_to_string(pos)) + end + end +end) + +-- Modifies positions `pos1` and `pos2` so that each component of `pos1` +-- is less than or equal to its corresponding component of `pos2`, +-- returning two new positions +function areas:sortPos(pos1, pos2) + if pos1.x > pos2.x then + pos2.x, pos1.x = pos1.x, pos2.x + end + if pos1.y > pos2.y then + pos2.y, pos1.y = pos1.y, pos2.y + end + if pos1.z > pos2.z then + pos2.z, pos1.z = pos1.z, pos2.z + end + return pos1, pos2 +end + +-- Rounds a position to the nearest integer +function areas:roundPos(pos) + pos.x = math.floor(pos.x+0.5) + pos.y = math.floor(pos.y+0.5) + pos.z = math.floor(pos.z+0.5) + return pos +end + +-- Marks area position 1 +areas.markPos1 = function(name) + local pos = areas.pos1[name] + if areas.marker1[name] ~= nil then -- Marker already exists + areas.marker1[name]:remove() -- Remove marker + areas.marker1[name] = nil + end + if pos ~= nil then -- Add marker + areas.marker1[name] = minetest.add_entity(pos, "areas:pos1") + areas.marker1[name]:get_luaentity().active = true + end +end + +-- Marks area position 2 +areas.markPos2 = function(name) + local pos = areas.pos2[name] + if areas.marker2[name] ~= nil then -- Marker already exists + areas.marker2[name]:remove() -- Remove marker + areas.marker2[name] = nil + end + if pos ~= nil then -- Add marker + areas.marker2[name] = minetest.add_entity(pos, "areas:pos2") + areas.marker2[name]:get_luaentity().active = true + end +end + +minetest.register_entity("areas:pos1", { + initial_properties = { + visual = "cube", + visual_size = {x=1.1, y=1.1}, + textures = {"areas_pos1.png", "areas_pos1.png", + "areas_pos1.png", "areas_pos1.png", + "areas_pos1.png", "areas_pos1.png"}, + collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, + }, + on_step = function(self, dtime) + if self.active == nil then + self.object:remove() + end + end, + on_punch = function(self, hitter) + self.object:remove() + local name = hitter:get_player_name() + areas.marker1[name] = nil + end, +}) + +minetest.register_entity("areas:pos2", { + initial_properties = { + visual = "cube", + visual_size = {x=1.1, y=1.1}, + textures = {"areas_pos2.png", "areas_pos2.png", + "areas_pos2.png", "areas_pos2.png", + "areas_pos2.png", "areas_pos2.png"}, + collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, + }, + on_step = function(self, dtime) + if self.active == nil then + self.object:remove() + end + end, + on_punch = function(self, hitter) + self.object:remove() + local name = hitter:get_player_name() + areas.marker2[name] = nil + end, +}) + diff --git a/settings.lua b/settings.lua new file mode 100644 index 0000000..ba579ff --- /dev/null +++ b/settings.lua @@ -0,0 +1,35 @@ +local worldpath = minetest.get_worldpath() + +local function setting_getbool_default(setting, default) + local value = minetest.setting_getbool(setting) + if value == nil then + value = default + end + return value +end + +areas.filename = + minetest.setting_get("areas.filename") or worldpath.."/areas.dat" + +-- Allow players with a privilege create their own areas within the max_size and number +areas.self_protection = + setting_getbool_default("areas.self_protection", false) +areas.self_protection_privilege = + minetest.setting_get("areas.self_protection_privilege") or "interact" +areas.self_protection_max_size = + minetest.setting_get_pos("areas.self_protection_max_size") or {x=50, y=100, z=50} +areas.self_protection_max_areas = + tonumber(minetest.setting_get("areas.self_protection_max_areas")) or 3 + +-- Register compatability functions for node_ownership. +-- legacy_table (owner_defs) compatibility is untested +-- and can not be used if security_safe_mod_api is on. +areas.legacy_table = + setting_getbool_default("areas.legacy_table", false) + +-- Prevent players from punching nodes in a protected area. +-- Usefull for things like delayers, usualy annoying and +-- prevents usage of things like buttons. +areas.protect_punches = + setting_getbool_default("areas.protect_punches", false) + diff --git a/textures/areas_pos1.png b/textures/areas_pos1.png new file mode 100644 index 0000000..4c304aa Binary files /dev/null and b/textures/areas_pos1.png differ diff --git a/textures/areas_pos2.png b/textures/areas_pos2.png new file mode 100644 index 0000000..1502f16 Binary files /dev/null and b/textures/areas_pos2.png differ