From 4ae050a3ae13d5fc7a01b72fe2576508916e5145 Mon Sep 17 00:00:00 2001 From: ShadowNinja Date: Mon, 2 Sep 2013 19:16:14 -0400 Subject: [PATCH] Initial commit --- .gitignore | 2 + api.lua | 51 +++++++ chatcommands.lua | 302 ++++++++++++++++++++++++++++++++++++++++ depends.txt | 0 init.lua | 31 +++++ interact.lua | 39 ++++++ internal.lua | 193 +++++++++++++++++++++++++ legacy.lua | 145 +++++++++++++++++++ pos.lua | 249 +++++++++++++++++++++++++++++++++ settings.lua | 35 +++++ textures/areas_pos1.png | Bin 0 -> 142 bytes textures/areas_pos2.png | Bin 0 -> 157 bytes 12 files changed, 1047 insertions(+) create mode 100644 .gitignore create mode 100644 api.lua create mode 100644 chatcommands.lua create mode 100644 depends.txt create mode 100644 init.lua create mode 100644 interact.lua create mode 100644 internal.lua create mode 100644 legacy.lua create mode 100644 pos.lua create mode 100644 settings.lua create mode 100644 textures/areas_pos1.png create mode 100644 textures/areas_pos2.png 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 0000000000000000000000000000000000000000..4c304aa88d80420e8f4f005d7f0030234210d5ed GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9F5hd`K7RKu$QDCpqn z;uvCaIyogF;m3Ig#RW$i{`*Y1b