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:
@@ -23,6 +23,7 @@ if ADSmartPickup.dir ~= nil and source ~= nil then
|
|||||||
source(ADSmartPickup.dir .. "gui/SmartPickupSupplyTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupSupplyTab.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupOutboundTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupOutboundTab.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupStorageTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupStorageTab.lua")
|
||||||
|
source(ADSmartPickup.dir .. "gui/SmartPickupStorageView.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupFlowTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupFlowTab.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupFieldTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupFieldTab.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupMonthTab.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupMonthTab.lua")
|
||||||
|
|||||||
@@ -108,3 +108,178 @@ function ADStorageStats.filterKind(inventory, kind)
|
|||||||
end
|
end
|
||||||
return result
|
return result
|
||||||
end
|
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
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ end
|
|||||||
|
|
||||||
-- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker.
|
-- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker.
|
||||||
function SmartPickupFrame:initialize()
|
function SmartPickupFrame:initialize()
|
||||||
for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab", "productPrefab", "statPrefab", "gridPrefab", "flowPrefab", "fieldPrefab"}) do
|
for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab", "productPrefab", "statPrefab", "gridPrefab", "flowPrefab", "fieldPrefab", "kpiPrefab", "alertPrefab", "itemPrefab", "subPrefab"}) do
|
||||||
local prefab = self[name]
|
local prefab = self[name]
|
||||||
if prefab ~= nil then
|
if prefab ~= nil then
|
||||||
prefab:unlinkElement()
|
prefab:unlinkElement()
|
||||||
@@ -329,6 +329,7 @@ function SmartPickupFrame:clearRows()
|
|||||||
self.productRows, self.outboundStatusRows = {}, {}
|
self.productRows, self.outboundStatusRows = {}, {}
|
||||||
self.storageRows = {}
|
self.storageRows = {}
|
||||||
self.storageDetailRows = {}
|
self.storageDetailRows = {}
|
||||||
|
self.itemView = nil
|
||||||
self.boardRows = {}
|
self.boardRows = {}
|
||||||
self.ingredientModeRowOption, self.collectRoundRowOption, self.directionRowOption = nil, nil, nil
|
self.ingredientModeRowOption, self.collectRoundRowOption, self.directionRowOption = nil, nil, nil
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -106,6 +106,41 @@
|
|||||||
<Text profile="spuCell" name="c2" position="290px 0px" size="260px 40px" textMaxWidth="252px"/>
|
<Text profile="spuCell" name="c2" position="290px 0px" size="260px 40px" textMaxWidth="252px"/>
|
||||||
<Text profile="spuCell" name="c3" position="550px 0px" size="260px 40px" textMaxWidth="252px"/>
|
<Text profile="spuCell" name="c3" position="550px 0px" size="260px 40px" textMaxWidth="252px"/>
|
||||||
</Bitmap>
|
</Bitmap>
|
||||||
|
<!-- Lagre, vareoversigt (v1.52). Rækken er ~815 px bred. -->
|
||||||
|
<!-- nøgletal: 4 felter (tal + label); højden sættes i Lua -->
|
||||||
|
<Bitmap profile="fs25_multiTextOptionContainer" id="kpiPrefab">
|
||||||
|
<Text profile="spuKpiValue" name="v1" position="20px 0px" size="195px 40px" textMaxWidth="190px"/>
|
||||||
|
<Text profile="spuKpiLabel" name="l1" position="20px 0px" size="195px 40px" textMaxWidth="190px"/>
|
||||||
|
<Text profile="spuKpiValue" name="v2" position="220px 0px" size="195px 40px" textMaxWidth="190px"/>
|
||||||
|
<Text profile="spuKpiLabel" name="l2" position="220px 0px" size="195px 40px" textMaxWidth="190px"/>
|
||||||
|
<Text profile="spuKpiValue" name="v3" position="420px 0px" size="195px 40px" textMaxWidth="190px"/>
|
||||||
|
<Text profile="spuKpiLabel" name="l3" position="420px 0px" size="195px 40px" textMaxWidth="190px"/>
|
||||||
|
<Text profile="spuKpiValue" name="v4" position="620px 0px" size="190px 40px" textMaxWidth="185px"/>
|
||||||
|
<Text profile="spuKpiLabel" name="l4" position="620px 0px" size="190px 40px" textMaxWidth="185px"/>
|
||||||
|
</Bitmap>
|
||||||
|
<!-- kræver handling: farvet markør + tekst -->
|
||||||
|
<Bitmap profile="fs25_multiTextOptionContainer" id="alertPrefab">
|
||||||
|
<Bitmap profile="spuDot" name="dot" position="20px 0px"/>
|
||||||
|
<Text profile="spuCell" name="c1" position="40px 0px" size="770px 40px" textMaxWidth="765px"/>
|
||||||
|
</Bitmap>
|
||||||
|
<!-- vare: +/– navn · bjælke · mængde · værdi · netto/døgn og rækkevidde (klikbar) -->
|
||||||
|
<Bitmap profile="fs25_multiTextOptionContainer" id="itemPrefab">
|
||||||
|
<Text profile="spuCellBold" name="c1" position="20px 0px" size="170px 40px" textMaxWidth="165px"/>
|
||||||
|
<Bitmap profile="spuBarBg" name="barBg" position="190px 0px"/>
|
||||||
|
<Bitmap profile="spuBar" name="bar" position="190px 0px"/>
|
||||||
|
<Text profile="spuCell" name="c2" position="400px 0px" size="140px 40px" textMaxWidth="135px"/>
|
||||||
|
<Text profile="spuCell" name="c3" position="540px 0px" size="110px 40px" textMaxWidth="105px"/>
|
||||||
|
<Text profile="spuCell" name="c4" position="650px 0px" size="160px 40px" textMaxWidth="155px"/>
|
||||||
|
</Bitmap>
|
||||||
|
<!-- bygning under en udfoldet vare: indrykket navn · tynd bjælke · mængde · værdi · procent -->
|
||||||
|
<Bitmap profile="fs25_multiTextOptionContainer" id="subPrefab">
|
||||||
|
<Text profile="spuCellDim" name="c1" position="45px 0px" size="145px 40px" textMaxWidth="140px"/>
|
||||||
|
<Bitmap profile="spuBarThinBg" name="barBg" position="190px 0px"/>
|
||||||
|
<Bitmap profile="spuBarThin" name="bar" position="190px 0px"/>
|
||||||
|
<Text profile="spuCellDim" name="c2" position="400px 0px" size="140px 40px" textMaxWidth="135px"/>
|
||||||
|
<Text profile="spuCellDim" name="c3" position="540px 0px" size="110px 40px" textMaxWidth="105px"/>
|
||||||
|
<Text profile="spuCellDim" name="c4" position="650px 0px" size="160px 40px" textMaxWidth="155px"/>
|
||||||
|
</Bitmap>
|
||||||
<!-- udkørsel: én række pr. vare med politik Fra/Lager/Salg/Salg, ellers lager -->
|
<!-- udkørsel: én række pr. vare med politik Fra/Lager/Salg/Salg, ellers lager -->
|
||||||
<Bitmap profile="fs25_multiTextOptionContainer" id="productPrefab">
|
<Bitmap profile="fs25_multiTextOptionContainer" id="productPrefab">
|
||||||
<MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickProduct" name="option">
|
<MultiTextOption profile="fs25_settingsMultiTextOption" onClick="onClickProduct" name="option">
|
||||||
|
|||||||
@@ -116,6 +116,9 @@ function SmartPickupFrame:onClickProduct(state, element)
|
|||||||
if clicked ~= nil and clicked.kind == "guideRow" then
|
if clicked ~= nil and clicked.kind == "guideRow" then
|
||||||
return self:onClickGuideRow(state, clicked)
|
return self:onClickGuideRow(state, clicked)
|
||||||
end
|
end
|
||||||
|
if clicked ~= nil and clicked.kind == "storageView" then
|
||||||
|
return self:safe(function(frame) frame:onClickStorageView(state) end)
|
||||||
|
end
|
||||||
if clicked ~= nil and clicked.kind == "storageBuilding" then
|
if clicked ~= nil and clicked.kind == "storageBuilding" then
|
||||||
return self:safe(function(frame) frame:onClickStorageBuilding(state) end)
|
return self:safe(function(frame) frame:onClickStorageBuilding(state) end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ end
|
|||||||
|
|
||||||
function SmartPickupFrame:fillStorageDetail(adEnv, inventory, fullInventory)
|
function SmartPickupFrame:fillStorageDetail(adEnv, inventory, fullInventory)
|
||||||
self.storageDetailRows = {}
|
self.storageDetailRows = {}
|
||||||
|
self.storageDetailPlaceable = nil
|
||||||
if #inventory == 0 then
|
if #inventory == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -177,6 +178,12 @@ function SmartPickupFrame:fillStorageRows(adEnv)
|
|||||||
self.storageRows = {}
|
self.storageRows = {}
|
||||||
local fullInventory = self:getInventory(adEnv)
|
local fullInventory = self:getInventory(adEnv)
|
||||||
local inventory = ADStorageStats.filterKind(fullInventory, SmartPickupFrame.getTabKind(self.currentTab))
|
local inventory = ADStorageStats.filterKind(fullInventory, SmartPickupFrame.getTabKind(self.currentTab))
|
||||||
|
self:addStorageViewRow()
|
||||||
|
if self:getStorageView() == SmartPickupFrame.STORAGE_VIEW_ITEMS then
|
||||||
|
self.storageDetailPlaceable = nil
|
||||||
|
self:fillItemOverview(adEnv, inventory, fullInventory)
|
||||||
|
return
|
||||||
|
end
|
||||||
self:fillStorageDetail(adEnv, inventory, fullInventory)
|
self:fillStorageDetail(adEnv, inventory, fullInventory)
|
||||||
self:addTextRow(self.i18n:getText("spu_section_storage_" .. tostring(SmartPickupFrame.getTabKind(self.currentTab))))
|
self:addTextRow(self.i18n:getText("spu_section_storage_" .. tostring(SmartPickupFrame.getTabKind(self.currentTab))))
|
||||||
for _, entry in ipairs(inventory) do
|
for _, entry in ipairs(inventory) do
|
||||||
@@ -193,6 +200,11 @@ end
|
|||||||
|
|
||||||
function SmartPickupFrame:refreshStorageRows(adEnv)
|
function SmartPickupFrame:refreshStorageRows(adEnv)
|
||||||
local kind = SmartPickupFrame.getTabKind(self.currentTab)
|
local kind = SmartPickupFrame.getTabKind(self.currentTab)
|
||||||
|
if kind ~= nil and self.itemView ~= nil then
|
||||||
|
local fullInventory = self:getInventory(adEnv)
|
||||||
|
self:refreshItemOverview(adEnv, ADStorageStats.filterKind(fullInventory, kind), fullInventory)
|
||||||
|
return
|
||||||
|
end
|
||||||
if kind == nil or #(self.storageRows or {}) == 0 then
|
if kind == nil or #(self.storageRows or {}) == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,320 @@
|
|||||||
|
-- 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)
|
||||||
|
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
|
||||||
|
|
||||||
|
function SmartPickupFrame:alertText(entry)
|
||||||
|
local stat, fc, i18n = entry.stat, SmartPickupFrame.formatCompact, self.i18n
|
||||||
|
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.c1:setText(self:alertText(entry))
|
||||||
|
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 = ADStorageStats.attentionList(stats, SmartPickupFrame.MAX_ATTENTION)
|
||||||
|
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)
|
||||||
|
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 = ADStorageStats.attentionList(stats, SmartPickupFrame.MAX_ATTENTION)
|
||||||
|
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
|
||||||
@@ -19,4 +19,38 @@
|
|||||||
<Profile name="spuCellBold" extends="spuCell">
|
<Profile name="spuCellBold" extends="spuCell">
|
||||||
<textBold value="true"/>
|
<textBold value="true"/>
|
||||||
</Profile>
|
</Profile>
|
||||||
|
<!-- Lagre (v1.52): bjælker = hvid pixel i spillets UI-atlas farvet med imageColor
|
||||||
|
(samme teknik som Precision Farming). Bredden på fyldet sættes i Lua. -->
|
||||||
|
<Profile name="spuBar" extends="baseReference" with="anchorMiddleLeft">
|
||||||
|
<imageUVs value="10px 1010px 4px 4px"/>
|
||||||
|
<positionOrigin value="middleLeft"/>
|
||||||
|
<imageColor value="0.30 0.75 0.35 1"/>
|
||||||
|
<size value="200px 16px"/>
|
||||||
|
</Profile>
|
||||||
|
<Profile name="spuBarBg" extends="spuBar">
|
||||||
|
<imageColor value="0 0 0 0.45"/>
|
||||||
|
</Profile>
|
||||||
|
<Profile name="spuBarThin" extends="spuBar">
|
||||||
|
<size value="200px 10px"/>
|
||||||
|
</Profile>
|
||||||
|
<Profile name="spuBarThinBg" extends="spuBarThin">
|
||||||
|
<imageColor value="0 0 0 0.45"/>
|
||||||
|
</Profile>
|
||||||
|
<!-- markør foran en "kræver handling"-linje -->
|
||||||
|
<Profile name="spuDot" extends="spuBar">
|
||||||
|
<size value="8px 26px"/>
|
||||||
|
</Profile>
|
||||||
|
<!-- nøgletal: stort tal over lille label -->
|
||||||
|
<Profile name="spuKpiValue" extends="spuCellBold">
|
||||||
|
<textSize value="24px"/>
|
||||||
|
<textOffset value="0px 8px"/>
|
||||||
|
</Profile>
|
||||||
|
<Profile name="spuKpiLabel" extends="spuCell">
|
||||||
|
<textSize value="14px"/>
|
||||||
|
<textOffset value="0px -18px"/>
|
||||||
|
<textColor value="0.75 0.75 0.75 1"/>
|
||||||
|
</Profile>
|
||||||
|
<Profile name="spuCellDim" extends="spuCell">
|
||||||
|
<textColor value="0.75 0.75 0.75 1"/>
|
||||||
|
</Profile>
|
||||||
</GUIProfiles>
|
</GUIProfiles>
|
||||||
|
|||||||
@@ -526,5 +526,27 @@
|
|||||||
<text name="spu_fs_days" text="%d døgn"/>
|
<text name="spu_fs_days" text="%d døgn"/>
|
||||||
<text name="spu_fs_toFactory" text="→ fabrik"/>
|
<text name="spu_fs_toFactory" text="→ fabrik"/>
|
||||||
<text name="spu_fs_harvestHeld" text="%d tømmevogn(e) holdes tilbage til høstere der arbejder"/>
|
<text name="spu_fs_harvestHeld" text="%d tømmevogn(e) holdes tilbage til høstere der arbejder"/>
|
||||||
|
<text name="spu_view_title" text="Vis"/>
|
||||||
|
<text name="spu_view_items" text="Varer"/>
|
||||||
|
<text name="spu_view_buildings" text="Bygninger"/>
|
||||||
|
<text name="spu_view_tooltip" text="Varer: samlet beholdning pr. vare (klik for at se hvor den ligger). Bygninger: tabel for én bygning."/>
|
||||||
|
<text name="spu_kpi_value" text="Lagerværdi nu"/>
|
||||||
|
<text name="spu_kpi_gain" text="Ekstra ved toppris"/>
|
||||||
|
<text name="spu_kpi_empty" text="Løber tør snart"/>
|
||||||
|
<text name="spu_kpi_full" text="Næsten fuldt"/>
|
||||||
|
<text name="spu_attention" text="— Kræver handling —"/>
|
||||||
|
<text name="spu_attention_none" text="Intet kræver handling lige nu"/>
|
||||||
|
<text name="spu_alert_empty" text="%s løber tør om %s — gården har %s, bruger %s/døgn"/>
|
||||||
|
<text name="spu_alert_fullIn" text="%s er %d %% fuld — fuld om %s"/>
|
||||||
|
<text name="spu_alert_full" text="%s er %d %% fuld"/>
|
||||||
|
<text name="spu_alert_sell" text="Sælg %s nu: prisen er %d %% af årets top — %s"/>
|
||||||
|
<text name="spu_items_hint" text="(klik på en vare)"/>
|
||||||
|
<text name="spu_icol_amount" text="Beholdning"/>
|
||||||
|
<text name="spu_icol_value" text="Værdi nu"/>
|
||||||
|
<text name="spu_icol_rate" text="Gården netto · rækker"/>
|
||||||
|
<text name="spu_item_perDay" text="%s/døgn"/>
|
||||||
|
<text name="spu_item_fullIn" text="fuld om %s"/>
|
||||||
|
<text name="spu_item_balanced" text="i balance"/>
|
||||||
|
<text name="spu_item_share" text="%d %% af varen"/>
|
||||||
</texts>
|
</texts>
|
||||||
</l10n>
|
</l10n>
|
||||||
|
|||||||
@@ -526,5 +526,27 @@
|
|||||||
<text name="spu_fs_days" text="%d days"/>
|
<text name="spu_fs_days" text="%d days"/>
|
||||||
<text name="spu_fs_toFactory" text="→ factory"/>
|
<text name="spu_fs_toFactory" text="→ factory"/>
|
||||||
<text name="spu_fs_harvestHeld" text="%d unloading trailer(s) held back for working harvesters"/>
|
<text name="spu_fs_harvestHeld" text="%d unloading trailer(s) held back for working harvesters"/>
|
||||||
|
<text name="spu_view_title" text="Show"/>
|
||||||
|
<text name="spu_view_items" text="Goods"/>
|
||||||
|
<text name="spu_view_buildings" text="Buildings"/>
|
||||||
|
<text name="spu_view_tooltip" text="Goods: total stock per good (click to see where it is stored). Buildings: table for one building."/>
|
||||||
|
<text name="spu_kpi_value" text="Stock value now"/>
|
||||||
|
<text name="spu_kpi_gain" text="Extra at top price"/>
|
||||||
|
<text name="spu_kpi_empty" text="Running out soon"/>
|
||||||
|
<text name="spu_kpi_full" text="Almost full"/>
|
||||||
|
<text name="spu_attention" text="— Needs attention —"/>
|
||||||
|
<text name="spu_attention_none" text="Nothing needs attention right now"/>
|
||||||
|
<text name="spu_alert_empty" text="%s runs out in %s — farm has %s, uses %s/day"/>
|
||||||
|
<text name="spu_alert_fullIn" text="%s is %d %% full — full in %s"/>
|
||||||
|
<text name="spu_alert_full" text="%s is %d %% full"/>
|
||||||
|
<text name="spu_alert_sell" text="Sell %s now: price is %d %% of the yearly top — %s"/>
|
||||||
|
<text name="spu_items_hint" text="(click a good)"/>
|
||||||
|
<text name="spu_icol_amount" text="Stock"/>
|
||||||
|
<text name="spu_icol_value" text="Value now"/>
|
||||||
|
<text name="spu_icol_rate" text="Farm net · lasts"/>
|
||||||
|
<text name="spu_item_perDay" text="%s/day"/>
|
||||||
|
<text name="spu_item_fullIn" text="full in %s"/>
|
||||||
|
<text name="spu_item_balanced" text="balanced"/>
|
||||||
|
<text name="spu_item_share" text="%d %% of the good"/>
|
||||||
</texts>
|
</texts>
|
||||||
</l10n>
|
</l10n>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
<modDesc descVersion="107">
|
<modDesc descVersion="107">
|
||||||
<author>masterdraco</author>
|
<author>masterdraco</author>
|
||||||
<version>1.51.5.0</version>
|
<version>1.52.0.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -76,5 +76,111 @@ check("ST3 paller med", egg.count, 24)
|
|||||||
check("ST3 pladser med", egg.slots, 4000)
|
check("ST3 pladser med", egg.slots, 4000)
|
||||||
check("ST3 procent af pladser", egg.percent, 1)
|
check("ST3 procent af pladser", egg.percent, 1)
|
||||||
|
|
||||||
|
|
||||||
|
-- ---- v1.52 vareoversigt ----
|
||||||
|
|
||||||
|
-- SV1: gruppér fanens bygninger pr. vare; bygninger sorteret mest først, varer efter titel
|
||||||
|
local titles = {[31] = "Straw", [5] = "Sugarbeet", [9] = "Milk"}
|
||||||
|
local titleOf = function(ft) return titles[ft] end
|
||||||
|
local siloInv = {
|
||||||
|
{name = "Farma #1", placeable = "p1", entries = {{fillType = 31, level = 300, capacity = 1000}, {fillType = 5, level = 50, capacity = 100}}},
|
||||||
|
{name = "Farma #2", placeable = "p2", entries = {{fillType = 31, level = 700, capacity = 1000}}},
|
||||||
|
{name = "Tom", placeable = "p3", entries = {}},
|
||||||
|
}
|
||||||
|
local groups = S.groupByFillType(siloInv, titleOf)
|
||||||
|
check("SV1 to varer", #groups, 2)
|
||||||
|
check("SV1 alfabetisk: Straw først", groups[1].title, "Straw")
|
||||||
|
check("SV1 straw total", groups[1].level, 1000)
|
||||||
|
check("SV1 straw kapacitet", groups[1].capacity, 2000)
|
||||||
|
check("SV1 straw procent", groups[1].percent, 50)
|
||||||
|
check("SV1 to siloer med straw", #groups[1].buildings, 2)
|
||||||
|
check("SV1 flest først", groups[1].buildings[1].name, "Farma #2")
|
||||||
|
check("SV1 bygning procent", groups[1].buildings[1].percent, 70)
|
||||||
|
check("SV1 placeable med", groups[1].buildings[2].placeable, "p1")
|
||||||
|
check("SV1 tomt inventar", #S.groupByFillType({}, titleOf), 0)
|
||||||
|
|
||||||
|
-- SV2: gårdens kapacitet pr. vare
|
||||||
|
local caps = S.farmCapacities(siloInv)
|
||||||
|
check("SV2 kapacitet straw", caps[31], 2000)
|
||||||
|
|
||||||
|
-- SV3: varens statistik: døgn til tom på gårdens netto, pris i % af top
|
||||||
|
local straw = S.groupStats(groups[1], {level = 1500, capacity = 3000}, {consumption = 1000, production = 0}, {price = 0.5, top = 1.0})
|
||||||
|
check("SV3 døgn til tom", straw.days, 1.5)
|
||||||
|
check("SV3 døgn til fuld nil (falder)", straw.daysToFull, nil)
|
||||||
|
check("SV3 værdi nu", straw.valueNow, 500)
|
||||||
|
check("SV3 pris % af top", straw.priceRatio, 0.5)
|
||||||
|
check("SV3 tomt-niveau gul (< 3 døgn)", straw.emptyLevel, "yellow")
|
||||||
|
check("SV3 samlet niveau", straw.severity, "yellow")
|
||||||
|
|
||||||
|
-- SV4: produceret vare -> døgn til fuld på gårdens ledige kapacitet
|
||||||
|
local sugar = S.groupStats({fillType = 5, title = "Sugar", level = 950, capacity = 1000, percent = 95, buildings = {}},
|
||||||
|
{level = 950, capacity = 1000}, {consumption = 0, production = 100}, nil)
|
||||||
|
check("SV4 døgn til fuld", sugar.daysToFull, 0.5)
|
||||||
|
check("SV4 producerer", sugar.producing, true)
|
||||||
|
check("SV4 fuld-niveau rød (95 %)", sugar.fullLevel, "red")
|
||||||
|
check("SV4 ingen pris -> nil", sugar.valueNow, nil)
|
||||||
|
|
||||||
|
-- SV5: fuld lagervare der ikke produceres er ikke et problem
|
||||||
|
local stock = S.groupStats({fillType = 31, title = "Straw", level = 990, capacity = 1000, percent = 99, buildings = {}}, nil, nil, nil)
|
||||||
|
check("SV5 fuld lager uden produktion -> intet", stock.fullLevel, nil)
|
||||||
|
check("SV5 severity nil", stock.severity, nil)
|
||||||
|
|
||||||
|
-- SV6: tærskler
|
||||||
|
check("SV6 < 1 døgn rød", S.emptyLevel(0.5), "red")
|
||||||
|
check("SV6 < 3 døgn gul", S.emptyLevel(2.9), "yellow")
|
||||||
|
check("SV6 5 døgn intet", S.emptyLevel(5), nil)
|
||||||
|
check("SV6 nil intet", S.emptyLevel(nil), nil)
|
||||||
|
check("SV6 fuld 85 % gul", S.fullLevel(85, true), "yellow")
|
||||||
|
check("SV6 fuld 84 % intet", S.fullLevel(84, true), nil)
|
||||||
|
|
||||||
|
-- SV7: bygningsniveau: stald-foder næsten tomt, fabrik-output næsten fuldt
|
||||||
|
check("SV7 stald 8 % rød", S.buildingLevel({percent = 8}, "husbandry", false), "red")
|
||||||
|
check("SV7 stald 20 % gul", S.buildingLevel({percent = 20}, "husbandry", false), "yellow")
|
||||||
|
check("SV7 stald-output (mælk) 8 % intet", S.buildingLevel({percent = 8}, "husbandry", true), nil)
|
||||||
|
check("SV7 output 96 % rød", S.buildingLevel({percent = 96, isOutput = true}, "factory", false), "red")
|
||||||
|
check("SV7 silo 50 % intet", S.buildingLevel({percent = 50}, "silo", false), nil)
|
||||||
|
|
||||||
|
-- SV8: kræver handling — rød tom > rød fuld > gul tom > gul fuld > sælg (efter kroner); højst max
|
||||||
|
local mk = function(t) t.buildings = {}; return t end
|
||||||
|
local list = S.attentionList({
|
||||||
|
mk({title = "A", emptyLevel = "yellow", days = 2, consumption = 1}),
|
||||||
|
mk({title = "B", fullLevel = "red", percent = 97, producing = true}),
|
||||||
|
mk({title = "C", emptyLevel = "red", days = 0.4, consumption = 1}),
|
||||||
|
mk({title = "D", priceRatio = 0.95, valueNow = 50000, net = 0}),
|
||||||
|
mk({title = "E", priceRatio = 0.97, valueNow = 900000, net = 0}),
|
||||||
|
mk({title = "F", priceRatio = 0.99, valueNow = 900000, net = -5}), -- gården æder den: sælg ikke
|
||||||
|
mk({title = "G", priceRatio = 0.99, valueNow = 5000, net = 0}), -- for lille
|
||||||
|
}, 4)
|
||||||
|
check("SV8 højst 4", #list, 4)
|
||||||
|
check("SV8 1: rød tom", list[1].stat.title, "C")
|
||||||
|
check("SV8 1: art", list[1].kind, "empty")
|
||||||
|
check("SV8 2: rød fuld", list[2].stat.title, "B")
|
||||||
|
check("SV8 3: gul tom", list[3].stat.title, "A")
|
||||||
|
check("SV8 4: største salg", list[4].stat.title, "E")
|
||||||
|
check("SV8 4: art", list[4].kind, "sell")
|
||||||
|
|
||||||
|
-- SV9: nøgletal for fanen
|
||||||
|
local kpi = S.kpis({
|
||||||
|
mk({valueNow = 100, valueTop = 150, emptyLevel = "red"}),
|
||||||
|
mk({valueNow = 50, valueTop = 50, fullLevel = "yellow"}),
|
||||||
|
mk({}),
|
||||||
|
})
|
||||||
|
check("SV9 værdi nu", kpi.valueNow, 150)
|
||||||
|
check("SV9 ekstra ved top", kpi.gain, 50)
|
||||||
|
check("SV9 løber tør", kpi.emptyCount, 1)
|
||||||
|
check("SV9 fyldt", kpi.fullCount, 1)
|
||||||
|
|
||||||
|
-- SV10: fyldningsbrøk til bjælker, klemt til [0,1]
|
||||||
|
check("SV10 halv", S.fraction(50), 0.5)
|
||||||
|
check("SV10 over 100", S.fraction(130), 1)
|
||||||
|
check("SV10 nil", S.fraction(nil), 0)
|
||||||
|
|
||||||
|
-- SV11: skur uden pladsgrænse (kapacitet 0) -> varens procent ukendt, ikke 940 %
|
||||||
|
local shedGroups = S.groupByFillType({
|
||||||
|
{name = "A", entries = {{fillType = 9, level = 40, capacity = 100}}},
|
||||||
|
{name = "B", entries = {{fillType = 9, level = 900, capacity = 0}}},
|
||||||
|
}, titleOf)
|
||||||
|
check("SV11 procent nil ved ubegrænset skur", shedGroups[1].percent, nil)
|
||||||
|
|
||||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||||
os.exit(failures == 0 and 0 or 1)
|
os.exit(failures == 0 and 0 or 1)
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
-- Kør: luajit tests/test_storageView.lua (fra repo-roden)
|
||||||
|
-- Røgtest af vareoversigten i fanerne Lagre (v1.52) med falske GUI-elementer: opbygning, fold ud,
|
||||||
|
-- opdatering hvert sekund og skift til bygningsvisning må ikke kaste, og tallene skal lande i cellerne.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
-- ---- falske GUI-elementer ----
|
||||||
|
local Element = {}
|
||||||
|
Element.__index = Element
|
||||||
|
local function newElement(names, size)
|
||||||
|
local element = setmetatable({children = {}, text = nil, visible = true, size = size or {0.5, 0.02}}, Element)
|
||||||
|
for _, name in ipairs(names or {}) do
|
||||||
|
element.children[name] = setmetatable({children = {}, name = name, visible = true, size = {0.2, 0.01}}, Element)
|
||||||
|
end
|
||||||
|
return element
|
||||||
|
end
|
||||||
|
function Element:clone() local copy = newElement({}, {self.size[1], self.size[2]}); for n, c in pairs(self.children) do copy.children[n] = setmetatable({children = {}, name = n, visible = true, size = {c.size[1], c.size[2]}}, Element) end; return copy end
|
||||||
|
function Element:getDescendantByName(name) return self.children[name] end
|
||||||
|
function Element:setText(text) self.text = text end
|
||||||
|
function Element:setTexts(texts) self.texts = texts end
|
||||||
|
function Element:setState(state) self.state = state end
|
||||||
|
function Element:setVisible(visible) self.visible = visible end
|
||||||
|
function Element:setSize(x, y) self.size = {x, y} end
|
||||||
|
function Element:setImageColor(_, r, g, b, a) self.color = {r, g, b, a} end
|
||||||
|
function Element:setTextColor(r, g, b, a) self.textColor = {r, g, b, a} end
|
||||||
|
function Element:delete() end
|
||||||
|
function Element:mouseEvent() return false end
|
||||||
|
|
||||||
|
-- ---- spil-globaler ----
|
||||||
|
Class = function() return {} end
|
||||||
|
TabbedMenuFrameElement = {}
|
||||||
|
InputAction = {}
|
||||||
|
FocusManager = {loadElementFromCustomValues = function() end, removeElement = function() end}
|
||||||
|
Logging = {warning = function(...) print("WARN", string.format(...)) end, info = function() end}
|
||||||
|
local titles = {[31] = "Straw", [5] = "Sugar", [9] = "Milk"}
|
||||||
|
g_fillTypeManager = {getFillTypeTitleByIndex = function(_, i) return titles[i] end, getFillTypeNameByIndex = function(_, i) return titles[i] end}
|
||||||
|
g_currentMission = {getFarmId = function() return 1 end}
|
||||||
|
g_i18n = nil
|
||||||
|
|
||||||
|
dofile("FS25_ADSmartPickup/adStorageStats.lua")
|
||||||
|
ADRunsController = {store = {runs = {}}}
|
||||||
|
ADRuns = {getRun = function() return nil end}
|
||||||
|
ADFlowView = {wrapText = function(text) return {text} end}
|
||||||
|
ADSources = {
|
||||||
|
getFarmInventory = function() return ADSources.inventory end,
|
||||||
|
getFarmRates = function() return {[31] = {consumption = 1000, production = 0}, [5] = {consumption = 0, production = 500}} end,
|
||||||
|
getMarketQuote = function(ft) if ft == 5 then return {price = 1.0, top = 1.05} end return nil end,
|
||||||
|
}
|
||||||
|
ADSources.inventory = {
|
||||||
|
{kind = "silo", name = "Farma #1", placeable = "p1", entries = {{fillType = 31, level = 300, capacity = 1000}, {fillType = 5, level = 20000, capacity = 21000}}},
|
||||||
|
{kind = "silo", name = "Farma #2", placeable = "p2", entries = {{fillType = 31, level = 700, capacity = 1000}}},
|
||||||
|
{kind = "factory", name = "Sugar mill", placeable = "p3", entries = {{fillType = 5, level = 10, capacity = 100, isOutput = true}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
local i18nTexts = {}
|
||||||
|
local i18n = {getText = function(_, key) i18nTexts[key] = true; if key:find("days") then return "%.1f d" end
|
||||||
|
if key:find("share") then return "%d %%" end
|
||||||
|
if key:find("alert_full") then return "%s %d %% %s" end
|
||||||
|
if key:find("alert_empty") then return "%s %s %s %s" end
|
||||||
|
if key:find("alert_sell") then return "%s %d %s" end
|
||||||
|
if key:find("pallets") then return "%d p" end
|
||||||
|
if key:find("perDay") or key:find("fullIn") then return "%s" end
|
||||||
|
return key end}
|
||||||
|
|
||||||
|
SmartPickupFrame = {}
|
||||||
|
local function load(path) local chunk = assert(loadfile(path)); chunk() end
|
||||||
|
SmartPickupFrame.new = nil
|
||||||
|
load("FS25_ADSmartPickup/gui/SmartPickupFrame.lua")
|
||||||
|
load("FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua")
|
||||||
|
load("FS25_ADSmartPickup/gui/SmartPickupStorageView.lua")
|
||||||
|
load("FS25_ADSmartPickup/gui/SmartPickupFlowTab.lua")
|
||||||
|
|
||||||
|
-- en frame uden spillets klasse-maskineri: kun det fanen bruger
|
||||||
|
local frame = setmetatable({}, {__index = SmartPickupFrame})
|
||||||
|
frame.i18n = i18n
|
||||||
|
frame.clonedRows, frame.rowData = {}, {}
|
||||||
|
frame.currentTab = 7 -- Siloer
|
||||||
|
local rows = {}
|
||||||
|
frame.boxLayout = {elements = rows, invalidateLayout = function() end}
|
||||||
|
frame.productPrefab = newElement({"option", "title"})
|
||||||
|
frame.productPrefab.children.option.children.ignore = newElement()
|
||||||
|
frame.textPrefab = newElement({"title"})
|
||||||
|
frame.kpiPrefab = newElement({"v1", "l1", "v2", "l2", "v3", "l3", "v4", "l4"})
|
||||||
|
frame.alertPrefab = newElement({"dot", "c1"})
|
||||||
|
frame.itemPrefab = newElement({"c1", "c2", "c3", "c4", "bar", "barBg"})
|
||||||
|
frame.subPrefab = newElement({"c1", "c2", "c3", "c4", "bar", "barBg"})
|
||||||
|
frame.statPrefab = newElement({"c1", "c2", "c3", "c4", "c5", "c6", "c7"})
|
||||||
|
frame.gridPrefab = newElement({"c1", "c2", "c3"})
|
||||||
|
for _, prefab in ipairs({frame.productPrefab, frame.textPrefab, frame.kpiPrefab, frame.alertPrefab, frame.itemPrefab, frame.subPrefab, frame.statPrefab, frame.gridPrefab}) do
|
||||||
|
local base = prefab.clone
|
||||||
|
prefab.clone = function(self) local copy = base(self); if self.children.option then copy.children.option.children.ignore = newElement() end; table.insert(rows, copy); return copy end
|
||||||
|
end
|
||||||
|
frame.getAdEnv = function() return {} end
|
||||||
|
frame.setStatusText = function() end
|
||||||
|
frame.refreshButtons = function() end
|
||||||
|
frame.restoreFocusIfLost = function() end
|
||||||
|
frame.colorRows = function() end
|
||||||
|
for _, name in ipairs({"startButtonInfo", "renameButtonInfo", "deleteButtonInfo", "stopAllButtonInfo", "renameBuildingButtonInfo"}) do frame[name] = {} end
|
||||||
|
|
||||||
|
local function texts()
|
||||||
|
local list = {}
|
||||||
|
for _, row in ipairs(rows) do
|
||||||
|
for _, name in ipairs({"title", "c1", "c2", "c3", "c4", "v1", "v3"}) do
|
||||||
|
local child = row.children[name]
|
||||||
|
if child ~= nil and child.text ~= nil then table.insert(list, child.text) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return table.concat(list, "|")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- SVW1: opbygning af Siloer i varevisning
|
||||||
|
local ok, err = pcall(frame.rebuild, frame)
|
||||||
|
check("SVW1 opbygning kaster ikke", ok, true)
|
||||||
|
if not ok then print(err) end
|
||||||
|
local all = texts()
|
||||||
|
check("SVW1 straw-række", all:find("+ Straw", 1, true) ~= nil, true)
|
||||||
|
check("SVW1 sugar-række", all:find("+ Sugar", 1, true) ~= nil, true)
|
||||||
|
check("SVW1 straw 1.0k/2.0k 50 %", all:find("1.0k/2.0k · 50 %", 1, true) ~= nil, true)
|
||||||
|
check("SVW1 ingen bygninger før klik", all:find("Farma #2", 1, true), nil)
|
||||||
|
check("SVW1 fabrikkens sukker ikke i siloer", all:find("Sugar mill", 1, true), nil)
|
||||||
|
check("SVW1 kræver handling: straw løber tør", all:find("spu_alert", 1, true) == nil and all:find("Straw 1.0 d", 1, true) ~= nil, true)
|
||||||
|
|
||||||
|
-- SVW2: klik på straw folder ud; bygninger med mest først
|
||||||
|
local strawRow = frame.itemView.itemRows[1]
|
||||||
|
ok, err = pcall(frame.toggleItem, frame, 31)
|
||||||
|
check("SVW2 fold ud kaster ikke", ok, true)
|
||||||
|
if not ok then print(err) end
|
||||||
|
all = texts()
|
||||||
|
local first, second = all:find("Farma #2", 1, true), all:find("Farma #1", 1, true)
|
||||||
|
check("SVW2 Farma #2 vist", first ~= nil, true)
|
||||||
|
check("SVW2 flest først", first ~= nil and second ~= nil and first < second, true)
|
||||||
|
check("SVW2 markør skiftet", all:find("– Straw", 1, true) ~= nil, true)
|
||||||
|
check("SVW2 andel", all:find("70 %", 1, true) ~= nil, true)
|
||||||
|
|
||||||
|
-- SVW3: opdatering hvert sekund med nye tal, samme struktur
|
||||||
|
ADSources.inventory[2].entries[1].level = 900
|
||||||
|
ok, err = pcall(frame.refreshLive, frame)
|
||||||
|
check("SVW3 opdatering kaster ikke", ok, true)
|
||||||
|
if not ok then print(err) end
|
||||||
|
check("SVW3 ny total i cellen", texts():find("1.2k/2.0k · 60 %", 1, true) ~= nil, true)
|
||||||
|
local bar = frame.itemView.itemRows[1].bar
|
||||||
|
check("SVW3 bjælke 60 %", math.floor(bar.size[1] / 0.2 * 100 + 0.5), 60)
|
||||||
|
check("SVW3 bjælke gul (løber tør < 3 d)", bar.color ~= nil and bar.color[1] > 0.9 and bar.color[2] > 0.7, true)
|
||||||
|
|
||||||
|
-- SVW4: skift til bygningsvisning og tilbage
|
||||||
|
ok, err = pcall(frame.onClickStorageView, frame, SmartPickupFrame.STORAGE_VIEW_BUILDINGS)
|
||||||
|
check("SVW4 bygningsvisning kaster ikke", ok, true)
|
||||||
|
if not ok then print(err) end
|
||||||
|
check("SVW4 ingen varevisning", frame.itemView, nil)
|
||||||
|
ok = pcall(frame.onClickStorageView, frame, SmartPickupFrame.STORAGE_VIEW_ITEMS)
|
||||||
|
check("SVW4 tilbage til varer", ok and frame.itemView ~= nil, true)
|
||||||
|
|
||||||
|
-- SVW5: fabrikker-fanen: output næsten fuldt -> rød
|
||||||
|
frame.currentTab = 6
|
||||||
|
ok, err = pcall(frame.rebuild, frame)
|
||||||
|
check("SVW5 fabrikker kaster ikke", ok, true)
|
||||||
|
if not ok then print(err) end
|
||||||
|
|
||||||
|
-- SVW7: udfoldet vare hvor to siloer bytter plads i mængde -> ingen genopbygning, rækkerne følger bygningen
|
||||||
|
frame.currentTab = 7
|
||||||
|
frame.expandedItems = nil
|
||||||
|
frame:rebuild()
|
||||||
|
frame:toggleItem(31)
|
||||||
|
local subsBefore = frame.itemView.itemRows[1].subs
|
||||||
|
ADSources.inventory[1].entries[1].level = 950 -- Farma #1 (300 -> 950) forbi Farma #2 (900)
|
||||||
|
for _ = 1, 12 do frame:refreshLive() end
|
||||||
|
check("SVW7 samme rækker (ingen genopbygning)", frame.itemView.itemRows[1].subs, subsBefore)
|
||||||
|
check("SVW7 første underrække er stadig Farma #2", subsBefore[1].cells[1].text, "Farma #2")
|
||||||
|
check("SVW7 anden underrække viser Farma #1's nye tal", subsBefore[2].cells[2].text:find("950", 1, true) ~= nil, true)
|
||||||
|
|
||||||
|
-- SVW6: alle brugte l10n-nøgler findes i begge sprogfiler
|
||||||
|
for _, file in ipairs({"FS25_ADSmartPickup/l10n/l10n_da.xml", "FS25_ADSmartPickup/l10n/l10n_en.xml"}) do
|
||||||
|
local content = io.open(file):read("*a")
|
||||||
|
for key in pairs(i18nTexts) do
|
||||||
|
if key:find("^spu_") and not content:find('name="' .. key .. '"', 1, true) then
|
||||||
|
failures = failures + 1
|
||||||
|
print("FAIL SVW6 mangler " .. key .. " i " .. file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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