feat(kæde): kæden kører — skifter frie marker, tænder mellemled, opretter flows
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
@@ -1476,6 +1476,8 @@ function ADRunsController.save()
|
|||||||
pcall(ADFieldJobs.save)
|
pcall(ADFieldJobs.save)
|
||||||
end
|
end
|
||||||
if ADFieldSilos ~= nil then pcall(ADFieldSilos.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
|
-- 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)
|
-- 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
|
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
|
if ADNames ~= nil then pcall(ADNames.load) end
|
||||||
pcall(ADRunsController.migrateBuildings, adEnv)
|
pcall(ADRunsController.migrateBuildings, adEnv)
|
||||||
if ADFieldSilos ~= nil then pcall(ADFieldSilos.load) end
|
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
|
if ADCpResume ~= nil then pcall(ADCpResume.load) end
|
||||||
local vehiclesById = getFarmVehiclesById(adEnv)
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
||||||
for _, run in ipairs(store.runs) do
|
for _, run in ipairs(store.runs) do
|
||||||
|
|||||||
@@ -1309,6 +1309,12 @@ function ADSmartPickup:update(dt)
|
|||||||
Logging.warning(ADSmartPickup.LOG_PREFIX .. "marksilo-flow fejlede: %s", tostring(errSilos))
|
Logging.warning(ADSmartPickup.LOG_PREFIX .. "marksilo-flow fejlede: %s", tostring(errSilos))
|
||||||
end
|
end
|
||||||
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
|
if ADFlowWagons ~= nil then
|
||||||
local okFlow, errFlow = pcall(ADFlowWagons.tick, getAutoDriveEnv(), dt)
|
local okFlow, errFlow = pcall(ADFlowWagons.tick, getAutoDriveEnv(), dt)
|
||||||
if not okFlow then Logging.warning(ADSmartPickup.LOG_PREFIX .. "flow-vogne fejlede: %s", tostring(errFlow)) end
|
if not okFlow then Logging.warning(ADSmartPickup.LOG_PREFIX .. "flow-vogne fejlede: %s", tostring(errFlow)) end
|
||||||
|
|||||||
@@ -59,6 +59,8 @@
|
|||||||
<sourceFile filename="adCropPlan.lua"/>
|
<sourceFile filename="adCropPlan.lua"/>
|
||||||
<sourceFile filename="adChainActions.lua"/>
|
<sourceFile filename="adChainActions.lua"/>
|
||||||
<sourceFile filename="adChainData.lua"/>
|
<sourceFile filename="adChainData.lua"/>
|
||||||
|
<sourceFile filename="adChain.lua"/>
|
||||||
|
<sourceFile filename="adChainRun.lua"/>
|
||||||
<sourceFile filename="adSmartPickup.lua"/>
|
<sourceFile filename="adSmartPickup.lua"/>
|
||||||
</extraSourceFiles>
|
</extraSourceFiles>
|
||||||
</modDesc>
|
</modDesc>
|
||||||
|
|||||||
Reference in New Issue
Block a user