From 6138b6b874a79002a18de3b69cde99d5c2d550d7 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sat, 26 Sep 2026 12:51:03 +0200 Subject: [PATCH] =?UTF-8?q?feat(markarbejde):=20tom=20spreder/spr=C3=B8jte?= =?UTF-8?q?=20kan=20bruges=20(roller=20efter=20hvad=20den=20kan=20tage;=20?= =?UTF-8?q?p=C3=A5fyldning=20med=20rollens=20vare);=20spreder=20med=20fork?= =?UTF-8?q?ert=20vare=20t=C3=B8mmes=20f=C3=B8rst=20og=20v=C3=A6lges=20kun?= =?UTF-8?q?=20hvis=20ingen=20anden=20kan=20(v1.28.8.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adCpBridge.lua | 94 +++++++++++++++++++++++++---- FS25_ADSmartPickup/adFieldFleet.lua | 11 +++- FS25_ADSmartPickup/adFieldWork.lua | 9 ++- FS25_ADSmartPickup/modDesc.xml | 2 +- docs/TESTPLAN-markarbejde.md | 5 ++ tests/test_adCpBridge.lua | 12 ++++ tests/test_adFieldFleet.lua | 11 ++++ 7 files changed, 128 insertions(+), 16 deletions(-) diff --git a/FS25_ADSmartPickup/adCpBridge.lua b/FS25_ADSmartPickup/adCpBridge.lua index 08623a2..b806b91 100644 --- a/FS25_ADSmartPickup/adCpBridge.lua +++ b/FS25_ADSmartPickup/adCpBridge.lua @@ -62,16 +62,78 @@ local function collect(vehicle, list) return list end --- Sprøjtens/sprederens rolle ud fra det fyld den har på (tom -> nil). +-- Hvilke varer der hører til en rolle (påfyldning af en tom spreder/sprøjte), i foretrukken rækkefølge. +ADCpBridge.ROLE_FILLTYPES = { + fertilizer = {"FERTILIZER", "LIQUIDFERTILIZER", "MANURE", "LIQUIDMANURE", "DIGESTATE"}, + limer = {"LIME"}, + weeder = {"HERBICIDE"}, +} +ADCpBridge.ROLE_ORDER = {"fertilizer", "limer", "weeder"} + +-- Sprøjtens/sprederens roller: alle roller dens fyldenhed kan tage (Bredal-spreder: gødning OG kalk). +-- Primær rolle = fyldets rolle (har den kalk på, er den kalkspreder), ellers den første den kan tage. +-- Returnerer primær, sæt {[rolle] = true}, fyldets rolle (nil = tom); nil hvis ingen roller. +function ADCpBridge.sprayRolesFor(filledName, supportedNames) + local roles, first = {}, nil + for _, role in ipairs(ADCpBridge.ROLE_ORDER) do + for _, name in ipairs(ADCpBridge.ROLE_FILLTYPES[role]) do + if supportedNames[name] then + roles[role] = true + first = first or role + break + end + end + end + local filledRole = filledName ~= nil and ADCpBridge.sprayRoleForFillType(filledName) or nil + if filledRole ~= nil then roles[filledRole] = true end + local primary = filledRole or first + return primary, primary ~= nil and roles or nil, filledRole +end + +local function sprayerNames(object) + local unitIndex = object:getSprayerFillUnitIndex() + local filledName = nil + if (object:getFillUnitFillLevel(unitIndex) or 0) > 0 then + filledName = g_fillTypeManager:getFillTypeNameByIndex(object:getFillUnitFillType(unitIndex)) + end + local supported = {} + local unit = object.spec_fillUnit ~= nil and object.spec_fillUnit.fillUnits ~= nil and object.spec_fillUnit.fillUnits[unitIndex] or nil + for fillType, isSupported in pairs(unit ~= nil and unit.supportedFillTypes or {}) do + if isSupported then + local name = g_fillTypeManager:getFillTypeNameByIndex(fillType) + if name ~= nil then supported[name] = true end + end + end + return filledName, supported +end + local function sprayRole(object) if object.spec_sprayer == nil or object.getSprayerFillUnitIndex == nil then return nil end - local ok, role = pcall(function() - local unit = object:getSprayerFillUnitIndex() - if (object:getFillUnitFillLevel(unit) or 0) <= 0 then return nil end - local name = g_fillTypeManager:getFillTypeNameByIndex(object:getFillUnitFillType(unit)) - return ADCpBridge.sprayRoleForFillType(name) + local ok, role, roles, filledRole = pcall(function() + local filledName, supported = sprayerNames(object) + return ADCpBridge.sprayRolesFor(filledName, supported) end) - return ok and role or nil + if not ok then return nil end + return role, roles, filledRole +end + +-- Tøm sprøjten/sprederen for en vare der ikke hører til opgaven (kalk på, skal gøde). FS25 kan ikke +-- tippe gødning/kalk af, så resten kasseres. Returnerer liter og varenavn (0 hvis intet blev tømt). +function ADCpBridge.emptySprayerIfWrong(vehicle, role) + local liters, name = 0, nil + for _, object in ipairs(collect(vehicle)) do + if object.spec_sprayer ~= nil and object.getSprayerFillUnitIndex ~= nil then + pcall(function() + local filledName = sprayerNames(object) + if filledName == nil or ADCpBridge.sprayRoleForFillType(filledName) == role then return end + local unit = object:getSprayerFillUnitIndex() + local level = object:getFillUnitFillLevel(unit) or 0 + object:addFillUnitFillLevel(object:getOwnerFarmId(), unit, -level, object:getFillUnitFillType(unit), ToolType.UNDEFINED, nil) + liters, name = liters + level, filledName + end) + end + end + return liters, name end function ADCpBridge.getSpecs(vehicle) @@ -80,7 +142,7 @@ function ADCpBridge.getSpecs(vehicle) for _, name in ipairs(ADCpBridge.SPECS) do if object[name] ~= nil then specs[name] = true end end - specs.sprayRole = specs.sprayRole or sprayRole(object) + if specs.sprayRole == nil then specs.sprayRole, specs.sprayRoles, specs.filledRole = sprayRole(object) end end if vehicle.getCanStartCpCombineUnloader ~= nil then local ok, can = pcall(vehicle.getCanStartCpCombineUnloader, vehicle) @@ -164,8 +226,9 @@ function ADCpBridge.isPlantableNow(fruitIndex) end -- Hvilken vare sættet bruger og kan få fyldt på af AutoDrive: frø (såmaskine) eller sprøjtens/sprederens --- nuværende fyld (gødning, kalk, sprøjtemiddel). nil = intet at fylde på (strigle, plov ...). -function ADCpBridge.getRefillFillType(vehicle) +-- fyld; en TOM spreder får rollens vare (gødning ved gødskning, kalk ved kalkning), hvis dens fyldenhed +-- kan tage den. nil = intet at fylde på (strigle, plov ...). +function ADCpBridge.getRefillFillType(vehicle, role) for _, object in ipairs(collect(vehicle)) do if object.spec_sowingMachine ~= nil and g_fillTypeManager ~= nil then return g_fillTypeManager:getFillTypeIndexByName("SEEDS") @@ -173,7 +236,16 @@ function ADCpBridge.getRefillFillType(vehicle) end for _, object in ipairs(collect(vehicle)) do if object.spec_sprayer ~= nil and object.getSprayerFillUnitIndex ~= nil then - local ok, fillType = pcall(function() return object:getFillUnitFillType(object:getSprayerFillUnitIndex()) end) + local ok, fillType = pcall(function() + local filledName, supported = sprayerNames(object) + if filledName ~= nil and (role == nil or ADCpBridge.sprayRoleForFillType(filledName) == role) then + return g_fillTypeManager:getFillTypeIndexByName(filledName) + end + for _, name in ipairs(ADCpBridge.ROLE_FILLTYPES[role] or {}) do + if supported[name] then return g_fillTypeManager:getFillTypeIndexByName(name) end + end + return nil + end) if ok and fillType ~= nil and fillType ~= 0 then return fillType end end end diff --git a/FS25_ADSmartPickup/adFieldFleet.lua b/FS25_ADSmartPickup/adFieldFleet.lua index 2f4e360..abfa943 100644 --- a/FS25_ADSmartPickup/adFieldFleet.lua +++ b/FS25_ADSmartPickup/adFieldFleet.lua @@ -40,7 +40,7 @@ local function sortCandidates(candidates, role, x, z, requiredWidth, accept) local usable, sawRole, sawAccepted = {}, false, false local widthReason, widthArgs = nil, nil for _, candidate in ipairs(candidates) do - if candidate.role == role and candidate.enabled then + if (candidate.role == role or (candidate.roles ~= nil and candidate.roles[role])) and candidate.enabled then sawRole = true if accept == nil or accept(candidate) then sawAccepted = true @@ -48,7 +48,9 @@ local function sortCandidates(candidates, role, x, z, requiredWidth, accept) local ok, reason, args = ADFieldModel.checkWidth(role, candidate.width or 0, requiredWidth) if ok then local dx, dz = candidate.x - x, candidate.z - z - table.insert(usable, {candidate = candidate, distance = dx * dx + dz * dz}) + -- spreder med en anden vare på skal tømmes først: kun hvis ingen anden kan + local wrongFill = candidate.filledRole ~= nil and candidate.filledRole ~= role + table.insert(usable, {candidate = candidate, distance = dx * dx + dz * dz, wrongFill = wrongFill}) elseif widthReason == nil then widthReason, widthArgs = reason, args end @@ -56,7 +58,10 @@ local function sortCandidates(candidates, role, x, z, requiredWidth, accept) end end end - table.sort(usable, function(a, b) return a.distance < b.distance end) + table.sort(usable, function(a, b) + if a.wrongFill ~= b.wrongFill then return not a.wrongFill end + return a.distance < b.distance + end) local reason, args = nil, nil if not sawRole then reason = "noRig" diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index e50f302..e97a5d0 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -120,6 +120,8 @@ function ADFieldWork.getRigs(adEnv) busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle), controlled = isControlledByPlayer(vehicle), width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z, + -- tom spreder kan flere ting (gødning og kalk): alle dens roller + roles = specs.sprayRoles, filledRole = specs.filledRole, } if role == "harvester" then rig.fruits = ADCpBridge.getHarvestFruits(vehicle) or {} end if role == "seeder" then @@ -377,7 +379,12 @@ end -- og genstarter CP. Findes ingen påfyldning, stopper sættet når det er tomt ("tom for …", se finish). local function prepareRefill(adEnv, field, info, assignment) local vehicle = assignment.vehicle - local fillType = ADCpBridge.getRefillFillType(vehicle) + -- forkert vare på (kalk, skal gøde): tøm først, så AutoDrive fylder den rigtige på + local dumped, dumpedName = ADCpBridge.emptySprayerIfWrong(vehicle, assignment.role) + if dumped > 0 then + log(field.id, "'%s' tømt for %.0f l %s (skal bruges til %s)", ADRuns.vehicleLabel(vehicle), dumped, tostring(dumpedName), tostring(assignment.role)) + end + local fillType = ADCpBridge.getRefillFillType(vehicle, assignment.role) if fillType == nil then return end assignment.refillFillType = fillType local name = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or tostring(fillType) diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 4125de9..7e1768e 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.28.7.0 + 1.28.8.0 <en>AD Smart Pickup</en> diff --git a/docs/TESTPLAN-markarbejde.md b/docs/TESTPLAN-markarbejde.md index 9ca9f9a..8c7c1f0 100644 --- a/docs/TESTPLAN-markarbejde.md +++ b/docs/TESTPLAN-markarbejde.md @@ -119,6 +119,11 @@ Afgrøde "Samme som høstet", Tromle Ja. Kalk Ja. (stort ukrudt, > 4). Græs ukrudtsbehandles ikke. - [ ] **6.4 Kalk.** Jord der trænger til kalk → kalkspreder. - [ ] **6.5 Påfyldning.** Sprøjte/spreder der løber tør → AutoDrive fylder op og kører tilbage. +- [ ] **6.6 Tom spreder (v1.28.8).** Tom Bredal-spreder bag en traktor vises i markflåden og sendes til + gødskning (F109) → Courseplay melder tom med det samme → AutoDrive fylder gødning og kører tilbage. + ⚠ Usikkert: starter Courseplay overhovedet med en tom spreder? Hvis ikke, skriv loggen. +- [ ] **6.7 Forkert vare (v1.28.8).** Spreder med kalk sendes til gødskning kun hvis ingen tom/gødet spreder + findes → log `tømt for N l LIME (skal bruges til fertilizer)` → fyldes med gødning. ## 7. Multi-maskine (Courseplay multi-tool) diff --git a/tests/test_adCpBridge.lua b/tests/test_adCpBridge.lua index c99749f..bbc7af4 100644 --- a/tests/test_adCpBridge.lua +++ b/tests/test_adCpBridge.lua @@ -70,6 +70,18 @@ check("SR digestat", ADCpBridge.sprayRoleForFillType("DIGESTATE"), "fertilizer") check("SR kalk", ADCpBridge.sprayRoleForFillType("LIME"), "limer") check("SR ukrudt", ADCpBridge.sprayRoleForFillType("HERBICIDE"), "weeder") check("SR vand/ukendt", ADCpBridge.sprayRoleForFillType("WATER"), nil) +-- SR2: tom Bredal-spreder (tager gødning OG kalk) kan begge dele; fyldt med kalk er den primært kalkspreder +local bredal = {FERTILIZER = true, LIME = true} +local p1, r1, f1 = ADCpBridge.sprayRolesFor(nil, bredal) +check("SR2 tom spreder primært gødning", p1, "fertilizer") +check("SR2 tom spreder kan kalke", r1 and r1.limer, true) +check("SR2 tom = intet fyld", f1, nil) +local p2, r2, f2 = ADCpBridge.sprayRolesFor("LIME", bredal) +check("SR2 med kalk = kalkspreder", p2, "limer") +check("SR2 med kalk kan stadig gøde (efter tømning)", r2 and r2.fertilizer, true) +check("SR2 fyldets rolle", f2, "limer") +check("SR2 ukrudtssprøjte", (ADCpBridge.sprayRolesFor(nil, {HERBICIDE = true, WATER = true})), "weeder") +check("SR2 vandvogn er ingen sprøjte", (ADCpBridge.sprayRolesFor(nil, {WATER = true})), nil) -- HF: høsterens afgrøder fra skærebordet (liste eller opslag); finsnitter tager ingen local list = ADCpBridge.harvestFruitSet({fruitTypes = {3, 5}}) diff --git a/tests/test_adFieldFleet.lua b/tests/test_adFieldFleet.lua index c5b82be..1ca86de 100644 --- a/tests/test_adFieldFleet.lua +++ b/tests/test_adFieldFleet.lua @@ -94,5 +94,16 @@ check("FL12 ballesamler", F.roleFromSpecs({canBaleCollect = true}), "baleCollect check("FL12 ballesamler før aflæsningsvogn", F.roleFromSpecs({canBaleCollect = true, canUnload = true}), "baleCollector") check("FL12 høster før presser", F.roleFromSpecs({spec_combine = true, spec_baler = true}), "harvester") +-- FL-spreder: tom spreder med rollerne {gødning, kalk} vælges til kalkning; spreder med forkert vare +-- på (kalk, skal gøde) kun hvis ingen anden kan — og den nærmeste med rigtig/ingen vare først +local spreaders = { + {id = "kalk", role = "limer", roles = {fertilizer = true, limer = true}, filledRole = "limer", enabled = true, x = 1, z = 0, width = 12}, + {id = "tom", role = "fertilizer", roles = {fertilizer = true, limer = true}, enabled = true, x = 50, z = 0, width = 12}, +} +check("FLS tom spreder tager kalkning", F.pick({spreaders[2]}, "limer", 0, 0) and F.pick({spreaders[2]}, "limer", 0, 0).id, "tom") +check("FLS gødskning: tom før nærmere med kalk på", F.pick(spreaders, "fertilizer", 0, 0).id, "tom") +check("FLS kun kalk-sprederen: bruges (tømmes)", F.pick({spreaders[1]}, "fertilizer", 0, 0).id, "kalk") +check("FLS kalkning: den med kalk på foretrækkes", F.pick(spreaders, "limer", 0, 0).id, "kalk") + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)