feat: adPrices — toppris fra årets prisforløb og pris-% pr. salgssted
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
-- AD Prices
|
||||
-- Prisregel for udkørsel: hvor tæt er salgsstedets pris på årets toppris for varen?
|
||||
-- Toppris = stationens grundpris lige nu × (årets højeste periodefaktor ÷ nuværende periodes
|
||||
-- faktor) — dvs. det Priser-menuen ville vise som årets bedste måned for netop den station.
|
||||
-- Effektiv pris (inkl. prisfald efter salg og stor efterspørgsel) ÷ toppris = pris-%;
|
||||
-- stor efterspørgsel giver > 100 % og opfylder dermed altid reglen.
|
||||
-- De rene funktioner øverst har ingen spil-API; getQuote er spil-adapteren.
|
||||
|
||||
ADPrices = {}
|
||||
|
||||
ADPrices.LOG_PREFIX = "ADSmartPickup: "
|
||||
|
||||
function ADPrices.maxFactor(factors)
|
||||
local best = nil
|
||||
for _, factor in pairs(factors or {}) do
|
||||
if type(factor) == "number" and (best == nil or factor > best) then
|
||||
best = factor
|
||||
end
|
||||
end
|
||||
return best or 1
|
||||
end
|
||||
|
||||
function ADPrices.topPrice(baseNow, factorNow, maxFactor)
|
||||
if baseNow == nil or baseNow <= 0 then
|
||||
return 0
|
||||
end
|
||||
if factorNow == nil or factorNow <= 0 or maxFactor == nil or maxFactor <= 0 then
|
||||
return baseNow
|
||||
end
|
||||
return baseNow * maxFactor / factorNow
|
||||
end
|
||||
|
||||
function ADPrices.percentOfTop(price, top)
|
||||
if top == nil or top <= 0 or price == nil then
|
||||
return 0
|
||||
end
|
||||
return math.floor(100 * price / top + 0.5)
|
||||
end
|
||||
|
||||
-- Rigtige salgssteder: SellingStation der sælger (fabrikkers aflæsningsstationer er
|
||||
-- også SellingStation-objekter, men med isSellingPoint = false) og vises i Priser-menuen.
|
||||
function ADPrices.isSellingStation(station)
|
||||
return station ~= nil and station.isa ~= nil and SellingStation ~= nil and station:isa(SellingStation) == true
|
||||
and station.isSellingPoint == true and not station.hideFromPricesMenu
|
||||
end
|
||||
|
||||
local function getCurrentPeriod()
|
||||
local environment = g_currentMission ~= nil and g_currentMission.environment or nil
|
||||
return environment ~= nil and environment.currentPeriod or nil
|
||||
end
|
||||
|
||||
local function getDifficultyMultiplier()
|
||||
local economy = g_currentMission ~= nil and g_currentMission.economyManager or nil
|
||||
if economy ~= nil and economy.getPriceMultiplier ~= nil then
|
||||
local ok, value = pcall(economy.getPriceMultiplier, economy)
|
||||
if ok and type(value) == "number" then
|
||||
return value
|
||||
end
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
-- {price, top, percent} for varen ved stationen; nil hvis stationen ikke kan prissætte den.
|
||||
function ADPrices.getQuote(station, fillType)
|
||||
if station == nil or station.getEffectiveFillTypePrice == nil then
|
||||
return nil
|
||||
end
|
||||
local ok, effective = pcall(station.getEffectiveFillTypePrice, station, fillType)
|
||||
if not ok or type(effective) ~= "number" then
|
||||
return nil
|
||||
end
|
||||
local fillTypeDesc = g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeByIndex ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
|
||||
local factors = fillTypeDesc ~= nil and fillTypeDesc.economy ~= nil and fillTypeDesc.economy.factors or nil
|
||||
local period = getCurrentPeriod()
|
||||
local factorNow = factors ~= nil and period ~= nil and tonumber(factors[period]) or 1
|
||||
local baseNow = station.fillTypePrices ~= nil and tonumber(station.fillTypePrices[fillType]) or nil
|
||||
if baseNow == nil or baseNow <= 0 then
|
||||
baseNow = (fillTypeDesc ~= nil and tonumber(fillTypeDesc.pricePerLiter) or 0) * factorNow * getDifficultyMultiplier()
|
||||
end
|
||||
local top = ADPrices.topPrice(baseNow, factorNow, ADPrices.maxFactor(factors))
|
||||
return {price = effective, top = top, percent = ADPrices.percentOfTop(effective, top)}
|
||||
end
|
||||
|
||||
-- Højeste pris-%, ved lighed højeste pris. nil ved tom liste.
|
||||
function ADPrices.bestQuote(quotes)
|
||||
local best = nil
|
||||
for _, quote in ipairs(quotes or {}) do
|
||||
if best == nil or quote.percent > best.percent or (quote.percent == best.percent and quote.price > best.price) then
|
||||
best = quote
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
-- Kør: luajit tests/test_adPrices.lua (fra repo-roden)
|
||||
-- Prisregel: toppris fra årets prisforløb, pris-% pr. salgssted, bedste tilbud.
|
||||
|
||||
dofile("FS25_ADSmartPickup/adPrices.lua")
|
||||
|
||||
local failures = 0
|
||||
local function check(name, actual, expected)
|
||||
if actual == expected then
|
||||
print("OK " .. name)
|
||||
else
|
||||
failures = failures + 1
|
||||
print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual)))
|
||||
end
|
||||
end
|
||||
|
||||
-- P1: højeste faktor
|
||||
check("P1 maxFactor", ADPrices.maxFactor({EARLY_SPRING = 0.9, LATE_AUTUMN = 1.2, MID_SUMMER = 1.0}), 1.2)
|
||||
check("P1 uden faktorer = 1", ADPrices.maxFactor(nil), 1)
|
||||
|
||||
-- P2: toppris = grundpris nu × (maks ÷ nu)
|
||||
check("P2 topPrice", ADPrices.topPrice(500, 1.0, 1.25), 625)
|
||||
check("P2 factorNow 0 -> grundpris", ADPrices.topPrice(500, 0, 1.25), 500)
|
||||
check("P2 grundpris 0 -> 0", ADPrices.topPrice(0, 1, 1.25), 0)
|
||||
|
||||
-- P3: procent, afrundet; stor efterspørgsel > 100
|
||||
check("P3 75 %", ADPrices.percentOfTop(470, 625), 75)
|
||||
check("P3 112 %", ADPrices.percentOfTop(700, 625), 112)
|
||||
check("P3 top 0 -> 0", ADPrices.percentOfTop(1, 0), 0)
|
||||
|
||||
-- P4: spil-adapter med stationens grundpris
|
||||
SellingStation = {}
|
||||
local SUGAR = 5
|
||||
g_fillTypeManager = {getFillTypeByIndex = function(_, index)
|
||||
if index == SUGAR then return {economy = {factors = {A = 1.0, B = 1.25}}, pricePerLiter = 400} end
|
||||
end}
|
||||
g_currentMission = {environment = {currentPeriod = "A"}, economyManager = {getPriceMultiplier = function() return 1.0 end}}
|
||||
local station = {
|
||||
fillTypePrices = {[SUGAR] = 500},
|
||||
getEffectiveFillTypePrice = function(_, fillType) return 470 end,
|
||||
isa = function(_, class) return class == SellingStation end,
|
||||
isSellingPoint = true,
|
||||
}
|
||||
local quote = ADPrices.getQuote(station, SUGAR)
|
||||
check("P4 pris", quote.price, 470)
|
||||
check("P4 top", quote.top, 625)
|
||||
check("P4 procent", quote.percent, 75)
|
||||
check("P4 er salgssted", ADPrices.isSellingStation(station), true)
|
||||
check("P4 skjult = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = true, hideFromPricesMenu = true}), false)
|
||||
check("P4 fabrik-station = ikke salgssted", ADPrices.isSellingStation({isa = function() return true end, isSellingPoint = false}), false)
|
||||
|
||||
-- P5: uden stationens grundpris: pricePerLiter × faktor nu × sværhedsgrad
|
||||
local bare = {getEffectiveFillTypePrice = function() return 470 end}
|
||||
local quote2 = ADPrices.getQuote(bare, SUGAR)
|
||||
check("P5 top fra pricePerLiter", quote2.top, 500)
|
||||
check("P5 procent", quote2.percent, 94)
|
||||
check("P5 fejlende station -> nil", ADPrices.getQuote({getEffectiveFillTypePrice = function() error("x") end}, SUGAR), nil)
|
||||
|
||||
-- P6: bedste tilbud = højeste procent, dernæst pris
|
||||
local best = ADPrices.bestQuote({{percent = 80, price = 1}, {percent = 92, price = 2}, {percent = 92, price = 3}})
|
||||
check("P6 højeste procent", best.percent, 92)
|
||||
check("P6 ved lighed højeste pris", best.price, 3)
|
||||
check("P6 tom -> nil", ADPrices.bestQuote({}), nil)
|
||||
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
Reference in New Issue
Block a user