fix(pulje): review v1.50 — egne tømmevogne virkede ikke under høst (global 'implement'), hentning kan afbrydes (høst slut/Stop/spiller), hjemtur ved tømmevogns-fejl, marksilo-lån kan ikke hænge i 'henter' (tidsgrænse, spiller, fejl i start), ingen ekstra parkeringstur med puljevogn, Stop holder en bar mark; regressionstest for fejlagtige globale (v1.50.1.0)
This commit is contained in:
@@ -40,7 +40,11 @@ end
|
||||
ADFieldFleet.EXTRA_ROLES = {plow = {plow = true, cultivator = true}}
|
||||
|
||||
function ADFieldFleet.rolesFor(role)
|
||||
return ADFieldFleet.EXTRA_ROLES[role]
|
||||
local roles = ADFieldFleet.EXTRA_ROLES[role]
|
||||
if roles == nil then return nil end
|
||||
local copy = {}
|
||||
for key, value in pairs(roles) do copy[key] = value end
|
||||
return copy
|
||||
end
|
||||
|
||||
-- Kandidater med rollen, sorteret nærmest først, delt i brugbare og afviste (med første grund).
|
||||
|
||||
@@ -159,6 +159,7 @@ end
|
||||
function ADFieldModel.setTarget(field, fruitName)
|
||||
local copy = copyField(field)
|
||||
copy.plan.targetFruit = fruitName or ""
|
||||
copy.held = nil
|
||||
copy.plan.mode = copy.plan.targetFruit ~= "" and "auto" or "manual"
|
||||
if not ADFieldModel.isWorking(field) and field.stage == "done" then
|
||||
copy.stage = "idle"
|
||||
@@ -172,6 +173,7 @@ end
|
||||
function ADFieldModel.shouldStartBare(field, kind)
|
||||
if field.plan.targetFruit == nil or field.plan.targetFruit == "" then return false end
|
||||
-- kun fra idle: efter egen såning står marken "done" (nysået kan læses som bar) — ingen dobbelt-såning
|
||||
if field.held and not field.startRequested then return false end
|
||||
return kind == "bare" and field.stage == "idle"
|
||||
end
|
||||
|
||||
@@ -277,6 +279,7 @@ end
|
||||
-- currentFruit (valgfri): afgrøden på marken nu; med mål-afgrøde udfyldes planen ud fra den.
|
||||
function ADFieldModel.start(field, kind, needs, currentFruit)
|
||||
local copy = copyField(field)
|
||||
copy.held = nil
|
||||
copy.plan = ADFieldModel.applyTarget(copy.plan, kind, currentFruit)
|
||||
local stages = ADFieldModel.workStages(copy.plan, kind, needs)
|
||||
copy.stage = stages[1] or "done"
|
||||
@@ -320,6 +323,8 @@ function ADFieldModel.stop(field)
|
||||
end
|
||||
copy.startRequested = false
|
||||
copy.reason = nil
|
||||
-- stoppet af spilleren: en bar mark med mål-afgrøde starter ikke selv igen (Start eller nyt mål slipper)
|
||||
copy.held = true
|
||||
return copy
|
||||
end
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ ADFieldSilos.CARGO_MIN_LITERS = 100 -- rest under dette tæller ikke som last
|
||||
ADFieldSilos.FARM_RADIUS = 100
|
||||
-- Siloer højst så langt fra en nabo hører til samme klynge (Farma-rækker: 15 m mellem siloer, 18 m mellem rækker)
|
||||
ADFieldSilos.CLUSTER_LINK = 60
|
||||
ADFieldSilos.FETCH_TIMEOUT_MS = 300000 -- så længe må en traktor være om at hente en puljevogn
|
||||
ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive
|
||||
ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml"
|
||||
ADFieldSilos.ROOT = "ADSmartPickupFieldSilos"
|
||||
@@ -340,7 +341,7 @@ local function fillTypeName(fillType)
|
||||
return desc ~= nil and (desc.title or desc.name) or tostring(fillType)
|
||||
end
|
||||
|
||||
local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
|
||||
local function dispatchOne(adEnv, order, vehicle, silosById, storesById, isPool)
|
||||
local module = vehicle.ad.stateModule
|
||||
module:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
|
||||
module:setFirstMarkerByWayPointId(order.siloMarkerId)
|
||||
@@ -349,7 +350,9 @@ local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
|
||||
module:setFillType(order.fillType)
|
||||
module.loopCounter = 1
|
||||
if module.setLoopsDone ~= nil then module:setLoopsDone(0) end
|
||||
ADFieldWork.enableParkAtJobFinished(vehicle)
|
||||
-- puljevogn: ingen AD-parkering — ved turens slut sætter returnHome vognen af og kører traktoren hjem
|
||||
-- (med AD-parkering kørte den først hjem med vognen og så tilbage efter den)
|
||||
if not isPool then ADFieldWork.enableParkAtJobFinished(vehicle) end
|
||||
-- rotation fra: ellers kan modens egne pickup-hooks sende vognen til en anden silo i mappen
|
||||
if ADRunsController ~= nil and ADRunsController.setSetting ~= nil then
|
||||
ADRunsController.setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_NONE)
|
||||
@@ -371,7 +374,6 @@ local function dispatchPool(adEnv, order, pair, silosById, storesById)
|
||||
local owner = {siloId = order.siloId, storeId = order.storeId, siloName = silo ~= nil and silo.name or order.siloId,
|
||||
fillType = order.fillType, liters = order.liters, startedMs = nowMs, lastProgressMs = nowMs, lastFill = 0, fetching = true}
|
||||
loans[tractor] = owner
|
||||
ADPool.reserve(owner, {pair.implement.id, pair.tractor.id})
|
||||
ADHitch.fetch(adEnv, tractor, pair.implement.connectId, function(_, ok, detail)
|
||||
ADPool.release(owner)
|
||||
if loans[tractor] ~= owner then return end -- lånet blev afbrudt imens (Stop alt / flow slået fra)
|
||||
@@ -385,8 +387,14 @@ local function dispatchPool(adEnv, order, pair, silosById, storesById)
|
||||
if park ~= nil then ADFieldWork.startAd(adEnv, tractor, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
|
||||
return
|
||||
end
|
||||
dispatchOne(adEnv, order, tractor, silosById, storesById)
|
||||
local okDispatch, err = pcall(dispatchOne, adEnv, order, tractor, silosById, storesById, true)
|
||||
if not okDispatch then
|
||||
Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' kunne ikke starte turen med puljevognen: %s", tostring(ADRuns.vehicleLabel(tractor)), tostring(err))
|
||||
ADFieldSilos.releaseVehicle(adEnv, tractor, "failed")
|
||||
end
|
||||
end)
|
||||
-- reservationen først når hentningen er sat i gang (fejler fetch, er der intet at frigive)
|
||||
ADPool.reserve(owner, {pair.implement.id, pair.tractor.id})
|
||||
Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' henter puljevognen '%s' til %d l %s i %s", tostring(ADRuns.vehicleLabel(tractor)),
|
||||
tostring(pair.implement.name), math.floor(order.liters), fillTypeName(order.fillType), tostring(silo ~= nil and silo.name or order.siloId))
|
||||
end
|
||||
@@ -395,6 +403,11 @@ local function release(adEnv, vehicle, reason)
|
||||
local loan = loans[vehicle]
|
||||
loans[vehicle] = nil
|
||||
if loan == nil then return end
|
||||
if loan.fetching then
|
||||
-- henter stadig puljevognen: afbryd koblingen og frigiv reservationen
|
||||
if ADHitch ~= nil then ADHitch.cancel(vehicle) end
|
||||
if ADPool ~= nil then ADPool.release(loan) end
|
||||
end
|
||||
if reason == "stuck" or reason == "failed" then
|
||||
local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType)
|
||||
paused[k] = nowMs + ADFieldSilos.PAUSE_MS
|
||||
@@ -416,6 +429,10 @@ local function release(adEnv, vehicle, reason)
|
||||
end
|
||||
end
|
||||
|
||||
function ADFieldSilos.releaseVehicle(adEnv, vehicle, reason)
|
||||
release(adEnv, vehicle, reason)
|
||||
end
|
||||
|
||||
function ADFieldSilos.stopAll(adEnv)
|
||||
local list = {}
|
||||
for vehicle in pairs(loans) do table.insert(list, vehicle) end
|
||||
@@ -431,8 +448,17 @@ local function checkLoans(adEnv)
|
||||
else
|
||||
local ok, err = pcall(function()
|
||||
local loan = loans[vehicle]
|
||||
-- henter puljevognen: AD holder stille under koblingen — ikke "færdig"
|
||||
if loan.fetching then return end
|
||||
-- henter puljevognen: AD holder stille under koblingen — ikke "færdig"; men spilleren kan overtage,
|
||||
-- og en hentning der ikke bliver færdig (trafik, rute), opgives
|
||||
if loan.fetching then
|
||||
local controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false
|
||||
if controlled then
|
||||
release(adEnv, vehicle, "player")
|
||||
elseif nowMs - (loan.startedMs or nowMs) > ADFieldSilos.FETCH_TIMEOUT_MS then
|
||||
release(adEnv, vehicle, "stuck")
|
||||
end
|
||||
return
|
||||
end
|
||||
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
||||
local fill = fillLevel(adEnv, vehicle)
|
||||
local vehicleState = {
|
||||
|
||||
@@ -287,6 +287,10 @@ end
|
||||
function ADFieldWork.release(assignment, phase)
|
||||
restoreToolOffset(assignment)
|
||||
if assignment.handle ~= nil then assignment.handle.cancelled = true end
|
||||
-- henter stadig puljeredskabet: afbryd koblingen, så det ikke kobles på uden en opgave
|
||||
if assignment.phase == "fetching" and assignment.hitch ~= nil and not assignment.hitch.done and ADHitch ~= nil then
|
||||
ADHitch.cancel(assignment.vehicle)
|
||||
end
|
||||
assignment.phase = phase or "released"
|
||||
if ADPool ~= nil and assignment.pool ~= nil then ADPool.release(assignment) end
|
||||
ADFieldJobs.state.assignments[assignment.vehicleId] = nil
|
||||
@@ -447,8 +451,6 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
|
||||
local offNetwork = {}
|
||||
local accept = function(candidate)
|
||||
if roleAccept ~= nil and not roleAccept(candidate) then return false end
|
||||
-- tømmevogn: skal kunne bære høstens vare
|
||||
if role == "unloader" and not ADPool.canCarry(implement, info.harvestFillType) then return false end
|
||||
-- manuel transport: spilleren kører den — afstand til AutoDrive-vejnettet er ligegyldig
|
||||
if role == "harvester" and candidate.manual then return true end
|
||||
local d = candidate.x ~= nil and ADFieldWork.distanceToNetwork(adEnv, candidate.x, candidate.z) or nil
|
||||
@@ -697,6 +699,7 @@ local function beginUnloader(adEnv, field, info, assignment)
|
||||
local store, why = ADFieldWork.chooseStore(adEnv, field, info, vehicle, fillType)
|
||||
if store == nil then
|
||||
ADFieldWork.release(assignment)
|
||||
ADHitch.returnHome(adEnv, vehicle)
|
||||
fail(field.id, "intet lager til høsten: " .. tostring(why))
|
||||
return
|
||||
end
|
||||
@@ -716,11 +719,13 @@ local function beginUnloader(adEnv, field, info, assignment)
|
||||
log(field.id, "aflæsningsvogn '%s' følger høsteren -> %s", ADRuns.vehicleLabel(vehicle), tostring(store.name))
|
||||
else
|
||||
ADFieldWork.release(assignment)
|
||||
ADHitch.returnHome(adEnv, vehicle)
|
||||
fail(field.id, "aflæsningsvogn: " .. tostring(reason))
|
||||
end
|
||||
end)
|
||||
if handle == nil then
|
||||
ADFieldWork.release(assignment)
|
||||
ADHitch.returnHome(adEnv, vehicle)
|
||||
fail(field.id, "aflæsningsvogn: " .. tostring(err))
|
||||
return
|
||||
end
|
||||
@@ -1284,7 +1289,16 @@ end
|
||||
-- Høsten er færdig: aflæsningsvognene stoppes; har de rest, kører den til lager, ellers parkering.
|
||||
function ADFieldWork.windDownHelpers(adEnv, fieldId, stage)
|
||||
for _, assignment in ipairs(ADFieldJobs.assignmentsFor(fieldId)) do
|
||||
if assignment.helper and assignment.stage == stage then
|
||||
if assignment.helper and assignment.stage == stage and assignment.phase == "fetching" then
|
||||
-- høsten sluttede mens puljevognen blev hentet: afbryd, vognen tilbage hvis den nåede at komme på
|
||||
local vehicle = assignment.vehicle
|
||||
ADFieldWork.release(assignment)
|
||||
if not ADHitch.returnHome(adEnv, vehicle) then
|
||||
local park = parkMarkerId(adEnv, vehicle)
|
||||
if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
|
||||
end
|
||||
log(fieldId, "tømmevogn '%s' afbrudt under hentning — høsten er færdig", ADRuns.vehicleLabel(vehicle))
|
||||
elseif assignment.helper and assignment.stage == stage then
|
||||
if assignment.handle ~= nil then assignment.handle.cancelled = true end
|
||||
ADCpBridge.stop(assignment.vehicle)
|
||||
assignment.phase = "working"
|
||||
@@ -1315,7 +1329,12 @@ function ADFieldWork.stopAll(adEnv, fieldId)
|
||||
for _, assignment in ipairs(ADFieldJobs.assignmentsFor(fieldId)) do
|
||||
pcall(stopAd, assignment.vehicle)
|
||||
ADCpBridge.stop(assignment.vehicle)
|
||||
local wasFetching = assignment.phase == "fetching"
|
||||
ADFieldWork.release(assignment)
|
||||
pcall(ADHitch.returnHome, adEnv, assignment.vehicle)
|
||||
local okHome, pooled = pcall(ADHitch.returnHome, adEnv, assignment.vehicle)
|
||||
if wasFetching and not (okHome and pooled) then
|
||||
local park = parkMarkerId(adEnv, assignment.vehicle)
|
||||
if park ~= nil then pcall(startAd, adEnv, assignment.vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -253,6 +253,16 @@ local function start(adEnv, vehicle, markerId, action, onDone)
|
||||
return job
|
||||
end
|
||||
|
||||
-- Afbryd et hente-/koble-job (Stop alt, høsten slut, spilleren overtager): intet kobles på bagefter, og
|
||||
-- onDone kaldes ikke. -> true hvis der var et job.
|
||||
function ADHitch.cancel(vehicle)
|
||||
local had = jobs[vehicle] ~= nil
|
||||
jobs[vehicle] = nil
|
||||
if ADPool ~= nil and ADPool.setReturning ~= nil then ADPool.setReturning(vehicle, false) end
|
||||
if had and vehicle.stopAutoDrive ~= nil then pcall(vehicle.stopAutoDrive, vehicle) end
|
||||
return had
|
||||
end
|
||||
|
||||
-- Kør til en Connect-markør og kobl på. onDone(vehicle, ok, redskab eller årsag) kaldes én gang.
|
||||
function ADHitch.fetch(adEnv, vehicle, markerId, onDone)
|
||||
return start(adEnv, vehicle, markerId, "attach", onDone)
|
||||
|
||||
@@ -277,8 +277,18 @@ function ADPool.readNeededHp(vehicle)
|
||||
return item.specs ~= nil and ADPool.numberOf(item.specs.neededPower) or nil
|
||||
end
|
||||
|
||||
-- Traktorens effekt i hk: motorens topeffekt (kW) omregnet; ellers butiksdata.
|
||||
local tractorHpCache = setmetatable({}, {__mode = "k"}) -- vehicle -> hk eller false
|
||||
|
||||
-- Traktorens effekt i hk: motorens topeffekt (kW) omregnet; ellers butiksdata. Gemmes pr. traktor.
|
||||
local function tractorHpOf(vehicle)
|
||||
local cached = tractorHpCache[vehicle]
|
||||
if cached ~= nil then return cached or nil end
|
||||
local value = ADPool.readTractorHp(vehicle)
|
||||
tractorHpCache[vehicle] = value or false
|
||||
return value
|
||||
end
|
||||
|
||||
function ADPool.readTractorHp(vehicle)
|
||||
local motor = vehicle.spec_motorized ~= nil and vehicle.spec_motorized.motor or nil
|
||||
local kw = motor ~= nil and motor.peakMotorPower or nil
|
||||
if type(kw) == "number" and kw > 0 then return kw * 1.35962 end
|
||||
@@ -385,8 +395,9 @@ function ADPool.overview(adEnv)
|
||||
local state = ADFieldJobs.state
|
||||
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
|
||||
local id = ADRunsController.getVehicleId(vehicle)
|
||||
-- en traktor med et redskab der giver den en markrolle, står allerede i flåden som kombination
|
||||
if id ~= nil and vehicle.spec_attacherJoints ~= nil and ADRunsController.getRunForVehicle(vehicle) == nil
|
||||
and ADPool.canPull(ADPool.ownRole(vehicle)) then
|
||||
and ADPool.canPull(ADPool.ownRole(vehicle)) and ADFieldFleet.roleFromSpecs(ADCpBridge.getSpecs(vehicle)) == nil then
|
||||
local x, z = positionOf(vehicle)
|
||||
local distance = ADFieldWork.distanceToNetwork(adEnv, x, z)
|
||||
local assignment = state.assignments[id]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.50.0.0</version>
|
||||
<version>1.50.1.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
|
||||
@@ -140,5 +140,6 @@ check("FLP plov kan pløje", F.rolesFor("plow").plow, true)
|
||||
check("FLP kultivator kan ikke pløje", F.rolesFor("cultivator"), nil)
|
||||
local plough = {id = "p", role = "plow", roles = F.rolesFor("plow"), enabled = true, x = 0, z = 0, width = 3}
|
||||
check("FLP plov vælges til kultivering", (F.pick({plough}, "cultivator", 0, 0, nil)).id, "p")
|
||||
check("FLP rolesFor giver en kopi", F.rolesFor("plow") ~= F.rolesFor("plow"), true)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
|
||||
@@ -310,5 +310,11 @@ check("M24 klar = ikke passiv", M.isPassive(M.with(calm, {stage = "ready"})), fa
|
||||
check("M24 i gang = ikke passiv", M.isPassive(started), false)
|
||||
check("M24 Start nu = ikke passiv", M.isPassive(M.with(calm, {startRequested = true})), false)
|
||||
|
||||
-- Stop holder en bar mark (ellers starter klargøringen straks forfra); Start eller ny mål-afgrøde slipper
|
||||
local stoppedBare = M.stop(M.with(target, {stage = "cultivating"}))
|
||||
check("M21 stoppet bar mark starter ikke selv", M.shouldStartBare(stoppedBare, "bare"), false)
|
||||
check("M21 Start slipper", M.shouldStartBare(M.with(stoppedBare, {startRequested = true}), "bare"), true)
|
||||
check("M21 ny mål-afgrøde slipper", M.shouldStartBare(M.setTarget(stoppedBare, "BARLEY"), "bare"), true)
|
||||
check("M21 start nulstiller hold (næste sæson starter selv)", M.start(stoppedBare, "bare", {}, "WHEAT").held, nil)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
|
||||
@@ -89,5 +89,14 @@ check("hjem: to trin", #home, 2); check("hjem: redskab først", home[1], 4); che
|
||||
check("hjem: uden redskab kun traktor", #H.homeRoute({}, 2), 1)
|
||||
check("hjem: samme parkering to gange tæller én", #H.homeRoute({4, 4}, 2), 2)
|
||||
check("hjem: uden traktorparkering", #H.homeRoute({4}, nil), 1)
|
||||
-- afbrydelse: et hente-job kan annulleres (Stop alt, høst slut, spilleren overtager)
|
||||
ADFieldWork = ADFieldWork or {startAd = function() end}
|
||||
Logging = Logging or {info = function() end, warning = function() end}
|
||||
local stubEnv = {ADGraphManager = {getMapMarkerByWayPointId = function() return {name = "Connect X"} end}, AutoDrive = {MODE_DRIVETO = 1}}
|
||||
local tractorStub = {getFullName = function() return "T" end, stopAutoDrive = function(v) v.stopped = true end}
|
||||
H.fetch(stubEnv, tractorStub, 9, function() end)
|
||||
check("annullér: job fandtes", H.cancel(tractorStub), true)
|
||||
check("annullér: AD stoppet", tractorStub.stopped, true)
|
||||
check("annullér: intet job tilbage", H.cancel(tractorStub), false)
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
-- Kør: luajit tests/test_noStrayGlobals.lua (fra repo-roden)
|
||||
-- Fanger lokale navne der ved en fejl bruges som globale (fx 'implement' uden for sin closure — v1.49 gjorde
|
||||
-- egne tømmevogne ubrugelige under høst). Læser luajit-bytecode: GGET/GSET af typiske lokale navne.
|
||||
local SUSPECT = {implement = true, candidate = true, vehicle = true, rig = true, pair = true, assignment = true,
|
||||
tractor = true, loan = true, field = true, info = true, marker = true, job = true, owner = true, order = true}
|
||||
local failures = 0
|
||||
local files = io.popen("ls FS25_ADSmartPickup/*.lua FS25_ADSmartPickup/gui/*.lua"):read("*a")
|
||||
for file in files:gmatch("[^\n]+") do
|
||||
local listing = io.popen("luajit -bl " .. file .. " 2>&1"):read("*a")
|
||||
for op, name in listing:gmatch("(G[GS]ET)%s+%d+%s+%d+%s+; \"([%w_]+)\"") do
|
||||
if SUSPECT[name] then
|
||||
failures = failures + 1
|
||||
print(string.format("FAIL %s: %s af global '%s'", file, op, name))
|
||||
end
|
||||
end
|
||||
end
|
||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||
os.exit(failures == 0 and 0 or 1)
|
||||
Reference in New Issue
Block a user