feat(lagre): vælg bygning -> statistik pr. vare: værdi nu/ved toppris, gårdens beholdning i døgn, forbrug og produktion pr. døgn (v1.15.0.0)
This commit is contained in:
@@ -624,6 +624,92 @@ function ADSources.getFarmDemand(adEnv, fillType, farmId, excludedPlaceables)
|
||||
return {liters = total, consumers = consumers}
|
||||
end
|
||||
|
||||
-- ---- forbrug og produktion pr. døgn (fanen Lagre) ----
|
||||
|
||||
local function addRate(rates, fillType, field, liters)
|
||||
if fillType == nil or liters == nil or liters <= 0 then return end
|
||||
rates[fillType] = rates[fillType] or {consumption = 0, production = 0}
|
||||
rates[fillType][field] = rates[fillType][field] + liters
|
||||
end
|
||||
|
||||
local function addFactoryRates(rates, placeable)
|
||||
local productionPoint = getProductionPoint(placeable)
|
||||
for _, production in ipairs(productionPoint ~= nil and productionPoint.productions or {}) do
|
||||
if isProductionWanted(productionPoint, production) then
|
||||
local perDay = (production.cyclesPerMinute or ((production.cyclesPerHour or 0) / 60)) * 1440
|
||||
for _, input in ipairs(production.inputs or {}) do addRate(rates, input.type, "consumption", (input.amount or 0) * perDay) end
|
||||
for _, output in ipairs(production.outputs or {}) do addRate(rates, output.type, "production", (output.amount or 0) * perDay) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function getFoodWeights(placeable)
|
||||
local foodSpec = placeable.spec_husbandryFood
|
||||
local system = g_currentMission ~= nil and g_currentMission.animalFoodSystem or nil
|
||||
local weights = {}
|
||||
local food = system ~= nil and system.getAnimalFood ~= nil and foodSpec ~= nil and system:getAnimalFood(foodSpec.animalTypeIndex) or nil
|
||||
for _, group in pairs(food ~= nil and food.groups or {}) do
|
||||
local weight = tonumber(group.productionWeight) or tonumber(group.eatWeight) or 1
|
||||
for _, fillType in pairs(group.fillTypes or {}) do weights[fillType] = math.max(weights[fillType] or 0, weight) end
|
||||
end
|
||||
return weights
|
||||
end
|
||||
|
||||
local function addHusbandryRates(rates, placeable)
|
||||
local food, straw, water = placeable.spec_husbandryFood, placeable.spec_husbandryStraw, placeable.spec_husbandryWater
|
||||
local byName = function(name) return g_fillTypeManager ~= nil and g_fillTypeManager.getFillTypeIndexByName ~= nil and g_fillTypeManager:getFillTypeIndexByName(name) or nil end
|
||||
local nameToIndex = function(name)
|
||||
local index = byName(name)
|
||||
if index == nil and g_fillTypeManager ~= nil then
|
||||
for i = 1, 256 do if g_fillTypeManager:getFillTypeNameByIndex(i) == name then return i end end
|
||||
end
|
||||
return index
|
||||
end
|
||||
if food ~= nil and (food.litersPerHour or 0) > 0 then
|
||||
for fillType, liters in pairs(ADStorageStats.splitTroughConsumption(food.litersPerHour * 24, food.fillLevels, getFoodWeights(placeable))) do
|
||||
addRate(rates, fillType, "consumption", liters)
|
||||
end
|
||||
end
|
||||
if straw ~= nil and (straw.inputLitersPerHour or 0) > 0 then
|
||||
addRate(rates, nameToIndex("STRAW"), "consumption", straw.inputLitersPerHour * 24)
|
||||
end
|
||||
if water ~= nil and not water.automaticWaterSupply and (water.litersPerHour or 0) > 0 then
|
||||
addRate(rates, nameToIndex("WATER"), "consumption", water.litersPerHour * 24)
|
||||
end
|
||||
end
|
||||
|
||||
-- {[fillType] = {consumption, production}} i liter pr. døgn for gårdens fabrikker og stalde.
|
||||
function ADSources.getFarmRates(adEnv, farmId)
|
||||
local placeables = g_currentMission ~= nil and g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {}
|
||||
local rates = {}
|
||||
for _, placeable in pairs(placeables) do
|
||||
if isOwnedBy(placeable, farmId) then
|
||||
if getProductionPoint(placeable) ~= nil then
|
||||
addFactoryRates(rates, placeable)
|
||||
elseif placeable.getHusbandryFillLevel ~= nil then
|
||||
addHusbandryRates(rates, placeable)
|
||||
end
|
||||
end
|
||||
end
|
||||
return rates
|
||||
end
|
||||
|
||||
-- Bedste pris lige nu blandt alle spillets salgssteder der tager varen (uanset markør/vejnet):
|
||||
-- {price, top} pr. liter; top = prisen ved årets højeste periode. nil når ingen tager varen.
|
||||
function ADSources.getMarketQuote(fillType)
|
||||
local storage = g_currentMission ~= nil and g_currentMission.storageSystem or nil
|
||||
local best = nil
|
||||
for _, station in pairs(storage ~= nil and storage:getUnloadingStations() or {}) do
|
||||
if ADPrices.isSellingStation(station) and ADSources.stationAccepts(station, fillType) then
|
||||
local quote = ADPrices.getQuote(station, fillType)
|
||||
if quote ~= nil and (best == nil or quote.price > best.price) then
|
||||
best = {price = quote.price, top = quote.top}
|
||||
end
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
|
||||
-- ---- inventar: alt gården har liggende, pr. bygning ----
|
||||
|
||||
local function addEntry(entries, fillType, level, capacity, extra)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
-- AD Storage Stats
|
||||
-- Rene beregninger (ingen spil-API) til fanen Lagre: pr. vare i en bygning fyldning, værdi nu og
|
||||
-- ved årets toppris, gårdens samlede beholdning og hvor mange døgn den rækker ved gårdens forbrug.
|
||||
|
||||
ADStorageStats = {}
|
||||
|
||||
-- item = {fillType, level, capacity}; farmTotal = liter af varen på hele gården;
|
||||
-- rate = {consumption, production} liter pr. døgn (eller nil); quote = {price, top} pr. liter (eller nil).
|
||||
function ADStorageStats.itemStats(item, farmTotal, rate, quote)
|
||||
local level = item.level or 0
|
||||
local capacity = item.capacity or 0
|
||||
local consumption = rate ~= nil and rate.consumption or 0
|
||||
local production = rate ~= nil and rate.production or 0
|
||||
return {
|
||||
fillType = item.fillType,
|
||||
level = level,
|
||||
capacity = capacity,
|
||||
percent = capacity > 0 and math.floor(100 * level / capacity + 0.5) or nil,
|
||||
valueNow = quote ~= nil and quote.price ~= nil and math.floor(level * quote.price + 0.5) or nil,
|
||||
valueTop = quote ~= nil and quote.top ~= nil and math.floor(level * quote.top + 0.5) or nil,
|
||||
farmTotal = farmTotal or level,
|
||||
consumption = consumption,
|
||||
production = production,
|
||||
net = production - consumption,
|
||||
days = consumption > 0 and (farmTotal or level) / consumption or nil,
|
||||
}
|
||||
end
|
||||
|
||||
function ADStorageStats.buildingTotals(stats)
|
||||
local now, top = 0, 0
|
||||
for _, stat in ipairs(stats) do
|
||||
now = now + (stat.valueNow or 0)
|
||||
top = top + (stat.valueTop or 0)
|
||||
end
|
||||
return {valueNow = now, valueTop = top}
|
||||
end
|
||||
|
||||
-- inventory = { {entries = {{fillType, level}}} } -> {[fillType] = liter}
|
||||
function ADStorageStats.farmTotals(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.level or 0)
|
||||
end
|
||||
end
|
||||
return totals
|
||||
end
|
||||
|
||||
-- Et stald-trug æder `perDay` liter i alt; fordel på fodertyperne efter hvad truget indeholder.
|
||||
-- Tomt trug: alt på foderet med højest vægt (det der hentes først).
|
||||
function ADStorageStats.splitTroughConsumption(perDay, levels, weights)
|
||||
local total = 0
|
||||
for _, level in pairs(levels or {}) do total = total + (level or 0) end
|
||||
local result = {}
|
||||
if total > 0 then
|
||||
for fillType, level in pairs(levels) do
|
||||
if (level or 0) > 0 then result[fillType] = perDay * level / total end
|
||||
end
|
||||
return result
|
||||
end
|
||||
local best, bestWeight = nil, -1
|
||||
for fillType, weight in pairs(weights or {}) do
|
||||
if weight > bestWeight or (weight == bestWeight and fillType < best) then best, bestWeight = fillType, weight end
|
||||
end
|
||||
if best ~= nil then result[best] = perDay end
|
||||
return result
|
||||
end
|
||||
@@ -288,6 +288,7 @@ function SmartPickupFrame:clearRows()
|
||||
self.overviewRows = {}
|
||||
self.productRows, self.outboundStatusRows = {}, {}
|
||||
self.storageRows = {}
|
||||
self.storageDetailRows = {}
|
||||
self.boardRows = {}
|
||||
self.ingredientModeRowOption, self.collectRoundRowOption, self.directionRowOption = nil, nil, nil
|
||||
end
|
||||
|
||||
@@ -178,6 +178,10 @@ function SmartPickupFrame:onClickSource(state, element)
|
||||
end
|
||||
|
||||
function SmartPickupFrame:onClickProduct(state, element)
|
||||
local clicked = self.rowData[element]
|
||||
if clicked ~= nil and clicked.kind == "storageBuilding" then
|
||||
return self:safe(function(frame) frame:onClickStorageBuilding(state) end)
|
||||
end
|
||||
self:safe(function(frame)
|
||||
local run, data = frame:getRun(), frame.rowData[element]
|
||||
if run == nil or data == nil or data.kind ~= "product" then return end
|
||||
|
||||
@@ -26,10 +26,114 @@ function SmartPickupFrame:getInventory(adEnv)
|
||||
return ADSources.getFarmInventory(adEnv, g_currentMission:getFarmId())
|
||||
end
|
||||
|
||||
-- ---- detaljer for den valgte bygning ----
|
||||
|
||||
function SmartPickupFrame.formatMoney(value)
|
||||
if value == nil then return "-" end
|
||||
if g_i18n ~= nil and g_i18n.formatMoney ~= nil then
|
||||
local ok, text = pcall(g_i18n.formatMoney, g_i18n, value, 0, true, true)
|
||||
if ok and text ~= nil then return text end
|
||||
end
|
||||
return string.format("%d", math.floor(value + 0.5))
|
||||
end
|
||||
|
||||
function SmartPickupFrame:fillTypeTitle(fillType)
|
||||
local title = g_fillTypeManager.getFillTypeTitleByIndex ~= nil and g_fillTypeManager:getFillTypeTitleByIndex(fillType) or nil
|
||||
return tostring(title or g_fillTypeManager:getFillTypeNameByIndex(fillType))
|
||||
end
|
||||
|
||||
-- Beregner stats for alle varer i bygningen (gårdens totaler og rater regnes én gang pr. opdatering).
|
||||
function SmartPickupFrame:computeBuildingStats(adEnv, entry, inventory)
|
||||
local farmId = g_currentMission:getFarmId()
|
||||
local totals = ADStorageStats.farmTotals(inventory)
|
||||
local rates = ADSources.getFarmRates ~= nil and ADSources.getFarmRates(adEnv, farmId) or {}
|
||||
local stats = {}
|
||||
for _, item in ipairs(entry.entries) do
|
||||
local quote = ADSources.getMarketQuote ~= nil and ADSources.getMarketQuote(item.fillType) or nil
|
||||
table.insert(stats, ADStorageStats.itemStats(item, totals[item.fillType], rates[item.fillType], quote))
|
||||
end
|
||||
return stats
|
||||
end
|
||||
|
||||
-- "Sukker — 150.0k/180.0k (83 %) · nu 240.300 kr · top 300.400 kr · gården 412.0k = 3.2 døgn · forbrug 130.0k/døgn · prod. 48.0k/døgn"
|
||||
function SmartPickupFrame:describeStat(stat)
|
||||
local i18n = self.i18n
|
||||
local fc = SmartPickupFrame.formatCompact
|
||||
local parts = {string.format("%s — %s/%s%s", self:fillTypeTitle(stat.fillType), fc(stat.level), fc(stat.capacity), stat.percent ~= nil and string.format(" (%d %%)", stat.percent) or "")}
|
||||
if stat.valueNow ~= nil then
|
||||
table.insert(parts, string.format(i18n:getText("spu_stat_now"), SmartPickupFrame.formatMoney(stat.valueNow)))
|
||||
table.insert(parts, string.format(i18n:getText("spu_stat_top"), SmartPickupFrame.formatMoney(stat.valueTop)))
|
||||
else
|
||||
table.insert(parts, i18n:getText("spu_stat_noSell"))
|
||||
end
|
||||
local days = stat.days ~= nil and string.format(i18n:getText("spu_stat_days"), stat.days) or i18n:getText("spu_stat_noUse")
|
||||
table.insert(parts, string.format(i18n:getText("spu_stat_farm"), fc(stat.farmTotal), days))
|
||||
if stat.consumption > 0 then table.insert(parts, string.format(i18n:getText("spu_stat_use"), fc(stat.consumption))) end
|
||||
if stat.production > 0 then table.insert(parts, string.format(i18n:getText("spu_stat_prod"), fc(stat.production))) end
|
||||
return " " .. table.concat(parts, " · ")
|
||||
end
|
||||
|
||||
function SmartPickupFrame:describeStatTotals(stats)
|
||||
local totals = ADStorageStats.buildingTotals(stats)
|
||||
return string.format(" " .. self.i18n:getText("spu_stat_total"), SmartPickupFrame.formatMoney(totals.valueNow), SmartPickupFrame.formatMoney(totals.valueTop))
|
||||
end
|
||||
|
||||
-- Navne til vælgeren; ens navne (40 Farma-siloer) får løbenummer.
|
||||
function SmartPickupFrame:storageLabels(inventory)
|
||||
local counts, seen, labels = {}, {}, {}
|
||||
for _, entry in ipairs(inventory) do counts[entry.name] = (counts[entry.name] or 0) + 1 end
|
||||
for index, entry in ipairs(inventory) do
|
||||
seen[entry.name] = (seen[entry.name] or 0) + 1
|
||||
labels[index] = counts[entry.name] > 1 and string.format("%s #%d", entry.name, seen[entry.name]) or entry.name
|
||||
end
|
||||
return labels
|
||||
end
|
||||
|
||||
function SmartPickupFrame:fillStorageDetail(adEnv, inventory)
|
||||
self.storageDetailRows = {}
|
||||
if #inventory == 0 then
|
||||
return
|
||||
end
|
||||
self.storageIndex = math.max(1, math.min(self.storageIndex or 1, #inventory))
|
||||
local row = self:addRow(self.productPrefab, {kind = "storageBuilding"})
|
||||
local option = row:getDescendantByName("option")
|
||||
option:setTexts(self:storageLabels(inventory))
|
||||
option:setState(self.storageIndex, false)
|
||||
row:getDescendantByName("title"):setText(self.i18n:getText("spu_storage_select"))
|
||||
local entry = inventory[self.storageIndex]
|
||||
self.storageDetailPlaceable = entry.placeable
|
||||
local stats = self:computeBuildingStats(adEnv, entry, inventory)
|
||||
for _, stat in ipairs(stats) do
|
||||
table.insert(self.storageDetailRows, self:addTextRow(self:describeStat(stat)))
|
||||
end
|
||||
table.insert(self.storageDetailRows, self:addTextRow(self:describeStatTotals(stats)))
|
||||
end
|
||||
|
||||
function SmartPickupFrame:refreshStorageDetail(adEnv, inventory)
|
||||
local entry = nil
|
||||
for _, candidate in ipairs(inventory) do
|
||||
if candidate.placeable == self.storageDetailPlaceable then entry = candidate end
|
||||
end
|
||||
if entry == nil or #(self.storageDetailRows or {}) ~= #entry.entries + 1 then
|
||||
return
|
||||
end
|
||||
local stats = self:computeBuildingStats(adEnv, entry, inventory)
|
||||
for index, stat in ipairs(stats) do
|
||||
self.storageDetailRows[index]:setText(self:describeStat(stat))
|
||||
end
|
||||
self.storageDetailRows[#stats + 1]:setText(self:describeStatTotals(stats))
|
||||
end
|
||||
|
||||
function SmartPickupFrame:onClickStorageBuilding(state)
|
||||
self.storageIndex = state
|
||||
self:requestRebuild()
|
||||
end
|
||||
|
||||
function SmartPickupFrame:fillStorageRows(adEnv)
|
||||
self.storageRows = {}
|
||||
self:addTextRow(self.i18n:getText("spu_section_storage"))
|
||||
local inventory = self:getInventory(adEnv)
|
||||
self:fillStorageDetail(adEnv, inventory)
|
||||
self:addTextRow(self.i18n:getText("spu_section_storage"))
|
||||
for _, entry in ipairs(inventory) do
|
||||
table.insert(self.storageRows, {placeable = entry.placeable, title = self:addTextRow(self:describeInventory(entry))})
|
||||
end
|
||||
@@ -42,10 +146,12 @@ function SmartPickupFrame:refreshStorageRows(adEnv)
|
||||
if self.currentTab ~= SmartPickupFrame.TAB_STORAGE or #(self.storageRows or {}) == 0 then
|
||||
return
|
||||
end
|
||||
local inventory = self:getInventory(adEnv)
|
||||
local byPlaceable = {}
|
||||
for _, entry in ipairs(self:getInventory(adEnv)) do
|
||||
for _, entry in ipairs(inventory) do
|
||||
byPlaceable[entry.placeable] = entry
|
||||
end
|
||||
pcall(self.refreshStorageDetail, self, adEnv, inventory)
|
||||
for _, row in ipairs(self.storageRows) do
|
||||
local entry = byPlaceable[row.placeable]
|
||||
if entry ~= nil then
|
||||
|
||||
@@ -124,5 +124,15 @@
|
||||
<text name="spu_noBuildings" text="Ingen bygninger fundet"/>
|
||||
<text name="spu_buildingGone" text="bygningen findes ikke længere"/>
|
||||
<text name="spu_reason_noMarker" text="'%s' mangler en AD-markør ved triggeren"/>
|
||||
<text name="spu_storage_select" text="Vis bygning"/>
|
||||
<text name="spu_stat_now" text="nu %s"/>
|
||||
<text name="spu_stat_top" text="ved toppris %s"/>
|
||||
<text name="spu_stat_noSell" text="intet salgssted"/>
|
||||
<text name="spu_stat_days" text="%.1f døgn"/>
|
||||
<text name="spu_stat_noUse" text="forbruges ikke"/>
|
||||
<text name="spu_stat_farm" text="gården %s = %s"/>
|
||||
<text name="spu_stat_use" text="forbrug %s/døgn"/>
|
||||
<text name="spu_stat_prod" text="prod. %s/døgn"/>
|
||||
<text name="spu_stat_total" text="Bygningens værdi: nu %s · ved toppris %s"/>
|
||||
</texts>
|
||||
</l10n>
|
||||
|
||||
@@ -124,5 +124,15 @@
|
||||
<text name="spu_noBuildings" text="No buildings found"/>
|
||||
<text name="spu_buildingGone" text="building no longer exists"/>
|
||||
<text name="spu_reason_noMarker" text="'%s' has no AD marker at its trigger"/>
|
||||
<text name="spu_storage_select" text="Show building"/>
|
||||
<text name="spu_stat_now" text="now %s"/>
|
||||
<text name="spu_stat_top" text="at top price %s"/>
|
||||
<text name="spu_stat_noSell" text="no selling point"/>
|
||||
<text name="spu_stat_days" text="%.1f days"/>
|
||||
<text name="spu_stat_noUse" text="not consumed"/>
|
||||
<text name="spu_stat_farm" text="farm %s = %s"/>
|
||||
<text name="spu_stat_use" text="use %s/day"/>
|
||||
<text name="spu_stat_prod" text="prod. %s/day"/>
|
||||
<text name="spu_stat_total" text="Building value: now %s · at top price %s"/>
|
||||
</texts>
|
||||
</l10n>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.14.3.0</version>
|
||||
<version>1.15.0.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
@@ -20,6 +20,7 @@
|
||||
<sourceFile filename="adWaitPool.lua"/>
|
||||
<sourceFile filename="adPrices.lua"/>
|
||||
<sourceFile filename="adOutboundPlanner.lua"/>
|
||||
<sourceFile filename="adStorageStats.lua"/>
|
||||
<sourceFile filename="adSources.lua"/>
|
||||
<sourceFile filename="adBuildings.lua"/>
|
||||
<sourceFile filename="adOutbound.lua"/>
|
||||
|
||||
Reference in New Issue
Block a user