1356 lines
62 KiB
Lua
1356 lines
62 KiB
Lua
-- AD Runs Controller
|
|
-- Oversætter et run til AutoDrive-tilstand pr. traktor: validering, Start/Stop, status,
|
|
-- Auto-vareliste, og "hvilket run hører traktoren til". GUI'et kalder kun herind;
|
|
-- de eksisterende hooks (choosePickup, Wait-punkter) spørger getRunForVehicle/getRunIngredients.
|
|
|
|
ADRunsController = {}
|
|
|
|
ADRunsController.LOG_PREFIX = "ADSmartPickup: "
|
|
-- Længere væk fra vejnettet end dette kan AutoDrive ikke starte (samme grænse som AD's HUD).
|
|
ADRunsController.MAX_NETWORK_DISTANCE = 30
|
|
|
|
ADRunsController.store = ADRuns.newStore()
|
|
-- Trækkerens egne tanke (diesel/AdBlue) kan give en lille "kapacitet" uden vogn.
|
|
ADRunsController.MIN_LOAD_CAPACITY = 100
|
|
-- run-id -> true når run'et er startet fra fanen (Start/Stop-knappen følger DETTE, ikke om
|
|
-- traktorerne tilfældigvis kører AutoDrive — en manuelt startet traktor må ikke vende knappen).
|
|
ADRunsController.runningRuns = {}
|
|
|
|
local function copyList(list)
|
|
local copy = {}
|
|
for index, value in ipairs(list or {}) do
|
|
copy[index] = value
|
|
end
|
|
return copy
|
|
end
|
|
|
|
-- Stabil nøgle: FS25's uniqueId ("vehicle…"), ellers savegame-id. Navne bruges aldrig.
|
|
function ADRunsController.getVehicleId(vehicle)
|
|
if vehicle.uniqueId ~= nil then
|
|
return tostring(vehicle.uniqueId)
|
|
end
|
|
if vehicle.getUniqueId ~= nil then
|
|
local id = vehicle:getUniqueId()
|
|
if id ~= nil then
|
|
return tostring(id)
|
|
end
|
|
end
|
|
if vehicle.currentSavegameId ~= nil then
|
|
return tostring(vehicle.currentSavegameId)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function ADRunsController.getFarmVehicles(adEnv)
|
|
local farmId = g_currentMission:getFarmId()
|
|
local result = {}
|
|
for _, vehicle in pairs(adEnv.AutoDrive.getAllVehicles()) do
|
|
-- kun motoriserede (traktorer, høstere); AD-spec'en sidder også på redskaber
|
|
local isMotorized = vehicle.spec_motorized ~= nil or vehicle.spec_motorized == nil and vehicle.startAutoDrive ~= nil
|
|
if vehicle.ad ~= nil and isMotorized and vehicle.getOwnerFarmId ~= nil and vehicle:getOwnerFarmId() == farmId then
|
|
table.insert(result, vehicle)
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Ét map pr. kald i stedet for at genscanne getFarmVehicles pr. vehicleId — startRun/stopRun/
|
|
-- getRunStatus/load slår flere id'er op pr. kald, og GUI'et kalder getRunStatus hvert sekund.
|
|
local function getFarmVehiclesById(adEnv)
|
|
local byId = {}
|
|
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
|
|
local id = ADRunsController.getVehicleId(vehicle)
|
|
if id ~= nil then
|
|
byId[id] = vehicle
|
|
end
|
|
end
|
|
return byId
|
|
end
|
|
|
|
function ADRunsController.findVehicleById(adEnv, vehicleId)
|
|
return getFarmVehiclesById(adEnv)[vehicleId]
|
|
end
|
|
|
|
function ADRunsController.getRunForVehicle(vehicle)
|
|
local id = ADRunsController.getVehicleId(vehicle)
|
|
return id ~= nil and ADRuns.findRunByVehicle(ADRunsController.store, id) or nil
|
|
end
|
|
|
|
-- ---- store-wrappers (erstatter altid hele store'en; modellen muterer aldrig) ----
|
|
|
|
function ADRunsController.createRun(name, kind)
|
|
local store, run = ADRuns.createRun(ADRunsController.store, name, kind)
|
|
ADRunsController.store = store
|
|
return run
|
|
end
|
|
|
|
function ADRunsController.deleteRun(runId, adEnv)
|
|
local run = ADRuns.getRun(ADRunsController.store, runId)
|
|
if run ~= nil and adEnv ~= nil then
|
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
|
ADRunsController.stopIfRunning(ADRunsController.findVehicleById(adEnv, vehicleId), "run slettet")
|
|
end
|
|
end
|
|
ADRunsController.store = ADRuns.deleteRun(ADRunsController.store, runId)
|
|
end
|
|
|
|
-- Hver ændring logges, så et klik der ikke når modellen kan ses i log.txt.
|
|
function ADRunsController.updateRun(runId, changes)
|
|
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, runId, changes)
|
|
local run = ADRuns.getRun(ADRunsController.store, runId)
|
|
local parts = {}
|
|
for key, value in pairs(changes) do
|
|
local text
|
|
if type(value) == "table" then
|
|
if key == "products" then
|
|
text = ADRuns.serializeProducts(value)
|
|
else
|
|
local items = {}
|
|
for _, item in ipairs(value) do table.insert(items, tostring(item)) end
|
|
text = table.concat(items, " ")
|
|
end
|
|
else
|
|
text = tostring(value)
|
|
end
|
|
table.insert(parts, string.format("%s=%s", tostring(key), text))
|
|
end
|
|
table.sort(parts)
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': %s", run ~= nil and tostring(run.name) or tostring(runId), table.concat(parts, ", "))
|
|
end
|
|
|
|
function ADRunsController.assignVehicle(runId, vehicle)
|
|
local id = ADRunsController.getVehicleId(vehicle)
|
|
if id == nil then
|
|
return nil
|
|
end
|
|
local store, previousRunId = ADRuns.assignVehicle(ADRunsController.store, runId, id)
|
|
ADRunsController.store = store
|
|
return previousRunId
|
|
end
|
|
|
|
function ADRunsController.unassignVehicle(vehicle)
|
|
local id = ADRunsController.getVehicleId(vehicle)
|
|
if id ~= nil then
|
|
ADRunsController.store = ADRuns.unassignVehicle(ADRunsController.store, id)
|
|
ADRunsController.stopIfRunning(vehicle, "fjernet fra run")
|
|
end
|
|
end
|
|
|
|
-- Markører udkørsels-forløb HENTER fra: {[wayPointId] = true}. Lagerture læsser helst ikke af dér.
|
|
function ADRunsController.getPickupMarkers()
|
|
local result = {}
|
|
for _, run in ipairs(ADRunsController.store ~= nil and ADRunsController.store.runs or {}) do
|
|
if run.kind == "outbound" then
|
|
for _, wayPointId in ipairs(run.sourceWayPointIds or {}) do result[wayPointId] = true end
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Varer flowet kører til lager (politik Lager / Salg, ellers lager): {{fillType, isPallet}}.
|
|
-- Er ingen vare sat til lager endnu, alle kildernes varer (så listen ikke er tom mens man opsætter).
|
|
function ADRunsController.getStoreGoods(adEnv, run)
|
|
local products = ADRunsController.getRunProducts(adEnv, run)
|
|
local goods = {}
|
|
for _, product in ipairs(products) do
|
|
if product.policy == "store" or product.policy == "sellElseStore" then
|
|
table.insert(goods, {fillType = product.fillType, isPallet = product.isPallet == true})
|
|
end
|
|
end
|
|
if #goods == 0 then
|
|
for _, product in ipairs(products) do
|
|
table.insert(goods, {fillType = product.fillType, isPallet = product.isPallet == true})
|
|
end
|
|
end
|
|
return goods
|
|
end
|
|
|
|
-- Kan bygningen tage mindst én af varerne som lager? Palleskur: pallevarer det tager. Silo/egen fabrik:
|
|
-- bulkvarer dens aflæsningsstation tager (samme regel som lagerturene: ADLoadSwap.isStoreTarget).
|
|
function ADRunsController.canStoreAt(adEnv, building, goods)
|
|
if building.kind == "objectStorage" then
|
|
for _, good in ipairs(goods or {}) do
|
|
if good.isPallet and ADSources ~= nil and ADSources.objectStorageAccepts ~= nil and ADSources.objectStorageAccepts(building.placeable, good.fillType) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
if building.unloadMarker == nil or ADLoadSwap == nil or ADLoadSwap.isStoreTarget == nil then
|
|
return false
|
|
end
|
|
local farmId = g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1
|
|
local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, building.unloadMarker.id, farmId)
|
|
if station == nil then
|
|
return false
|
|
end
|
|
for _, good in ipairs(goods or {}) do
|
|
if not good.isPallet and ADLoadSwap.isStoreTarget(station, good.fillType, nil, true)
|
|
and (station.getIsFillTypeSupported == nil or station:getIsFillTypeSupported(good.fillType)) then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- "Lager til"-bygningerne selv (i valgt rækkefølge) — til palleture der skal til et bestemt palleskur.
|
|
function ADRunsController.getStoreBuildings(adEnv, run)
|
|
local result = {}
|
|
if ADBuildings == nil or run == nil then return result end
|
|
local farmId = g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1
|
|
for _, buildingId in ipairs(run.storeBuildingIds or {}) do
|
|
local building = ADBuildings.find(adEnv, farmId, buildingId)
|
|
if building ~= nil then table.insert(result, building) end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- "Lager til": aflæsningsmarkører for flowets valgte lagerbygninger, i valgt rækkefølge (tom = ingen tvang).
|
|
function ADRunsController.getStoreMarkers(adEnv, run)
|
|
local result = {}
|
|
if ADBuildings == nil or run == nil then return result end
|
|
local farmId = g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1
|
|
for _, buildingId in ipairs(run.storeBuildingIds or {}) do
|
|
local building = ADBuildings.find(adEnv, farmId, buildingId)
|
|
if building ~= nil and building.unloadMarker ~= nil then
|
|
table.insert(result, building.unloadMarker.id)
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- ---- køretøjsnavne ----
|
|
|
|
ADRunsController.NAME_INTERVAL_MS = 2000
|
|
local nameTimerMs = 0
|
|
local namedByUs = {}
|
|
|
|
-- Sætter AutoDrives førernavn efter forløbet ("Milk Collect 1"). Et køretøj der ikke længere er i et
|
|
-- forløb, får modelnavnet tilbage — men kun hvis navnet er et vi selv har sat (brugerens egne navne røres ikke).
|
|
function ADRunsController.applyVehicleNames(adEnv)
|
|
local store = ADRunsController.store
|
|
local names = ADRuns.vehicleNames(store)
|
|
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
|
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
if stateModule ~= nil and stateModule.setName ~= nil then
|
|
local id = ADRunsController.getVehicleId(vehicle)
|
|
local wanted = id ~= nil and names[id] or nil
|
|
local current = stateModule.getName ~= nil and stateModule:getName() or stateModule.driverName
|
|
if wanted ~= nil then
|
|
if current ~= wanted then
|
|
stateModule:setName(wanted)
|
|
if stateModule.raiseDirtyFlag ~= nil then stateModule:raiseDirtyFlag() end
|
|
end
|
|
namedByUs[vehicle] = wanted
|
|
elseif (namedByUs[vehicle] ~= nil and current == namedByUs[vehicle]) or ADRuns.isRunVehicleName(store, current) then
|
|
stateModule:setName(tostring(vehicle:getName()))
|
|
if stateModule.raiseDirtyFlag ~= nil then stateModule:raiseDirtyFlag() end
|
|
namedByUs[vehicle] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ADRunsController.applyVehicleNamesThrottled(adEnv, dt)
|
|
nameTimerMs = nameTimerMs + (dt or 0)
|
|
if nameTimerMs < ADRunsController.NAME_INTERVAL_MS then return end
|
|
nameTimerMs = 0
|
|
ADRunsController.applyVehicleNames(adEnv)
|
|
end
|
|
|
|
-- ---- varer ----
|
|
|
|
function ADRunsController.getCarriableFillTypes(adEnv, vehicle)
|
|
return adEnv.AutoDrive.getSupportedFillTypesOfAllUnitsAlphabetically(vehicle) or {}
|
|
end
|
|
|
|
-- Auto-listen: det vognen kan bære, som målet også tager. Er målet en fabrik, indsnævrer
|
|
-- findSupplyPickup selv til de ønskede produktioners inputs bagefter.
|
|
-- Run'ets mål som liste (flere stalde pr. run); gamle runs har kun targetWayPointId.
|
|
function ADRunsController.getRunTargets(run)
|
|
if run.kind == "outbound" then
|
|
return {}
|
|
end
|
|
if run.targetWayPointIds ~= nil and #run.targetWayPointIds > 0 then
|
|
return run.targetWayPointIds
|
|
end
|
|
return run.targetWayPointId ~= nil and {run.targetWayPointId} or {}
|
|
end
|
|
|
|
local function isWaitWayPoint(adEnv, wayPointId)
|
|
local marker = adEnv.ADGraphManager.getMapMarkerByWayPointId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId) or nil
|
|
return marker ~= nil and ADSupplyPlanner ~= nil and ADSupplyPlanner.isWaitMarker ~= nil and ADSupplyPlanner.isWaitMarker(marker)
|
|
end
|
|
|
|
-- Wait-markører er ventepladser og aldrig mål, selv om de ligger inden for staldens trigger-rækkevidde.
|
|
local function getTargetStations(adEnv, run, farmId)
|
|
local stations = {}
|
|
for _, wayPointId in ipairs(ADRunsController.getRunTargets(run)) do
|
|
local station = not isWaitWayPoint(adEnv, wayPointId) and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, wayPointId, farmId) or nil
|
|
if station ~= nil then
|
|
table.insert(stations, {wayPointId = wayPointId, station = station})
|
|
end
|
|
end
|
|
return stations
|
|
end
|
|
|
|
function ADRunsController.getTargetFillTypes(adEnv, run, vehicle)
|
|
local farmId = ADSmartPickup.getFarmId(vehicle)
|
|
-- En kørende traktor følger AD's eget mål (stateModule:getSecondMarker(), det samme
|
|
-- choosePickup/findSupplyPickup bruger) indtil næste tur — IKKE run'ets gemte mål.
|
|
-- Redigerer brugeren run'ets Mål mens tildelte traktorer kører, skal Auto-listen derfor
|
|
-- følge det LIVE mål, ellers kan den blive tom og fejlagtigt smide traktoren over i
|
|
-- mappe-logikken midt i turen. Inaktiv: union over alle run'ets mål.
|
|
local stations = {}
|
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
if stateModule ~= nil and stateModule.isActive ~= nil and stateModule:isActive() and stateModule.getSecondMarker ~= nil then
|
|
local secondMarker = stateModule:getSecondMarker()
|
|
local station = secondMarker ~= nil and secondMarker.id ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, secondMarker.id, farmId) or nil
|
|
if station ~= nil then
|
|
stations = {{wayPointId = secondMarker.id, station = station}}
|
|
end
|
|
end
|
|
if #stations == 0 then
|
|
stations = getTargetStations(adEnv, run, farmId)
|
|
end
|
|
local result, seen = {}, {}
|
|
for _, fillType in ipairs(ADRunsController.getCarriableFillTypes(adEnv, vehicle)) do
|
|
for _, entry in ipairs(stations) do
|
|
if not seen[fillType] and ADSmartPickup.getStationLevelAndCapacity(entry.station, fillType, farmId) ~= nil then
|
|
seen[fillType] = true
|
|
table.insert(result, fillType)
|
|
end
|
|
end
|
|
end
|
|
table.sort(result)
|
|
return result
|
|
end
|
|
|
|
function ADRunsController.getRunIngredients(adEnv, run, vehicle)
|
|
if run.ingredientMode == "manual" then
|
|
local result = {}
|
|
for _, name in ipairs(run.ingredients) do
|
|
local index = g_fillTypeManager:getFillTypeIndexByName(name)
|
|
if index ~= nil then
|
|
table.insert(result, index)
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
return ADRunsController.getTargetFillTypes(adEnv, run, vehicle)
|
|
end
|
|
|
|
-- Alle varer målet tager (uanset vogn) — afkrydsningslisten i GUI'et. {index, name, title}, sorteret på titel.
|
|
function ADRunsController.getTargetAcceptedFillTypes(adEnv, run)
|
|
local farmId = g_currentMission:getFarmId()
|
|
local stations = getTargetStations(adEnv, run, farmId)
|
|
if #stations == 0 then
|
|
return {}
|
|
end
|
|
local fillTypes = g_fillTypeManager.getFillTypes ~= nil and g_fillTypeManager:getFillTypes() or g_fillTypeManager.fillTypes or {}
|
|
local result = {}
|
|
for _, fillType in ipairs(fillTypes) do
|
|
if fillType.index ~= nil then
|
|
for _, entry in ipairs(stations) do
|
|
if ADSmartPickup.getStationLevelAndCapacity(entry.station, fillType.index, farmId) ~= nil then
|
|
table.insert(result, {index = fillType.index, name = fillType.name, title = fillType.title or fillType.name})
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(result, function(a, b) return tostring(a.title) < tostring(b.title) end)
|
|
return result
|
|
end
|
|
|
|
-- Flere mål: den stald der mangler mest lige nu (bedste rangerede behov pr. mål; et FULDT
|
|
-- læs der passer vinder). nil uden mål. Ét mål = det.
|
|
function ADRunsController.chooseTarget(adEnv, run, vehicle)
|
|
local targets = ADRunsController.getRunTargets(run)
|
|
if #targets <= 1 then
|
|
return targets[1]
|
|
end
|
|
local farmId = ADSmartPickup.getFarmId(vehicle)
|
|
local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle)
|
|
local bestId, bestNeed, bestInbound = nil, nil, 0
|
|
for _, entry in ipairs(getTargetStations(adEnv, run, farmId)) do
|
|
-- læs andre traktorer er på vej med til stalden, tæller med — så fordeler de sig på staldene
|
|
local pending = ADSmartPickup.getPending ~= nil and ADSmartPickup.getPending(entry.wayPointId, vehicle) or nil
|
|
local ranked = ADSmartPickup.getTargetNeedsRanked(entry.station, ingredients, farmId, pending)
|
|
local need = ranked[1]
|
|
if need ~= nil and need.freeSpace >= (ADSupplyPlanner.MIN_FREE_LITERS or 1000) then
|
|
-- færrest andre vogne på vej først: et drop (drive-on-bunker, trug) tager én ad gangen, så tre
|
|
-- vogne mod det mindst fyldte mål holder i kø mens de andre mål står tomme (in-game 26/9)
|
|
local inbound = ADSmartPickup.countEnRoute ~= nil and ADSmartPickup.countEnRoute(entry.wayPointId, vehicle) or 0
|
|
local isBetter = bestNeed == nil
|
|
or inbound < bestInbound
|
|
or (inbound == bestInbound and need.fits == true and bestNeed.fits ~= true)
|
|
or (inbound == bestInbound and need.fits == bestNeed.fits and need.score > bestNeed.score)
|
|
if isBetter then
|
|
bestId, bestNeed, bestInbound = entry.wayPointId, need, inbound
|
|
end
|
|
end
|
|
end
|
|
return bestId or targets[1]
|
|
end
|
|
|
|
-- AutoDrive genstarter aktive traktorer efter indlæsning uden om run-starten: fyldte run-traktorer
|
|
-- registreres "på vej" til deres nuværende mål (ellers ser mål-valget 0 vogne på vej overalt).
|
|
ADRunsController.ADOPT_INTERVAL_MS = 5000
|
|
local adoptTimerMs = 0
|
|
function ADRunsController.adoptLoadedThrottled(adEnv, dt)
|
|
adoptTimerMs = adoptTimerMs + (dt or 0)
|
|
if adEnv == nil or adoptTimerMs < ADRunsController.ADOPT_INTERVAL_MS then return end
|
|
adoptTimerMs = 0
|
|
for _, vehicle in pairs(getFarmVehiclesById(adEnv)) do
|
|
local run = ADRunsController.getRunForVehicle(vehicle)
|
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
if run ~= nil and (run.kind or "supply") == "supply" and stateModule ~= nil and stateModule:isActive()
|
|
and ADSmartPickup.getEnRoute(vehicle) == nil then
|
|
local second = stateModule.getSecondMarker ~= nil and stateModule:getSecondMarker() or nil
|
|
if second ~= nil and ADSmartPickup.registerCargo(adEnv, vehicle, second.id, ADRunsController.getRunIngredients(adEnv, run, vehicle)) then
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "'%s' har læs på vej til %s (overtaget efter start/indlæsning)", tostring(ADRuns.vehicleLabel(vehicle)), tostring(second.name or second.id))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Fire traktorer kan hedde det samme; løbenummeret er pladsen i farmens AD-køretøjsliste.
|
|
function ADRunsController.getVehicleLabel(adEnv, vehicle)
|
|
local label, model = tostring(ADRuns.vehicleLabel(vehicle)), tostring(vehicle:getName())
|
|
-- førernavn efter forløbet: "Milk Collect 1 (Virtus 135 RVshift)"; ellers modelnavn + løbenummer
|
|
if label ~= model then
|
|
return string.format("%s (%s)", label, model)
|
|
end
|
|
for index, candidate in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
|
|
if candidate == vehicle then
|
|
return string.format("%s (#%d)", model, index)
|
|
end
|
|
end
|
|
return model
|
|
end
|
|
|
|
-- ---- bygninger (flow-tavlen): bygnings-id'er -> markører ----
|
|
|
|
local function listEquals(a, b)
|
|
if #a ~= #b then return false end
|
|
for index = 1, #a do
|
|
if a[index] ~= b[index] then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Udleder run'ets markørlister fra de valgte bygninger: udkørsel -> sourceWayPointIds (bygningens
|
|
-- læssemarkør), forsyning -> targetWayPointIds (aflæsningsmarkør). Markører der ikke hører til
|
|
-- nogen bygning ("rå", fra før flow-tavlen) bevares; markører der hører til en FRAVALGT bygning
|
|
-- fjernes. Returnerer {missing = {bygningsnavne uden markør}}. Store opdateres kun ved ændring.
|
|
function ADRunsController.resolveMarkers(adEnv, run)
|
|
local missing = {}
|
|
if ADBuildings == nil or run == nil then
|
|
return {missing = missing}
|
|
end
|
|
local farmId = g_currentMission:getFarmId()
|
|
local role = run.kind == "outbound" and "load" or "unload"
|
|
local buildingIds = run.kind == "outbound" and run.sourceBuildingIds or run.targetBuildingIds
|
|
local existing = run.kind == "outbound" and run.sourceWayPointIds or ADRunsController.getRunTargets(run)
|
|
local resolved, seen = {}, {}
|
|
for _, buildingId in ipairs(buildingIds or {}) do
|
|
local building = ADBuildings.find(adEnv, farmId, buildingId)
|
|
local markers = building ~= nil and (ADBuildings.getMarkers ~= nil and ADBuildings.getMarkers(building, role) or (role == "load" and (building.loadMarkers or {building.loadMarker}) or {building.unloadMarker})) or {}
|
|
if markers[1] ~= nil then
|
|
for _, marker in ipairs(markers) do
|
|
if marker ~= nil and not seen[marker.id] then
|
|
seen[marker.id] = true
|
|
table.insert(resolved, marker.id)
|
|
end
|
|
end
|
|
else
|
|
table.insert(missing, building ~= nil and building.name or (tostring(buildingId) .. " (mangler)"))
|
|
end
|
|
end
|
|
-- Rå markører (fra før flow-tavlen): beholdes kun hvis de er en genkendelig kilde og ikke hører til
|
|
-- en bygning der allerede er valgt (så ville de dublere bygningens egne markører).
|
|
local chosenPlaceables = {}
|
|
for _, buildingId in ipairs(buildingIds or {}) do
|
|
local building = ADBuildings.find(adEnv, farmId, buildingId)
|
|
if building ~= nil and building.placeable ~= nil then chosenPlaceables[building.placeable] = true end
|
|
end
|
|
local pruned = {}
|
|
for _, wayPointId in ipairs(existing or {}) do
|
|
if not seen[wayPointId] and ADBuildings.byMarker(adEnv, farmId, wayPointId, role) == nil then
|
|
local keep = true
|
|
-- et rå id skal være en AD-markør: sletter man waypoints i AutoDrive, omnummereres alle
|
|
-- senere punkter, og et gammelt markør-id peger så på et tilfældigt vejpunkt
|
|
local isMarker = adEnv.ADGraphManager.getMapMarkerByWayPointId == nil or adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId) ~= nil
|
|
if role == "load" and ADSources ~= nil and ADSources.identify ~= nil then
|
|
local source = isMarker and ADSources.identify(adEnv, wayPointId, farmId) or nil
|
|
keep = source ~= nil and not (source.placeable ~= nil and chosenPlaceables[source.placeable])
|
|
elseif role == "unload" then
|
|
-- mål skal være en AD-MARKØR med aflæsningsstation (et slettet markør-id er kun et waypoint,
|
|
-- og AutoDrive kan ikke aflæsse dér); dubletter af valgte bygninger fjernes
|
|
local marker = adEnv.ADGraphManager.getMapMarkerByWayPointId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId) or nil
|
|
local station = marker ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, wayPointId, farmId) or nil
|
|
keep = station ~= nil and not (station.owningPlaceable ~= nil and chosenPlaceables[station.owningPlaceable])
|
|
end
|
|
seen[wayPointId] = true
|
|
if keep then
|
|
table.insert(resolved, wayPointId)
|
|
else
|
|
table.insert(pruned, tostring(wayPointId))
|
|
end
|
|
end
|
|
end
|
|
if #pruned > 0 then
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': gamle markører fjernet (ingen kilde eller dublet af valgt bygning): %s", tostring(run.name), table.concat(pruned, " "))
|
|
end
|
|
if not listEquals(resolved, existing or {}) then
|
|
local changes = run.kind == "outbound" and {sourceWayPointIds = resolved} or {targetWayPointIds = resolved}
|
|
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, run.id, changes)
|
|
local updated = ADRuns.getRun(ADRunsController.store, run.id)
|
|
if updated ~= nil then
|
|
run.sourceWayPointIds, run.targetWayPointIds, run.targetWayPointId = updated.sourceWayPointIds, updated.targetWayPointIds, updated.targetWayPointId
|
|
end
|
|
end
|
|
return {missing = missing}
|
|
end
|
|
|
|
-- Ved indlæsning: runs fra før flow-tavlen har kun markører — oversæt dem til bygninger, hvor
|
|
-- markøren er en bygnings læsse-/aflæsningsmarkør. Rå markører uden bygning beholdes.
|
|
function ADRunsController.migrateBuildings(adEnv)
|
|
if ADBuildings == nil then
|
|
return
|
|
end
|
|
local farmId = g_currentMission:getFarmId()
|
|
for _, run in ipairs(ADRunsController.store.runs) do
|
|
local role = run.kind == "outbound" and "load" or "unload"
|
|
local ids = copyList(run.kind == "outbound" and run.sourceBuildingIds or run.targetBuildingIds)
|
|
local known = {}
|
|
for _, id in ipairs(ids) do known[id] = true end
|
|
local added = {}
|
|
for _, wayPointId in ipairs(run.kind == "outbound" and run.sourceWayPointIds or ADRunsController.getRunTargets(run)) do
|
|
local building = ADBuildings.byMarker(adEnv, farmId, wayPointId, role)
|
|
if building ~= nil and not known[building.id] then
|
|
known[building.id] = true
|
|
table.insert(ids, building.id)
|
|
table.insert(added, building.name)
|
|
end
|
|
end
|
|
if #added > 0 then
|
|
local changes = run.kind == "outbound" and {sourceBuildingIds = ids} or {targetBuildingIds = ids}
|
|
ADRunsController.store = ADRuns.updateRun(ADRunsController.store, run.id, changes)
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': markører oversat til bygninger: %s", tostring(run.name), table.concat(added, ", "))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Data til én række i flow-tavlen (GUI'et formaterer teksten).
|
|
function ADRunsController.getFlowRow(adEnv, run)
|
|
local farmId = g_currentMission:getFarmId()
|
|
local row = {productNames = {}, productAuto = false, fromNames = {}, toNames = {}, fromAllSilos = run.kind ~= "outbound", missing = {}, policies = {}, vehicleNames = {}, collectRound = run.collectRound == true, sellAtPercent = run.sellAtPercent, storeAbovePercent = run.storeAbovePercent, loops = run.loops, kind = run.kind}
|
|
local ids = run.kind == "outbound" and run.sourceBuildingIds or run.targetBuildingIds
|
|
local names = run.kind == "outbound" and row.fromNames or row.toNames
|
|
for _, buildingId in ipairs(ids or {}) do
|
|
local building = ADBuildings ~= nil and ADBuildings.find(adEnv, farmId, buildingId) or nil
|
|
local marker = building ~= nil and (run.kind == "outbound" and building.loadMarker or building.unloadMarker) or nil
|
|
local name = building ~= nil and building.name or (tostring(buildingId) .. " (mangler)")
|
|
table.insert(names, name)
|
|
if marker == nil then table.insert(row.missing, name) end
|
|
end
|
|
-- rå markører (uden bygning) vises med markørnavn
|
|
local raw = run.kind == "outbound" and run.sourceWayPointIds or ADRunsController.getRunTargets(run)
|
|
for _, wayPointId in ipairs(raw or {}) do
|
|
if ADBuildings == nil or ADBuildings.byMarker(adEnv, farmId, wayPointId, run.kind == "outbound" and "load" or "unload") == nil then
|
|
local marker = adEnv.ADGraphManager.getMapMarkerByWayPointId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId) or nil
|
|
table.insert(names, marker ~= nil and ("markør '" .. tostring(marker.name) .. "'") or ("markør " .. tostring(wayPointId)))
|
|
end
|
|
end
|
|
if run.kind == "outbound" then
|
|
for _, product in ipairs(run.products or {}) do
|
|
table.insert(row.productNames, product.fillType)
|
|
table.insert(row.policies, product.policy)
|
|
end
|
|
local status = ADRunsController.getOutboundStatus(adEnv, run)
|
|
row.reason, row.warning = status.reasons[1], status.warning
|
|
else
|
|
row.productAuto = run.ingredientMode ~= "manual"
|
|
if not row.productAuto then
|
|
for _, name in ipairs(run.ingredients or {}) do table.insert(row.productNames, name) end
|
|
end
|
|
end
|
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
|
local vehicle = vehiclesById[vehicleId]
|
|
table.insert(row.vehicleNames, vehicle ~= nil and tostring(ADRuns.vehicleLabel(vehicle)) or "?")
|
|
end
|
|
local status = ADRunsController.getRunStatus(adEnv, run)
|
|
row.running, row.total, row.isRunning = status.running, status.total, status.isRunning
|
|
return row
|
|
end
|
|
|
|
-- ---- validering ----
|
|
|
|
-- Liter-kapacitet for vognen; nil uden vogn. Universal Autoload opgiver ikke liter men ANTAL paller
|
|
-- (læssede + 1), så et autoload-køretøj får nil her og markeres via isAutoload i valideringen.
|
|
local function getLoadCapacity(adEnv, vehicle)
|
|
local trailers = adEnv.AutoDrive.getAllUnits(vehicle)
|
|
if trailers == nil then
|
|
return nil
|
|
end
|
|
local _, capacity = adEnv.AutoDrive.getAllFillLevels(trailers)
|
|
return capacity ~= nil and capacity >= ADRunsController.MIN_LOAD_CAPACITY and capacity or nil
|
|
end
|
|
|
|
local function intersects(listA, listB)
|
|
for _, a in ipairs(listA) do
|
|
for _, b in ipairs(listB) do
|
|
if a == b then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Autoload = AutoDrives eget tjek (Universal Autoload med isAutoloadAvailable og ikke autoloadDisabled,
|
|
-- eller aPalletAutoLoader). Findes ingen, logges én gang pr. traktor hvad enhederne har, så det kan
|
|
-- ses i log.txt hvorfor (fx UAL uden konfiguration for køretøjet).
|
|
local autoloadDiagnosed = setmetatable({}, {__mode = "k"})
|
|
local function describeAutoloadUnit(unit)
|
|
local name = unit.getName ~= nil and tostring(unit:getName()) or "?"
|
|
local spec = unit.spec_universalAutoload
|
|
if spec == nil then
|
|
return string.format("%s (ingen spec_universalAutoload%s)", name, unit.spec_aPalletAutoLoader ~= nil and ", aPalletAutoLoader" or "")
|
|
end
|
|
return string.format("%s (isAutoloadAvailable=%s, autoloadDisabled=%s)", name, tostring(spec.isAutoloadAvailable), tostring(spec.autoloadDisabled))
|
|
end
|
|
|
|
local function hasAutoload(adEnv, vehicle)
|
|
local trailers = adEnv.AutoDrive.getAllUnits ~= nil and adEnv.AutoDrive.getAllUnits(vehicle) or nil
|
|
for _, trailer in ipairs(trailers or {}) do
|
|
if adEnv.AutoDrive.hasAL ~= nil and adEnv.AutoDrive:hasAL(trailer) then
|
|
autoloadDiagnosed[vehicle] = nil
|
|
return true
|
|
end
|
|
end
|
|
if not autoloadDiagnosed[vehicle] then
|
|
autoloadDiagnosed[vehicle] = true
|
|
local parts = {}
|
|
for _, unit in ipairs(trailers or {}) do table.insert(parts, describeAutoloadUnit(unit)) end
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "'%s': ingen autoload fundet på enhederne: %s", tostring(ADRuns.vehicleLabel(vehicle)), #parts > 0 and table.concat(parts, "; ") or "(ingen enheder)")
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Er langt fra vejnettet? (AD's getDistanceFromNetwork kalder vehicle:getClosestWayPoint(),
|
|
-- som ikke alle AD-køretøjer har — set in-game på en høster — så uden metoden springes tjekket over.)
|
|
local function isFarFromNetwork(adEnv, vehicle)
|
|
local distance = 0
|
|
if vehicle.getClosestWayPoint ~= nil and adEnv.ADGraphManager.getDistanceFromNetwork ~= nil then
|
|
local ok, value = pcall(adEnv.ADGraphManager.getDistanceFromNetwork, adEnv.ADGraphManager, vehicle)
|
|
distance = ok and value or 0
|
|
end
|
|
return distance == nil or distance > ADRunsController.MAX_NETWORK_DISTANCE
|
|
end
|
|
|
|
-- ---- udkørsel ----
|
|
|
|
-- Kilder ved markørerne (fabrik/silo/objektlager) — {source} i run'ets rækkefølge; ukendte udelades.
|
|
local function getRunSources(adEnv, run, farmId)
|
|
local sources = {}
|
|
if ADSources == nil then
|
|
return sources
|
|
end
|
|
for _, wayPointId in ipairs(run.sourceWayPointIds or {}) do
|
|
local source = ADSources.identify(adEnv, wayPointId, farmId)
|
|
if source ~= nil then
|
|
table.insert(sources, source)
|
|
end
|
|
end
|
|
return sources
|
|
end
|
|
|
|
-- Alle varer kilderne rummer/producerer med run'ets politik (uanset vogn) — til fanen Udkørsel.
|
|
-- {fillType, name, title, policy, isPallet, available, level, capacity}, sorteret på titel.
|
|
function ADRunsController.getRunProducts(adEnv, run)
|
|
local farmId = g_currentMission:getFarmId()
|
|
local byName, result = {}, {}
|
|
for _, source in ipairs(getRunSources(adEnv, run, farmId)) do
|
|
for _, item in ipairs(ADSources.getProducts(adEnv, source, farmId)) do
|
|
local product = byName[item.name]
|
|
if product == nil then
|
|
product = {fillType = item.fillType, name = item.name, title = item.title or item.name, policy = ADRuns.getProductPolicy(run, item.name), isPallet = item.isPallet, canPallet = item.canPallet, available = 0, level = 0, capacity = 0}
|
|
byName[item.name] = product
|
|
table.insert(result, product)
|
|
end
|
|
product.available = product.available + (item.available or 0)
|
|
product.level = product.level + (item.level or 0)
|
|
product.capacity = product.capacity + (item.capacity or 0)
|
|
end
|
|
end
|
|
table.sort(result, function(a, b) return tostring(a.title) < tostring(b.title) end)
|
|
return result
|
|
end
|
|
|
|
-- Run'ets varer (fillType-indeks) som denne vogn kan bære: bulk via AD's liste, paller via UAL.
|
|
local function getOutboundFillTypes(adEnv, run, vehicle)
|
|
local carriable = {}
|
|
for _, fillType in ipairs(ADRunsController.getCarriableFillTypes(adEnv, vehicle)) do
|
|
carriable[fillType] = true
|
|
end
|
|
local canLoadPallets = hasAutoload(adEnv, vehicle)
|
|
local result, palletCount, chosen = {}, 0, 0
|
|
for _, product in ipairs(ADRunsController.getRunProducts(adEnv, run)) do
|
|
if product.policy ~= "off" then
|
|
chosen = chosen + 1
|
|
if product.isPallet then
|
|
palletCount = palletCount + 1
|
|
if canLoadPallets then table.insert(result, product.fillType) end
|
|
elseif carriable[product.fillType] then
|
|
table.insert(result, product.fillType)
|
|
elseif product.canPallet and canLoadPallets then
|
|
-- bulk-vare der også står på paller (sukkerfabrik): pallebilen tager pallerne
|
|
table.insert(result, product.fillType)
|
|
end
|
|
end
|
|
end
|
|
return result, chosen, palletCount, canLoadPallets
|
|
end
|
|
|
|
-- Last på vognen ved Start: nil = tom vogn, waypoint-id = mål for lasten, false = intet mål.
|
|
local function getCargoTarget(adEnv, run, vehicle)
|
|
if ADUnloadWait == nil or ADUnloadWait.getRestLoad == nil or ADOutbound == nil or ADOutbound.planCargoTarget == nil then
|
|
return nil
|
|
end
|
|
local fillType, liters = ADUnloadWait.getRestLoad(adEnv, vehicle)
|
|
if fillType == nil or liters < 100 then
|
|
return nil
|
|
end
|
|
return ADOutbound.planCargoTarget(adEnv, run, vehicle, fillType, liters) or false
|
|
end
|
|
|
|
-- Én gang pr. (run, bygningsliste): valgte bygninger uden markør springes over.
|
|
local warnedMissing = {}
|
|
local function warnMissingMarkers(run, missing)
|
|
local text = table.concat(missing, ", ")
|
|
if warnedMissing[run.id] == text then
|
|
return
|
|
end
|
|
warnedMissing[run.id] = text
|
|
Logging.warning(ADRunsController.LOG_PREFIX .. "run '%s': %s har ingen AD-markør ved triggeren (inden for 40 m) og springes over", tostring(run.name), text)
|
|
end
|
|
|
|
local function validateOutboundVehicle(adEnv, run, vehicle, result)
|
|
local resolved = ADRunsController.resolveMarkers(adEnv, run)
|
|
if resolved.missing[1] ~= nil then
|
|
-- afvis kun når INGEN valgt bygning (eller rå markør) har en markør; ellers kør med de andre
|
|
if #(run.sourceWayPointIds or {}) == 0 then
|
|
result.reason, result.missingBuilding = "noMarker", resolved.missing[1]
|
|
return result
|
|
end
|
|
warnMissingMarkers(run, resolved.missing)
|
|
end
|
|
if #getRunSources(adEnv, run, ADSmartPickup.getFarmId(vehicle)) == 0 then
|
|
result.reason = "noSource"
|
|
return result
|
|
end
|
|
if result.capacity == nil and not result.isAutoload then
|
|
result.reason = "noTrailer"
|
|
return result
|
|
end
|
|
local fillTypes, chosen, palletCount, canLoadPallets = getOutboundFillTypes(adEnv, run, vehicle)
|
|
if chosen == 0 then
|
|
result.reason = "noProducts"
|
|
return result
|
|
end
|
|
if #fillTypes == 0 then
|
|
result.reason = (palletCount == chosen and not canLoadPallets) and "needsAutoload" or "cannotCarry"
|
|
return result
|
|
end
|
|
-- Uden Wait-punkt ville traktoren holde ved kilden og læsse selv om planlæggeren sagde "vent".
|
|
if ADWaitPool ~= nil and #ADWaitPool.getCandidates(adEnv, run.waitPoolGroup) == 0 then
|
|
result.reason = "noWaitPoint"
|
|
return result
|
|
end
|
|
if getCargoTarget(adEnv, run, vehicle) == false then
|
|
result.reason = "cargoNoTarget"
|
|
return result
|
|
end
|
|
if isFarFromNetwork(adEnv, vehicle) then
|
|
result.reason = "farFromNetwork"
|
|
return result
|
|
end
|
|
result.ok = true
|
|
return result
|
|
end
|
|
|
|
-- Markører med en genkendelig kilde: {marker, kind} sorteret på navn — dropdown/afkrydsning i fanen.
|
|
function ADRunsController.getSourceMarkers(adEnv, farmId)
|
|
local result = {}
|
|
if ADSources == nil then
|
|
return result
|
|
end
|
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
|
local source = ADSources.identify(adEnv, marker.id, farmId)
|
|
if source ~= nil then
|
|
table.insert(result, {marker = marker, kind = source.kind, name = source.name})
|
|
end
|
|
end
|
|
table.sort(result, function(a, b) return tostring(a.marker.name) < tostring(b.marker.name) end)
|
|
return result
|
|
end
|
|
|
|
-- Seneste planlægger-begrundelser og advarsel for run'et (fra ADOutbound) + produkter.
|
|
function ADRunsController.getOutboundStatus(adEnv, run)
|
|
local status = ADOutbound ~= nil and ADOutbound.getRunStatus ~= nil and ADOutbound.getRunStatus(run.id) or {reasons = {}}
|
|
return {reasons = status.reasons or {}, warning = status.warning, products = ADRunsController.getRunProducts(adEnv, run)}
|
|
end
|
|
|
|
-- reason: "noTarget" | "noTrailer" | "cannotCarry" | "farFromNetwork" (l10n-nøgler i GUI'et);
|
|
-- udkørsel desuden "noSource" | "noProducts" | "needsAutoload".
|
|
function ADRunsController.validateVehicle(adEnv, run, vehicle)
|
|
local carriable = ADRunsController.getCarriableFillTypes(adEnv, vehicle)
|
|
local result = {ok = false, carriable = carriable, capacity = getLoadCapacity(adEnv, vehicle), isAutoload = hasAutoload(adEnv, vehicle)}
|
|
if run.kind == "outbound" then
|
|
return validateOutboundVehicle(adEnv, run, vehicle, result)
|
|
end
|
|
local resolvedIn = ADRunsController.resolveMarkers(adEnv, run)
|
|
if resolvedIn.missing[1] ~= nil then
|
|
if #ADRunsController.getRunTargets(run) == 0 then
|
|
result.reason, result.missingBuilding = "noMarker", resolvedIn.missing[1]
|
|
return result
|
|
end
|
|
warnMissingMarkers(run, resolvedIn.missing)
|
|
end
|
|
local hasTarget = false
|
|
for _, wayPointId in ipairs(ADRunsController.getRunTargets(run)) do
|
|
if adEnv.ADGraphManager:getMapMarkerByWayPointId(wayPointId) ~= nil then
|
|
hasTarget = true
|
|
end
|
|
end
|
|
if not hasTarget then
|
|
result.reason = "noTarget"
|
|
return result
|
|
end
|
|
-- en pallebil har ingen liter-kapacitet, men er ikke "uden vogn" — den falder på cannotCarry nedenfor
|
|
if result.capacity == nil and not result.isAutoload then
|
|
result.reason = "noTrailer"
|
|
return result
|
|
end
|
|
if not intersects(ADRunsController.getRunIngredients(adEnv, run, vehicle), carriable) then
|
|
-- AutoLoad slået til: AutoDrive regner vognen som ren palle-/ballevogn (in-game 27/9: tipvogn med UAL
|
|
-- aktiv blev afvist i Goat Food som "kan ikke bære") — sig det, så spilleren kan slå det fra
|
|
result.reason = result.isAutoload and "autoloadOn" or "cannotCarry"
|
|
return result
|
|
end
|
|
if isFarFromNetwork(adEnv, vehicle) then
|
|
result.reason = "farFromNetwork"
|
|
return result
|
|
end
|
|
result.ok = true
|
|
return result
|
|
end
|
|
|
|
-- Vognens last som INGEN af run'ets mål tager -> {marker, fillType, liters} for en silo der gør
|
|
-- (plads >= resten, vejnet til siloen og fra siloen til målet); nil når lasten passer eller
|
|
-- ingen silo findes (så kører AD som normalt og Wait-logikken tager over).
|
|
function ADRunsController.findWrongCargoDump(adEnv, run, vehicle, targetId)
|
|
if ADUnloadWait == nil or ADUnloadWait.getRestLoad == nil or ADLoadSwap == nil or ADLoadSwap.findDumpMarker == nil then
|
|
return nil
|
|
end
|
|
local fillType, liters = ADUnloadWait.getRestLoad(adEnv, vehicle)
|
|
if fillType == nil or liters < 100 then
|
|
return nil
|
|
end
|
|
local farmId = ADSmartPickup.getFarmId(vehicle)
|
|
-- Lasten hører til run'et: valgte varer ("Vælg"), eller alt målet tager ("Auto"). Kun så afleveres
|
|
-- den ved målet — ellers havnede fx gødning i en ensilage-DOB, fordi DOB'en også tager gødning.
|
|
if run.ingredientMode == "manual" then
|
|
for _, index in ipairs(ADRunsController.getRunIngredients(adEnv, run, vehicle)) do
|
|
if index == fillType then return nil end
|
|
end
|
|
else
|
|
for _, entry in ipairs(getTargetStations(adEnv, run, farmId)) do
|
|
if ADSmartPickup.getStationLevelAndCapacity(entry.station, fillType, farmId) ~= nil then
|
|
return nil
|
|
end
|
|
end
|
|
end
|
|
local isReachable = function(markerId)
|
|
return ADUnloadWait.hasRouteTo == nil or ADUnloadWait.hasRouteTo(adEnv, vehicle, markerId)
|
|
end
|
|
local marker, why = ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, liters, targetId, isReachable)
|
|
local fillTypeName = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or tostring(fillType)
|
|
if marker == nil then
|
|
Logging.warning(ADRunsController.LOG_PREFIX .. "run '%s': '%s' har %.0f l %s på vognen, som målet ikke tager — %s; AutoDrive starter som normalt", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)), liters, tostring(fillTypeName), tostring(why))
|
|
return nil
|
|
end
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' læsser først %.0f l %s af i %s (markør %s) — målet tager ikke varen", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)), liters, tostring(fillTypeName), tostring(marker.name), tostring(marker.markerIndex or marker.id))
|
|
return {marker = marker, fillType = fillType, liters = liters}
|
|
end
|
|
|
|
-- ---- Start / Stop ----
|
|
|
|
-- AD gemmer indstillinger som INDEKS i values; useFolders er global, rotateTargets pr. køretøj.
|
|
-- Er indstillingen køretøjs-specifik, men køretøjet mangler sin egen settings-tabel, skrives
|
|
-- der IKKE til den globale tabel — det ville ødelægge AD's globale default for alle køretøjer.
|
|
local function setSetting(adEnv, vehicle, settingName, value)
|
|
local global = adEnv.AutoDrive.settings[settingName]
|
|
if global == nil then
|
|
return
|
|
end
|
|
local setting = global
|
|
if global.isVehicleSpecific then
|
|
if vehicle.ad.settings == nil or vehicle.ad.settings[settingName] == nil then
|
|
return
|
|
end
|
|
setting = vehicle.ad.settings[settingName]
|
|
end
|
|
for index, candidate in ipairs(setting.values) do
|
|
if candidate == value then
|
|
setting.current = index
|
|
setting.new = index
|
|
return
|
|
end
|
|
end
|
|
end
|
|
ADRunsController.setSetting = setSetting
|
|
|
|
-- Nærmeste markør med en læssestation: kun en gyldig start — moden vælger den reelle kilde pr. tur.
|
|
local function findNearestLoadMarker(adEnv, vehicle, farmId)
|
|
local x, _, z = getWorldTranslation(vehicle.rootNode)
|
|
local best, bestDistance = nil, math.huge
|
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
|
if ADSmartPickup.getLoadStationAtWayPointId(adEnv, marker.id, farmId) ~= nil then
|
|
local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id)
|
|
-- En stale markør uden waypoint skal springes over — ellers fejler Start med "error"
|
|
-- for ALLE tildelte traktorer, ikke kun den der ramte den ugyldige markør.
|
|
if wayPoint ~= nil then
|
|
local dx, dz = wayPoint.x - x, wayPoint.z - z
|
|
local distance = dx * dx + dz * dz
|
|
if distance < bestDistance then
|
|
best, bestDistance = marker, distance
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return best
|
|
end
|
|
|
|
local function getSettingValue(adEnv, vehicle, settingName)
|
|
local global = adEnv.AutoDrive.settings[settingName]
|
|
if global == nil then
|
|
return nil
|
|
end
|
|
local setting = global
|
|
if global.isVehicleSpecific and vehicle.ad.settings ~= nil and vehicle.ad.settings[settingName] ~= nil then
|
|
setting = vehicle.ad.settings[settingName]
|
|
end
|
|
return setting.values ~= nil and setting.values[setting.current] or nil
|
|
end
|
|
|
|
-- AutoDrives "AL unload" (aflæsning af autoload-vogn ved målet) står som standard på FRA (0):
|
|
-- bilen ville holde ved salgsstedet uden at læsse af. "center" (1) løsner stropperne i triggeren,
|
|
-- så salgsstedet tager pallerne; left/behind/right lader UAL sætte pallerne af ved siden af.
|
|
-- Et valg brugeren selv har lavet, røres ikke.
|
|
ADRunsController.AL_UNLOAD_CENTER = 1
|
|
local function ensureAutoloadUnload(adEnv, vehicle)
|
|
if getSettingValue(adEnv, vehicle, "ALUnload") == 0 then
|
|
setSetting(adEnv, vehicle, "ALUnload", ADRunsController.AL_UNLOAD_CENTER)
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "'%s': AutoDrives 'AL unload' var fra — sat til 'center' (stropper løsnes i triggeren)", tostring(ADRuns.vehicleLabel(vehicle)))
|
|
end
|
|
end
|
|
|
|
-- Udkørsel: 1. og 2. markør = første kilde (pladsholder — kilde og mål vælges pr. tur af
|
|
-- ADOutbound), varevalg = run'ets varer vognen kan bære, rotation "kun pålæsning" (hook'en).
|
|
local function applyOutboundRunToVehicle(adEnv, run, vehicle)
|
|
local stateModule = vehicle.ad.stateModule
|
|
if hasAutoload(adEnv, vehicle) then
|
|
ensureAutoloadUnload(adEnv, vehicle)
|
|
end
|
|
stateModule:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
|
|
local firstId = run.sourceWayPointIds[1]
|
|
stateModule:setFirstMarkerByWayPointId(firstId)
|
|
-- Læsset vogn: AD kører først til aflæsning (2. markør) — dér skal lastens mål stå.
|
|
local cargoTarget = getCargoTarget(adEnv, run, vehicle)
|
|
stateModule:setSecondMarkerByWayPointId(cargoTarget or firstId)
|
|
local fillTypes = getOutboundFillTypes(adEnv, run, vehicle)
|
|
stateModule.selectedFillTypes = copyList(fillTypes)
|
|
if fillTypes[1] ~= nil then
|
|
stateModule:setFillType(fillTypes[1])
|
|
end
|
|
stateModule.loopCounter = 0
|
|
stateModule:setLoopsDone(0)
|
|
setSetting(adEnv, vehicle, "useFolders", true)
|
|
setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_ONLYPICKUP)
|
|
stateModule:raiseDirtyFlag()
|
|
end
|
|
|
|
local function applyRunToVehicle(adEnv, run, vehicle, ingredients)
|
|
if run.kind == "outbound" then
|
|
return applyOutboundRunToVehicle(adEnv, run, vehicle)
|
|
end
|
|
local stateModule = vehicle.ad.stateModule
|
|
local farmId = ADSmartPickup.getFarmId(vehicle)
|
|
stateModule:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
|
|
-- Forkert last på vognen (fx halm fra et tidligere run, mål = sukkerfabrik): første stop
|
|
-- bliver en silo der tager varen; næste tur sætter choosePickup målet tilbage til run'ets.
|
|
local targetId = ADRunsController.chooseTarget(adEnv, run, vehicle) or run.targetWayPointId
|
|
local dump = ADRunsController.findWrongCargoDump(adEnv, run, vehicle, targetId)
|
|
stateModule:setSecondMarkerByWayPointId(targetId)
|
|
-- allerede fyldt: registrér læsset på vej, så næste traktor i samme start vælger et andet drop
|
|
if dump == nil and ADSmartPickup.registerCargo ~= nil then
|
|
ADSmartPickup.registerCargo(adEnv, vehicle, targetId, ingredients)
|
|
end
|
|
if dump ~= nil then
|
|
if ADUnloadWait ~= nil and ADUnloadWait.setDumpTarget ~= nil then
|
|
ADUnloadWait.setDumpTarget(vehicle, dump.marker.id)
|
|
else
|
|
stateModule:setSecondMarkerByWayPointId(dump.marker.id)
|
|
end
|
|
end
|
|
local loadMarker = findNearestLoadMarker(adEnv, vehicle, farmId)
|
|
if loadMarker ~= nil then
|
|
stateModule:setFirstMarkerByWayPointId(loadMarker.id)
|
|
end
|
|
stateModule.selectedFillTypes = copyList(ingredients)
|
|
stateModule:setFillType(ingredients[1])
|
|
stateModule.loopCounter = run.loops
|
|
stateModule:setLoopsDone(0)
|
|
setSetting(adEnv, vehicle, "useFolders", true)
|
|
setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_ONLYPICKUP)
|
|
stateModule:raiseDirtyFlag()
|
|
end
|
|
|
|
local function isVehicleActive(vehicle)
|
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
|
return stateModule ~= nil and stateModule.isActive ~= nil and stateModule:isActive()
|
|
end
|
|
|
|
-- Som ADInputManager:input_start_stop: loops nulstilles og AD stoppes.
|
|
local function stopVehicle(vehicle)
|
|
local stateModule = vehicle.ad.stateModule
|
|
if stateModule.setLoopsDone ~= nil then
|
|
stateModule:setLoopsDone(0)
|
|
end
|
|
vehicle:stopAutoDrive()
|
|
end
|
|
|
|
-- Anvend run-tilstand OG start traktoren i ét pcall'et kald: kaster vehicle:startAutoDrive()
|
|
-- (fx AD's egen validering), skal den ene traktor fejle uden at hele startRun-loopet
|
|
-- afbrydes — ellers når de øvrige tildelte traktorer aldrig at blive startet.
|
|
-- Start præcis som AD's HUD (ADInputManager:input_start_stop): det er MODE'ens start(),
|
|
-- der opretter køreopgaverne og selv kalder startAutoDrive — startAutoDrive alene sætter
|
|
-- kun "aktiv", og traktoren bliver holdende.
|
|
-- Kører traktoren AutoDrive i forvejen (startet manuelt eller i en gammel tilstand), stoppes
|
|
-- den først, så run'ets tilstand og opgaver gælder fra nu.
|
|
local function applyAndStart(adEnv, run, vehicle, ingredients)
|
|
if isVehicleActive(vehicle) then
|
|
stopVehicle(vehicle)
|
|
end
|
|
applyRunToVehicle(adEnv, run, vehicle, ingredients)
|
|
local stateModule = vehicle.ad.stateModule
|
|
local farmId = ADSmartPickup.getFarmId(vehicle)
|
|
if stateModule.setActualFarmId ~= nil and farmId ~= nil then
|
|
stateModule:setActualFarmId(farmId)
|
|
end
|
|
stateModule:setLoopsDone(0)
|
|
local mode = stateModule.getCurrentMode ~= nil and stateModule:getCurrentMode() or nil
|
|
if mode ~= nil and mode.start ~= nil then
|
|
mode:start(adEnv.AutoDrive.USER_PLAYER or 1)
|
|
else
|
|
vehicle:startAutoDrive()
|
|
end
|
|
if vehicle.isServer and SpecializationUtil ~= nil and SpecializationUtil.raiseEvent ~= nil then
|
|
SpecializationUtil.raiseEvent(vehicle, "onUpdate", 16, false, false, false)
|
|
end
|
|
end
|
|
|
|
-- Pr. tildelt traktor: validér, skriv AD-tilstand, start. Afviste traktorer springes over
|
|
-- og logges med årsag (samme nøgle som fanen viser).
|
|
function ADRunsController.startRun(adEnv, runId)
|
|
local run = ADRuns.getRun(ADRunsController.store, runId)
|
|
local results = {}
|
|
if run == nil then
|
|
return results
|
|
end
|
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
|
local started = 0
|
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
|
local vehicle = vehiclesById[vehicleId]
|
|
if vehicle ~= nil then
|
|
local validation = ADRunsController.validateVehicle(adEnv, run, vehicle)
|
|
local result = {vehicle = vehicle, ok = validation.ok, reason = validation.reason, missingBuilding = validation.missingBuilding}
|
|
if not validation.ok then
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' sprunget over (%s)", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)), tostring(validation.reason))
|
|
end
|
|
if validation.ok then
|
|
local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle)
|
|
local ok, err = pcall(applyAndStart, adEnv, run, vehicle, ingredients)
|
|
if ok then
|
|
started = started + 1
|
|
local second = vehicle.ad.stateModule.getSecondMarker ~= nil and vehicle.ad.stateModule:getSecondMarker() or nil
|
|
local target = run.kind == "outbound" and ("kilde " .. tostring(run.sourceWayPointIds[1]))
|
|
or ("markør " .. tostring(second ~= nil and second.id or run.targetWayPointId))
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' startet mod %s", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)), target)
|
|
else
|
|
result.ok, result.reason = false, "error"
|
|
Logging.warning(ADRunsController.LOG_PREFIX .. "run '%s': kunne ikke starte '%s': %s", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)), tostring(err))
|
|
end
|
|
end
|
|
table.insert(results, result)
|
|
end
|
|
end
|
|
if started > 0 then
|
|
ADRunsController.runningRuns[runId] = true
|
|
end
|
|
return results
|
|
end
|
|
|
|
-- En kørende traktor der fjernes fra sit run (eller hvis run slettes) kører run'ets job —
|
|
-- den stoppes, så den ikke fortsætter uden run. Returnerer true hvis den blev stoppet.
|
|
function ADRunsController.stopIfRunning(vehicle, reason)
|
|
if vehicle == nil or vehicle.ad == nil or not isVehicleActive(vehicle) then
|
|
return false
|
|
end
|
|
stopVehicle(vehicle)
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "'%s' stoppet (%s)", tostring(ADRuns.vehicleLabel(vehicle)), tostring(reason))
|
|
return true
|
|
end
|
|
|
|
function ADRunsController.stopRun(adEnv, runId)
|
|
local run = ADRuns.getRun(ADRunsController.store, runId)
|
|
local stopped = 0
|
|
if run == nil then
|
|
return stopped
|
|
end
|
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
|
local vehicle = vehiclesById[vehicleId]
|
|
if vehicle ~= nil and isVehicleActive(vehicle) then
|
|
stopVehicle(vehicle)
|
|
stopped = stopped + 1
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' stoppet", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)))
|
|
end
|
|
end
|
|
ADRunsController.runningRuns[runId] = nil
|
|
return stopped
|
|
end
|
|
|
|
-- running = traktorer med AutoDrive aktiv (uanset hvem der startede dem); isRunning = run'et er
|
|
-- startet fra fanen og mindst én traktor kører stadig (Start/Stop-knappen følger isRunning).
|
|
function ADRunsController.getRunStatus(adEnv, run)
|
|
local running = 0
|
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
|
local vehicle = vehiclesById[vehicleId]
|
|
if vehicle ~= nil and isVehicleActive(vehicle) then
|
|
running = running + 1
|
|
end
|
|
end
|
|
local isRunning = ADRunsController.runningRuns[run.id] == true and running > 0
|
|
if ADRunsController.runningRuns[run.id] and running == 0 then
|
|
ADRunsController.runningRuns[run.id] = nil
|
|
end
|
|
return {running = running, total = #run.vehicleIds, isRunning = isRunning}
|
|
end
|
|
|
|
-- Start alle (Oversigt): flows der allerede kører røres ikke, så et halvt kørende gårdbillede kan
|
|
-- "fyldes op" uden at traktorer midt i en tur afbrydes. Flows uden traktorer springes over.
|
|
function ADRunsController.startAllRuns(adEnv)
|
|
local summary = {runsStarted = 0, alreadyRunning = 0, vehiclesStarted = 0, results = {}}
|
|
for _, run in ipairs(ADRunsController.store.runs) do
|
|
if #run.vehicleIds > 0 then
|
|
if ADRunsController.getRunStatus(adEnv, run).isRunning then
|
|
summary.alreadyRunning = summary.alreadyRunning + 1
|
|
else
|
|
local startedHere = 0
|
|
for _, result in ipairs(ADRunsController.startRun(adEnv, run.id)) do
|
|
table.insert(summary.results, result)
|
|
if result.ok then startedHere = startedHere + 1 end
|
|
end
|
|
summary.vehiclesStarted = summary.vehiclesStarted + startedHere
|
|
if startedHere > 0 then summary.runsStarted = summary.runsStarted + 1 end
|
|
end
|
|
end
|
|
end
|
|
return summary
|
|
end
|
|
|
|
-- Stop alle: stopper alle aktive traktorer i alle flows (også dem der er startet manuelt i AD).
|
|
function ADRunsController.stopAllRuns(adEnv)
|
|
local stopped = 0
|
|
for _, run in ipairs(ADRunsController.store.runs) do
|
|
stopped = stopped + ADRunsController.stopRun(adEnv, run.id)
|
|
end
|
|
return stopped
|
|
end
|
|
|
|
function ADRunsController.hasActiveRuns(adEnv)
|
|
for _, run in ipairs(ADRunsController.store.runs) do
|
|
if ADRunsController.getRunStatus(adEnv, run).running > 0 then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- ---- overblik ----
|
|
|
|
-- Hvad laver traktoren lige nu? {kind = "stopped"|"waiting"|"loaded"|"empty", fillType, liters, place}
|
|
-- place = Wait-markør, aflæsningsmål eller den silo moden valgte. Kun eksisterende data bruges.
|
|
function ADRunsController.getVehicleActivity(adEnv, vehicle)
|
|
if not isVehicleActive(vehicle) then
|
|
return {kind = "stopped"}
|
|
end
|
|
local wait = ADUnloadWait ~= nil and ADUnloadWait.getWaitFor ~= nil and ADUnloadWait.getWaitFor(vehicle) or nil
|
|
if wait ~= nil then
|
|
return {kind = "waiting", fillType = wait.fillType, liters = wait.rest, place = wait.markerName}
|
|
end
|
|
local hold = ADOutbound ~= nil and ADOutbound.getHoldFor ~= nil and ADOutbound.getHoldFor(vehicle) or nil
|
|
if hold ~= nil then
|
|
return {kind = "holding", place = hold.markerName, reason = hold.reasons ~= nil and hold.reasons[1] or nil}
|
|
end
|
|
local fillType, liters = nil, 0
|
|
if ADUnloadWait ~= nil and ADUnloadWait.getRestLoad ~= nil then
|
|
fillType, liters = ADUnloadWait.getRestLoad(adEnv, vehicle)
|
|
end
|
|
local stateModule = vehicle.ad.stateModule
|
|
if fillType ~= nil and liters >= 100 then
|
|
local target = stateModule.getSecondMarker ~= nil and stateModule:getSecondMarker() or nil
|
|
return {kind = "loaded", fillType = fillType, liters = liters, place = target ~= nil and target.name or nil}
|
|
end
|
|
local pickup = ADSmartPickup.getActivePickup ~= nil and ADSmartPickup.getActivePickup(vehicle) or nil
|
|
if pickup ~= nil then
|
|
return {kind = "empty", fillType = pickup.fillType, place = pickup.markerName}
|
|
end
|
|
local first = stateModule.getFirstMarker ~= nil and stateModule:getFirstMarker() or nil
|
|
return {kind = "empty", place = first ~= nil and first.name or nil}
|
|
end
|
|
|
|
-- Niveauer pr. mål for run'ets varer: {{wayPointId, name, levels = {{fillType, level, capacity, free}}}}
|
|
-- i samme rangering som valget bruger (trug som én trang, fodervægt som prioritet); fulde sidst.
|
|
function ADRunsController.getTargetsLevels(adEnv, run)
|
|
local farmId = g_currentMission:getFarmId()
|
|
local fillTypes = {}
|
|
if run.ingredientMode == "manual" then
|
|
for _, name in ipairs(run.ingredients) do
|
|
local index = g_fillTypeManager:getFillTypeIndexByName(name)
|
|
if index ~= nil then table.insert(fillTypes, index) end
|
|
end
|
|
else
|
|
for _, fillType in ipairs(ADRunsController.getTargetAcceptedFillTypes(adEnv, run)) do
|
|
table.insert(fillTypes, fillType.index)
|
|
end
|
|
end
|
|
local result = {}
|
|
for _, entry in ipairs(getTargetStations(adEnv, run, farmId)) do
|
|
local marker = adEnv.ADGraphManager:getMapMarkerByWayPointId(entry.wayPointId)
|
|
local levels = {}
|
|
for _, need in ipairs(ADSmartPickup.getTargetNeedsRanked(entry.station, fillTypes, farmId)) do
|
|
table.insert(levels, {fillType = need.fillType, level = need.level, capacity = need.capacity, free = need.freeSpace})
|
|
end
|
|
table.insert(result, {wayPointId = entry.wayPointId, name = marker ~= nil and tostring(marker.name) or tostring(entry.wayPointId), levels = levels})
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- ---- opslag til dropdowns ----
|
|
|
|
-- Mål er steder der SKAL holdes fyldt: stalde (1) og fabrikker (2). Siloer tager alt og
|
|
-- er kilder, ikke mål. Returnerer markører sorteret efter type, dernæst navn.
|
|
local function getTargetKind(station)
|
|
local placeable = station.owningPlaceable
|
|
if placeable == nil then
|
|
return nil
|
|
end
|
|
if placeable.getHusbandryFillLevel ~= nil then
|
|
return 1
|
|
end
|
|
if placeable.spec_productionPoint ~= nil then
|
|
return 2
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function ADRunsController.getTargetMarkers(adEnv, farmId)
|
|
local entries = {}
|
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
|
local isWait = ADSupplyPlanner ~= nil and ADSupplyPlanner.isWaitMarker ~= nil and ADSupplyPlanner.isWaitMarker(marker)
|
|
local station = not isWait and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId) or nil
|
|
local kind = station ~= nil and getTargetKind(station) or nil
|
|
if kind ~= nil then
|
|
table.insert(entries, {marker = marker, kind = kind})
|
|
end
|
|
end
|
|
table.sort(entries, function(a, b)
|
|
if a.kind ~= b.kind then
|
|
return a.kind < b.kind
|
|
end
|
|
return tostring(a.marker.name) < tostring(b.marker.name)
|
|
end)
|
|
local result = {}
|
|
for index, entry in ipairs(entries) do
|
|
result[index] = entry.marker
|
|
end
|
|
return result
|
|
end
|
|
|
|
function ADRunsController.getWaitPools(adEnv)
|
|
local counts = {}
|
|
for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do
|
|
if ADSupplyPlanner.isWaitMarker(marker) then
|
|
counts[marker.group] = (counts[marker.group] or 0) + 1
|
|
end
|
|
end
|
|
local result = {}
|
|
for group, count in pairs(counts) do
|
|
table.insert(result, {group = group, count = count})
|
|
end
|
|
table.sort(result, function(a, b) return a.group < b.group end)
|
|
return result
|
|
end
|
|
|
|
-- ---- gem / indlæs ----
|
|
|
|
function ADRunsController.save()
|
|
local ok, saved = pcall(ADRunsStorage.save, ADRunsController.store)
|
|
if not ok then
|
|
Logging.warning(ADRunsController.LOG_PREFIX .. "kunne ikke gemme runs: %s", tostring(saved))
|
|
return false
|
|
end
|
|
if ADFieldJobs ~= nil and ADFieldJobs.loaded then
|
|
pcall(ADFieldJobs.save)
|
|
end
|
|
if ADFieldSilos ~= nil then pcall(ADFieldSilos.save) end
|
|
return saved
|
|
end
|
|
|
|
-- Køretøjs-referencer der ikke findes (solgt, andet savegame) logges; run'et beholdes.
|
|
function ADRunsController.load(adEnv)
|
|
local ok, store = pcall(ADRunsStorage.load)
|
|
if not ok then
|
|
Logging.warning(ADRunsController.LOG_PREFIX .. "kunne ikke indlæse runs: %s", tostring(store))
|
|
return
|
|
end
|
|
ADRunsController.store = store
|
|
pcall(ADRunsController.migrateBuildings, adEnv)
|
|
if ADFieldSilos ~= nil then pcall(ADFieldSilos.load) end
|
|
local vehiclesById = getFarmVehiclesById(adEnv)
|
|
for _, run in ipairs(store.runs) do
|
|
for _, vehicleId in ipairs(run.vehicleIds) do
|
|
if vehiclesById[vehicleId] == nil then
|
|
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': køretøj %s findes ikke (endnu) i dette savegame", tostring(run.name), tostring(vehicleId))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ADRunsController.installSaveHook()
|
|
if ItemSystem == nil or ItemSystem.save == nil or Utils == nil then
|
|
return
|
|
end
|
|
ItemSystem.save = Utils.prependedFunction(ItemSystem.save, function()
|
|
ADRunsController.save()
|
|
end)
|
|
end
|