From 32f2886a556b3e278d08f6d18d0053645f434499 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Wed, 30 Sep 2026 13:46:01 +0200 Subject: [PATCH] =?UTF-8?q?feat(k=C3=A6de):=20k=C3=A6den=20k=C3=B8rer=20?= =?UTF-8?q?=E2=80=94=20skifter=20frie=20marker,=20t=C3=A6nder=20mellemled,?= =?UTF-8?q?=20opretter=20flows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adChain.lua | 321 ++++++++++++++++++++++++ FS25_ADSmartPickup/adChainRun.lua | 186 ++++++++++++++ FS25_ADSmartPickup/adRunsController.lua | 3 + FS25_ADSmartPickup/adSmartPickup.lua | 6 + FS25_ADSmartPickup/modDesc.xml | 2 + 5 files changed, 518 insertions(+) create mode 100644 FS25_ADSmartPickup/adChain.lua create mode 100644 FS25_ADSmartPickup/adChainRun.lua diff --git a/FS25_ADSmartPickup/adChain.lua b/FS25_ADSmartPickup/adChain.lua new file mode 100644 index 0000000..93c1517 --- /dev/null +++ b/FS25_ADSmartPickup/adChain.lua @@ -0,0 +1,321 @@ +-- AD Chain (kæde-kørsel) +-- Hvert minut (efter opstartsfristen): læs gården (ADChainData), byg kæden (ADChainModel), find manglende +-- afgrøder (ADCropPlan) og lad ADChainRun skifte frie marker, tænde/slukke mellemled og oprette/stoppe flows. +-- Resultatet gemmes i ADChain.last til Kæder-fanen. Tilstanden (byChain, pausede rødder, afviste forslag) +-- gemmes i ADSmartPickup_chain.xml under spillets gem. Spiladgang kun inde i funktioner (filen dofile's i test). + +ADChain = {} +ADChain.FILE_NAME = "ADSmartPickup_chain.xml" +ADChain.ROOT = "ADSmartPickupChain" +ADChain.TICK_MS = 60000 +ADChain.STARTUP_GRACE_MS = 120000 +ADChain.MAX_FIELD_SWITCHES = 2 +ADChain.MAX_NEW_FLOWS = 2 +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 = {}} +ADChain.last = nil +-- run-id -> true: flows kæden oprettede stoppet, fordi der ikke var traktor/vogn (kun i hukommelsen) +ADChain.noVehicle = {} +ADChain.switchCount = 0 +ADChain.now = 0 +ADChain.timer = 0 +ADChain.collectFailed = false + +local function contains(list, value) + for _, item in ipairs(list or {}) do if item == value then return true end end + return false +end + +local function sortedKeys(map) + local keys = {} + for key in pairs(map or {}) do table.insert(keys, key) end + table.sort(keys, function(a, b) return tostring(a) < tostring(b) end) + return keys +end + +-- ---- rene hjælpere ---- + +function ADChain.newState() + return {byChain = {}, paused = {}, dismissed = {}} +end + +function ADChain.stateToRecords(state) + local records = {byChain = sortedKeys(state.byChain), paused = sortedKeys(state.paused), dismissed = {}} + local ids = {} + for id in pairs(state.dismissed or {}) do table.insert(ids, id) end + table.sort(ids) + for _, id in ipairs(ids) do + local entry = state.dismissed[id] + table.insert(records.dismissed, {id = id, fruit = entry.fruit, missingHa = entry.missingHa}) + end + return records +end + +function ADChain.stateFromRecords(records) + local state = ADChain.newState() + if records == nil then return state end + 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 _, entry in ipairs(records.dismissed or {}) do + local id = tonumber(entry.id) + if id ~= nil and entry.fruit ~= nil and entry.fruit ~= "" then + state.dismissed[id] = {fruit = entry.fruit, missingHa = tonumber(entry.missingHa) or 0} + end + end + return state +end + +-- Afviste forslag beholdes kun mens manglen for afgrøden findes og har ændret sig højst 25 %. +function ADChain.pruneDismissed(dismissed, shortages) + local byFruit = {} + for _, shortage in ipairs(shortages or {}) do byFruit[shortage.fruit] = shortage.missingHa end + local kept = {} + for id, entry in pairs(dismissed or {}) do + local now = byFruit[entry.fruit] + local before = entry.missingHa or 0 + if now ~= nil and math.abs(now - before) <= math.abs(before) * ADChain.DISMISS_CHANGE then + kept[id] = {fruit = entry.fruit, missingHa = before} + end + end + return kept +end + +-- {[mark] = {fruit, missingHa}} -> {[mark] = afgrøde} (ADCropPlan.choose's dismissed) +function ADChain.dismissedFruits(dismissed) + local result = {} + for id, entry in pairs(dismissed or {}) do result[id] = entry.fruit end + return result +end + +function ADChain.unionFruits(usedFruits, fieldDemand) + local result = {} + for fruit in pairs(usedFruits or {}) do result[fruit] = true end + for fruit in pairs(fieldDemand or {}) do result[fruit] = true end + return result +end + +-- Kopi af opskrifterne hvor pausede rødder er slukket (så de ikke skaber nye behov). +function ADChain.recipesWithPaused(recipes, paused) + local result = {} + for index, recipe in ipairs(recipes or {}) do + local copy = {} + for key, value in pairs(recipe) do copy[key] = value end + if (paused or {})[recipe.key] then copy.enabled = false end + result[index] = copy + end + return result +end + +-- Rødder hvis flows ikke må stoppes: kædens aktuelle rødder + pausede rødder (pause rører intet). +function ADChain.activeRoots(chain, paused) + local result = {} + for _, root in ipairs((chain or {}).roots or {}) do result[root.key] = true end + for key in pairs(paused or {}) do result[key] = true end + return result +end + +-- Mest brugte Wait-pulje blandt forsyningsflows til bygningen, ellers "". +function ADChain.waitPoolFor(runs, building) + local counts, best, bestCount = {}, "", 0 + for _, run in ipairs(runs or {}) do + local group = run.waitPoolGroup + if run.kind == "supply" and group ~= nil and group ~= "" and contains(run.targetBuildingIds, building) then + counts[group] = (counts[group] or 0) + 1 + end + end + for _, group in ipairs(sortedKeys(counts)) do + if counts[group] > bestCount then best, bestCount = group, counts[group] end + end + return best +end + +function ADChain.flowName(fillTitle, buildingName) + return "Kæde: " .. tostring(fillTitle) .. " → " .. tostring(buildingName) +end + +local function covers(run, edge) + return contains(run.targetBuildingIds, edge.consumer) + and (run.ingredientMode ~= "manual" or contains(run.ingredients, edge.fillType)) +end + +-- Bruger en af de aktuelle kanter stadig flowet? -> kantens rodnøgle (flowet flyttes dertil), ellers nil. +function ADChain.stillNeeded(runView, edges) + for _, edge in ipairs(edges or {}) do + if covers(runView, edge) then return edge.rootKey end + end + return nil +end + +-- {[forbruger|vare] = "ok"|"missing"|"stopped"|"noVehicle"} for hver kant. +function ADChain.flowStatus(edges, runs, noVehicle) + local result = {} + for _, edge in ipairs(edges or {}) do + local status = "missing" + for _, run in ipairs(runs or {}) do + if run.kind == "supply" and covers(run, edge) then + if not run.stopped then status = "ok"; break end + if (noVehicle or {})[run.id] then status = "noVehicle" + elseif status == "missing" then status = "stopped" end + end + end + result[tostring(edge.consumer) .. "|" .. tostring(edge.fillType)] = status + end + return result +end + +-- Flows i det format ADChainActions regner på. +function ADChain.runsView(runs) + local list = {} + for _, run in ipairs(runs or {}) do + table.insert(list, {id = run.id, name = run.name, kind = run.kind, targetBuildingIds = run.targetBuildingIds, + ingredientMode = run.ingredientMode, ingredients = run.ingredients, stopped = run.stopped == true, + chain = run.chain, waitPoolGroup = run.waitPoolGroup}) + end + return list +end + +-- ---- evaluering ---- + +local function farmId() + return g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1 +end + +local function plan(data, state) + local recipes = ADChain.recipesWithPaused(data.recipes, state.paused) + local chain = ADChainModel.build(recipes, data.husbandry, data.crops) + local used = ADChain.unionFruits(data.usedFruits, chain.fieldDemand) + local shortages = ADCropPlan.shortages(chain.fieldDemand, data.stock, data.fields, data.yields) + state.dismissed = ADChain.pruneDismissed(state.dismissed, shortages) + local actions = ADCropPlan.choose(shortages, data.fields, used, data.yields, data.prices, ADChain.dismissedFruits(state.dismissed)) + -- de ægte opskrifter (ikke kopien): en pauset rod er stadig tændt og må ikke "tændes" og overtages af kæden + local prod = ADChainActions.productions(data.recipes, chain.needRecipes, next(state.paused) ~= nil) + return {chain = chain, shortages = shortages, actions = actions, prod = prod, edges = ADChainActions.edges(chain)} +end + +-- act = true: udfør handlingerne (tick). Uden act kun en frisk visning (GUI). +function ADChain.evaluate(adEnv, act) + local state = ADChain.state + local ok, data = pcall(ADChainData.collect, adEnv, farmId(), state.byChain) + if not ok then + if not ADChain.collectFailed then Logging.warning(ADChain.LOG_PREFIX .. "gården kunne ikke læses: %s", tostring(data)) end + ADChain.collectFailed = true + return ADChain.last + end + ADChain.collectFailed = false + local okPlan, result = pcall(plan, data, state) + if not okPlan then + Logging.warning(ADChain.LOG_PREFIX .. "kæden kunne ikke beregnes: %s", tostring(result)) + return ADChain.last + end + if act and ADChainRun ~= nil then ADChainRun.apply(adEnv, data, result) end + local runs = ADChain.runsView(ADRunsController.store.runs) + local paused = {} + 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, data = data, fields = data.fields, titles = data.fillTitles, at = ADChain.now, switchCount = ADChain.switchCount} + return ADChain.last +end + +function ADChain.tick(adEnv, dt) + if adEnv == nil then return end + ADChain.now = ADChain.now + (dt or 0) + ADChain.timer = ADChain.timer + (dt or 0) + if ADChain.now < ADChain.STARTUP_GRACE_MS or ADChain.timer < ADChain.TICK_MS then return end + ADChain.timer = 0 + ADChain.evaluate(adEnv, true) +end + +-- ---- GUI-handlinger ---- + +local function suggestionFor(fieldId) + for _, action in ipairs(ADChain.last ~= nil and ADChain.last.actions or {}) do + if action.fieldId == fieldId then return action end + end + return nil +end + +-- Godkend forslag: marken får kædens afgrøde som mål (kilde "chain"). +function ADChain.approve(fieldId) + local action = suggestionFor(fieldId) + if action == nil then return false end + ADFieldJobs.setTarget(fieldId, action.to, "chain") + Logging.info(ADChain.LOG_PREFIX .. "F%s: forslag godkendt: %s -> %s", tostring(fieldId), tostring(action.from), tostring(action.to)) + return true +end + +-- Afvis forslag: vises ikke igen, før manglen for afgrøden ændrer sig > 25 %. +function ADChain.dismiss(fieldId) + local action = suggestionFor(fieldId) + if action == nil then return false end + local missingHa = 0 + for _, shortage in ipairs(ADChain.last.shortages or {}) do + if shortage.fruit == action.to then missingHa = shortage.missingHa end + end + ADChain.state.dismissed[fieldId] = {fruit = action.to, missingHa = missingHa} + Logging.info(ADChain.LOG_PREFIX .. "F%s: forslag %s afvist", tostring(fieldId), tostring(action.to)) + return true +end + +function ADChain.setPaused(rootKey, isPaused) + if rootKey == nil then return end + ADChain.state.paused[rootKey] = isPaused and true or nil + Logging.info(ADChain.LOG_PREFIX .. "%s %s", tostring(rootKey), isPaused and "på pause" or "genoptaget") +end + +-- ---- gem / indlæs (samme sti-opslag som runs; skrives kun fra ADRunsController.save) ---- + +local function getPath() + local path = ADRunsStorage ~= nil and ADRunsStorage.getPath() or nil + if path == nil then return nil end + return (path:gsub(ADRunsStorage.FILE_NAME .. "$", ADChain.FILE_NAME)) +end + +function ADChain.save() + local path = getPath() + if path == nil then return false end + local xmlFile = XMLFile.create("ADSmartPickupChain", path, ADChain.ROOT) + if xmlFile == nil then return false end + local records = ADChain.stateToRecords(ADChain.state) + for index, key in ipairs(records.byChain) do + xmlFile:setString(string.format("%s.byChain.recipe(%d)#key", ADChain.ROOT, index - 1), key) + end + for index, key in ipairs(records.paused) do + xmlFile:setString(string.format("%s.paused.root(%d)#key", ADChain.ROOT, index - 1), key) + end + for index, entry in ipairs(records.dismissed) do + local key = string.format("%s.dismissed.field(%d)", ADChain.ROOT, index - 1) + xmlFile:setInt(key .. "#id", entry.id) + xmlFile:setString(key .. "#fruit", entry.fruit) + xmlFile:setString(key .. "#missingHa", string.format("%.2f", entry.missingHa or 0)) + end + xmlFile:save() + xmlFile:delete() + return true +end + +function ADChain.load() + ADChain.state = ADChain.newState() + ADChain.noVehicle = {} + local path = getPath() + 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 = {}, dismissed = {}} + xmlFile:iterate(ADChain.ROOT .. ".byChain.recipe", function(_, key) + table.insert(records.byChain, xmlFile:getString(key .. "#key")) + end) + xmlFile:iterate(ADChain.ROOT .. ".paused.root", function(_, key) + table.insert(records.paused, xmlFile:getString(key .. "#key")) + end) + xmlFile:iterate(ADChain.ROOT .. ".dismissed.field", function(_, key) + table.insert(records.dismissed, {id = xmlFile:getInt(key .. "#id"), fruit = xmlFile:getString(key .. "#fruit"), + missingHa = tonumber(xmlFile:getString(key .. "#missingHa"))}) + end) + xmlFile:delete() + ADChain.state = ADChain.stateFromRecords(records) + Logging.info(ADChain.LOG_PREFIX .. "%d produktioner tændt af kæden, %d pausede kæder indlæst", #records.byChain, #records.paused) +end diff --git a/FS25_ADSmartPickup/adChainRun.lua b/FS25_ADSmartPickup/adChainRun.lua new file mode 100644 index 0000000..5e28adc --- /dev/null +++ b/FS25_ADSmartPickup/adChainRun.lua @@ -0,0 +1,186 @@ +-- AD Chain Run (kædens handlinger i spillet) +-- Udfører ADChain's plan: skifter frie marker (kun "auto", højst MAX_FIELD_SWITCHES pr. tick), tænder/slukker +-- mellemled, opretter manglende forsyningsflows (højst MAX_NEW_FLOWS pr. tick) og stopper flows for rødder der +-- er slået fra. Hver del kører i pcall, så en fejl ikke stopper resten. Spilkald kun inde i funktioner. + +ADChainRun = {} + +local function log(fmt, ...) Logging.info(ADChain.LOG_PREFIX .. fmt, ...) end +local function warn(fmt, ...) Logging.warning(ADChain.LOG_PREFIX .. fmt, ...) end + +-- Første puljevogn der kan bære varen + den bedst egnede ledige traktor (maskinpuljens regler: hk og +-- kategori via ADPool.bestFit). used = {[id] = true}: maskiner der allerede er brugt i dette tick. +function ADChainRun.choosePair(implements, tractors, fillTypeIndex, used) + if fillTypeIndex == nil then return nil end + used = used or {} + local free = {} + for _, tractor in ipairs(tractors or {}) do + if not used[tractor.id] and not tractor.offNetwork then table.insert(free, tractor) end + end + for _, implement in ipairs(implements or {}) do + if not used[implement.id] and ADPool.canCarry(implement, fillTypeIndex) then + local tractor = ADPool.bestFit(free, implement, "unloader") + if tractor ~= nil then return {implement = implement, tractor = tractor} end + end + end + return nil +end + +-- ---- marker ---- + +local function fieldById(fields, id) + for _, field in ipairs(fields or {}) do + if field.id == id then return field end + end + return nil +end + +function ADChainRun.switchFields(data, actions) + local switched = 0 + for _, action in ipairs(actions or {}) do + if switched >= ADChain.MAX_FIELD_SWITCHES then break end + local field = fieldById(data.fields, action.fieldId) + -- brugerens egen målafgrøde røres aldrig (dobbelttjek oven i ADCropPlan) + if action.mode == "auto" and field ~= nil and field.targetSource ~= "user" then + local ok, err = pcall(ADFieldJobs.setTarget, action.fieldId, action.to, "chain") + if ok then + switched = switched + 1 + ADChain.switchCount = ADChain.switchCount + 1 + local text = string.format("F%s: %s -> %s (%.1f ha) til %s", tostring(action.fieldId), tostring(action.from), + tostring(action.to), action.hectares or 0, tostring(action.reason)) + log("%s", text) + ADFieldJobs.notify(text, false) + else + warn("F%s kunne ikke skiftes: %s", tostring(action.fieldId), tostring(err)) + end + end + end +end + +-- ---- mellemled ---- + +local function setProduction(data, key, isOn) + local entry = data.points[key] + if entry == nil or entry.point == nil or entry.point.setProductionState == nil then + warn("produktion %s findes ikke (længere)", tostring(key)) + return false + end + local ok, err = pcall(entry.point.setProductionState, entry.point, entry.productionId, isOn) + if not ok then warn("produktion %s kunne ikke %s: %s", tostring(key), isOn and "tændes" or "slukkes", tostring(err)) end + return ok +end + +function ADChainRun.toggleProductions(data, prod) + local byChain = ADChain.state.byChain + for _, key in ipairs(prod.enable or {}) do + if setProduction(data, key, true) then + byChain[key] = true + log("tænder %s (mellemled i kæden)", key) + end + end + for _, key in ipairs(prod.disable or {}) do + if setProduction(data, key, false) then + byChain[key] = nil + log("slukker %s (kæden bruger den ikke længere)", key) + end + end +end + +-- ---- flows ---- + +local function collectPool(adEnv) + local ok, implements, tractors = pcall(ADPool.collect, adEnv, function(v) + return ADFieldWork.isBusyVehicle(v, ADRunsController.getVehicleId(v)) + end) + if not ok then + warn("maskinpuljen kunne ikke læses: %s", tostring(implements)) + return {}, {} + end + return implements or {}, tractors or {} +end + +local function createStopped(run, reason, ...) + run.stopped = true + ADChain.noVehicle[run.id] = true + 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, "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 + local pair = ADChainRun.choosePair(pool.implements, pool.tractors, fillIndex, used) + if pair == nil then + createStopped(run, "ingen ledig traktor/vogn til %s", tostring(title)) + return + end + used[pair.tractor.id], used[pair.implement.id] = true, true + ADRunsController.assignVehicle(runId, pair.tractor.vehicle) + run = ADRuns.getRun(ADRunsController.store, runId) + local wagons = ADRuns.copyWagons(run.wagons) + wagons[pair.tractor.id] = pair.implement.id + run.wagons = wagons + ADRunsController.resolveMarkers(adEnv, run) + local results = ADRunsController.startRun(adEnv, runId) + local started = 0 + for _, result in ipairs(results or {}) do if result.ok then started = started + 1 end end + log("flow '%s' oprettet med '%s' + vogn '%s' (%d startet)", tostring(run.name), tostring(pair.tractor.name), + tostring(pair.implement.name), started) +end + +function ADChainRun.createFlows(adEnv, data, edges) + local missing = ADChainActions.missingFlows(edges, ADChain.runsView(ADRunsController.store.runs)) + local pool, used, count = {}, {}, 0 + for _, edge in ipairs(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 + end + return count +end + +-- Flows for rødder der er slået fra: flyt til en anden rod der stadig bruger kanten, ellers stop (slettes ikke). +function ADChainRun.stopFlows(adEnv, chain, edges) + local changed = 0 + local active = ADChain.activeRoots(chain, ADChain.state.paused) + for _, run in ipairs(ADChainActions.flowsToStop(ADChain.runsView(ADRunsController.store.runs), active)) do + local rootKey = ADChain.stillNeeded(run, edges) + if rootKey ~= nil then + ADRunsController.updateRun(run.id, {chain = rootKey}) + log("flow '%s' hører nu til kæden %s", tostring(run.name), tostring(rootKey)) + else + local ok, err = pcall(ADRunsController.stopRun, adEnv, run.id) + if ok then log("flow '%s' stoppet: kæden %s er slået fra", tostring(run.name), tostring(run.chain)) + else warn("flow '%s' kunne ikke stoppes: %s", tostring(run.name), tostring(err)) end + end + changed = changed + 1 + end + return changed +end + +-- Hele planen; hver del for sig i pcall. +function ADChainRun.apply(adEnv, data, result) + local okFields, errFields = pcall(ADChainRun.switchFields, data, result.actions) + 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 okNew, created = pcall(ADChainRun.createFlows, adEnv, data, result.edges) + if not okNew then warn("nye flows fejlede: %s", tostring(created)) end + local okStop, stopped = pcall(ADChainRun.stopFlows, adEnv, result.chain, result.edges) + if not okStop then warn("stop af flows fejlede: %s", tostring(stopped)) end + if (okNew and created > 0) or (okStop and stopped > 0) then + local okSave, errSave = pcall(ADRunsController.save) + if not okSave then warn("flows kunne ikke gemmes: %s", tostring(errSave)) end + end +end diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index b3b3e8a..5f61ded 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -1476,6 +1476,8 @@ function ADRunsController.save() pcall(ADFieldJobs.save) end if ADFieldSilos ~= nil then pcall(ADFieldSilos.save) end + -- kædens tilstand (tændte mellemled, pausede kæder, afviste forslag) — samme gem-regel som navnefilen + if ADChain ~= nil and ADChain.save ~= nil then pcall(ADChain.save) end -- navnefilen skal skrives under spillets gem: FS25 genskriver savegame-mappen, og filer ingen skriver -- under gemningen forsvinder (in-game 27/9: ADSmartPickup_names.xml blev slettet ved første gem) if ADNames ~= nil and ADNames.byId ~= nil then pcall(ADNames.save) end @@ -1495,6 +1497,7 @@ function ADRunsController.load(adEnv) if ADNames ~= nil then pcall(ADNames.load) end pcall(ADRunsController.migrateBuildings, adEnv) if ADFieldSilos ~= nil then pcall(ADFieldSilos.load) end + if ADChain ~= nil and ADChain.load ~= nil then pcall(ADChain.load) end if ADCpResume ~= nil then pcall(ADCpResume.load) end local vehiclesById = getFarmVehiclesById(adEnv) for _, run in ipairs(store.runs) do diff --git a/FS25_ADSmartPickup/adSmartPickup.lua b/FS25_ADSmartPickup/adSmartPickup.lua index 5a361f4..fb27cb9 100644 --- a/FS25_ADSmartPickup/adSmartPickup.lua +++ b/FS25_ADSmartPickup/adSmartPickup.lua @@ -1309,6 +1309,12 @@ function ADSmartPickup:update(dt) Logging.warning(ADSmartPickup.LOG_PREFIX .. "marksilo-flow fejlede: %s", tostring(errSilos)) end end + if ADChain ~= nil then + local okChain, errChain = pcall(ADChain.tick, getAutoDriveEnv(), dt) + if not okChain then + Logging.warning(ADSmartPickup.LOG_PREFIX .. "kæde fejlede: %s", tostring(errChain)) + end + end if ADFlowWagons ~= nil then local okFlow, errFlow = pcall(ADFlowWagons.tick, getAutoDriveEnv(), dt) if not okFlow then Logging.warning(ADSmartPickup.LOG_PREFIX .. "flow-vogne fejlede: %s", tostring(errFlow)) end diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 60e1c04..f65dcd7 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -59,6 +59,8 @@ + +