diff --git a/.luacheckrc b/.luacheckrc index 3534326..eae6e72 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -4,6 +4,7 @@ allow_defined_top = true exclude_files = { "mods/mtg", "mods/libs/lib_chatcmdbuilder", + "mods/libs/lib_utils/vector.lua", "mods/mechanics", "mods/areas", "mods/craftguide", @@ -13,7 +14,8 @@ globals = { "minetest", "company", "areas", "sfinv", "shop", - ChatCmdBuilder = {fields = {"types"}} + ChatCmdBuilder = {fields = {"types"}}, + vector = { fields = {"sqdist"}}, } read_globals = { diff --git a/mods/capitalism/land/api.lua b/mods/capitalism/land/api.lua index fe67bde..97c9cbc 100644 --- a/mods/capitalism/land/api.lua +++ b/mods/capitalism/land/api.lua @@ -136,6 +136,26 @@ function land.get_by_area_id(id) end +--- Get zone for a plot +-- +-- Gets the base zone for a particular area +function land.get_zone(area) + assert(type(area) == "table") + + local zone = area + while zone do + local next = areas.areas[zone.parent] + if next and next.land_type ~= area.land_type then + return zone + end + + zone = next + end + + return nil +end + + --- Transfers a plot between two owners, after checking relevant permissions. -- -- @int id @@ -259,6 +279,33 @@ function land.set_price(area, pname, price) end +--- Calculates a recommended value for the lot, based on the position and more. +-- +-- @tparam table area +-- @treturn number value +function land.calc_value(area) + assert(type(area) == "table") + + -- Calculate a weighted metric based on volume of area + local pos1, pos2 = vector.sort(area.pos1, area.pos2) + local size = vector.subtract(pos2, pos1) + local value = 200 * (size.x * size.z * 0.33 * size.y) + + -- Find distance to zone center + local zone = land.get_zone(area) + if zone then + local zone_center = zone.spawn_point or zone.pos1 + local area_center = vector.divide(vector.add(area.pos1, area.pos2), 2) + local dist = vector.sqdist(zone_center, area_center) + + value = value + (zone.land_value or 1000000) * 1 / (dist*0.1 + 2) + end + + area.land_value = value + return value +end + + --- Whether a user can buy a plot -- -- @tparam table area diff --git a/mods/capitalism/land/tests/api_spec.lua b/mods/capitalism/land/tests/api_spec.lua index ab6fc99..36d3495 100644 --- a/mods/capitalism/land/tests/api_spec.lua +++ b/mods/capitalism/land/tests/api_spec.lua @@ -7,6 +7,9 @@ _G.audit = function() return { post = function() end } end +_G.vector = {} + +require("libs/lib_utils/vector") require("capitalism/land/api") _G.company = {} @@ -153,4 +156,21 @@ describe("land", function() assert.is_true(suc) assert.equals(area.land_sale, 100) end) + + it("calc_value", function() + local function v(x, y, z) + return { x=x, y=y, z=z } + end + + areas.areas = { + { owner="c:government", id=1, name="Root", pos1=v(0,0,0), pos2=v(100,100,100), parent=nil }, + { owner="c:government", id=2, name="City", pos1=v(0,0,0), pos2=v(100,100,100), parent=1 }, + { owner="c:government", id=3, name="Commercial", land_type="commercial", pos1=v(10,10,10), pos2=v(50,50,50), + parent=2, land_value=1000000 }, + { owner="c:test", id=4, name="Mall", land_type="commercial", pos1=v(10,10,10), pos2=v(30,30,30), parent=3 }, + { owner="c:test", id=5, name="Shop", land_type="commercial", pos1=v(10,10,10), pos2=v(20,13,20), parent=4 }, + } + + assert.equals(land.calc_value(areas.areas[5]), 200*(10*10*3*0.33) + 1000000 * 1 / (52.25*0.1 + 2)) + end) end) diff --git a/mods/libs/lib_utils/init.lua b/mods/libs/lib_utils/init.lua index 87cd60e..dc1c3c5 100644 --- a/mods/libs/lib_utils/init.lua +++ b/mods/libs/lib_utils/init.lua @@ -57,3 +57,10 @@ function lib_utils.make_saveload(tab, storage, itemarraykey, registername, class tab.dirty = false end end + +function vector.sqdist(a, b) + local x = a.x - b.x + local y = a.y - b.y + local z = a.z - b.z + return x*x + y*y + z*z +end diff --git a/mods/libs/lib_utils/vector.lua b/mods/libs/lib_utils/vector.lua new file mode 100644 index 0000000..a8fe1ca --- /dev/null +++ b/mods/libs/lib_utils/vector.lua @@ -0,0 +1,149 @@ +vector = {} + +function vector.new(a, b, c) + if type(a) == "table" then + assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()") + return {x=a.x, y=a.y, z=a.z} + elseif a then + assert(b and c, "Invalid arguments for vector.new()") + return {x=a, y=b, z=c} + end + return {x=0, y=0, z=0} +end + +function vector.equals(a, b) + return a.x == b.x and + a.y == b.y and + a.z == b.z +end + +function vector.length(v) + return math.hypot(v.x, math.hypot(v.y, v.z)) +end + +function vector.normalize(v) + local len = vector.length(v) + if len == 0 then + return {x=0, y=0, z=0} + else + return vector.divide(v, len) + end +end + +function vector.floor(v) + return { + x = math.floor(v.x), + y = math.floor(v.y), + z = math.floor(v.z) + } +end + +function vector.round(v) + return { + x = math.floor(v.x + 0.5), + y = math.floor(v.y + 0.5), + z = math.floor(v.z + 0.5) + } +end + +function vector.apply(v, func) + return { + x = func(v.x), + y = func(v.y), + z = func(v.z) + } +end + +function vector.distance(a, b) + local x = a.x - b.x + local y = a.y - b.y + local z = a.z - b.z + return math.hypot(x, math.hypot(y, z)) +end + +function vector.direction(pos1, pos2) + return vector.normalize({ + x = pos2.x - pos1.x, + y = pos2.y - pos1.y, + z = pos2.z - pos1.z + }) +end + +function vector.angle(a, b) + local dotp = vector.dot(a, b) + local cp = vector.cross(a, b) + local crossplen = vector.length(cp) + return math.atan2(crossplen, dotp) +end + +function vector.dot(a, b) + return a.x * b.x + a.y * b.y + a.z * b.z +end + +function vector.cross(a, b) + return { + x = a.y * b.z - a.z * b.y, + y = a.z * b.x - a.x * b.z, + z = a.x * b.y - a.y * b.x + } +end + +function vector.add(a, b) + if type(b) == "table" then + return {x = a.x + b.x, + y = a.y + b.y, + z = a.z + b.z} + else + return {x = a.x + b, + y = a.y + b, + z = a.z + b} + end +end + +function vector.subtract(a, b) + if type(b) == "table" then + return {x = a.x - b.x, + y = a.y - b.y, + z = a.z - b.z} + else + return {x = a.x - b, + y = a.y - b, + z = a.z - b} + end +end + +function vector.multiply(a, b) + if type(b) == "table" then + return {x = a.x * b.x, + y = a.y * b.y, + z = a.z * b.z} + else + return {x = a.x * b, + y = a.y * b, + z = a.z * b} + end +end + +function vector.divide(a, b) + if type(b) == "table" then + return {x = a.x / b.x, + y = a.y / b.y, + z = a.z / b.z} + else + return {x = a.x / b, + y = a.y / b, + z = a.z / b} + end +end + +function vector.sort(a, b) + return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)}, + {x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)} +end + +function vector.sqdist(a, b) + local x = a.x - b.x + local y = a.y - b.y + local z = a.z - b.z + return x*x + y*y + z*z +end