diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ce4956..5a3063c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@ står i [docs/BRUGERMANUAL.md](docs/BRUGERMANUAL.md) (dansk) og [docs/USER_MANUA
## 1.61 — Produktionskæder
+- **1.61.4** Kæder mere realistiske: **ét kædeflow pr. bygning** med alle bygningens manglende varer (før ét pr. vare og bygning); nye varer lægges i bygningens eksisterende kædeflow, og tidligere tomme kædeflows (uden traktor) til samme bygning lægges sammen. **Slutprodukter sælges straks** som standard (fabrikkens udgang sættes til *Sælg direkte*); feltet *Sælg*/*Behold* på kædelinjen skifter, og *Behold* sætter varer, kæden selv satte til salg, tilbage. I Flows-listen foldes kædeflows uden traktor sammen i én linje. En såmaskine med gødningstank (fx Kinze 4905) bruges kun til såning, ikke som gødningsspreder.
- **1.61.3** Fanen **Kæder** sorteret: forslag til ny afgrøde står samlet øverst, én gang pr. mark og efter marknummer (før gentaget under hver kæde, der bruger afgrøden); kæderne står efter status (mangel/fejl, venter, dækket), så bygning og produkt. Kædelinjen viser stadig "· N forslag".
- **1.61.2** Flows-listen: dine egne flows står øverst; kædernes auto-oprettede flows samles nederst under *Kædernes egne flows* og vises uden "Kæde: ", så bygningen kan ses.
- **1.61.1** Fanen **Kæder** står nu efter **Marker**, og navne i kædelisten afkortes ikke længere (fx "+ Fermented Chaff", "+ Whole Crop Silage").
diff --git a/FS25_ADSmartPickup/adChain.lua b/FS25_ADSmartPickup/adChain.lua
index 7bf55fd..091e980 100644
--- a/FS25_ADSmartPickup/adChain.lua
+++ b/FS25_ADSmartPickup/adChain.lua
@@ -17,7 +17,7 @@ ADChain.LOG_PREFIX = "ADSmartPickup: kæde: "
-- Et afvist forslag vises igen, når manglen for afgrøden har ændret sig mere end dette.
ADChain.DISMISS_CHANGE = 0.25
-ADChain.state = {byChain = {}, paused = {}, dismissed = {}, userOff = {}}
+ADChain.state = {byChain = {}, paused = {}, dismissed = {}, userOff = {}, finalKeep = {}, soldByChain = {}}
ADChain.last = nil
-- {[nøgle] = true}: produktioner (ikke kædens) der var tændt ved sidste tick — til at se spillerens slukninger
ADChain.prevEnabled = nil
@@ -53,12 +53,14 @@ end
-- userOff = {[recipeKey] = true}: produktioner spilleren slukkede selv — kæden tænder dem ikke igen.
-- roots = {[recipeKey] = true} rødderne ved sidste tick (nil = ukendt, fx gem fra en ældre version).
function ADChain.newState()
- return {byChain = {}, paused = {}, dismissed = {}, userOff = {}, roots = nil}
+ -- finalKeep = {[rodnøgle] = true}: slutproduktet beholdes (standard: sælg straks); soldByChain = {["rod|vare"] = true}
+ return {byChain = {}, paused = {}, dismissed = {}, userOff = {}, roots = nil, finalKeep = {}, soldByChain = {}}
end
function ADChain.stateToRecords(state)
local records = {byChain = sortedKeys(state.byChain), paused = sortedKeys(state.paused), userOff = sortedKeys(state.userOff), dismissed = {},
- roots = state.roots ~= nil and sortedKeys(state.roots) or nil}
+ roots = state.roots ~= nil and sortedKeys(state.roots) or nil,
+ finalKeep = sortedKeys(state.finalKeep or {}), soldByChain = sortedKeys(state.soldByChain or {})}
local ids = {}
for id in pairs(state.dismissed or {}) do table.insert(ids, id) end
table.sort(ids)
@@ -75,6 +77,8 @@ function ADChain.stateFromRecords(records)
for _, key in ipairs(records.byChain or {}) do state.byChain[key] = true end
for _, key in ipairs(records.paused or {}) do state.paused[key] = true end
for _, key in ipairs(records.userOff or {}) do state.userOff[key] = true end
+ for _, key in ipairs(records.finalKeep or {}) do state.finalKeep[key] = true end
+ for _, key in ipairs(records.soldByChain or {}) do state.soldByChain[key] = true end
if records.roots ~= nil then
state.roots = {}
for _, key in ipairs(records.roots) do state.roots[key] = true end
@@ -378,7 +382,7 @@ function ADChain.evaluate(adEnv, act)
for key in pairs(state.paused) do paused[key] = true end
ADChain.last = {chain = result.chain, shortages = result.shortages, actions = result.actions, edges = result.edges,
missingFlows = ADChainActions.missingFlows(result.edges, runs), flowStatus = ADChain.flowStatus(result.edges, runs, ADChain.noVehicle),
- paused = paused, userOff = copySet(state.userOff), data = data, fields = data.fields, fieldsIncomplete = data.fieldsIncomplete == true, titles = data.fillTitles, at = ADChain.now, switchCount = ADChain.switchCount,
+ paused = paused, userOff = copySet(state.userOff), finalKeep = copySet(state.finalKeep or {}), data = data, fields = data.fields, fieldsIncomplete = data.fieldsIncomplete == true, titles = data.fillTitles, at = ADChain.now, switchCount = ADChain.switchCount,
dependency = dependency}
return ADChain.last
end
@@ -491,6 +495,13 @@ function ADChain.dismiss(fieldId)
return true
end
+-- Slutprodukt for en kæde: keep = true -> "Behold", ellers "Sælg straks" (standard). Anvendes ved næste tick.
+function ADChain.setFinalKeep(rootKey, keep)
+ if rootKey == nil then return end
+ ADChain.state.finalKeep[rootKey] = keep and true or nil
+ Logging.info(ADChain.LOG_PREFIX .. "%s: slutprodukt %s", tostring(rootKey), keep and "beholdes" or "sælges straks")
+end
+
function ADChain.setPaused(rootKey, isPaused)
if rootKey == nil then return end
ADChain.state.paused[rootKey] = isPaused and true or nil
@@ -520,6 +531,12 @@ function ADChain.save()
for index, key in ipairs(records.userOff) do
xmlFile:setString(string.format("%s.userOff.recipe(%d)#key", ADChain.ROOT, index - 1), key)
end
+ for index, key in ipairs(records.finalKeep) do
+ xmlFile:setString(string.format("%s.finalKeep.root(%d)#key", ADChain.ROOT, index - 1), key)
+ end
+ for index, key in ipairs(records.soldByChain) do
+ xmlFile:setString(string.format("%s.soldByChain.output(%d)#key", ADChain.ROOT, index - 1), key)
+ end
if records.roots ~= nil then
xmlFile:setBool(ADChain.ROOT .. ".roots#known", true)
for index, key in ipairs(records.roots) do
@@ -547,7 +564,13 @@ function ADChain.load()
if path == nil or not fileExists(path) then return end
local xmlFile = XMLFile.load("ADSmartPickupChain", path)
if xmlFile == nil then return end
- local records = {byChain = {}, paused = {}, userOff = {}, dismissed = {}}
+ local records = {byChain = {}, paused = {}, userOff = {}, dismissed = {}, finalKeep = {}, soldByChain = {}}
+ xmlFile:iterate(ADChain.ROOT .. ".finalKeep.root", function(_, key)
+ table.insert(records.finalKeep, xmlFile:getString(key .. "#key"))
+ end)
+ xmlFile:iterate(ADChain.ROOT .. ".soldByChain.output", function(_, key)
+ table.insert(records.soldByChain, xmlFile:getString(key .. "#key"))
+ end)
xmlFile:iterate(ADChain.ROOT .. ".byChain.recipe", function(_, key)
table.insert(records.byChain, xmlFile:getString(key .. "#key"))
end)
diff --git a/FS25_ADSmartPickup/adChainActions.lua b/FS25_ADSmartPickup/adChainActions.lua
index a353bae..0dbd55d 100644
--- a/FS25_ADSmartPickup/adChainActions.lua
+++ b/FS25_ADSmartPickup/adChainActions.lua
@@ -65,3 +65,80 @@ function ADChainActions.flowsToStop(runs, activeRootKeys)
end
return list
end
+
+-- ---- ét kædeflow pr. bygning (in-game 30/9: ét flow pr. vare og bygning gav mange stoppede flows) ----
+
+-- Manglende kanter samlet pr. modtagerbygning: {consumer, consumerName, rootKey, fillTypes} i fundet rækkefølge.
+function ADChainActions.groupMissing(missing)
+ local groups, byConsumer = {}, {}
+ for _, edge in ipairs(missing or {}) do
+ local group = byConsumer[edge.consumer]
+ if group == nil then
+ group = {consumer = edge.consumer, consumerName = edge.consumerName, rootKey = edge.rootKey, fillTypes = {}}
+ byConsumer[edge.consumer] = group
+ table.insert(groups, group)
+ end
+ if not contains(group.fillTypes, edge.fillType) then table.insert(group.fillTypes, edge.fillType) end
+ end
+ return groups
+end
+
+local function isChainRun(run)
+ return run.chain ~= nil and run.chain ~= "" and run.kind == "supply" and run.ingredientMode == "manual"
+end
+
+-- Kædens eget flow (manuelle varer) til bygningen -> run-id, så nye varer lægges i det i stedet for et nyt flow.
+function ADChainActions.chainRunFor(runs, consumer)
+ for _, run in ipairs(runs or {}) do
+ if isChainRun(run) and contains(run.targetBuildingIds, consumer) then return run.id end
+ end
+ return nil
+end
+
+-- Flere tomme kædeflows (uden traktorer) til samme bygning: det første beholdes med alle varerne, resten slettes.
+-- -> {{keepId, add = {varer}, deleteIds = {id}}}
+function ADChainActions.mergeIdle(runs)
+ local byConsumer, order = {}, {}
+ for _, run in ipairs(runs or {}) do
+ local target = (run.targetBuildingIds or {})[1]
+ if isChainRun(run) and target ~= nil and #(run.targetBuildingIds or {}) == 1 and #(run.vehicleIds or {}) == 0 then
+ if byConsumer[target] == nil then byConsumer[target] = {}; table.insert(order, target) end
+ table.insert(byConsumer[target], run)
+ end
+ end
+ local merges = {}
+ for _, target in ipairs(order) do
+ local list = byConsumer[target]
+ if #list > 1 then
+ local keep, add, deleteIds = list[1], {}, {}
+ for index = 2, #list do
+ for _, name in ipairs(list[index].ingredients or {}) do
+ if not contains(keep.ingredients, name) and not contains(add, name) then table.insert(add, name) end
+ end
+ table.insert(deleteIds, list[index].id)
+ end
+ table.insert(merges, {keepId = keep.id, add = add, deleteIds = deleteIds})
+ end
+ end
+ return merges
+end
+
+-- Slutprodukter der skal sælges straks: aktive rødders udgange, som ikke selv er en kant i kæden (mellemvare).
+-- keep = {[rodnøgle] = true} for rødder spilleren har sat til "Behold". -> {{key, fillType}}
+function ADChainActions.finalSales(chain, recipes, edges, keep)
+ local intermediate, byKey, list = {}, {}, {}
+ for _, edge in ipairs(edges or {}) do intermediate[edge.fillType] = true end
+ for _, recipe in ipairs(recipes or {}) do byKey[recipe.key] = recipe end
+ for _, root in ipairs((chain or {}).roots or {}) do
+ local recipe = byKey[root.key]
+ if recipe ~= nil and not (keep or {})[root.key] then
+ local names = {}
+ for name in pairs(recipe.outputs or {}) do table.insert(names, name) end
+ table.sort(names)
+ for _, name in ipairs(names) do
+ if not intermediate[name] then table.insert(list, {key = root.key, fillType = name}) end
+ end
+ end
+ end
+ return list
+end
diff --git a/FS25_ADSmartPickup/adChainRun.lua b/FS25_ADSmartPickup/adChainRun.lua
index a5ba698..1665fc2 100644
--- a/FS25_ADSmartPickup/adChainRun.lua
+++ b/FS25_ADSmartPickup/adChainRun.lua
@@ -125,26 +125,15 @@ local function createStopped(run, kind, reason, ...)
log("flow '%s' oprettet stoppet: " .. reason, tostring(run.name), ...)
end
-local function createFlow(adEnv, data, edge, pool, used)
- local title = data.fillTitles[edge.fillType] or edge.fillType
- local created = ADRunsController.createRun(ADChain.flowName(title, edge.consumerName or edge.consumer), "supply")
- local runId = created.id
- ADRunsController.updateRun(runId, {targetBuildingIds = {edge.consumer}, ingredientMode = "manual", ingredients = {edge.fillType},
- waitPoolGroup = ADChain.waitPoolFor(ADRunsController.store.runs, edge.consumer), chain = edge.rootKey})
- -- updateRun erstatter store'en: slå altid run'et op igen
- local run = ADRuns.getRun(ADRunsController.store, runId)
- local fillIndex = data.fillIndex[edge.fillType]
- if fillIndex ~= nil and ADSources.isPalletFillType(fillIndex) then
- createStopped(run, "pallet", "palle-vare: vælg vogn med Universal Autoload (%s)", tostring(title))
- return
- end
- if pool.implements == nil then pool.implements, pool.tractors = collectPool(adEnv) end
- if pool.blocked == nil then pool.blocked = ADPool.blocked(ADFieldJobs.now or 0) end
- local pair = ADChainRun.choosePair(pool.implements, pool.tractors, fillIndex, used, pool.blocked)
- if pair == nil then
- createStopped(run, "noVehicle", "ingen ledig traktor/vogn til %s", tostring(title))
- return
- end
+-- Ét flow pr. modtagerbygning med alle dens manglende bulkvarer (manuelle varer); palle-varer får hver sit
+-- stoppede flow. group = {consumer, consumerName, rootKey, fillTypes}.
+local function flowTitle(data, fillTypes)
+ local first = data.fillTitles[fillTypes[1]] or fillTypes[1]
+ if #fillTypes == 1 then return first end
+ return string.format("%s +%d", tostring(first), #fillTypes - 1)
+end
+
+local function startWithPair(adEnv, run, runId, pair, used)
used[pair.tractor.id], used[pair.implement.id] = true, true
ADRunsController.assignVehicle(runId, pair.tractor.vehicle)
run = ADRuns.getRun(ADRunsController.store, runId)
@@ -159,18 +148,137 @@ local function createFlow(adEnv, data, edge, pool, used)
tostring(pair.implement.name), started)
end
+local function createGroupFlow(adEnv, data, group, fillTypes, pool, used)
+ local title = flowTitle(data, fillTypes)
+ local created = ADRunsController.createRun(ADChain.flowName(title, group.consumerName or group.consumer), "supply")
+ local runId = created.id
+ ADRunsController.updateRun(runId, {targetBuildingIds = {group.consumer}, ingredientMode = "manual", ingredients = fillTypes,
+ waitPoolGroup = ADChain.waitPoolFor(ADRunsController.store.runs, group.consumer), chain = group.rootKey})
+ -- updateRun erstatter store'en: slå altid run'et op igen
+ local run = ADRuns.getRun(ADRunsController.store, runId)
+ local fillIndex = data.fillIndex[fillTypes[1]]
+ if fillIndex ~= nil and ADSources.isPalletFillType(fillIndex) then
+ createStopped(run, "pallet", "palle-vare: vælg vogn med Universal Autoload (%s)", tostring(title))
+ return
+ end
+ if pool.implements == nil then pool.implements, pool.tractors = collectPool(adEnv) end
+ if pool.blocked == nil then pool.blocked = ADPool.blocked(ADFieldJobs.now or 0) end
+ local pair = ADChainRun.choosePair(pool.implements, pool.tractors, fillIndex, used, pool.blocked)
+ if pair == nil then
+ createStopped(run, "noVehicle", "ingen ledig traktor/vogn til %s", tostring(title))
+ return
+ end
+ startWithPair(adEnv, run, runId, pair, used)
+end
+
+-- Nye varer til en bygning, der allerede har et kædeflow: lægges i det flow.
+local function extendFlow(runId, fillTypes)
+ local run = ADRuns.getRun(ADRunsController.store, runId)
+ if run == nil then return false end
+ local ingredients = {}
+ for _, name in ipairs(run.ingredients or {}) do table.insert(ingredients, name) end
+ local added = {}
+ for _, name in ipairs(fillTypes) do
+ local present = false
+ for _, have in ipairs(ingredients) do if have == name then present = true end end
+ if not present then table.insert(ingredients, name); table.insert(added, name) end
+ end
+ if #added == 0 then return false end
+ ADRunsController.updateRun(runId, {ingredients = ingredients})
+ log("flow '%s' får også %s", tostring(run.name), table.concat(added, ", "))
+ return true
+end
+
+-- Tidligere kædeflows (ét pr. vare) til samme bygning uden traktorer lægges sammen i ét.
+function ADChainRun.mergeIdleFlows(adEnv)
+ local merged = 0
+ for _, merge in ipairs(ADChainActions.mergeIdle(ADChain.runsView(ADRunsController.store.runs))) do
+ if #merge.add > 0 then extendFlow(merge.keepId, merge.add) end
+ for _, id in ipairs(merge.deleteIds) do
+ local ok, err = pcall(ADRunsController.deleteRun, id, adEnv)
+ if ok then
+ ADChain.noVehicle[id] = nil
+ merged = merged + 1
+ else
+ warn("kædeflow %s kunne ikke lægges sammen: %s", tostring(id), tostring(err))
+ end
+ end
+ end
+ if merged > 0 then log("%d tomme kædeflows lagt sammen (ét flow pr. bygning)", merged) end
+ return merged
+end
+
function ADChainRun.createFlows(adEnv, data, edges)
- local missing = ADChainActions.missingFlows(edges, ADChain.runsView(ADRunsController.store.runs))
+ local runs = ADChain.runsView(ADRunsController.store.runs)
+ local missing = ADChainActions.missingFlows(edges, runs)
local pool, used, count = {}, {}, 0
- for _, edge in ipairs(missing) do
+ for _, group in ipairs(ADChainActions.groupMissing(missing)) do
if count >= ADChain.MAX_NEW_FLOWS then break end
- count = count + 1
- local ok, err = pcall(createFlow, adEnv, data, edge, pool, used)
- if not ok then warn("flow til %s (%s) kunne ikke oprettes: %s", tostring(edge.consumerName), tostring(edge.fillType), tostring(err)) end
+ local bulk, pallets = {}, {}
+ for _, name in ipairs(group.fillTypes) do
+ local index = data.fillIndex[name]
+ table.insert((index ~= nil and ADSources.isPalletFillType(index)) and pallets or bulk, name)
+ end
+ local ok, err = pcall(function()
+ if #bulk > 0 then
+ local existing = ADChainActions.chainRunFor(runs, group.consumer)
+ if existing ~= nil then
+ if extendFlow(existing, bulk) then count = count + 1 end
+ else
+ createGroupFlow(adEnv, data, group, bulk, pool, used)
+ count = count + 1
+ end
+ end
+ for _, name in ipairs(pallets) do
+ createGroupFlow(adEnv, data, group, {name}, pool, used)
+ count = count + 1
+ end
+ end)
+ if not ok then warn("flow til %s kunne ikke oprettes: %s", tostring(group.consumerName), tostring(err)) end
end
return count
end
+-- Slutprodukter: sælg straks (fabrikkens udgang -> "Sælg direkte"), medmindre kæden står på "Behold". Varer kæden
+-- selv satte til salg, sættes tilbage til "Behold", når spilleren vælger det.
+local function outputMode(point, fillIndex)
+ if point.getOutputDistributionMode == nil then return nil end
+ local ok, mode = pcall(point.getOutputDistributionMode, point, fillIndex)
+ return ok and mode or nil
+end
+
+function ADChainRun.applyFinalSales(data, chain, edges)
+ if ProductionPoint == nil or ProductionPoint.OUTPUT_MODE == nil then return 0 end
+ local state, changed = ADChain.state, 0
+ local wanted = {}
+ for _, sale in ipairs(ADChainActions.finalSales(chain, data.recipes, edges, state.finalKeep)) do
+ local id = sale.key .. "|" .. sale.fillType
+ wanted[id] = true
+ local entry, fillIndex = (data.points or {})[sale.key], data.fillIndex[sale.fillType]
+ if entry ~= nil and entry.point ~= nil and fillIndex ~= nil and entry.point.setOutputDistributionMode ~= nil
+ and outputMode(entry.point, fillIndex) ~= ProductionPoint.OUTPUT_MODE.DIRECT_SELL then
+ entry.point:setOutputDistributionMode(fillIndex, ProductionPoint.OUTPUT_MODE.DIRECT_SELL)
+ state.soldByChain[id] = true
+ changed = changed + 1
+ log("slutprodukt %s i %s sælges straks", tostring(sale.fillType), tostring(entry.buildingName or sale.key))
+ end
+ end
+ for id in pairs(state.soldByChain) do
+ local key, fillType = id:match("^(.*)|([^|]+)$")
+ if key ~= nil and state.finalKeep[key] and not wanted[id] then
+ local entry, fillIndex = (data.points or {})[key], data.fillIndex[fillType]
+ if entry ~= nil and entry.point ~= nil and fillIndex ~= nil
+ and outputMode(entry.point, fillIndex) == ProductionPoint.OUTPUT_MODE.DIRECT_SELL then
+ entry.point:setOutputDistributionMode(fillIndex, ProductionPoint.OUTPUT_MODE.KEEP)
+ changed = changed + 1
+ log("slutprodukt %s beholdes igen", tostring(fillType))
+ end
+ state.soldByChain[id] = nil
+ end
+ end
+ return changed
+end
+
-- Flows for rødder der er slået fra: flyt til en anden rod der stadig bruger kanten, ellers stop og slip (chain
-- ryddes, så spilleren kan starte flowet igen uden at kæden stopper det næste minut; det slettes ikke).
function ADChainRun.stopFlows(adEnv, chain, edges)
@@ -202,7 +310,12 @@ function ADChainRun.apply(adEnv, data, result)
if not okFields then warn("markskift fejlede: %s", tostring(errFields)) end
local okProd, errProd = pcall(ADChainRun.toggleProductions, data, result.prod)
if not okProd then warn("mellemled fejlede: %s", tostring(errProd)) end
+ local okMerge, mergedCount = pcall(ADChainRun.mergeIdleFlows, adEnv)
+ if not okMerge then warn("sammenlægning af kædeflows fejlede: %s", tostring(mergedCount)) end
local okNew, created = pcall(ADChainRun.createFlows, adEnv, data, result.edges)
+ local okSell, sellErr = pcall(ADChainRun.applyFinalSales, data, result.chain, result.edges)
+ if not okSell then warn("slutprodukter fejlede: %s", tostring(sellErr)) end
+ if okMerge and mergedCount > 0 then created = (okNew and created or 0) + mergedCount end
if not okNew then warn("nye flows fejlede: %s", tostring(created)) end
-- delvise data: en rod der ikke kunne læses, må ikke få sine flows stoppet
local okStop, stopped = true, 0
diff --git a/FS25_ADSmartPickup/adChainView.lua b/FS25_ADSmartPickup/adChainView.lua
index 7ae5c8c..b819d6c 100644
--- a/FS25_ADSmartPickup/adChainView.lua
+++ b/FS25_ADSmartPickup/adChainView.lua
@@ -20,7 +20,7 @@ ADChainView.TEXT_ROW_CHARS = 70
-- "stramt": lager for en mark-afgrøde under så mange måneders behov -> gul
ADChainView.TIGHT_MONTHS = 3
-- nøgler rækkerne bruger (til l10n-testen)
-ADChainView.TEXT_KEYS = {"spu_chain_suggestHeader", "spu_chain_empty", "spu_chain_approve", "spu_chain_dismiss", "spu_chain_auto",
+ADChainView.TEXT_KEYS = {"spu_chain_finalSell", "spu_chain_finalKeep", "spu_chain_suggestHeader", "spu_chain_empty", "spu_chain_approve", "spu_chain_dismiss", "spu_chain_auto",
"spu_chain_flowOk", "spu_chain_flowMissing", "spu_chain_flowPallet", "spu_chain_flowNone", "spu_chain_flowStopped", "spu_chain_bottleneck",
"spu_chain_buy", "spu_chain_cycle", "spu_chain_fieldShort", "spu_chain_tight", "spu_chain_noField", "spu_chain_buySource", "spu_chain_fromFields",
"spu_chain_pause", "spu_chain_resume", "spu_chain_scanning", "spu_chain_rootOk", "spu_chain_rootError", "spu_chain_rootWaiting",
@@ -313,9 +313,12 @@ local function rootRows(rows, root, last, ctx, isExpanded)
local status = ctx.textOf(ROOT_STATUS[level])
if #suggestions > 0 then status = string.format(ctx.textOf("spu_chain_rootSummary"), status, #suggestions) end
local marker = (#(root.children or {}) > 0) and (isExpanded and "– " or "+ ") or ""
- table.insert(rows, makeRow("root", 0, level, {marker .. ctx.titleOf(root.name), tostring(root.buildingName or ""), "", status,
+ -- slutproduktet: "Sælg straks" (standard) eller "Behold" — klik på feltet skifter
+ local keep = (last.finalKeep or {})[root.key] == true
+ table.insert(rows, makeRow("root", 0, level, {marker .. ctx.titleOf(root.name), tostring(root.buildingName or ""),
+ ctx.textOf(keep and "spu_chain_finalKeep" or "spu_chain_finalSell"), status,
ctx.textOf(paused and "spu_chain_resume" or "spu_chain_pause")},
- {[5] = {kind = paused and "resume" or "pause", rootKey = root.key}}, root.key))
+ {[3] = {kind = "finalMode", rootKey = root.key, keep = not keep}, [5] = {kind = paused and "resume" or "pause", rootKey = root.key}}, root.key))
if not isExpanded then return end
local shown, problems, told = {}, {}, {}
for _, problem in ipairs((last.chain or {}).problems or {}) do
diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua
index f0d2f1d..b0dcfbb 100644
--- a/FS25_ADSmartPickup/adFieldWork.lua
+++ b/FS25_ADSmartPickup/adFieldWork.lua
@@ -166,7 +166,7 @@ function ADFieldWork.getRigs(adEnv)
manual = (state.manual or {})[id] == true,
width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z,
-- tom spreder kan flere ting (gødning og kalk): alle dens roller
- roles = specs.sprayRoles or ADFieldFleet.rolesFor(role), filledRole = specs.filledRole, dumpable = specs.dumpable,
+ roles = ADPool.rolesOf(role, specs.sprayRoles), filledRole = specs.filledRole, dumpable = specs.dumpable,
}
if role == "harvester" then rig.fruits = ADCpBridge.getHarvestFruits(vehicle) or {} end
if role == "seeder" then
diff --git a/FS25_ADSmartPickup/adFlowView.lua b/FS25_ADSmartPickup/adFlowView.lua
index f3e7dc9..8a05287 100644
--- a/FS25_ADSmartPickup/adFlowView.lua
+++ b/FS25_ADSmartPickup/adFlowView.lua
@@ -74,6 +74,15 @@ function ADFlowView.boardGroups(runs)
return groups
end
+-- Kædeflows uden traktor foldes sammen i listen (de venter på en ledig traktor/vogn). -> active, idle
+function ADFlowView.splitIdle(runs)
+ local active, idle = {}, {}
+ for _, run in ipairs(runs or {}) do
+ table.insert(#(run.vehicleIds or {}) > 0 and active or idle, run)
+ end
+ return active, idle
+end
+
-- Navn i listen: kædeflows står under overskriften "Kædernes egne flows", så "Kæde: " fjernes (ellers skjuler
-- afkortningen hvilken bygning det gælder).
function ADFlowView.listName(run)
diff --git a/FS25_ADSmartPickup/adPool.lua b/FS25_ADSmartPickup/adPool.lua
index 194e7bb..44defe2 100644
--- a/FS25_ADSmartPickup/adPool.lua
+++ b/FS25_ADSmartPickup/adPool.lua
@@ -18,6 +18,13 @@ function ADPool.roleOf(specs)
return role
end
+-- Ekstra roller for et redskab: sprøjtens roller (gødning/kalk/ukrudt) — men ikke for en såmaskine med
+-- gødningstank (Kinze 4905), den er kun såmaskine (in-game 30/9: hentet til ren gødning).
+function ADPool.rolesOf(role, sprayRoles)
+ if role ~= "seeder" and sprayRoles ~= nil then return sprayRoles end
+ return ADFieldFleet.rolesFor(role)
+end
+
-- Butiksdata er enten et tal eller en tabel pr. konfiguration: største tal, ellers nil.
function ADPool.numberOf(value)
if type(value) == "number" then return value end
@@ -402,7 +409,7 @@ function ADPool.collect(adEnv, isBusy)
local carries, capacity = ADPool.cargoOf(vehicle)
table.insert(implements, {carries = carries, capacity = capacity,
id = ADRunsController.getVehicleId(vehicle), vehicle = vehicle, name = ADRuns.vehicleLabel(vehicle),
- role = role, roles = specs.sprayRoles or ADFieldFleet.rolesFor(role), x = x, z = z, width = ADCpBridge.getWorkWidth(vehicle),
+ role = role, roles = ADPool.rolesOf(role, specs.sprayRoles), x = x, z = z, width = ADCpBridge.getWorkWidth(vehicle),
neededHp = neededHpOf(vehicle), connectId = connect.id, parkId = park.id,
})
end
diff --git a/FS25_ADSmartPickup/gui/SmartPickupChainTab.lua b/FS25_ADSmartPickup/gui/SmartPickupChainTab.lua
index 9baa790..6b213a6 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupChainTab.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupChainTab.lua
@@ -27,6 +27,8 @@ function SmartPickupFrame:onChainAction(action)
ok = ADChain.approve(action.fieldId)
elseif action.kind == "dismiss" then
ok = ADChain.dismiss(action.fieldId)
+ elseif action.kind == "finalMode" then
+ ADChain.setFinalKeep(action.rootKey, action.keep)
elseif action.kind == "pause" or action.kind == "resume" then
ADChain.setPaused(action.rootKey, action.kind == "pause")
end
diff --git a/FS25_ADSmartPickup/gui/SmartPickupFlowGuide.lua b/FS25_ADSmartPickup/gui/SmartPickupFlowGuide.lua
index 6b1d3e7..9f3e44e 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupFlowGuide.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupFlowGuide.lua
@@ -232,10 +232,8 @@ function SmartPickupFrame:fillFlowTabRows(adEnv)
return
end
self:addTextRow(i18n:getText("spu_flowList_hint"))
- for _, group in ipairs(ADFlowView.boardGroups(ADRunsController.store.runs)) do
- self:addTextRow(i18n:getText(group.chain and "spu_board_chain" or group.kind == "outbound" and "spu_board_outbound" or "spu_board_supply"))
- for _, run in ipairs(group.runs) do
- local row = self:addRow(self.flowListPrefab, {kind = "flowList"})
+ local function addFlowRow(run)
+ local row = self:addRow(self.flowListPrefab, {kind = "flowList"})
local entry = {runId = run.id, row = row, dot = row:getDescendantByName("dot"),
cells = {row:getDescendantByName("c1"), row:getDescendantByName("c2"), row:getDescendantByName("c3"),
row:getDescendantByName("c4"), row:getDescendantByName("c5")}}
@@ -252,6 +250,22 @@ function SmartPickupFrame:fillFlowTabRows(adEnv)
end
end)
table.insert(self.flowListRows, entry)
+ end
+ for _, group in ipairs(ADFlowView.boardGroups(ADRunsController.store.runs)) do
+ self:addTextRow(i18n:getText(group.chain and "spu_board_chain" or group.kind == "outbound" and "spu_board_outbound" or "spu_board_supply"))
+ local runs, idle = group.runs, {}
+ -- kædeflows uden traktor (venter på en ledig traktor/vogn) foldes sammen (in-game 30/9)
+ if group.chain then runs, idle = ADFlowView.splitIdle(group.runs) end
+ for _, run in ipairs(runs) do addFlowRow(run) end
+ if #idle > 0 then
+ local key = self.showIdleChainFlows and "spu_board_chainIdleHide" or "spu_board_chainIdleShow"
+ self:addLinkRow(" " .. string.format(i18n:getText(key), #idle), function(frame)
+ frame.showIdleChainFlows = not frame.showIdleChainFlows
+ frame:requestRebuild()
+ end)
+ if self.showIdleChainFlows then
+ for _, run in ipairs(idle) do addFlowRow(run) end
+ end
end
end
self:refreshFlowListRows(adEnv)
diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml
index 0841894..3d8f1d6 100644
--- a/FS25_ADSmartPickup/l10n/l10n_da.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_da.xml
@@ -377,6 +377,8 @@
+
+
@@ -577,6 +579,8 @@
+
+
diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml
index 9263cb8..8e084f9 100644
--- a/FS25_ADSmartPickup/l10n/l10n_en.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_en.xml
@@ -377,6 +377,8 @@
+
+
@@ -577,6 +579,8 @@
+
+
diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml
index ce0bc09..21860f0 100644
--- a/FS25_ADSmartPickup/modDesc.xml
+++ b/FS25_ADSmartPickup/modDesc.xml
@@ -1,7 +1,7 @@
masterdraco
- 1.61.3.0
+ 1.61.4.0
AD Smart Pickup