-- 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 local UNLOADER = "unloader" -- Samme rolletjek som ADPool.pairAll("unloader", …) (hasRole er lokal i adPool). local function isUnloader(implement) return implement.role == UNLOADER or (implement.roles ~= nil and implement.roles[UNLOADER] == true) end -- Traktor i et flow (også et STOPPET): ADPool.collect regner den ledig til korte lån, men ADRuns.assignVehicle -- ville flytte den permanent ud af spillerens flow — kæden tager den aldrig. local function belongsToFlow(tractor) return tractor.vehicle ~= nil and ADRunsController.getRunForVehicle(tractor.vehicle) ~= nil end -- Første puljetømmevogn der kan bære varen + den bedst egnede ledige traktor (maskinpuljens regler: hk og -- kategori via ADPool.bestFit). used = {[id] = true}: brugt i dette tick; blocked = ADPool.blocked(nu). function ADChainRun.choosePair(implements, tractors, fillTypeIndex, used, blocked) if fillTypeIndex == nil then return nil end used, blocked = used or {}, blocked or {} local free = {} for _, tractor in ipairs(tractors or {}) do if not used[tractor.id] and not blocked[tractor.id] and not tractor.offNetwork and not belongsToFlow(tractor) then table.insert(free, tractor) end end for _, implement in ipairs(implements or {}) do if not used[implement.id] and not blocked[implement.id] and isUnloader(implement) 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) pcall(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 -- Spillerens slukninger er allerede lagt i state.userOff (ADChain.reconcile før planen): de tændes ikke igen. -- Delvise data: intet slukkes (en rod der ikke kunne læses, ligner en rod der er slået fra). function ADChainRun.toggleProductions(data, prod) local state = ADChain.state local byChain = state.byChain for _, key in ipairs(prod.enable or {}) do if not state.userOff[key] and setProduction(data, key, true) then byChain[key] = true log("tænder %s (mellemled i kæden)", key) end end if data.partial then return 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 -- kind = "noVehicle"|"pallet": vises som status på kanten i fanen Kæder local function createStopped(run, kind, reason, ...) run.stopped = true ADChain.noVehicle[run.id] = kind 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 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 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) 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)) changed = changed + 1 else local ok, err = pcall(ADRunsController.stopRun, adEnv, run.id) if ok then ADRunsController.updateRun(run.id, {chain = ""}) log("flow '%s' stoppet og sluppet af kæden: kæden %s er slået fra", tostring(run.name), tostring(run.chain)) changed = changed + 1 else warn("flow '%s' kunne ikke stoppes: %s", tostring(run.name), tostring(err)) end end 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 -- delvise data: en rod der ikke kunne læses, må ikke få sine flows stoppet local okStop, stopped = true, 0 if not data.partial then okStop, stopped = pcall(ADChainRun.stopFlows, adEnv, result.chain, result.edges) if not okStop then warn("stop af flows fejlede: %s", tostring(stopped)) end 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