From 74537f7ec2256a7dce22acf0d04219eb41a2092e Mon Sep 17 00:00:00 2001 From: masterdraco Date: Wed, 30 Sep 2026 17:44:06 +0200 Subject: [PATCH] =?UTF-8?q?feat(k=C3=A6de):=20=C3=A9t=20flow=20pr.=20bygni?= =?UTF-8?q?ng,=20slutprodukter=20s=C3=A6lges=20straks=20(S=C3=A6lg/Behold)?= =?UTF-8?q?,=20tomme=20k=C3=A6deflows=20foldes=20sammen;=20s=C3=A5maskine?= =?UTF-8?q?=20ikke=20g=C3=B8dningsspreder=20(v1.61.4.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + FS25_ADSmartPickup/adChain.lua | 33 +++- FS25_ADSmartPickup/adChainActions.lua | 77 +++++++++ FS25_ADSmartPickup/adChainRun.lua | 163 +++++++++++++++--- FS25_ADSmartPickup/adChainView.lua | 9 +- FS25_ADSmartPickup/adFieldWork.lua | 2 +- FS25_ADSmartPickup/adFlowView.lua | 9 + FS25_ADSmartPickup/adPool.lua | 9 +- .../gui/SmartPickupChainTab.lua | 2 + .../gui/SmartPickupFlowGuide.lua | 22 ++- FS25_ADSmartPickup/l10n/l10n_da.xml | 4 + FS25_ADSmartPickup/l10n/l10n_en.xml | 4 + FS25_ADSmartPickup/modDesc.xml | 2 +- 13 files changed, 297 insertions(+), 40 deletions(-) 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 <en>AD Smart Pickup</en>