-- AD Flow Wagons -- Flows ejer deres vogne, men traktor og vogn er ikke låst sammen: den vogn der hænger på en flow-traktor huskes -- (run.wagons = {[traktorId] = vognId}). Starter flowet med en solo-traktor, henter den sin vogn ved "Connect ". Stoppes/slettes flowet, eller er dets bygning væk, sættes vognen på plads og traktoren kører hjem — -- så er begge ledige i maskinpuljen (en stoppet flows vogn må lånes; en kørende flows aldrig). ADFlowWagons = {} ADFlowWagons.LOG_PREFIX = "ADSmartPickup: flow-vogne: " ADFlowWagons.REMEMBER_MS = 10000 -- husk vogne / prøv ventende starter igen ADFlowWagons.BUILDING_CHECK_MS = 60000 ADFlowWagons.MISSING_CHECKS = 2 -- så mange tjek i træk før en bygning regnes for væk -- ---- ren logik ---- ADFlowWagons.serialize = function(wagons) return ADRuns.serializeWagons(wagons) end ADFlowWagons.parse = function(text) return ADRuns.parseWagons(text) end -- Husk traktorens vogn: ny tabel ved ændring, samme tabel når intet ændres. function ADFlowWagons.remember(wagons, tractorId, wagonId) if (wagons or {})[tractorId] == wagonId then return wagons end local copy = ADRuns.copyWagons(wagons) copy[tractorId] = wagonId return copy end -- s = {hasRear, wagonId, wagonExists, hasConnect, wagonAttached, wagonBusy} -> "start" | "fetch" | "wait" function ADFlowWagons.startAction(s) if s.hasRear or s.wagonId == nil or not s.wagonExists or not s.hasConnect then return "start" end if s.wagonAttached or s.wagonBusy then return "wait" end return "fetch" end function ADFlowWagons.ownerOf(runs, wagonId) for _, run in ipairs(runs or {}) do for _, id in pairs(run.wagons or {}) do if id == wagonId then return run end end end return nil end -- Holdes vognen af et flow der ikke er stoppet? (så må puljen ikke låne den) function ADFlowWagons.isHeld(runs, wagonId) local run = ADFlowWagons.ownerOf(runs, wagonId) return run ~= nil and not run.stopped end -- Et tjek af om bygningen mangler: -> ny tæller, væk? function ADFlowWagons.missingStep(count, missingNow) if not missingNow then return 0, false end local next = (count or 0) + 1 return next, next >= ADFlowWagons.MISSING_CHECKS end -- Flowets bygnings-id'er der ikke findes i exists = {[id] = true}. function ADFlowWagons.missingBuildings(run, exists) local missing = {} for _, key in ipairs({"sourceBuildingIds", "targetBuildingIds", "storeBuildingIds"}) do for _, id in ipairs(run[key] or {}) do if not exists[id] then table.insert(missing, id) end end end return missing end -- ---- spil ---- local waiting = setmetatable({}, {__mode = "k"}) -- traktor -> runId (venter på sin vogn) local fetching = setmetatable({}, {__mode = "k"}) -- traktor -> runId (henter sin vogn) local missingCount = {} -- runId -> tjek i træk local rememberTimer, buildingTimer = 0, 0 local function runs() return ADRunsController.store ~= nil and ADRunsController.store.runs or {} end local function findAnyVehicle(id) for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if ADRunsController.getVehicleId(vehicle) == id then return vehicle end end return nil end local function connectOf(adEnv, wagon) local index = wagon.advd ~= nil and wagon.advd.parkDestination or nil local park = type(index) == "number" and index >= 1 and adEnv.ADGraphManager:getMapMarkerById(index) or nil if park == nil then return nil end local marker = ADPool.connectFor(park.name, adEnv.ADGraphManager:getMapMarkers()) return marker ~= nil and marker.id or nil end -- Traktorer i stoppede flows er ledige i puljen; traktorer i kørende flows aldrig. function ADFlowWagons.isFreeTractor(vehicle) local run = ADRunsController.getRunForVehicle(vehicle) return run == nil or run.stopped == true end function ADFlowWagons.hasWagonFor(run, tractorId) return (run.wagons or {})[tractorId] ~= nil end function ADFlowWagons.isHeldWagon(wagonId) return ADFlowWagons.isHeld(runs(), wagonId) end -- Før en flow-traktor startes: hent sin vogn / vent på den. -> håndteret, resultat (som startVehicle) function ADFlowWagons.beforeStart(adEnv, run, vehicle) local tractorId = ADRunsController.getVehicleId(vehicle) if fetching[vehicle] ~= nil then return true, {vehicle = vehicle, ok = true, reason = "fetchingWagon"} end local wagonId = (run.wagons or {})[tractorId] local wagon = wagonId ~= nil and findAnyVehicle(wagonId) or nil local connectId = wagon ~= nil and connectOf(adEnv, wagon) or nil local action = ADFlowWagons.startAction({ hasRear = #ADHitch.rearImplementsOf(vehicle) > 0, wagonId = wagonId, wagonExists = wagon ~= nil, hasConnect = connectId ~= nil, wagonAttached = wagon ~= nil and wagon.getAttacherVehicle ~= nil and wagon:getAttacherVehicle() ~= nil, wagonBusy = wagonId ~= nil and ADPool.isReserved(wagonId), }) if action == "start" then return false end ADRunsController.runningRuns[run.id] = true if action == "wait" then if waiting[vehicle] == nil then Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' venter på sin vogn (i brug andetsteds)", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle))) end waiting[vehicle] = run.id return true, {vehicle = vehicle, ok = true, reason = "waitingWagon"} end waiting[vehicle] = nil fetching[vehicle] = run.id local runId = run.id ADHitch.fetch(adEnv, vehicle, connectId, function(tractor, ok, detail) fetching[tractor] = nil local current = ADRuns.getRun(ADRunsController.store, runId) if current == nil or current.stopped then ADHitch.returnHome(adEnv, tractor) return end if ok then ADRunsController.startVehicle(adEnv, current, tractor) else Logging.warning(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' kunne ikke hente sin vogn (%s)", tostring(current.name), tostring(ADRuns.vehicleLabel(tractor)), tostring(detail)) end end) Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' henter sin vogn", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle))) return true, {vehicle = vehicle, ok = true, reason = "fetchingWagon"} end -- Stop/slet/bygning væk: vognen på plads, traktoren hjem. function ADFlowWagons.sendHome(adEnv, run) for _, vehicleId in ipairs(run.vehicleIds or {}) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) if vehicle ~= nil then waiting[vehicle] = nil if fetching[vehicle] ~= nil then fetching[vehicle] = nil ADHitch.cancel(vehicle) end local okHome, pooled = pcall(ADHitch.returnHome, adEnv, vehicle) if not (okHome and pooled) then local park = ADFieldWork.parkMarkerId(adEnv, vehicle) if park ~= nil then pcall(ADFieldWork.startAd, adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end end end end end local function rememberAll(adEnv) for _, run in ipairs(runs()) do if not run.stopped then for _, vehicleId in ipairs(run.vehicleIds or {}) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) local rear = vehicle ~= nil and ADHitch.rearImplementsOf(vehicle) or {} local wagonId = rear[1] ~= nil and ADRunsController.getVehicleId(rear[1].object) or nil if wagonId ~= nil then local updated = ADFlowWagons.remember(run.wagons, vehicleId, wagonId) if updated ~= run.wagons then run.wagons = updated Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' har vognen '%s'", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)), tostring(ADRuns.vehicleLabel(rear[1].object))) end end end end end end local function retryWaiting(adEnv) local list = {} for vehicle, runId in pairs(waiting) do table.insert(list, {vehicle = vehicle, runId = runId}) end for _, item in ipairs(list) do waiting[item.vehicle] = nil local run = ADRuns.getRun(ADRunsController.store, item.runId) if run ~= nil and not run.stopped then pcall(ADRunsController.startVehicle, adEnv, run, item.vehicle) end end end local function checkBuildings(adEnv) local exists = {} local placeables = g_currentMission.placeableSystem ~= nil and g_currentMission.placeableSystem.placeables or {} for _, placeable in pairs(placeables) do local id = ADBuildings.getId(placeable) if id ~= nil then exists[id] = true end end if next(exists) == nil then return end -- listen er ikke klar: sig intet for _, run in ipairs(runs()) do if not run.stopped then local missing = ADFlowWagons.missingBuildings(run, exists) local count, gone = ADFlowWagons.missingStep(missingCount[run.id], #missing > 0) missingCount[run.id] = count if gone then missingCount[run.id] = 0 Logging.warning(ADFlowWagons.LOG_PREFIX .. "flow '%s': bygning findes ikke længere (%s) — stoppet, traktorer kørt hjem", tostring(run.name), table.concat(missing, ", ")) ADRunsController.stopRun(adEnv, run.id) if ADFieldJobs ~= nil and ADFieldJobs.notify ~= nil then pcall(ADFieldJobs.notify, string.format("Flow '%s': bygning findes ikke længere — stoppet, traktorer kørt hjem", tostring(run.name)), true) end end end end end -- Én gang efter indlæsning: et stoppet flows traktor der holder stille med sin vogn (gemt midt i hjemturen — AD -- kørte den færdig, men afkoblingen er glemt) køres hjem. Ikke løbende: spilleren kan selv have koblet på. ADFlowWagons.AFTER_LOAD_MS = 120000 local sinceLoad, cleanedUp = 0, false local function cleanupAfterLoad(adEnv) for _, run in ipairs(runs()) do if run.stopped then for _, vehicleId in ipairs(run.vehicleIds or {}) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) local module = vehicle ~= nil and vehicle.ad ~= nil and vehicle.ad.stateModule or nil local controlled = vehicle ~= nil and vehicle.getIsControlled ~= nil and vehicle:getIsControlled() if module ~= nil and not module:isActive() and not controlled and #ADHitch.poolParkIds(adEnv, vehicle) > 0 then Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s' er stoppet: '%s' sætter sin vogn på plads", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle))) pcall(ADHitch.returnHome, adEnv, vehicle) end end end end end function ADFlowWagons.tick(adEnv, dt) if adEnv == nil or ADRunsController.store == nil then return end if not cleanedUp then sinceLoad = sinceLoad + dt if sinceLoad >= ADFlowWagons.AFTER_LOAD_MS then cleanedUp = true local ok, err = pcall(cleanupAfterLoad, adEnv) if not ok then Logging.warning(ADFlowWagons.LOG_PREFIX .. "oprydning efter indlæsning: %s", tostring(err)) end end end rememberTimer = rememberTimer + dt buildingTimer = buildingTimer + dt if rememberTimer >= ADFlowWagons.REMEMBER_MS then rememberTimer = 0 rememberAll(adEnv) retryWaiting(adEnv) end if buildingTimer >= ADFlowWagons.BUILDING_CHECK_MS then buildingTimer = 0 checkBuildings(adEnv) end end