diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index 877405c..81c35b1 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -392,6 +392,7 @@ local function stepField(adEnv, info, env) ADFieldJobs.save() elseif decision.action == "rewind" then ADFieldJobs.setField(ADFieldModel.with(field, {stage = "ready", retryAt = 0, reason = false})) + ADFieldJobs.waitingSince[info.id] = nil ADFieldWork.windDownHelpers(adEnv, info.id, field.stage) ADFieldJobs.log(info.id, "%s -> ready: afgrøden står stadig uhøstet (%.0f %% klar)", field.stage, (info.readyRatio or 0) * 100) ADFieldJobs.save() diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index 67e3137..b77c789 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -474,8 +474,8 @@ function ADFieldWork.dispatch(adEnv, field, info, decision) local pooled, poolWhy, poolArgs = ADFieldWork.dispatchPool(adEnv, field, info, role, roleAccept) if pooled then return end if poolWhy == "tooWeak" then - note(field.id, {reason = {text = string.format("ingen ledig traktor med ≥%.0f hk til %s-redskabet", - (poolArgs or {})[1] or 0, tostring(role))}, retryAt = now() + ADFieldWork.NO_RIG_RETRY_MS}) + note(field.id, {reason = {text = string.format("ingen ledig traktor med ≥%.0f hk til '%s'", + (poolArgs or {})[1] or 0, tostring((poolArgs or {})[2] or role))}, retryAt = ADPool.alignedRetry(now(), ADFieldWork.NO_RIG_RETRY_MS)}) return end if poolWhy == "noPoolTractor" and why ~= "allBusy" then @@ -485,10 +485,10 @@ function ADFieldWork.dispatch(adEnv, field, info, decision) if #offNetwork > 0 and (why == "cannotDo" or why == "noRig") then -- de eneste med rollen står uden for AD-vejnettet note(field.id, {reason = {text = string.format("ingen %s kan køre til %s — står for langt fra AutoDrive-vejnettet (%s)", - tostring(role), tostring(info.markerName), table.concat(offNetwork, ", "))}, retryAt = now() + ADFieldWork.NO_RIG_RETRY_MS}) + tostring(role), tostring(info.markerName), table.concat(offNetwork, ", "))}, retryAt = ADPool.alignedRetry(now(), ADFieldWork.NO_RIG_RETRY_MS)}) return end - note(field.id, {reason = {key = "spu_field_reason_" .. tostring(why), role = role, args = args}, retryAt = now() + ADFieldWork.NO_RIG_RETRY_MS}) + note(field.id, {reason = {key = "spu_field_reason_" .. tostring(why), role = role, args = args}, retryAt = ADPool.alignedRetry(now(), ADFieldWork.NO_RIG_RETRY_MS)}) -- også i loggen (én gang pr. ændring): før stod grunden kun i fanen, og loggen var tavs (in-game 27/9) local noRigKey = tostring(role) .. ":" .. tostring(why) if lastNoRig[field.id] ~= noRigKey then @@ -1175,12 +1175,18 @@ function ADFieldWork.follow(adEnv, assignment) local hitch = assignment.hitch if not hitch.done then return end ADPool.release(assignment) + if info == nil or field == nil then + -- marken findes ikke længere (fx solgt): redskabet tilbage, traktoren hjem + ADFieldWork.release(assignment) + if hitch.ok then ADHitch.returnHome(adEnv, vehicle) end + return + end if not hitch.ok then -- koblingen fejlede: den skyldige i karantæne (kom traktoren ikke frem, er det traktoren), hjem, prøv igen local blamed = ADPool.failureBlames(hitch.detail) == "tractor" and assignment.vehicleId or assignment.pool.implementId ADPool.quarantine(blamed, now()) ADFieldWork.release(assignment) - note(assignment.fieldId, {retryAt = now() + ADFieldWork.NO_RIG_RETRY_MS, + note(assignment.fieldId, {retryAt = ADPool.alignedRetry(now(), ADFieldWork.NO_RIG_RETRY_MS), reason = {text = string.format("kunne ikke koble '%s' på (%s) — prøver et andet om lidt", tostring(assignment.pool.implementName), tostring(hitch.detail))}}) local park = parkMarkerId(adEnv, vehicle) @@ -1251,6 +1257,7 @@ function ADFieldWork.follow(adEnv, assignment) elseif assignment.phase == "waitingCourse" then if now() - assignment.since > ADFieldWork.COURSE_WAIT_MS then ADFieldWork.release(assignment) + ADHitch.returnHome(adEnv, vehicle) fail(assignment.fieldId, "multi-tool: lederens kursus kom ikke (3 min)") elseif coursesOf(field.id)[field.stage] ~= nil then beginWork(adEnv, field, info, assignment) @@ -1259,6 +1266,7 @@ function ADFieldWork.follow(adEnv, assignment) local generating = assignment.handle ~= nil and assignment.handle.generating == true if ADFieldJobs.startTimedOut(now() - assignment.since, generating) then ADFieldWork.release(assignment) + ADHitch.returnHome(adEnv, vehicle) fail(assignment.fieldId, generating and "Courseplay blev ikke færdig med kurset (5 min)" or "Courseplay svarede ikke (60 s)") end elseif assignment.phase == "working" then @@ -1290,8 +1298,10 @@ function ADFieldWork.stopAllAssignments(adEnv) for _, assignment in ipairs(list) do pcall(stopAd, assignment.vehicle) pcall(ADCpBridge.stop, assignment.vehicle) - table.insert(stopped, {vehicle = assignment.vehicle, manual = assignment.manual == true}) ADFieldWork.release(assignment) + -- puljeredskab: sættes af på sin plads og traktoren kører hjem (i stedet for almindelig parkering) + local okPool, pooled = pcall(ADHitch.returnHome, adEnv, assignment.vehicle) + table.insert(stopped, {vehicle = assignment.vehicle, manual = assignment.manual == true, pooled = okPool and pooled == true}) end return stopped end @@ -1301,5 +1311,6 @@ function ADFieldWork.stopAll(adEnv, fieldId) pcall(stopAd, assignment.vehicle) ADCpBridge.stop(assignment.vehicle) ADFieldWork.release(assignment) + pcall(ADHitch.returnHome, adEnv, assignment.vehicle) end end diff --git a/FS25_ADSmartPickup/adPool.lua b/FS25_ADSmartPickup/adPool.lua index b38dc2f..b470348 100644 --- a/FS25_ADSmartPickup/adPool.lua +++ b/FS25_ADSmartPickup/adPool.lua @@ -141,9 +141,15 @@ function ADPool.pairFor(role, fieldX, fieldZ, implements, tractors, blocked, acc for _, implement in ipairs(candidates) do local tractor = nearest(free, implement.x, implement.z, function(t) return ADPool.powerOk(t.hp, implement.neededHp) end) if tractor ~= nil then return {implement = implement, tractor = tractor} end - if weakest == nil or (implement.neededHp or 0) < weakest then weakest = implement.neededHp end + if weakest == nil or (implement.neededHp or 0) < (weakest.neededHp or 0) then weakest = implement end end - return nil, "tooWeak", {weakest or 0} + return nil, "tooWeak", {weakest ~= nil and weakest.neededHp or 0, weakest ~= nil and weakest.name or "?"} +end + +-- Ventende marker prøver igen på fælles taktslag (ikke hver sin 30 s fra eget forsøg): så udløber de i samme +-- tick, og køens rækkefølge (længst ventende først) afgør hvem der får den næste ledige traktor. +function ADPool.alignedRetry(nowMs, periodMs) + return (math.floor(nowMs / periodMs) + 1) * periodMs end -- Ventende marker først (længst ventende først, lige -> lavest id); resten bagefter i oprindelig rækkefølge. @@ -225,8 +231,18 @@ local function positionOf(vehicle) end -- Butiksdata: redskabets krævede effekt (hk). specs indlæses først, når butikken har vist varen. +local neededHpCache = {} -- configFileName -> hk eller false (butiksdata ændrer sig ikke i et spil) + local function neededHpOf(vehicle) if g_storeManager == nil or vehicle.configFileName == nil then return nil end + local cached = neededHpCache[vehicle.configFileName] + if cached ~= nil then return cached or nil end + local value = ADPool.readNeededHp(vehicle) + neededHpCache[vehicle.configFileName] = value or false + return value +end + +function ADPool.readNeededHp(vehicle) local item = g_storeManager:getItemByXMLFilename(vehicle.configFileName) if item == nil then return nil end if item.specs == nil and StoreItemUtil ~= nil and StoreItemUtil.loadSpecsFromXML ~= nil then diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index 5d98bde..d273be7 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -1179,7 +1179,9 @@ function ADRunsController.stopAllAndPark(adEnv) if ADFieldWork ~= nil and ADFieldWork.stopAllAssignments ~= nil then for _, stopped in ipairs(ADFieldWork.stopAllAssignments(adEnv) or {}) do summary.stopped = summary.stopped + 1 - if stopped.manual then + if stopped.pooled then + summary.parked = summary.parked + 1 + elseif stopped.manual then table.insert(summary.manual, tostring(ADRuns.vehicleLabel(stopped.vehicle))) elseif not seen[stopped.vehicle] then seen[stopped.vehicle] = true diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index e229f90..23c4890 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.48.3.0 + 1.48.4.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adPool.lua b/tests/test_adPool.lua index c1d9f44..08276d8 100644 --- a/tests/test_adPool.lua +++ b/tests/test_adPool.lua @@ -130,5 +130,11 @@ P.reserve("owner", {"r1"}) check("reserveret: ja", P.isReserved("r1"), true) P.release("owner") check("reserveret: nej", P.isReserved("r1"), false) +-- M3: ventende marker prøver igen i samme takt (så køens rækkefølge holder) +check("M3: næste takt", P.alignedRetry(61000, 30000), 90000) +check("M3: på grænsen -> næste", P.alignedRetry(60000, 30000), 90000) +check("M3: to marker samme takt", P.alignedRetry(62000, 30000) == P.alignedRetry(89000, 30000), true) +-- M4: for svag nævner redskabets navn +check("M4: navn i args", weakArgs[2], "Plov nær") print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)