979b7f72d5
Slukker spilleren en produktion, hvis vare et tændt flow kører (manuel vare eller målet tager den i auto), en stald æder eller en aktiv kæde bruger, kommer én besked pr. slukning med brugere og lagerdage, og en rød post 'Slukket men nødvendig' i Kræver handling og statusvinduet så længe den er slukket og nødvendig. Ingen advarsel når en anden tændt produktion dækker, lageret rækker over 60 dage, kæden selv slukkede den eller den var slukket ved indlæsning. Tjekket kører i kædens tick også uden kæder; ren logik i adDependency.lua.
356 lines
17 KiB
Lua
356 lines
17 KiB
Lua
-- Smart Pickup — fanerne Lagre, vareoversigten (v1.52)
|
||
-- Én række pr. vare med samlet beholdning, fyldningsbjælke, værdi og gårdens netto; klik folder varen ud
|
||
-- til bygningerne den ligger i. Øverst nøgletal og "kræver handling". Tallene regnes i ADStorageStats;
|
||
-- her tegnes kun. Indlæses efter SmartPickupStorageTab.lua (funktionerne lægges på SmartPickupFrame).
|
||
|
||
SmartPickupFrame.STORAGE_VIEW_ITEMS, SmartPickupFrame.STORAGE_VIEW_BUILDINGS = 1, 2
|
||
SmartPickupFrame.MAX_ATTENTION = 4
|
||
-- nøgletalsrækken er højere end de andre rækker (tal over label)
|
||
SmartPickupFrame.KPI_ROW_SCALE = 1.6
|
||
-- ændret struktur (ny vare, flere alarmer) genopbygges højst hvert N. sekund, så fokus ikke hopper
|
||
SmartPickupFrame.ITEM_REBUILD_TICKS = 10
|
||
|
||
SmartPickupFrame.LEVEL_COLORS = {
|
||
red = {0.90, 0.25, 0.20, 1},
|
||
yellow = {0.95, 0.72, 0.15, 1},
|
||
green = {0.30, 0.75, 0.35, 1},
|
||
}
|
||
SmartPickupFrame.NEUTRAL_BAR_COLOR = {0.35, 0.60, 0.85, 1}
|
||
SmartPickupFrame.TEXT_COLOR = {1, 1, 1, 1}
|
||
|
||
local unpackFn = table.unpack or unpack
|
||
|
||
-- ---- små tegne-hjælpere ----
|
||
|
||
local function setTextColor(element, color)
|
||
if element ~= nil and element.setTextColor ~= nil then element:setTextColor(unpackFn(color)) end
|
||
end
|
||
|
||
-- Fyld bjælken til brøken af baggrundens bredde; tom bjælke skjules (bredde 0 tegnes som en prik).
|
||
function SmartPickupFrame.setBar(bar, background, fraction, color)
|
||
if bar == nil or background == nil or background.size == nil then return end
|
||
if fraction <= 0 then
|
||
bar:setVisible(false)
|
||
return
|
||
end
|
||
bar:setVisible(true)
|
||
bar:setSize(background.size[1] * fraction, background.size[2])
|
||
if bar.setImageColor ~= nil then bar:setImageColor(nil, unpackFn(color)) end
|
||
end
|
||
|
||
-- Bjælkens farve: rød/gul ved problem, ellers grøn for varer i omløb og blå for rent lager.
|
||
function SmartPickupFrame.barColor(level, isActive)
|
||
if level ~= nil then return SmartPickupFrame.LEVEL_COLORS[level] end
|
||
return isActive and SmartPickupFrame.LEVEL_COLORS.green or SmartPickupFrame.NEUTRAL_BAR_COLOR
|
||
end
|
||
|
||
function SmartPickupFrame:formatDays(days)
|
||
return string.format(self.i18n:getText("spu_stat_days"), days)
|
||
end
|
||
|
||
-- "−14.4k/døgn · 3.2 døgn", "+3.6k/døgn · fuld om 2.0 døgn", "+1.2k/døgn · stiger" eller "-"
|
||
function SmartPickupFrame:itemRateText(stat)
|
||
local fc = SmartPickupFrame.formatCompact
|
||
if (stat.net or 0) == 0 then
|
||
return (stat.consumption or 0) > 0 and self.i18n:getText("spu_item_balanced") or "-"
|
||
end
|
||
local rate = string.format(self.i18n:getText("spu_item_perDay"), (stat.net < 0 and "-" or "+") .. fc(math.abs(stat.net)))
|
||
if stat.days ~= nil then
|
||
return rate .. " · " .. self:formatDays(stat.days)
|
||
elseif stat.daysToFull ~= nil then
|
||
return rate .. " · " .. string.format(self.i18n:getText("spu_item_fullIn"), self:formatDays(stat.daysToFull))
|
||
end
|
||
return rate .. " · " .. self.i18n:getText("spu_stat_rising")
|
||
end
|
||
|
||
-- "1.2M/1.5M · 80 %" eller "24 paller · 1 %"
|
||
function SmartPickupFrame:itemAmountText(entry)
|
||
local fc = SmartPickupFrame.formatCompact
|
||
local amount = entry.count ~= nil and string.format(self.i18n:getText("spu_storage_pallets"), entry.count)
|
||
or string.format("%s/%s", fc(entry.level or 0), fc(entry.capacity or 0))
|
||
if entry.percent ~= nil then amount = amount .. string.format(" · %d %%", entry.percent) end
|
||
return amount
|
||
end
|
||
|
||
-- ---- data ----
|
||
|
||
-- Fanens varer med statistik; gårdens totaler/kapacitet/rater regnes over HELE gården.
|
||
function SmartPickupFrame:computeItemStats(adEnv, inventory, fullInventory)
|
||
self.lastFullInventory = fullInventory -- til "kræver handling" -> flow (hvem bruger varen)
|
||
local farmId = g_currentMission:getFarmId()
|
||
local totals = ADStorageStats.farmTotals(fullInventory)
|
||
local capacities = ADStorageStats.farmCapacities(fullInventory)
|
||
local rates = ADSources.getFarmRates ~= nil and ADSources.getFarmRates(adEnv, farmId) or {}
|
||
local stats = {}
|
||
local titleOf = function(fillType) return self:fillTypeTitle(fillType) end
|
||
for _, group in ipairs(ADStorageStats.groupByFillType(inventory, titleOf)) do
|
||
local quote = ADSources.getMarketQuote ~= nil and ADSources.getMarketQuote(group.fillType) or nil
|
||
local farm = {level = totals[group.fillType], capacity = capacities[group.fillType]}
|
||
table.insert(stats, ADStorageStats.groupStats(group, farm, rates[group.fillType], quote))
|
||
end
|
||
return stats
|
||
end
|
||
|
||
-- Nøgle der ændrer sig når rækkerne skal være anderledes (varer, udfoldede bygninger, alarmer).
|
||
function SmartPickupFrame:itemStructureKey(stats, attention)
|
||
local parts = {}
|
||
for _, stat in ipairs(stats) do
|
||
table.insert(parts, tostring(stat.fillType))
|
||
if self:isItemExpanded(stat.fillType) then
|
||
-- sorteret: at to bygninger bytter plads i mængde må ikke udløse en genopbygning
|
||
local placeables = {}
|
||
for _, building in ipairs(stat.buildings) do table.insert(placeables, tostring(building.placeable)) end
|
||
table.sort(placeables)
|
||
table.insert(parts, table.concat(placeables, ";"))
|
||
end
|
||
end
|
||
table.insert(parts, "a" .. #attention)
|
||
return table.concat(parts, ",")
|
||
end
|
||
|
||
function SmartPickupFrame:isItemExpanded(fillType)
|
||
local expanded = self.expandedItems ~= nil and self.expandedItems[self.currentTab] or nil
|
||
return expanded ~= nil and expanded[fillType] == true
|
||
end
|
||
|
||
function SmartPickupFrame:toggleItem(fillType)
|
||
self.expandedItems = self.expandedItems or {}
|
||
self.expandedItems[self.currentTab] = self.expandedItems[self.currentTab] or {}
|
||
local expanded = self.expandedItems[self.currentTab]
|
||
expanded[fillType] = not expanded[fillType] or nil
|
||
-- listen hopper ellers til toppen ved genopbygningen
|
||
local layout = self.boxLayout
|
||
local scrollY = layout ~= nil and layout.firstVisibleY or nil
|
||
self:rebuild()
|
||
if scrollY ~= nil and layout.scrollTo ~= nil then pcall(layout.scrollTo, layout, scrollY, true) end
|
||
end
|
||
|
||
-- ---- visningsvælger (Varer | Bygninger) ----
|
||
|
||
function SmartPickupFrame:getStorageView()
|
||
local views = self.storageViewByTab or {}
|
||
return views[self.currentTab] or SmartPickupFrame.STORAGE_VIEW_ITEMS
|
||
end
|
||
|
||
function SmartPickupFrame:addStorageViewRow()
|
||
local row = self:addRow(self.productPrefab, {kind = "storageView"})
|
||
local option = row:getDescendantByName("option")
|
||
option:setTexts({self.i18n:getText("spu_view_items"), self.i18n:getText("spu_view_buildings")})
|
||
option:setState(self:getStorageView(), false)
|
||
row:getDescendantByName("title"):setText(self.i18n:getText("spu_view_title"))
|
||
local tooltip = option:getDescendantByName("ignore")
|
||
if tooltip ~= nil then tooltip:setText(self.i18n:getText("spu_view_tooltip")) end
|
||
option.toolTipText = self.i18n:getText("spu_view_tooltip")
|
||
end
|
||
|
||
function SmartPickupFrame:onClickStorageView(state)
|
||
self.storageViewByTab = self.storageViewByTab or {}
|
||
self.storageViewByTab[self.currentTab] = state
|
||
self:requestRebuild()
|
||
end
|
||
|
||
-- ---- opbygning ----
|
||
|
||
function SmartPickupFrame:kpiTexts(stats)
|
||
local kpi = ADStorageStats.kpis(stats)
|
||
local i18n = self.i18n
|
||
return {
|
||
{SmartPickupFrame.formatMoney(kpi.valueNow), i18n:getText("spu_kpi_value")},
|
||
{"+" .. SmartPickupFrame.formatMoney(kpi.gain), i18n:getText("spu_kpi_gain")},
|
||
{tostring(kpi.emptyCount), i18n:getText("spu_kpi_empty")},
|
||
{tostring(kpi.fullCount), i18n:getText("spu_kpi_full")},
|
||
}, kpi
|
||
end
|
||
|
||
function SmartPickupFrame:setKpiRow(row, stats)
|
||
local texts, kpi = self:kpiTexts(stats)
|
||
for index, pair in ipairs(texts) do
|
||
local value = row:getDescendantByName("v" .. index)
|
||
if value ~= nil then value:setText(pair[1]) end
|
||
local label = row:getDescendantByName("l" .. index)
|
||
if label ~= nil then label:setText(pair[2]) end
|
||
end
|
||
local colors = SmartPickupFrame.LEVEL_COLORS
|
||
setTextColor(row:getDescendantByName("v2"), kpi.gain > 0 and colors.green or SmartPickupFrame.TEXT_COLOR)
|
||
setTextColor(row:getDescendantByName("v3"), kpi.emptyCount > 0 and colors.red or SmartPickupFrame.TEXT_COLOR)
|
||
setTextColor(row:getDescendantByName("v4"), kpi.fullCount > 0 and colors.yellow or SmartPickupFrame.TEXT_COLOR)
|
||
end
|
||
|
||
function SmartPickupFrame:addKpiRow(stats)
|
||
local row = self:addRow(self.kpiPrefab, {kind = "text"})
|
||
if row.size ~= nil then row:setSize(row.size[1], row.size[2] * SmartPickupFrame.KPI_ROW_SCALE) end
|
||
self:setKpiRow(row, stats)
|
||
return row
|
||
end
|
||
|
||
-- Varepunkterne + kædens punkter (røde manglende led, højst 2, røde "Slukket men nødvendig", højst 2, og ét samlet
|
||
-- punkt for afgrødeforslag), samme loft MAX_ATTENTION. ADChain.last genbruges; er det ældre end CHAIN_MAX_AGE_MS,
|
||
-- evalueres der (visning, handler ikke), så en slukket nødvendig fabrik ses straks.
|
||
function SmartPickupFrame:attentionFor(stats)
|
||
local attention = ADStorageStats.attentionList(stats, SmartPickupFrame.MAX_ATTENTION)
|
||
if ADChain ~= nil and ADChain.evaluate ~= nil and self.chainLast ~= nil and self.getAdEnv ~= nil then
|
||
local okEnv, adEnv = pcall(self.getAdEnv)
|
||
if okEnv and adEnv ~= nil then pcall(self.chainLast, self, adEnv) end
|
||
end
|
||
if ADChain == nil or ADChain.last == nil then return attention end
|
||
return ADStorageStats.mergeAttention(attention, ADStorageStats.chainEntries(ADChain.last), SmartPickupFrame.MAX_ATTENTION)
|
||
end
|
||
|
||
function SmartPickupFrame:alertText(entry)
|
||
local stat, fc, i18n = entry.stat, SmartPickupFrame.formatCompact, self.i18n
|
||
if entry.kind == "chainSuggest" then
|
||
return string.format(i18n:getText("spu_attention_chainSuggests"), entry.count or 0)
|
||
end
|
||
if entry.kind == "chainProblem" then
|
||
local text = string.format(i18n:getText("spu_attention_chainProblem"), tostring(entry.rootTitle), tostring(entry.itemsText))
|
||
if (entry.more or 0) > 0 then text = text .. string.format(" (+%d)", entry.more) end
|
||
return text
|
||
end
|
||
if entry.kind == "dependencyOff" then
|
||
local text = string.format(i18n:getText("spu_attention_depOff"), tostring(entry.buildingName))
|
||
if (entry.more or 0) > 0 then text = text .. string.format(" (+%d)", entry.more) end
|
||
return text
|
||
end
|
||
if entry.kind == "empty" then
|
||
return string.format(i18n:getText("spu_alert_empty"), stat.title, self:formatDays(stat.days), fc(stat.farmLevel or 0), fc(math.abs(stat.net or 0)))
|
||
elseif entry.kind == "full" then
|
||
if stat.daysToFull ~= nil then
|
||
return string.format(i18n:getText("spu_alert_fullIn"), stat.title, stat.percent or 0, self:formatDays(stat.daysToFull))
|
||
end
|
||
return string.format(i18n:getText("spu_alert_full"), stat.title, stat.percent or 0)
|
||
end
|
||
return string.format(i18n:getText("spu_alert_sell"), stat.title, math.floor((stat.priceRatio or 0) * 100 + 0.5), SmartPickupFrame.formatMoney(stat.valueNow))
|
||
end
|
||
|
||
function SmartPickupFrame:setAlertRow(row, entry)
|
||
row.entry = entry
|
||
local hint = ""
|
||
if SmartPickupFrame.attentionAdvice ~= nil then
|
||
local ok, advice, info = pcall(self.attentionAdvice, self, entry)
|
||
if ok then hint = self:attentionHint(advice, info) end
|
||
end
|
||
row.c1:setText(self:alertText(entry) .. hint)
|
||
if row.dot ~= nil and row.dot.setImageColor ~= nil then
|
||
row.dot:setImageColor(nil, unpackFn(SmartPickupFrame.LEVEL_COLORS[entry.level] or SmartPickupFrame.LEVEL_COLORS.green))
|
||
end
|
||
end
|
||
|
||
function SmartPickupFrame:itemCells(stat)
|
||
local marker = self:isItemExpanded(stat.fillType) and "– " or "+ "
|
||
local value = stat.valueNow ~= nil and SmartPickupFrame.formatMoney(stat.valueNow) or self.i18n:getText("spu_stat_noSell")
|
||
return {marker .. stat.title, self:itemAmountText(stat), value, self:itemRateText(stat)}
|
||
end
|
||
|
||
function SmartPickupFrame:setItemRow(row, stat)
|
||
SmartPickupFrame.setCells(row.cells, self:itemCells(stat))
|
||
local isActive = (stat.consumption or 0) > 0 or (stat.production or 0) > 0
|
||
SmartPickupFrame.setBar(row.bar, row.barBg, ADStorageStats.fraction(stat.percent), SmartPickupFrame.barColor(stat.severity, isActive))
|
||
local rateColor = SmartPickupFrame.TEXT_COLOR
|
||
if stat.emptyLevel ~= nil then
|
||
rateColor = SmartPickupFrame.LEVEL_COLORS[stat.emptyLevel]
|
||
elseif stat.fullLevel ~= nil then
|
||
rateColor = SmartPickupFrame.LEVEL_COLORS[stat.fullLevel]
|
||
end
|
||
setTextColor(row.cells[4], rateColor)
|
||
end
|
||
|
||
-- Bygningens andel af varen: værdi efter samme pris som varen, bjælke efter bygningens egen fyldning.
|
||
function SmartPickupFrame:setSubRow(row, stat, building)
|
||
local kind = SmartPickupFrame.getTabKind(self.currentTab)
|
||
local isStableOutput = kind == "husbandry" and (stat.production or 0) > 0 and (stat.consumption or 0) == 0
|
||
local value = "-"
|
||
if stat.valueNow ~= nil and stat.level > 0 then
|
||
value = SmartPickupFrame.formatMoney(stat.valueNow * building.level / stat.level)
|
||
end
|
||
local share = stat.level > 0 and math.floor(100 * building.level / stat.level + 0.5) or 0
|
||
SmartPickupFrame.setCells(row.cells, {tostring(building.name), self:itemAmountText(building), value,
|
||
string.format(self.i18n:getText("spu_item_share"), share)})
|
||
local level = ADStorageStats.buildingLevel(building, kind, isStableOutput)
|
||
SmartPickupFrame.setBar(row.bar, row.barBg, ADStorageStats.fraction(building.percent), SmartPickupFrame.barColor(level, false))
|
||
end
|
||
|
||
-- Klon af item-/sub-prefab med referencer til cellerne og bjælken.
|
||
function SmartPickupFrame:addBarRow(prefab)
|
||
local row = self:addRow(prefab, {kind = "text"})
|
||
local cells = {}
|
||
for index = 1, 4 do cells[index] = row:getDescendantByName("c" .. index) end
|
||
return {element = row, cells = cells, bar = row:getDescendantByName("bar"), barBg = row:getDescendantByName("barBg")}
|
||
end
|
||
|
||
function SmartPickupFrame:addItemHeader()
|
||
local i18n = self.i18n
|
||
local header = self:addBarRow(self.itemPrefab)
|
||
SmartPickupFrame.setCells(header.cells, {i18n:getText("spu_col_item"), i18n:getText("spu_icol_amount"),
|
||
i18n:getText("spu_icol_value"), i18n:getText("spu_icol_rate")})
|
||
if header.bar ~= nil then header.bar:setVisible(false) end
|
||
if header.barBg ~= nil then header.barBg:setVisible(false) end
|
||
end
|
||
|
||
function SmartPickupFrame:fillItemOverview(adEnv, inventory, fullInventory)
|
||
local i18n = self.i18n
|
||
local stats = self:computeItemStats(adEnv, inventory, fullInventory)
|
||
local attention = self:attentionFor(stats)
|
||
self.itemView = {kpiRow = self:addKpiRow(stats), alertRows = {}, itemRows = {}, key = self:itemStructureKey(stats, attention)}
|
||
self.itemRebuildTicks = SmartPickupFrame.ITEM_REBUILD_TICKS
|
||
self:addTextRow(i18n:getText("spu_attention"))
|
||
if #attention == 0 then
|
||
self:addTextRow(i18n:getText("spu_attention_none"))
|
||
end
|
||
for _, entry in ipairs(attention) do
|
||
local element = self:addRow(self.alertPrefab, {kind = "text"})
|
||
local row = {c1 = element:getDescendantByName("c1"), dot = element:getDescendantByName("dot")}
|
||
self:setAlertRow(row, entry)
|
||
-- klik: åbn/opret flow til varen (gui/SmartPickupAttentionFlow.lua)
|
||
self:makeRowClickable(element, function(frame) frame:onAttentionClick(row.entry) end)
|
||
table.insert(self.itemView.alertRows, row)
|
||
end
|
||
self:addTextRow(i18n:getText("spu_section_storage_" .. tostring(SmartPickupFrame.getTabKind(self.currentTab))) .. " " .. i18n:getText("spu_items_hint"))
|
||
if #stats == 0 then
|
||
self:addTextRow(i18n:getText("spu_storage_empty"))
|
||
return
|
||
end
|
||
self:addItemHeader()
|
||
for _, stat in ipairs(stats) do
|
||
local row = self:addBarRow(self.itemPrefab)
|
||
local fillType = stat.fillType
|
||
self:makeRowClickable(row.element, function(frame) frame:toggleItem(fillType) end)
|
||
self:setItemRow(row, stat)
|
||
row.subs = {}
|
||
if self:isItemExpanded(fillType) then
|
||
for _, building in ipairs(stat.buildings) do
|
||
local sub = self:addBarRow(self.subPrefab)
|
||
sub.placeable = building.placeable
|
||
self:setSubRow(sub, stat, building)
|
||
table.insert(row.subs, sub)
|
||
end
|
||
end
|
||
table.insert(self.itemView.itemRows, row)
|
||
end
|
||
end
|
||
|
||
-- Hvert sekund: tal og bjælker opdateres på stedet; ny struktur -> genopbygning (højst hvert 10. s).
|
||
function SmartPickupFrame:refreshItemOverview(adEnv, inventory, fullInventory)
|
||
local view = self.itemView
|
||
if view == nil then return end
|
||
local stats = self:computeItemStats(adEnv, inventory, fullInventory)
|
||
local attention = self:attentionFor(stats)
|
||
if self:itemStructureKey(stats, attention) ~= view.key then
|
||
self.itemRebuildTicks = (self.itemRebuildTicks or 0) - 1
|
||
if self.itemRebuildTicks <= 0 then self:requestRebuild() end
|
||
return
|
||
end
|
||
self:setKpiRow(view.kpiRow, stats)
|
||
for index, entry in ipairs(attention) do self:setAlertRow(view.alertRows[index], entry) end
|
||
for index, stat in ipairs(stats) do
|
||
local row = view.itemRows[index]
|
||
self:setItemRow(row, stat)
|
||
-- underrækkerne beholder rækkefølgen fra opbygningen; tallene findes via bygningen
|
||
local byPlaceable = {}
|
||
for _, building in ipairs(stat.buildings) do byPlaceable[building.placeable] = building end
|
||
for _, sub in ipairs(row.subs) do
|
||
local building = byPlaceable[sub.placeable]
|
||
if building ~= nil then self:setSubRow(sub, stat, building) end
|
||
end
|
||
end
|
||
end
|