Files
fs25-adsmartpickup/FS25_ADSmartPickup/adFlowWagons.lua
T

636 lines
31 KiB
Lua

-- 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 <vognens
-- parkering>". 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)
-- lånt ud af puljen (markarbejde, marksilo, på hjemtur): vent til den er fri
if s.tractorBusy then return "wait" end
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
-- Hent vogn fejlede: ét nyt forsøg når AD standsede undervejs (forbigående, fx spærret af en anden traktor);
-- ikke ved kobling der fejler ved markøren (den skal rettes).
ADFlowWagons.FETCH_RETRIES = 1
function ADFlowWagons.retryFetch(reason, retries)
return tostring(reason):find("^stoppedAway") ~= nil and (retries or 0) < ADFlowWagons.FETCH_RETRIES
end
-- s = {flowRunning, ownWagon, adActive} -> start traktoren efter spillerens egen påkobling?
function ADFlowWagons.startAfterManualAttach(s)
return s.flowRunning == true and s.ownWagon == true and not s.adActive
end
-- Hvorfor "wait" (til loggen). s som startAction.
function ADFlowWagons.waitReason(s)
if s.tractorBusy then return "tractorBusy" end
if s.wagonAttached then return "wagonAttached" end
return "wagonReserved"
end
-- Ingen husket vogn: gæt ud fra parkeringsnavnet — en ledig vogn hvis parkering indeholder flowets navn, helst med
-- traktorens nummer til sidst ("Cow Food 3" -> "P Cow Food vogn 3"). candidates = {{id, parkName}}, taken = {[id]=true}
function ADFlowWagons.guessWagon(runName, tractorNumber, candidates, taken)
local wanted = tostring(runName or ""):lower()
if wanted == "" then return nil end
local matching = {}
for _, candidate in ipairs(candidates or {}) do
local park = tostring(candidate.parkName or ""):lower()
if not (taken or {})[candidate.id] and park:find(wanted, 1, true) ~= nil then table.insert(matching, candidate) end
end
table.sort(matching, function(a, b) return tostring(a.parkName) < tostring(b.parkName) end)
for _, candidate in ipairs(matching) do
if tractorNumber ~= nil and tostring(candidate.parkName):match("%s(%d+)$") == tostring(tractorNumber) then return candidate.id end
end
return matching[1] ~= nil and matching[1].id or nil
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? Flowets vogn er flowets, også når flowet er stoppet — puljen, markarbejde og
-- marksilo må ikke låne den (in-game 29/9: marksilo lånte Cow Food-traktoren med vognen på efter stop).
function ADFlowWagons.isHeld(runs, wagonId)
return ADFlowWagons.ownerOf(runs, wagonId) ~= nil
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}. Kun bygninger der er set i denne session
-- (seen) tæller: et forældet id i et gammelt flow må ikke stoppe et flow der virker.
function ADFlowWagons.missingBuildings(run, exists, seen)
local missing = {}
for _, key in ipairs({"sourceBuildingIds", "targetBuildingIds", "storeBuildingIds"}) do
for _, id in ipairs(run[key] or {}) do
if not exists[id] and (seen or {})[id] then table.insert(missing, id) end
end
end
return missing
end
-- Hvor læsset afleveres ved stop: AD's 2. markør kun hvis den er et af flowets mål — den kan være forældet
-- (gammel AD-tilstand) eller et Wait-punkt, Wait-logikken har skiftet til. Ellers flowets første mål; nil = silo.
function ADFlowWagons.emptyTarget(secondMarkerId, runTargetIds)
local targets = runTargetIds or {}
for _, id in ipairs(targets) do
if id == secondMarkerId then return id end
end
return targets[1]
end
-- Tøm vognen før den parkeres (flowet stoppet): flowets mål -> silo/egen fabrik -> hjem.
-- stage: "start" | "target" | "dump"; s = {cargo, hasTarget, adActive, elapsedMs}
-- -> "home" | "target" | "dump" | "wait" | "giveUp"
ADFlowWagons.EMPTY_MIN_LITERS = 100
ADFlowWagons.EMPTY_TIMEOUT_MS = 600000
ADFlowWagons.EMPTY_STALL_MS = 45000 -- ved målet uden at læsset falder så længe = målet er fuldt
ADFlowWagons.EMPTY_NEAR_M = 40 -- m fra aflæsningsmarkøren tæller som "ved målet"
-- s = {nearTarget, sinceChangeMs}
function ADFlowWagons.isStalled(s)
return s.nearTarget == true and (s.sinceChangeMs or 0) >= ADFlowWagons.EMPTY_STALL_MS
end
function ADFlowWagons.emptyStep(stage, s)
local cargo = s.cargo or 0
if stage == "start" then
if cargo < ADFlowWagons.EMPTY_MIN_LITERS then return "home" end
return s.hasTarget and "target" or "dump"
end
if (s.elapsedMs or 0) >= ADFlowWagons.EMPTY_TIMEOUT_MS then return "giveUp" end
if s.stalled and cargo >= ADFlowWagons.EMPTY_MIN_LITERS then return stage == "target" and "dump" or "giveUp" end
if s.adActive then return "wait" end
if cargo < ADFlowWagons.EMPTY_MIN_LITERS then return "home" end
if stage == "target" then return "dump" end
return "giveUp"
end
-- ---- spil ----
local waiting = setmetatable({}, {__mode = "k"}) -- traktor -> runId (venter på sin vogn)
local fetching = setmetatable({}, {__mode = "k"}) -- traktor -> runId (henter sin vogn)
local emptying = setmetatable({}, {__mode = "k"}) -- traktor -> {stage, since, fillType, runName}
local homing = setmetatable({}, {__mode = "k"}) -- traktor -> true (flowets hjemtur: vogn af, traktor hjem)
local fetchRetries = setmetatable({}, {__mode = "k"}) -- traktor -> antal nye hente-forsøg
local missingCount = {} -- runId -> tjek i træk
local seenBuildings = {} -- bygnings-id'er set i denne session
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.
-- s = {hasRun, stopped, carriesFlowWagon, homeTrip} -> må puljen/markarbejde/marksilo låne traktoren?
function ADFlowWagons.tractorFree(s)
if not s.hasRun then return true end
return s.stopped == true and not s.carriesFlowWagon and not s.homeTrip
end
function ADFlowWagons.isFreeTractor(vehicle)
local run = ADRunsController.getRunForVehicle(vehicle)
local carries = false
if run ~= nil then
for _, implement in ipairs(ADHitch.rearImplementsOf(vehicle)) do
local wagonId = ADRunsController.getVehicleId(implement.object)
if wagonId ~= nil and ADFlowWagons.ownerOf(runs(), wagonId) ~= nil then carries = true end
end
end
return ADFlowWagons.tractorFree({hasRun = run ~= nil, stopped = run ~= nil and run.stopped == true,
carriesFlowWagon = carries, homeTrip = emptying[vehicle] ~= nil or homing[vehicle] ~= nil})
end
-- Holder flowet noget: en traktor henter/venter på sin vogn eller har en vogn bagpå (til Stop-knappen).
function ADFlowWagons.hasActivity(run, vehiclesById)
for _, vehicleId in ipairs(run.vehicleIds or {}) do
local vehicle = vehiclesById ~= nil and vehiclesById[vehicleId] or nil
if vehicle ~= nil and (waiting[vehicle] ~= nil or fetching[vehicle] ~= nil or #ADHitch.rearImplementsOf(vehicle) > 0) then
return true
end
end
return false
end
function ADFlowWagons.hasWagonFor(run, tractorId)
return (run.wagons or {})[tractorId] ~= nil
end
function ADFlowWagons.isHeldWagon(wagonId)
return ADFlowWagons.isHeld(runs(), wagonId)
end
-- Frakoblede vogne med parkering, som intet kørende flow holder, og som flowet ikke allerede har givet en traktor.
local function guessFor(adEnv, run, vehicle, tractorId)
local taken = {}
for _, id in pairs(run.wagons or {}) do taken[id] = true end
local candidates = {}
local farmId = g_currentMission:getFarmId()
for _, any in pairs(g_currentMission.vehicleSystem.vehicles) do
local index = any.advd ~= nil and any.advd.parkDestination or nil
local attached = any.getAttacherVehicle ~= nil and any:getAttacherVehicle() ~= nil
if any.spec_motorized == nil and any.spec_attachable ~= nil and not attached and type(index) == "number" and index >= 1
and any.getOwnerFarmId ~= nil and any:getOwnerFarmId() == farmId then
local id = ADRunsController.getVehicleId(any)
local park = adEnv.ADGraphManager:getMapMarkerById(index)
if id ~= nil and park ~= nil and not ADFlowWagons.isHeld(runs(), id) then
table.insert(candidates, {id = id, parkName = park.name})
end
end
end
local number = nil
for index, id in ipairs(run.vehicleIds or {}) do
if id == tractorId then number = index end
end
local label = tostring(ADRuns.vehicleLabel(vehicle))
number = tonumber(label:match("%s(%d+)$")) or number
local wagonId = ADFlowWagons.guessWagon(run.name, number, candidates, taken)
if wagonId ~= nil then
Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' får vognen ved parkeringen der matcher flowets navn", tostring(run.name), label)
end
return wagonId
end
-- Flowet startes igen mens traktoren tømmer/kører hjem efter et stop: afbryd, så den kører videre med sin vogn.
-- Puljens egen hjemtur (fra markarbejde) røres ikke — der sidder et andet redskab bagpå.
-- s = {emptying, flowHoming, poolReturning} -> true = afbryd
function ADFlowWagons.abortHomeTripOnStart(s)
return s.emptying == true or s.flowHoming == true
end
-- Hvilken af isPoolBusy's grunde (til loggen).
function ADFlowWagons.busyWhy(vehicle, tractorId)
if ADFieldJobs ~= nil and ADFieldJobs.state.assignments[tractorId] ~= nil then return "markopgave" end
if ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle) then return "lånt af marksilo" end
if ADPool ~= nil and ADPool.isReturning(vehicle) then return "sætter redskab af" end
if ADCpBridge ~= nil and ADCpBridge.isWorking(vehicle) then return "Courseplay kører" end
if emptying[vehicle] ~= nil then return "tømmer vognen" end
return "?"
end
-- Lånt ud af puljen: markarbejde, marksilo-lån, på hjemtur eller Courseplay kører den.
function ADFlowWagons.isPoolBusy(vehicle, tractorId)
return (ADFieldJobs ~= nil and ADFieldJobs.state.assignments[tractorId] ~= nil)
or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle))
or (ADPool ~= nil and ADPool.isReturning(vehicle))
or (ADCpBridge ~= nil and ADCpBridge.isWorking(vehicle))
or (ADFlowWagons.isEmptying ~= nil and ADFlowWagons.isEmptying(vehicle))
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
if ADFlowWagons.abortHomeTripOnStart({emptying = emptying[vehicle] ~= nil, flowHoming = homing[vehicle] ~= nil}) then
Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' startet igen — tømning/hjemtur afbrudt, kører videre med vognen",
tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)))
emptying[vehicle] = nil
if homing[vehicle] ~= nil then
homing[vehicle] = nil
ADHitch.cancel(vehicle)
end
end
local wagonId = (run.wagons or {})[tractorId]
if wagonId == nil and #ADHitch.rearImplementsOf(vehicle) == 0 then
wagonId = guessFor(adEnv, run, vehicle, tractorId)
if wagonId ~= nil then run.wagons = ADFlowWagons.remember(run.wagons, tractorId, wagonId) end
end
local wagon = wagonId ~= nil and findAnyVehicle(wagonId) or nil
local connectId = wagon ~= nil and connectOf(adEnv, wagon) or nil
local startState = {
tractorBusy = ADFlowWagons.isPoolBusy(vehicle, tractorId),
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),
}
local action = ADFlowWagons.startAction(startState)
if action == "start" then return false end
ADRunsController.runningRuns[run.id] = true
if action == "wait" then
if waiting[vehicle] == nil then
local holder = wagon ~= nil and wagon.getAttacherVehicle ~= nil and wagon:getAttacherVehicle() or nil
Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' venter på sin vogn (%s: %s)", tostring(run.name),
tostring(ADRuns.vehicleLabel(vehicle)), ADFlowWagons.waitReason(startState),
startState.tractorBusy and ADFlowWagons.busyWhy(vehicle, tractorId)
or (holder ~= nil and ("sidder på " .. tostring(ADRuns.vehicleLabel(holder)))) or "reserveret af puljen")
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
local okFetch, errFetch = pcall(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
fetchRetries[tractor] = nil
local result = ADRunsController.startVehicle(adEnv, current, tractor)
if result ~= nil and not result.ok then
-- flowet kan ikke startes (markør, bygning …): ikke efterladt ved Connect med vognen
Logging.warning(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' kunne ikke starte (%s) — vognen sættes tilbage",
tostring(current.name), tostring(ADRuns.vehicleLabel(tractor)), tostring(result.reason))
ADHitch.returnHome(adEnv, tractor)
end
elseif ADFlowWagons.retryFetch(detail, fetchRetries[tractor]) then
fetchRetries[tractor] = (fetchRetries[tractor] or 0) + 1
waiting[tractor] = runId -- retryWaiting starter den igen om lidt
Logging.warning(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' kunne ikke hente sin vogn (%s) — prøver igen", tostring(current.name),
tostring(ADRuns.vehicleLabel(tractor)), tostring(detail))
else
fetchRetries[tractor] = nil
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)
if not okFetch then
fetching[vehicle] = nil
return true, {vehicle = vehicle, ok = false, reason = "error"}
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 tømmes (flowets mål, ellers silo/egen fabrik), sættes på plads, traktoren hjem.
local nowMs = 0
local function cargoOf(adEnv, vehicle)
local level, fillType = 0, nil
local ok, units = pcall(adEnv.AutoDrive.getAllDischargeableUnits, vehicle)
for _, unit in ipairs(ok and units or {}) do
local object, index = unit.object, unit.fillUnitIndex
if object ~= nil and object.getFillUnitFillLevel ~= nil then
local unitLevel = object:getFillUnitFillLevel(index) or 0
if unitLevel > 0 then
level = level + unitLevel
fillType = fillType or object:getFillUnitFillType(index)
end
end
end
return level, fillType
end
local function goHome(adEnv, vehicle)
emptying[vehicle] = nil
homing[vehicle] = true
local okHome, pooled = pcall(ADHitch.returnHome, adEnv, vehicle, function(tractor) homing[tractor] = nil end)
if not (okHome and pooled) then
homing[vehicle] = nil
local park = ADFieldWork.parkMarkerId(adEnv, vehicle)
if park ~= nil then pcall(ADFieldWork.startAd, adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
end
end
local function fillName(fillType)
local desc = fillType ~= nil and g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil
return desc ~= nil and (desc.title or desc.name) or tostring(fillType)
end
local function startDump(adEnv, vehicle, state)
local cargo, fillType = cargoOf(adEnv, vehicle)
local isReachable = function(markerId)
return ADUnloadWait == nil or ADUnloadWait.hasRouteTo == nil or ADUnloadWait.hasRouteTo(adEnv, vehicle, markerId)
end
local ok, marker, why = pcall(ADLoadSwap.findDumpMarker, adEnv, vehicle, fillType, cargo, nil, isReachable, nil, nil, true)
if not ok or marker == nil then
Logging.warning(ADFlowWagons.LOG_PREFIX .. "'%s' har stadig %d l %s — intet sted at aflæsse (%s); parkeres med last",
tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType), tostring(ok and why or marker))
goHome(adEnv, vehicle)
return
end
state.stage, state.since = "dump", nowMs
state.markerId, state.markerName, state.lastCargo = marker.id, marker.name, nil
ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DELIVERTO, marker.id, nil, fillType)
Logging.info(ADFlowWagons.LOG_PREFIX .. "'%s' aflæsser resten (%d l %s) ved %s før parkering",
tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType), tostring(marker.name))
end
-- Husk vognen bag traktoren som flowets (også når flowet lige er stoppet — rememberAll ser kun kørende flows).
-- in-game 29/9: Milk Store's tanke blev sat af ved Stop alt uden at være husket -> 'noTrailer' ved næste start.
local function rememberWagonOf(vehicle)
local run = ADRunsController.getRunForVehicle(vehicle)
local rear = run ~= nil and ADHitch.rearImplementsOf(vehicle) or {}
local tractorId = ADRunsController.getVehicleId(vehicle)
local wagonId = rear[1] ~= nil and ADRunsController.getVehicleId(rear[1].object) or nil
if wagonId == nil or tractorId == nil then return end
local updated = ADFlowWagons.remember(run.wagons, tractorId, 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
-- Én traktor hjem (fra et stoppet/slettet flow): tøm først, så parkering.
function ADFlowWagons.homeVehicle(adEnv, vehicle, runName)
waiting[vehicle] = nil
rememberWagonOf(vehicle) -- vognen sættes af nu: flowet skal kunne finde den igen ved næste start
ADHitch.stow(vehicle, "flowet stoppet")
if fetching[vehicle] ~= nil then
fetching[vehicle] = nil
ADHitch.cancel(vehicle)
end
local module = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
local second = module ~= nil and module.getSecondMarker ~= nil and module:getSecondMarker() or nil
local run = ADRunsController.getRunForVehicle(vehicle)
local targetId = ADFlowWagons.emptyTarget(second ~= nil and second.id or nil, run ~= nil and run.targetWayPointIds or nil)
local target = targetId ~= nil and adEnv.ADGraphManager:getMapMarkerByWayPointId(targetId) or nil
local cargo, fillType = cargoOf(adEnv, vehicle)
local action = ADFlowWagons.emptyStep("start", {cargo = cargo, hasTarget = target ~= nil})
if action == "home" then return goHome(adEnv, vehicle) end
local state = {stage = action, since = nowMs, runName = runName,
markerId = target ~= nil and target.id or nil, markerName = target ~= nil and target.name or nil}
emptying[vehicle] = state
if action == "target" then
ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DELIVERTO, target.id, nil, fillType)
Logging.info(ADFlowWagons.LOG_PREFIX .. "'%s' afleverer sit læs (%d l %s) ved %s før parkering",
tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType), tostring(target.name))
else
startDump(adEnv, vehicle, state)
end
end
-- ADHitch: spilleren har selv kørt traktoren til Connect og koblet på.
function ADFlowWagons.onManualAttach(adEnv, vehicle, wagon)
local run = ADRunsController.getRunForVehicle(vehicle)
if run == nil then return end
local tractorId = ADRunsController.getVehicleId(vehicle)
local wagonId = wagon ~= nil and ADRunsController.getVehicleId(wagon) or nil
local remembered = (run.wagons or {})[tractorId]
local module = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
if not ADFlowWagons.startAfterManualAttach({flowRunning = not run.stopped and ADRunsController.runningRuns[run.id] == true,
ownWagon = wagonId ~= nil and (remembered == nil or remembered == wagonId),
adActive = module ~= nil and module:isActive()}) then return end
fetchRetries[vehicle], waiting[vehicle] = nil, nil
Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' har selv hentet sin vogn — startes", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle)))
ADRunsController.startVehicle(adEnv, run, vehicle)
end
function ADFlowWagons.isEmptying(vehicle)
return emptying[vehicle] ~= nil
end
function ADFlowWagons.sendHome(adEnv, run)
for _, vehicleId in ipairs(run.vehicleIds or {}) do
local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId)
if vehicle ~= nil then
local ok, err = pcall(ADFlowWagons.homeVehicle, adEnv, vehicle, run.name)
if not ok then
Logging.warning(ADFlowWagons.LOG_PREFIX .. "hjem fejlede: %s", tostring(err))
goHome(adEnv, vehicle)
end
end
end
end
-- Står traktoren ved tømningens markør, uden at læsset er faldet i EMPTY_STALL_MS?
local function stalledAt(adEnv, vehicle, state, cargo)
if state.lastCargo == nil or cargo < state.lastCargo - 1 then
state.lastCargo, state.changedAt = cargo, nowMs
end
local wayPoint = state.markerId ~= nil and adEnv.ADGraphManager:getWayPointById(state.markerId) or nil
local near = false
if wayPoint ~= nil and vehicle.rootNode ~= nil then
local x, _, z = getWorldTranslation(vehicle.rootNode)
near = math.sqrt((x - wayPoint.x) ^ 2 + (z - wayPoint.z) ^ 2) <= ADFlowWagons.EMPTY_NEAR_M
end
return ADFlowWagons.isStalled({nearTarget = near, sinceChangeMs = nowMs - (state.changedAt or nowMs)})
end
local function tickEmptying(adEnv)
local list = {}
for vehicle, state in pairs(emptying) do table.insert(list, {vehicle = vehicle, state = state}) end
for _, item in ipairs(list) do
local vehicle, state = item.vehicle, item.state
local module = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
local cargo, fillType = cargoOf(adEnv, vehicle)
local controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled()
if controlled then
emptying[vehicle] = nil -- spilleren har overtaget
else
local stalled = stalledAt(adEnv, vehicle, state, cargo)
local action = ADFlowWagons.emptyStep(state.stage, {cargo = cargo, adActive = module ~= nil and module:isActive(),
elapsedMs = nowMs - state.since, stalled = stalled})
if stalled and action ~= "wait" then
Logging.info(ADFlowWagons.LOG_PREFIX .. "'%s' kan ikke aflæsse ved %s (fuldt?) — videre", tostring(ADRuns.vehicleLabel(vehicle)),
tostring(state.markerName))
end
if action == "home" then
goHome(adEnv, vehicle)
elseif action == "dump" then
startDump(adEnv, vehicle, state)
elseif action == "giveUp" then
Logging.warning(ADFlowWagons.LOG_PREFIX .. "'%s' kunne ikke tømme vognen (%d l %s tilbage) — parkeres med last",
tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType))
goHome(adEnv, vehicle)
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 id in pairs(exists) do seenBuildings[id] = true end
for _, run in ipairs(runs()) do
if not run.stopped then
local missing = ADFlowWagons.missingBuildings(run, exists, seenBuildings)
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
and not ADFlowWagons.isPoolBusy(vehicle, vehicleId) then
Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s' er stoppet: '%s' tømmer og sætter sin vogn på plads", tostring(run.name),
tostring(ADRuns.vehicleLabel(vehicle)))
pcall(ADFlowWagons.homeVehicle, adEnv, vehicle, run.name)
end
end
end
end
end
local emptyTimer = 0
function ADFlowWagons.tick(adEnv, dt)
if adEnv == nil or ADRunsController.store == nil then return end
if ADHitch ~= nil and ADHitch.onManualAttach == nil then ADHitch.onManualAttach = ADFlowWagons.onManualAttach end
nowMs = nowMs + dt
emptyTimer = emptyTimer + dt
if emptyTimer >= 2000 then
emptyTimer = 0
local okEmpty, errEmpty = pcall(tickEmptying, adEnv)
if not okEmpty then Logging.warning(ADFlowWagons.LOG_PREFIX .. "tømning: %s", tostring(errEmpty)) end
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