feat(lagre): vareoversigt med nøgletal, 'kræver handling' og fyldningsbjælker — klik på en vare folder den ud til bygningerne; bygningstabellen bag 'Vis: Varer | Bygninger' (v1.52.0.0)
This commit is contained in:
@@ -108,3 +108,178 @@ function ADStorageStats.filterKind(inventory, kind)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- ---- vareoversigt (v1.52): én række pr. vare, der folder sig ud til bygningerne ----
|
||||
|
||||
-- Tærskler for farverne: døgn til tom (gårdens netto), fyldning i % (produceret vare / stald-trug).
|
||||
ADStorageStats.DAYS_RED, ADStorageStats.DAYS_YELLOW = 1, 3
|
||||
ADStorageStats.FULL_RED, ADStorageStats.FULL_YELLOW = 95, 85
|
||||
ADStorageStats.BARN_LOW_RED, ADStorageStats.BARN_LOW_YELLOW = 10, 25
|
||||
-- "Sælg nu": prisen er mindst 90 % af årets top, og partiet er værd at køre med
|
||||
ADStorageStats.SELL_RATIO, ADStorageStats.SELL_MIN_VALUE = 0.9, 10000
|
||||
|
||||
local function percentOf(level, capacity)
|
||||
return (capacity or 0) > 0 and math.floor(100 * (level or 0) / capacity + 0.5) or nil
|
||||
end
|
||||
|
||||
-- inventory (fanens bygninger) -> { {fillType, title, level, capacity, percent, count, isOutput,
|
||||
-- buildings = {{name, placeable, level, capacity, percent, count, slots, isOutput}}} } — varer efter titel,
|
||||
-- bygninger med mest først. count (paller) summeres kun når alle bygninger melder paller.
|
||||
function ADStorageStats.groupByFillType(inventory, titleOf)
|
||||
local byType, groups = {}, {}
|
||||
for _, building in ipairs(inventory or {}) do
|
||||
for _, item in ipairs(building.entries or {}) do
|
||||
local group = byType[item.fillType]
|
||||
if group == nil then
|
||||
group = {fillType = item.fillType, title = tostring(titleOf(item.fillType) or item.fillType),
|
||||
level = 0, capacity = 0, count = 0, isOutput = false, buildings = {}}
|
||||
byType[item.fillType] = group
|
||||
table.insert(groups, group)
|
||||
end
|
||||
group.level = group.level + (item.level or 0)
|
||||
group.capacity = group.capacity + (item.capacity or 0)
|
||||
group.count = (group.count ~= nil and item.count ~= nil) and group.count + item.count or nil
|
||||
group.isOutput = group.isOutput or item.isOutput == true
|
||||
-- skur uden pladsgrænse har kapacitet 0: så er varens samlede procent ukendt
|
||||
if (item.capacity or 0) <= 0 and (item.level or 0) > 0 then group.unbounded = true end
|
||||
local percent = percentOf(item.level, item.capacity)
|
||||
if item.count ~= nil and (item.slots or 0) > 0 then percent = percentOf(item.count, item.slots) end
|
||||
table.insert(group.buildings, {name = building.name, placeable = building.placeable, level = item.level or 0,
|
||||
capacity = item.capacity or 0, percent = percent, count = item.count, slots = item.slots, isOutput = item.isOutput == true})
|
||||
end
|
||||
end
|
||||
for _, group in ipairs(groups) do
|
||||
group.percent = not group.unbounded and percentOf(group.level, group.capacity) or nil
|
||||
table.sort(group.buildings, function(a, b)
|
||||
if a.level ~= b.level then return a.level > b.level end
|
||||
return tostring(a.name) < tostring(b.name)
|
||||
end)
|
||||
end
|
||||
table.sort(groups, function(a, b)
|
||||
if a.title ~= b.title then return a.title < b.title end
|
||||
return a.fillType < b.fillType
|
||||
end)
|
||||
return groups
|
||||
end
|
||||
|
||||
-- inventory -> {[fillType] = kapacitet i liter} for hele gården
|
||||
function ADStorageStats.farmCapacities(inventory)
|
||||
local totals = {}
|
||||
for _, building in ipairs(inventory or {}) do
|
||||
for _, entry in ipairs(building.entries or {}) do
|
||||
totals[entry.fillType] = (totals[entry.fillType] or 0) + (entry.capacity or 0)
|
||||
end
|
||||
end
|
||||
return totals
|
||||
end
|
||||
|
||||
function ADStorageStats.emptyLevel(days)
|
||||
if days == nil then return nil end
|
||||
if days < ADStorageStats.DAYS_RED then return "red" end
|
||||
if days < ADStorageStats.DAYS_YELLOW then return "yellow" end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Fuldt er kun et problem for noget der bliver produceret (fyldes løbende); en fuld lagersilo er bare lager.
|
||||
function ADStorageStats.fullLevel(percent, producing)
|
||||
if not producing or percent == nil then return nil end
|
||||
if percent >= ADStorageStats.FULL_RED then return "red" end
|
||||
if percent >= ADStorageStats.FULL_YELLOW then return "yellow" end
|
||||
return nil
|
||||
end
|
||||
|
||||
local SEVERITY_RANK = {red = 2, yellow = 1}
|
||||
|
||||
function ADStorageStats.worst(a, b)
|
||||
return (SEVERITY_RANK[a] or 0) >= (SEVERITY_RANK[b] or 0) and a or b
|
||||
end
|
||||
|
||||
-- Én bygnings bjælke: stald-trug der tømmes (ikke mælk/gylle) er rødt når næsten tomt; produceret
|
||||
-- vare/fabriksoutput er rødt når næsten fuldt.
|
||||
function ADStorageStats.buildingLevel(building, kind, isStableOutput)
|
||||
local percent = building.percent
|
||||
if percent == nil then return nil end
|
||||
if kind == "husbandry" and not isStableOutput then
|
||||
if percent <= ADStorageStats.BARN_LOW_RED then return "red" end
|
||||
if percent <= ADStorageStats.BARN_LOW_YELLOW then return "yellow" end
|
||||
return nil
|
||||
end
|
||||
return ADStorageStats.fullLevel(percent, building.isOutput == true or isStableOutput)
|
||||
end
|
||||
|
||||
-- group fra groupByFillType; farm = {level, capacity} for varen på hele gården (nil = kun fanens tal);
|
||||
-- rate = {consumption, production} pr. døgn; quote = {price, top} pr. liter.
|
||||
function ADStorageStats.groupStats(group, farm, rate, quote)
|
||||
local farmLevel = farm ~= nil and farm.level or group.level
|
||||
local farmCapacity = farm ~= nil and farm.capacity or group.capacity
|
||||
local consumption = rate ~= nil and rate.consumption or 0
|
||||
local production = rate ~= nil and rate.production or 0
|
||||
local net = production - consumption
|
||||
local stat = {}
|
||||
for key, value in pairs(group) do stat[key] = value end
|
||||
stat.farmLevel, stat.farmCapacity = farmLevel, farmCapacity
|
||||
stat.consumption, stat.production, stat.net = consumption, production, net
|
||||
stat.days = consumption > production and farmLevel / (consumption - production) or nil
|
||||
stat.daysToFull = net > 0 and (farmCapacity or 0) > 0 and math.max(0, farmCapacity - farmLevel) / net or nil
|
||||
stat.producing = group.isOutput == true or net > 0
|
||||
if quote ~= nil and quote.price ~= nil then
|
||||
stat.valueNow = math.floor(group.level * quote.price + 0.5)
|
||||
stat.valueTop = math.floor(group.level * (quote.top or quote.price) + 0.5)
|
||||
stat.priceRatio = (quote.top or 0) > 0 and quote.price / quote.top or nil
|
||||
end
|
||||
stat.emptyLevel = ADStorageStats.emptyLevel(stat.days)
|
||||
stat.fullLevel = ADStorageStats.fullLevel(group.percent, stat.producing)
|
||||
stat.severity = ADStorageStats.worst(stat.emptyLevel, stat.fullLevel)
|
||||
return stat
|
||||
end
|
||||
|
||||
-- Rangering: rød tom > rød fuld > gul tom > gul fuld > sælg nu (største beløb først).
|
||||
-- Kortest tid til tom / mest fyldt vinder inden for samme trin.
|
||||
local function attentionEntries(stat)
|
||||
local entries = {}
|
||||
if stat.emptyLevel ~= nil then
|
||||
local base = stat.emptyLevel == "red" and 4000 or 2000
|
||||
table.insert(entries, {kind = "empty", level = stat.emptyLevel, stat = stat, score = base - math.min(999, (stat.days or 0) * 100)})
|
||||
end
|
||||
if stat.fullLevel ~= nil then
|
||||
local base = stat.fullLevel == "red" and 3000 or 1000
|
||||
table.insert(entries, {kind = "full", level = stat.fullLevel, stat = stat, score = base + (stat.percent or 0)})
|
||||
end
|
||||
-- sælg kun det gården ikke selv æder af
|
||||
if stat.priceRatio ~= nil and stat.priceRatio >= ADStorageStats.SELL_RATIO and (stat.valueNow or 0) >= ADStorageStats.SELL_MIN_VALUE
|
||||
and (stat.net or 0) >= 0 then
|
||||
table.insert(entries, {kind = "sell", level = "green", stat = stat, score = math.min(999, stat.valueNow / 1e6)})
|
||||
end
|
||||
return entries
|
||||
end
|
||||
|
||||
function ADStorageStats.attentionList(stats, max)
|
||||
local list = {}
|
||||
for _, stat in ipairs(stats or {}) do
|
||||
for _, entry in ipairs(attentionEntries(stat)) do table.insert(list, entry) end
|
||||
end
|
||||
table.sort(list, function(a, b)
|
||||
if a.score ~= b.score then return a.score > b.score end
|
||||
return tostring(a.stat.title) < tostring(b.stat.title)
|
||||
end)
|
||||
local result = {}
|
||||
for index = 1, math.min(#list, max or #list) do result[index] = list[index] end
|
||||
return result
|
||||
end
|
||||
|
||||
function ADStorageStats.kpis(stats)
|
||||
local kpi = {valueNow = 0, gain = 0, emptyCount = 0, fullCount = 0}
|
||||
for _, stat in ipairs(stats or {}) do
|
||||
kpi.valueNow = kpi.valueNow + (stat.valueNow or 0)
|
||||
kpi.gain = kpi.gain + math.max(0, (stat.valueTop or 0) - (stat.valueNow or 0))
|
||||
if stat.emptyLevel ~= nil then kpi.emptyCount = kpi.emptyCount + 1 end
|
||||
if stat.fullLevel ~= nil then kpi.fullCount = kpi.fullCount + 1 end
|
||||
end
|
||||
return kpi
|
||||
end
|
||||
|
||||
-- procent -> bjælkens brøk i [0,1]
|
||||
function ADStorageStats.fraction(percent)
|
||||
if percent == nil then return 0 end
|
||||
return math.max(0, math.min(1, percent / 100))
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user