Add land valuation

master
rubenwardy 2019-07-21 12:48:49 +01:00
parent 3c962d0f47
commit c577db0481
5 changed files with 226 additions and 1 deletions

View File

@ -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 = {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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