feat(lagre): 'Kræver handling' → flow: løber tør = åbn/opret forsyningsflow (eller 'producér/køb' når et flow ikke hjælper), sælg nu = åbn/opret salgsflow; hint på hver linje (v1.60.0.0)
This commit is contained in:
@@ -32,6 +32,7 @@ if ADSmartPickup.dir ~= nil and source ~= nil then
|
|||||||
source(ADSmartPickup.dir .. "gui/SmartPickupFieldGuide.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupFieldGuide.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupFlowGuide.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupFlowGuide.lua")
|
||||||
source(ADSmartPickup.dir .. "gui/SmartPickupEditPanel.lua")
|
source(ADSmartPickup.dir .. "gui/SmartPickupEditPanel.lua")
|
||||||
|
source(ADSmartPickup.dir .. "gui/SmartPickupAttentionFlow.lua")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
|
-- Markøren er dér hvor trækkerens næse stopper; læssetriggeren ligger et
|
||||||
|
|||||||
@@ -283,3 +283,40 @@ function ADStorageStats.fraction(percent)
|
|||||||
if percent == nil then return 0 end
|
if percent == nil then return 0 end
|
||||||
return math.max(0, math.min(1, percent / 100))
|
return math.max(0, math.min(1, percent / 100))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ---- "kræver handling" -> flow ----
|
||||||
|
|
||||||
|
-- Bygninger der BRUGER varen (fabrik-input, stald-foder) — silo/objektlager lagrer kun. -> {{name, placeable, level}}
|
||||||
|
function ADStorageStats.consumersOf(inventory, fillType)
|
||||||
|
local result = {}
|
||||||
|
for _, building in ipairs(inventory or {}) do
|
||||||
|
if building.kind == "factory" or building.kind == "husbandry" then
|
||||||
|
for _, entry in ipairs(building.entries or {}) do
|
||||||
|
if entry.fillType == fillType and not entry.isOutput then
|
||||||
|
table.insert(result, {name = building.name, placeable = building.placeable, level = entry.level or 0})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Hjælper et flow? Et flow flytter kun varer: det nytter når varen ligger andre steder end hos forbrugerne.
|
||||||
|
-- s = {farmLevel, consumerLevel, perDay, consumerCount, existingRunId} -> "open" | "flow" | "produce" | "none"
|
||||||
|
ADStorageStats.MIN_FLOW_LITERS = 5000
|
||||||
|
ADStorageStats.MIN_FLOW_DAY_SHARE = 0.25 -- mindst en kvart dags forbrug skal kunne flyttes
|
||||||
|
|
||||||
|
function ADStorageStats.flowAdvice(s)
|
||||||
|
if (s.consumerCount or 0) == 0 then return "none" end
|
||||||
|
if s.existingRunId ~= nil then return "open" end
|
||||||
|
local elsewhere = (s.farmLevel or 0) - (s.consumerLevel or 0)
|
||||||
|
local needed = math.max(ADStorageStats.MIN_FLOW_LITERS, (s.perDay or 0) * ADStorageStats.MIN_FLOW_DAY_SHARE)
|
||||||
|
return elsewhere >= needed and "flow" or "produce"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- "Sælg nu" -> udkørsels-flow: s = {sourceCount, existingRunId} -> "open" | "flow" | "none"
|
||||||
|
function ADStorageStats.sellAdvice(s)
|
||||||
|
if (s.sourceCount or 0) == 0 then return "none" end
|
||||||
|
if s.existingRunId ~= nil then return "open" end
|
||||||
|
return "flow"
|
||||||
|
end
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
-- Smart Pickup — "Kræver handling" -> flow (v1.60)
|
||||||
|
-- En "løber tør"-linje i lagerfanerne kan klikkes: findes der et flow der leverer varen til forbrugerne, åbnes
|
||||||
|
-- det; ligger varen andre steder på gården, åbnes "Nyt flow" udfyldt (ind til forbrugerne, varen valgt); ellers
|
||||||
|
-- siges det at et flow ikke hjælper (producér/køb). Afgørelsen er ADStorageStats.flowAdvice (testet).
|
||||||
|
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
|
||||||
|
|
||||||
|
local function fillTypeName(fillType)
|
||||||
|
return g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or tostring(fillType)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Forsyningsflow der allerede leverer varen til en af forbrugerne -> run eller nil.
|
||||||
|
local function existingSupplyRun(consumerIds, name)
|
||||||
|
for _, run in ipairs(ADRunsController.store.runs) do
|
||||||
|
if run.kind ~= "outbound" then
|
||||||
|
local carries = run.ingredientMode ~= "manual"
|
||||||
|
for _, ingredient in ipairs(run.ingredients or {}) do
|
||||||
|
if ingredient == name then carries = true end
|
||||||
|
end
|
||||||
|
if carries then
|
||||||
|
for _, id in ipairs(run.targetBuildingIds or {}) do
|
||||||
|
if consumerIds[id] then return run end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Udkørselsflow der allerede sælger varen fra en af bygningerne -> run eller nil.
|
||||||
|
local function existingSellRun(sourceIds, name)
|
||||||
|
for _, run in ipairs(ADRunsController.store.runs) do
|
||||||
|
if run.kind == "outbound" then
|
||||||
|
local sells = false
|
||||||
|
for _, product in ipairs(run.products or {}) do
|
||||||
|
if product.fillType == name and (product.policy == "sell" or product.policy == "sellElseStore") then sells = true end
|
||||||
|
end
|
||||||
|
if sells then
|
||||||
|
for _, id in ipairs(run.sourceBuildingIds or {}) do
|
||||||
|
if sourceIds[id] then return run end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- "Sælg nu": kilder = fanens bygninger med varen.
|
||||||
|
local function sellAdviceFor(stat)
|
||||||
|
local sourceIds, ids = {}, {}
|
||||||
|
for _, building in ipairs(stat.buildings or {}) do
|
||||||
|
local id = building.placeable ~= nil and ADBuildings.getId(building.placeable) or nil
|
||||||
|
if id ~= nil and not sourceIds[id] then
|
||||||
|
sourceIds[id] = true
|
||||||
|
table.insert(ids, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local name = fillTypeName(stat.fillType)
|
||||||
|
local run = existingSellRun(sourceIds, name)
|
||||||
|
local advice = ADStorageStats.sellAdvice({sourceCount = #ids, existingRunId = run ~= nil and run.id or nil})
|
||||||
|
return advice, {buildingIds = ids, run = run, name = name, stat = stat, sell = true}
|
||||||
|
end
|
||||||
|
|
||||||
|
-- entry fra ADStorageStats.attentionList -> advice ("open"|"flow"|"produce"|"none"), info
|
||||||
|
function SmartPickupFrame:attentionAdvice(entry)
|
||||||
|
if entry ~= nil and entry.kind == "sell" and entry.stat ~= nil then return sellAdviceFor(entry.stat) end
|
||||||
|
if entry == nil or entry.kind ~= "empty" or entry.stat == nil then return "none", nil end
|
||||||
|
local stat = entry.stat
|
||||||
|
local consumers = ADStorageStats.consumersOf(self.lastFullInventory, stat.fillType)
|
||||||
|
local consumerIds, consumerLevel, ids = {}, 0, {}
|
||||||
|
for _, consumer in ipairs(consumers) do
|
||||||
|
consumerLevel = consumerLevel + consumer.level
|
||||||
|
local id = consumer.placeable ~= nil and ADBuildings.getId(consumer.placeable) or nil
|
||||||
|
if id ~= nil and not consumerIds[id] then
|
||||||
|
consumerIds[id] = true
|
||||||
|
table.insert(ids, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local name = fillTypeName(stat.fillType)
|
||||||
|
local run = existingSupplyRun(consumerIds, name)
|
||||||
|
local advice = ADStorageStats.flowAdvice({farmLevel = stat.farmLevel or stat.level, consumerLevel = consumerLevel,
|
||||||
|
perDay = stat.consumption, consumerCount = #ids, existingRunId = run ~= nil and run.id or nil})
|
||||||
|
return advice, {consumers = consumers, buildingIds = ids, run = run, name = name, stat = stat}
|
||||||
|
end
|
||||||
|
|
||||||
|
function SmartPickupFrame:attentionHint(advice, info)
|
||||||
|
if advice == "open" then return string.format(self.i18n:getText("spu_attention_hintOpen"), tostring(info.run.name)) end
|
||||||
|
if advice == "flow" then return self.i18n:getText(info.sell and "spu_attention_hintSellFlow" or "spu_attention_hintFlow") end
|
||||||
|
if advice == "produce" then return self.i18n:getText("spu_attention_hintProduce") end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
function SmartPickupFrame:onAttentionClick(entry)
|
||||||
|
self:safe(function(frame)
|
||||||
|
local adEnv = frame.getAdEnv()
|
||||||
|
local advice, info = frame:attentionAdvice(entry)
|
||||||
|
if adEnv == nil or advice == "none" then return end
|
||||||
|
if advice == "open" then
|
||||||
|
frame.selectedRunId = info.run.id
|
||||||
|
frame:switchTab(SmartPickupFrame.TAB_FLOWS)
|
||||||
|
frame:openEditPanel("flow", info.run.id)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if advice == "produce" then
|
||||||
|
local text = string.format(frame.i18n:getText("spu_attention_produce"), tostring(info.stat.title),
|
||||||
|
SmartPickupFrame.formatCompact(info.stat.farmLevel or 0))
|
||||||
|
frame.statusOverride, frame.statusOverrideTicks = text, SmartPickupFrame.STATUS_OVERRIDE_TICKS
|
||||||
|
if ADFieldJobs ~= nil and ADFieldJobs.notify ~= nil then ADFieldJobs.notify(text, false) end
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local guide
|
||||||
|
if info.sell then
|
||||||
|
-- udfyldt "Nyt flow": ud af bygningerne med varen, varen til salg
|
||||||
|
local name = string.format(frame.i18n:getText("spu_attention_sellFlowName"), tostring(info.stat.title))
|
||||||
|
guide = ADFlowGuide.openNew(name, {})
|
||||||
|
guide.draft.kind = "outbound"
|
||||||
|
guide.draft.sourceBuildingIds = info.buildingIds
|
||||||
|
guide.draft.products = {{fillType = info.name, policy = "sell"}}
|
||||||
|
else
|
||||||
|
-- udfyldt "Nyt flow": ind til forbrugerne, kun denne vare; guiden åbnes på oversigten (Gem/Start eller [Ret])
|
||||||
|
local name = string.format(frame.i18n:getText("spu_attention_flowName"), tostring(info.stat.title), tostring(info.consumers[1].name))
|
||||||
|
guide = ADFlowGuide.openNew(name, {})
|
||||||
|
guide.draft.targetBuildingIds = info.buildingIds
|
||||||
|
guide.draft.ingredientMode = "manual"
|
||||||
|
guide.draft.ingredients = {info.name}
|
||||||
|
end
|
||||||
|
guide = ADGuide.new(ADFlowGuide.DEF, guide.draft, frame:flowGuideContext(adEnv, guide.draft), {step = "summary"})
|
||||||
|
frame:switchTab(SmartPickupFrame.TAB_FLOWS)
|
||||||
|
frame:openGuide("flow", guide)
|
||||||
|
end)
|
||||||
|
end
|
||||||
@@ -76,6 +76,7 @@ end
|
|||||||
|
|
||||||
-- Fanens varer med statistik; gårdens totaler/kapacitet/rater regnes over HELE gården.
|
-- Fanens varer med statistik; gårdens totaler/kapacitet/rater regnes over HELE gården.
|
||||||
function SmartPickupFrame:computeItemStats(adEnv, inventory, fullInventory)
|
function SmartPickupFrame:computeItemStats(adEnv, inventory, fullInventory)
|
||||||
|
self.lastFullInventory = fullInventory -- til "kræver handling" -> flow (hvem bruger varen)
|
||||||
local farmId = g_currentMission:getFarmId()
|
local farmId = g_currentMission:getFarmId()
|
||||||
local totals = ADStorageStats.farmTotals(fullInventory)
|
local totals = ADStorageStats.farmTotals(fullInventory)
|
||||||
local capacities = ADStorageStats.farmCapacities(fullInventory)
|
local capacities = ADStorageStats.farmCapacities(fullInventory)
|
||||||
@@ -196,7 +197,13 @@ function SmartPickupFrame:alertText(entry)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function SmartPickupFrame:setAlertRow(row, entry)
|
function SmartPickupFrame:setAlertRow(row, entry)
|
||||||
row.c1:setText(self:alertText(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
|
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))
|
row.dot:setImageColor(nil, unpackFn(SmartPickupFrame.LEVEL_COLORS[entry.level] or SmartPickupFrame.LEVEL_COLORS.green))
|
||||||
end
|
end
|
||||||
@@ -267,6 +274,8 @@ function SmartPickupFrame:fillItemOverview(adEnv, inventory, fullInventory)
|
|||||||
local element = self:addRow(self.alertPrefab, {kind = "text"})
|
local element = self:addRow(self.alertPrefab, {kind = "text"})
|
||||||
local row = {c1 = element:getDescendantByName("c1"), dot = element:getDescendantByName("dot")}
|
local row = {c1 = element:getDescendantByName("c1"), dot = element:getDescendantByName("dot")}
|
||||||
self:setAlertRow(row, entry)
|
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)
|
table.insert(self.itemView.alertRows, row)
|
||||||
end
|
end
|
||||||
self:addTextRow(i18n:getText("spu_section_storage_" .. tostring(SmartPickupFrame.getTabKind(self.currentTab))) .. " " .. i18n:getText("spu_items_hint"))
|
self:addTextRow(i18n:getText("spu_section_storage_" .. tostring(SmartPickupFrame.getTabKind(self.currentTab))) .. " " .. i18n:getText("spu_items_hint"))
|
||||||
|
|||||||
@@ -549,6 +549,13 @@
|
|||||||
<text name="spu_kpi_full" text="Næsten fuldt"/>
|
<text name="spu_kpi_full" text="Næsten fuldt"/>
|
||||||
<text name="spu_attention" text="— Kræver handling —"/>
|
<text name="spu_attention" text="— Kræver handling —"/>
|
||||||
<text name="spu_attention_none" text="Intet kræver handling lige nu"/>
|
<text name="spu_attention_none" text="Intet kræver handling lige nu"/>
|
||||||
|
<text name="spu_attention_hintFlow" text=" → klik: opret flow"/>
|
||||||
|
<text name="spu_attention_hintOpen" text=" → klik: åbn flow '%s'"/>
|
||||||
|
<text name="spu_attention_hintProduce" text=" → producér/køb"/>
|
||||||
|
<text name="spu_attention_produce" text="Et flow hjælper ikke — gården har kun %2$s %1$s i alt. Producér eller køb mere."/>
|
||||||
|
<text name="spu_attention_flowName" text="%s til %s"/>
|
||||||
|
<text name="spu_attention_hintSellFlow" text=" → klik: opret salgsflow"/>
|
||||||
|
<text name="spu_attention_sellFlowName" text="Sælg %s"/>
|
||||||
<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_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_fullIn" text="%s er %d %% fuld — fuld om %s"/>
|
||||||
<text name="spu_alert_full" text="%s er %d %% fuld"/>
|
<text name="spu_alert_full" text="%s er %d %% fuld"/>
|
||||||
|
|||||||
@@ -549,6 +549,13 @@
|
|||||||
<text name="spu_kpi_full" text="Almost full"/>
|
<text name="spu_kpi_full" text="Almost full"/>
|
||||||
<text name="spu_attention" text="— Needs attention —"/>
|
<text name="spu_attention" text="— Needs attention —"/>
|
||||||
<text name="spu_attention_none" text="Nothing needs attention right now"/>
|
<text name="spu_attention_none" text="Nothing needs attention right now"/>
|
||||||
|
<text name="spu_attention_hintFlow" text=" → click: create flow"/>
|
||||||
|
<text name="spu_attention_hintOpen" text=" → click: open flow '%s'"/>
|
||||||
|
<text name="spu_attention_hintProduce" text=" → produce/buy"/>
|
||||||
|
<text name="spu_attention_produce" text="A flow won't help — the farm only has %2$s %1$s in total. Produce or buy more."/>
|
||||||
|
<text name="spu_attention_flowName" text="%s to %s"/>
|
||||||
|
<text name="spu_attention_hintSellFlow" text=" → click: create sell flow"/>
|
||||||
|
<text name="spu_attention_sellFlowName" text="Sell %s"/>
|
||||||
<text name="spu_alert_empty" text="%s runs out in %s — farm has %s, uses %s/day"/>
|
<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_fullIn" text="%s is %d %% full — full in %s"/>
|
||||||
<text name="spu_alert_full" text="%s is %d %% full"/>
|
<text name="spu_alert_full" text="%s is %d %% full"/>
|
||||||
|
|||||||
@@ -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.59.11.0</version>
|
<version>1.60.0.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -182,5 +182,25 @@ local shedGroups = S.groupByFillType({
|
|||||||
}, titleOf)
|
}, titleOf)
|
||||||
check("SV11 procent nil ved ubegrænset skur", shedGroups[1].percent, nil)
|
check("SV11 procent nil ved ubegrænset skur", shedGroups[1].percent, nil)
|
||||||
|
|
||||||
|
-- "kræver handling" -> opret flow: hvem bruger varen, og hjælper et flow overhovedet? (brugerønske 29/9)
|
||||||
|
do
|
||||||
|
local inv = {
|
||||||
|
{name = "Mølle", kind = "factory", placeable = "pMill", entries = {{fillType = 7, level = 1000, isOutput = false}, {fillType = 8, level = 50, isOutput = true}}},
|
||||||
|
{name = "Silo", kind = "silo", placeable = "pSilo", entries = {{fillType = 7, level = 90000}}},
|
||||||
|
{name = "Kostald", kind = "husbandry", placeable = "pBarn", entries = {{fillType = 7, level = 500, isOutput = false}}},
|
||||||
|
}
|
||||||
|
local consumers = S.consumersOf(inv, 7)
|
||||||
|
check("forbrugere: fabrik + stald (ikke silo)", #consumers, 2)
|
||||||
|
check("forbrugere: output tæller ikke", #S.consumersOf(inv, 8), 0)
|
||||||
|
check("råd: eksisterende flow -> åbn det", S.flowAdvice({farmLevel = 91500, consumerLevel = 1500, perDay = 20000, consumerCount = 2, existingRunId = 4}), "open")
|
||||||
|
check("råd: lager andre steder -> opret flow", S.flowAdvice({farmLevel = 91500, consumerLevel = 1500, perDay = 20000, consumerCount = 2}), "flow")
|
||||||
|
check("råd: intet at flytte -> producér/køb", S.flowAdvice({farmLevel = 12700, consumerLevel = 12700, perDay = 228000, consumerCount = 1}), "produce")
|
||||||
|
check("råd: lidt andre steder, stort forbrug -> producér/køb", S.flowAdvice({farmLevel = 20000, consumerLevel = 12000, perDay = 228000, consumerCount = 1}), "produce")
|
||||||
|
check("råd: ingen forbrugere -> intet", S.flowAdvice({farmLevel = 5000, consumerLevel = 0, perDay = 0, consumerCount = 0}), "none")
|
||||||
|
end
|
||||||
|
-- "sælg nu" -> udkørsels-flow (brugerønske 29/9)
|
||||||
|
check("salg: eksisterende salgsflow -> åbn", S.sellAdvice({sourceCount = 1, existingRunId = 3}), "open")
|
||||||
|
check("salg: ingen -> opret flow", S.sellAdvice({sourceCount = 2}), "flow")
|
||||||
|
check("salg: ingen bygning med varen -> intet", S.sellAdvice({sourceCount = 0}), "none")
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user