-- 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() 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 if vehicle.ad ~= nil and vehicle.getOwnerFarmId ~= nil and vehicle:getOwnerFarmId() == farmId then table.insert(result, vehicle) end end return result end function ADRunsController.findVehicleById(adEnv, vehicleId) for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do if ADRunsController.getVehicleId(vehicle) == vehicleId then return vehicle end end return nil 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) local store, run = ADRuns.createRun(ADRunsController.store, name) ADRunsController.store = store return run end function ADRunsController.deleteRun(runId) ADRunsController.store = ADRuns.deleteRun(ADRunsController.store, runId) end function ADRunsController.updateRun(runId, changes) ADRunsController.store = ADRuns.updateRun(ADRunsController.store, runId, changes) 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) end 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. function ADRunsController.getTargetFillTypes(adEnv, run, vehicle) local farmId = ADSmartPickup.getFarmId(vehicle) local target = run.targetWayPointId ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, run.targetWayPointId, farmId) or nil if target == nil then return {} end local result = {} for _, fillType in ipairs(ADRunsController.getCarriableFillTypes(adEnv, vehicle)) do if ADSmartPickup.getStationLevelAndCapacity(target, fillType, farmId) ~= nil then table.insert(result, fillType) 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 -- ---- validering ---- 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 > 0 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 -- reason: "noTarget" | "noTrailer" | "cannotCarry" | "farFromNetwork" (l10n-nøgler i GUI'et). function ADRunsController.validateVehicle(adEnv, run, vehicle) local carriable = ADRunsController.getCarriableFillTypes(adEnv, vehicle) local result = {ok = false, carriable = carriable, capacity = getLoadCapacity(adEnv, vehicle)} if run.targetWayPointId == nil or adEnv.ADGraphManager:getMapMarkerByWayPointId(run.targetWayPointId) == nil then result.reason = "noTarget" return result end if result.capacity == nil then result.reason = "noTrailer" return result end if not intersects(ADRunsController.getRunIngredients(adEnv, run, vehicle), carriable) then result.reason = "cannotCarry" return result end local distance = adEnv.ADGraphManager.getDistanceFromNetwork ~= nil and adEnv.ADGraphManager:getDistanceFromNetwork(vehicle) or 0 if distance == nil or distance > ADRunsController.MAX_NETWORK_DISTANCE then result.reason = "farFromNetwork" return result end result.ok = true return result end -- ---- Start / Stop ---- -- AD gemmer indstillinger som INDEKS i values; useFolders er global, rotateTargets pr. køretøj. 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 and vehicle.ad.settings ~= nil and vehicle.ad.settings[settingName] ~= nil then 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 -- 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) 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 return best end local function applyRunToVehicle(adEnv, run, vehicle, ingredients) local stateModule = vehicle.ad.stateModule local farmId = ADSmartPickup.getFarmId(vehicle) stateModule:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER) stateModule:setSecondMarkerByWayPointId(run.targetWayPointId) 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 -- Pr. tildelt traktor: validér, skriv AD-tilstand, start. ✖-traktorer springes over. function ADRunsController.startRun(adEnv, runId) local run = ADRuns.getRun(ADRunsController.store, runId) local results = {} if run == nil then return results end for _, vehicleId in ipairs(run.vehicleIds) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) if vehicle ~= nil then local validation = ADRunsController.validateVehicle(adEnv, run, vehicle) local result = {vehicle = vehicle, ok = validation.ok, reason = validation.reason} if validation.ok then local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle) local ok, err = pcall(applyRunToVehicle, adEnv, run, vehicle, ingredients) if ok then if not isVehicleActive(vehicle) then vehicle:startAutoDrive() end Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' startet mod markør %s", tostring(run.name), tostring(vehicle:getName()), tostring(run.targetWayPointId)) else result.ok, result.reason = false, "error" Logging.warning(ADRunsController.LOG_PREFIX .. "run '%s': kunne ikke starte '%s': %s", tostring(run.name), tostring(vehicle:getName()), tostring(err)) end end table.insert(results, result) end end return results end function ADRunsController.stopRun(adEnv, runId) local run = ADRuns.getRun(ADRunsController.store, runId) local stopped = 0 if run == nil then return stopped end for _, vehicleId in ipairs(run.vehicleIds) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) if vehicle ~= nil and isVehicleActive(vehicle) then vehicle:stopAutoDrive() stopped = stopped + 1 end end return stopped end function ADRunsController.getRunStatus(adEnv, run) local running = 0 for _, vehicleId in ipairs(run.vehicleIds) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) if vehicle ~= nil and isVehicleActive(vehicle) then running = running + 1 end end return {running = running, total = #run.vehicleIds} end -- ---- opslag til dropdowns ---- function ADRunsController.getTargetMarkers(adEnv, farmId) local result = {} for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do if ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId) ~= nil then table.insert(result, marker) end end table.sort(result, function(a, b) return tostring(a.name) < tostring(b.name) 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 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 for _, run in ipairs(store.runs) do for _, vehicleId in ipairs(run.vehicleIds) do if ADRunsController.findVehicleById(adEnv, 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