diff --git a/FS25_ADSmartPickup/adCpBridge.lua b/FS25_ADSmartPickup/adCpBridge.lua
index 4d12418..08623a2 100644
--- a/FS25_ADSmartPickup/adCpBridge.lua
+++ b/FS25_ADSmartPickup/adCpBridge.lua
@@ -5,7 +5,7 @@
ADCpBridge = {}
-ADCpBridge.SPECS = {"spec_mower", "spec_tedder", "spec_windrower", "spec_forageWagon", "spec_combine",
+ADCpBridge.SPECS = {"spec_mower", "spec_tedder", "spec_windrower", "spec_forageWagon", "spec_combine", "spec_baler",
"spec_sowingMachine", "spec_cultivator", "spec_plow", "spec_roller", "spec_weeder", "spec_mulcher"}
-- Sprøjtens/sprederens fyld -> rolle (tom sprøjte har ingen rolle; den kan ikke sendes ud).
@@ -86,6 +86,10 @@ function ADCpBridge.getSpecs(vehicle)
local ok, can = pcall(vehicle.getCanStartCpCombineUnloader, vehicle)
specs.canUnload = ok and can == true or nil
end
+ if vehicle.getCanStartCpBaleFinder ~= nil then
+ local ok, can = pcall(vehicle.getCanStartCpBaleFinder, vehicle)
+ specs.canBaleCollect = ok and can == true or nil
+ end
return specs
end
@@ -189,6 +193,58 @@ function ADCpBridge.getRefillLevel(vehicle, fillType)
return total
end
+-- Halmskår bag mejetærskeren (Courseplays køretøjsindstilling strawSwath: 1 = fra/hakkes, 2 = til).
+function ADCpBridge.setStrawSwath(vehicle, on)
+ return (pcall(function() vehicle:getCpSettings().strawSwath:setValue(on and 2 or 1) end))
+end
+
+-- Ballesamler: Courseplays BALE_FINDER_CP (ballevogn eller Universal Autoload) samler markens baller.
+function ADCpBridge.startBaleFinder(vehicle, fieldX, fieldZ, onDone)
+ if AIJobType == nil or AIJobType.BALE_FINDER_CP == nil then
+ return nil, "Courseplay (BALE_FINDER_CP) findes ikke"
+ end
+ local farmId = vehicle:getOwnerFarmId()
+ local job = g_currentMission.aiJobTypeManager:createJob(AIJobType.BALE_FINDER_CP)
+ if job == nil then return nil, "kunne ikke oprette ballesamler-job" end
+ pcall(function() job:applyCurrentState(vehicle, g_currentMission, farmId, false, false) end)
+ local params = job.cpJobParameters
+ if params ~= nil and params.fieldPosition ~= nil then pcall(function() params.fieldPosition:setPosition(fieldX, fieldZ) end) end
+ if params ~= nil and params.startPosition ~= nil then
+ local vx, _, vz = getWorldTranslation(vehicle.rootNode)
+ pcall(function() params.startPosition:setPosition(vx, vz) end)
+ end
+ pcall(function() job:setValues() end)
+ local handle = {cancelled = false, fired = false, finished = false}
+ local function finish(success, reason)
+ if handle.finished then return end
+ handle.finished = true
+ if not handle.cancelled then onDone(success, reason) end
+ end
+ local function startJob()
+ if handle.cancelled then return end
+ local valid, err = job:validate(farmId)
+ if not valid then return finish(false, "Courseplay: " .. tostring(err)) end
+ g_currentMission.aiSystem:startJob(job, farmId)
+ finish(true)
+ end
+ handle.onBoundary = function(_, success, message)
+ if handle.fired then return end
+ handle.fired = true
+ if not success then return finish(false, "Courseplay fandt ikke markgrænsen: " .. tostring(message)) end
+ startJob()
+ end
+ if job.registerFieldBoundaryDetectionCallback ~= nil then
+ job:registerFieldBoundaryDetectionCallback(handle, handle.onBoundary)
+ end
+ local isValid, isRunning, message = job:detectFieldBoundary()
+ if isValid and not isRunning then
+ startJob()
+ elseif not isRunning then
+ return nil, "Courseplay: " .. tostring(message)
+ end
+ return handle
+end
+
-- Stop køretøjets AI-job (Courseplay/hjælper).
function ADCpBridge.stop(vehicle)
pcall(function()
diff --git a/FS25_ADSmartPickup/adFieldFleet.lua b/FS25_ADSmartPickup/adFieldFleet.lua
index 90ef464..2f4e360 100644
--- a/FS25_ADSmartPickup/adFieldFleet.lua
+++ b/FS25_ADSmartPickup/adFieldFleet.lua
@@ -8,6 +8,7 @@ ADFieldFleet = {}
-- plov, ikke aflæsningsvogn). sprayRole kommer fra sprøjtens/sprederens fyld (gødning/kalk/ukrudt).
ADFieldFleet.ROLE_SPECS = {
{role = "harvester", spec = "spec_combine"},
+ {role = "baler", spec = "spec_baler"},
{role = "collector", spec = "spec_forageWagon"},
{role = "rake", spec = "spec_windrower"},
{role = "tedder", spec = "spec_tedder"},
@@ -19,6 +20,7 @@ ADFieldFleet.ROLE_SPECS = {
{role = "mulcher", spec = "spec_mulcher"},
{role = "cultivator", spec = "spec_cultivator"},
{role = "plow", spec = "spec_plow"},
+ {role = "baleCollector", spec = "canBaleCollect"},
{role = "unloader", spec = "canUnload"},
}
diff --git a/FS25_ADSmartPickup/adFieldModel.lua b/FS25_ADSmartPickup/adFieldModel.lua
index 2db8be7..f40666d 100644
--- a/FS25_ADSmartPickup/adFieldModel.lua
+++ b/FS25_ADSmartPickup/adFieldModel.lua
@@ -28,7 +28,7 @@ ADFieldModel.ROLES = {
mowing = "mower", tedding = "tedder", raking = "rake", collecting = "collector",
harvesting = "harvester", mulching = "mulcher", liming = "limer", plowing = "plow",
cultivating = "cultivator", sowing = "seeder", rolling = "roller", weeding = "weeder",
- fertilizing = "fertilizer",
+ fertilizing = "fertilizer", strawCollecting = "collector", baling = "baler", baleCollecting = "baleCollector",
}
ADFieldModel.HELPERS = {harvesting = "unloader"}
ADFieldModel.CARE_STAGES = {fertilizing = true, weeding = true, careLiming = true}
@@ -42,8 +42,11 @@ KNOWN_STAGES.careLiming = true
local DEFAULT_PLAN = {
mode = "manual", product = "auto", rake = false, rigs = 1, unloaders = 1,
mulch = false, lime = true, soil = "auto", sow = false, sowFruit = "", roll = false,
- fertilize = true, weed = true, targetFruit = "", prepAfterMow = false,
+ fertilize = true, weed = true, targetFruit = "", prepAfterMow = false, straw = "chop",
}
+local STRAW_VALUES = {chop = true, collect = true, bale = true}
+-- halm-trin efter høst (mejetærskeren lægger skår når halmen skal bruges)
+local STRAW_STAGES = {collect = {"strawCollecting"}, bale = {"baling", "baleCollecting"}}
local SOIL_VALUES = {auto = true, cultivate = true, plow = true, none = true}
local PRODUCTS = {auto = true, silage = true, hay = true, harvest = true}
@@ -116,6 +119,7 @@ function ADFieldModel.workStages(plan, kind, needs)
local product = ADFieldModel.product(plan, kind)
if product == "harvest" then
local stages = {"harvesting"}
+ for _, stage in ipairs(STRAW_STAGES[plan.straw or "chop"] or {}) do table.insert(stages, stage) end
for _, stage in ipairs(ADFieldModel.prepStages(plan, needs)) do table.insert(stages, stage) end
return stages
end
@@ -168,9 +172,10 @@ function ADFieldModel.shouldStartBare(field, kind, plantableNow)
return plantableNow == true
end
--- Krævet bredde for et sæt: kun læssevognen uden riv skal dække slåmaskinens baner.
+-- Krævet bredde for et sæt: kun læssevognen uden riv skal dække slåmaskinens baner (halmskår ligger i
+-- høsterens spor — der er breddekravet ligegyldigt).
function ADFieldModel.requiredWidth(field, role)
- if role == "collector" and not field.plan.rake then
+ if role == "collector" and not field.plan.rake and field.stage ~= "strawCollecting" then
return field.mowWidth
end
return nil
@@ -178,6 +183,8 @@ end
-- Hvilket tidligere kursus sættet skal køre: læssevognen følger skårene; de andre laver deres eget.
function ADFieldModel.courseSource(field, role)
+ -- halm: læssevogn og presser kører høsterens kursus (skåret ligger i mejetærskerens spor)
+ if field.stage == "strawCollecting" or role == "baler" then return "harvester" end
if role ~= "collector" then return nil end
return field.plan.rake and "rake" or "mower"
end
@@ -247,6 +254,7 @@ local function freshCycle(copy)
copy.mowWidth = nil
copy.mowVehicleId = nil
copy.rakeVehicleId = nil
+ copy.harvestVehicleId = nil
copy.windrowOffset = nil
copy.careRuns = {}
end
@@ -276,7 +284,7 @@ function ADFieldModel.advance(field, needs)
local stages = ADFieldModel.workStages(field.plan, field.kind, needs)
copy.stage = "done"
local passed = false
- local order = {"mowing", "tedding", "raking", "collecting", "harvesting", "mulching", "liming", "plowing", "cultivating", "sowing", "rolling"}
+ local order = {"mowing", "tedding", "raking", "collecting", "harvesting", "strawCollecting", "baling", "baleCollecting", "mulching", "liming", "plowing", "cultivating", "sowing", "rolling"}
local rank = {}
for index, stage in ipairs(order) do rank[stage] = index end
for _, stage in ipairs(stages) do
@@ -303,7 +311,7 @@ end
-- Vejrfølsomme trin: afgrøde og skår skal være tørre (høst, slåning, vending, rivning, hø-opsamling).
-- Jordbehandling, såning, pleje og ensilage-opsamling kan køre i regn.
-local WEATHER_STAGES = {harvesting = true, mowing = true, tedding = true, raking = true}
+local WEATHER_STAGES = {harvesting = true, mowing = true, tedding = true, raking = true, strawCollecting = true, baling = true}
function ADFieldModel.isWeatherSensitive(stage, plan, kind)
if WEATHER_STAGES[stage] then return true end
return stage == "collecting" and ADFieldModel.product(plan, kind or "mowable") == "hay"
@@ -423,11 +431,12 @@ function ADFieldModel.toRecord(field)
rigs = plan.rigs, unloaders = plan.unloaders, mulch = flag(plan.mulch), lime = flag(plan.lime),
soil = plan.soil, sow = flag(plan.sow), sowFruit = plan.sowFruit or "", roll = flag(plan.roll),
fertilize = flag(plan.fertilize), weed = flag(plan.weed),
- targetFruit = plan.targetFruit or "", prepAfterMow = flag(plan.prepAfterMow),
+ targetFruit = plan.targetFruit or "", prepAfterMow = flag(plan.prepAfterMow), straw = plan.straw or "chop",
kind = field.kind or "", lastFruit = field.lastFruit or "", care = table.concat(care, " "),
mowWidth = field.mowWidth or 0,
mowVehicle = field.mowVehicleId or "",
rakeVehicle = field.rakeVehicleId or "",
+ harvestVehicle = field.harvestVehicleId or "",
windrowOffset = field.windrowOffset ~= nil and string.format("%.2f", field.windrowOffset) or "",
}
end
@@ -462,12 +471,14 @@ function ADFieldModel.fromRecord(record)
plan.weed = readFlag(record.weed, true)
plan.targetFruit = record.targetFruit or ""
plan.prepAfterMow = readFlag(record.prepAfterMow, false)
+ plan.straw = STRAW_VALUES[record.straw] and record.straw or "chop"
field.kind = readText(record.kind)
field.lastFruit = readText(record.lastFruit)
for stage, runs in string.gmatch(record.care or "", "(%w+)=(%d+)") do field.careRuns[stage] = tonumber(runs) end
field.mowWidth = (record.mowWidth or 0) > 0 and record.mowWidth or nil
field.mowVehicleId = readText(record.mowVehicle)
field.rakeVehicleId = readText(record.rakeVehicle)
+ field.harvestVehicleId = readText(record.harvestVehicle)
field.windrowOffset = tonumber(record.windrowOffset)
return field
end
diff --git a/FS25_ADSmartPickup/adFieldStorage.lua b/FS25_ADSmartPickup/adFieldStorage.lua
index 4b4c744..9d7f2cd 100644
--- a/FS25_ADSmartPickup/adFieldStorage.lua
+++ b/FS25_ADSmartPickup/adFieldStorage.lua
@@ -11,8 +11,8 @@ ADFieldStorage.FIELD_TYPES = {
id = "Int", stage = "String", mode = "String", product = "String", rake = "Int", store = "String",
rigs = "Int", unloaders = "Int", mulch = "Int", lime = "Int", soil = "String", sow = "Int",
sowFruit = "String", roll = "Int", fertilize = "Int", weed = "Int",
- kind = "String", lastFruit = "String", care = "String", targetFruit = "String", prepAfterMow = "Int",
- mowWidth = "Float", mowVehicle = "String", rakeVehicle = "String", windrowOffset = "String",
+ kind = "String", lastFruit = "String", care = "String", targetFruit = "String", prepAfterMow = "Int", straw = "String",
+ mowWidth = "Float", mowVehicle = "String", rakeVehicle = "String", harvestVehicle = "String", windrowOffset = "String",
}
function ADFieldStorage.getPath()
diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua
index 2ce76a1..df734f9 100644
--- a/FS25_ADSmartPickup/adFieldWork.lua
+++ b/FS25_ADSmartPickup/adFieldWork.lua
@@ -166,6 +166,9 @@ end
-- Hvilken vare markens trin fører til lager: skår (ensilage), tørskår (hø) eller høstet afgrøde.
function ADFieldWork.storeFillType(field, info)
if field.stage == "harvesting" then return info.harvestFillType end
+ if field.stage == "strawCollecting" or field.stage == "baling" or field.stage == "baleCollecting" then
+ return g_fillTypeManager:getFillTypeIndexByName("STRAW")
+ end
local product = ADFieldModel.product(field.plan, field.kind or info.kind)
return product == "hay" and info.hay or info.windrow
end
@@ -202,11 +205,11 @@ local function coursesOf(fieldId)
return ADFieldJobs.courses[fieldId]
end
--- Kurset et trin skal genbruge ("mower"/"rake"): gemt kopi, ellers maskinens nuværende kursus.
+-- Kurset et trin skal genbruge ("mower"/"rake"/"harvester"): gemt kopi, ellers maskinens nuværende kursus.
local function sourceCourse(adEnv, field, source)
local saved = coursesOf(field.id)[source]
if saved ~= nil then return saved end
- local vehicleId = source == "rake" and field.rakeVehicleId or field.mowVehicleId
+ local vehicleId = source == "rake" and field.rakeVehicleId or source == "harvester" and field.harvestVehicleId or field.mowVehicleId
if vehicleId == nil then return nil end
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
if ADRunsController.getVehicleId(vehicle) == vehicleId then return ADCpBridge.getCourse(vehicle) end
@@ -435,6 +438,51 @@ local function beginUnloader(adEnv, field, info, assignment)
assignment.handle = handle
end
+-- Ballesamler: Courseplays ballefinder; fuld vogn tømmes af AutoDrive i nærmeste ballelager (palleskur der
+-- tager halmballer), som læssevognen. Uden lager stopper den når den er fuld.
+local function beginBaleCollector(adEnv, field, info, assignment)
+ local vehicle = assignment.vehicle
+ local farmId = vehicle:getOwnerFarmId()
+ local straw = g_fillTypeManager:getFillTypeIndexByName("STRAW")
+ local isReachable = function(markerId) return ADUnloadWait.hasRouteTo(adEnv, vehicle, markerId) end
+ local store = ADSources.findPalletStore(adEnv, vehicle, straw, nil, isReachable, farmId)
+ if store ~= nil then
+ pcall(function()
+ local module = stateModule(vehicle)
+ module:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
+ module:setFirstMarkerByWayPointId(info.markerId)
+ module:setSecondMarkerByWayPointId(store.wayPointId)
+ module:setStartHelper(true)
+ module.usedHelper = helperCp(adEnv)
+ enableParkAtJobFinished(vehicle)
+ module:raiseDirtyFlag()
+ end)
+ assignment.adHandlesEnd = true
+ log(field.id, "ballesamler '%s' kører fulde læs til %s", ADRuns.vehicleLabel(vehicle), tostring(store.name))
+ else
+ log(field.id, "intet ballelager med plads og vej fundet — ballesamleren stopper når den er fuld")
+ end
+ local point = info.points[1] or {x = info.x, z = info.z}
+ assignment.phase = "starting"
+ assignment.since = now()
+ local handle, err = ADCpBridge.startBaleFinder(vehicle, point.x, point.z, function(ok, reason)
+ if ok then
+ assignment.phase = "working"
+ assignment.since = now()
+ log(field.id, "Courseplay startet (ballesamler)")
+ else
+ ADFieldWork.release(assignment)
+ fail(field.id, "ballesamler: " .. tostring(reason))
+ end
+ end)
+ if handle == nil then
+ ADFieldWork.release(assignment)
+ fail(field.id, "ballesamler: " .. tostring(err))
+ return
+ end
+ assignment.handle = handle
+end
+
-- Multi-tool: følgere venter på lederens kursus for trinnet og kopierer det med deres bane.
local function courseOptions(adEnv, field, assignment)
local source = ADFieldModel.courseSource(field, assignment.role)
@@ -459,6 +507,12 @@ end
local function beginWork(adEnv, field, info, assignment)
local vehicle = assignment.vehicle
if assignment.role == "unloader" then return beginUnloader(adEnv, field, info, assignment) end
+ if assignment.role == "baleCollector" then return beginBaleCollector(adEnv, field, info, assignment) end
+ -- høsteren: halmskår efter planen (hakkes = fra; samles op/presses = skår)
+ if assignment.role == "harvester" then
+ local keepStraw = field.plan.straw == "collect" or field.plan.straw == "bale"
+ ADCpBridge.setStrawSwath(vehicle, keepStraw)
+ end
local opts, source = courseOptions(adEnv, field, assignment)
if opts == nil then
-- følger uden lederens kursus endnu
@@ -504,6 +558,9 @@ local function beginWork(adEnv, field, info, assignment)
elseif assignment.role == "rake" then
note(field.id, {rakeVehicleId = assignment.vehicleId})
rememberCourse(field, "rake", vehicle)
+ elseif assignment.role == "harvester" then
+ note(field.id, {harvestVehicleId = assignment.vehicleId})
+ rememberCourse(field, "harvester", vehicle)
end
if assignment.leader and (assignment.groupSize or 1) > 1 then
rememberCourse(field, field.stage, vehicle)
diff --git a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
index 0731ba0..0d83ef9 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
@@ -9,7 +9,7 @@ SmartPickupFrame.FIELD_ROW_KINDS = {
fieldProduct = true, fieldMode = true, fieldRake = true, fieldStore = true, fleetVehicle = true,
fieldRigs = true, fieldUnloaders = true, fieldMulch = true, fieldLime = true, fieldSoil = true,
fieldSow = true, fieldSowFruit = true, fieldRoll = true, fieldFertilize = true, fieldWeed = true,
- fieldTarget = true, fleetMode = true,
+ fieldTarget = true, fleetMode = true, fieldStraw = true,
}
SmartPickupFrame.ROLE_KEYS = {
mower = "spu_field_role_mower", tedder = "spu_field_role_tedder", rake = "spu_field_role_rake",
@@ -17,6 +17,7 @@ SmartPickupFrame.ROLE_KEYS = {
mulcher = "spu_field_role_mulcher", limer = "spu_field_role_limer", plow = "spu_field_role_plow",
cultivator = "spu_field_role_cultivator", seeder = "spu_field_role_seeder", roller = "spu_field_role_roller",
weeder = "spu_field_role_weeder", fertilizer = "spu_field_role_fertilizer",
+ baler = "spu_field_role_baler", baleCollector = "spu_field_role_baleCollector",
}
SmartPickupFrame.SOIL_VALUES = {"auto", "cultivate", "plow", "none"}
-- simple Ja/Nej-valg på planen: rækketype -> plan-felt
@@ -25,7 +26,8 @@ SmartPickupFrame.PLAN_TOGGLES = {
fieldFertilize = "fertilize", fieldWeed = "weed",
}
-- valg med flere muligheder: rækketype -> plan-felt
-SmartPickupFrame.PLAN_CHOICES = {fieldRigs = "rigs", fieldUnloaders = "unloaders", fieldSoil = "soil", fieldSowFruit = "sowFruit"}
+SmartPickupFrame.PLAN_CHOICES = {fieldRigs = "rigs", fieldUnloaders = "unloaders", fieldSoil = "soil", fieldSowFruit = "sowFruit", fieldStraw = "straw"}
+SmartPickupFrame.STRAW_VALUES = {"chop", "collect", "bale"}
-- ---- tekster ----
@@ -200,14 +202,25 @@ function SmartPickupFrame:fillFieldDetail(adEnv, info)
end
local rigTexts, rigValues = numberTexts(1, ADFieldModel.MAX_RIGS)
self:addChoiceRow("fieldRigs", i18n:getText("spu_field_rigs"), rigTexts, rigValues, plan.rigs or 1)
- if info.kind == "mowable" then
+ -- græs-valgene (ensilage/hø, riv) også når målet er en slåafgrøde (fx græs på en kornmark)
+ local targetMowable = false
+ if hasTarget and ADFields ~= nil and g_fruitTypeManager ~= nil then
+ local targetFruit = g_fruitTypeManager:getFruitTypeByName(plan.targetFruit)
+ targetMowable = targetFruit ~= nil and ADFields.getMowable()[targetFruit.index] ~= nil
+ end
+ if info.kind == "mowable" or targetMowable then
local isHay = ADFieldModel.product(plan, "mowable") == "hay"
self:addToggleRow("fieldProduct", i18n:getText("spu_field_plan"), {i18n:getText("spu_field_plan_silage"), i18n:getText("spu_field_plan_hay")}, isHay)
self:addToggleRow("fieldRake", i18n:getText("spu_field_rake"), yesNo, plan.rake == true)
- else
+ end
+ -- klargøring: høst-/bare marker, og græsmarker der skal skifte til en anden mål-afgrøde
+ if info.kind ~= "mowable" or (hasTarget and info.fruitName ~= plan.targetFruit) then
if info.kind == "harvest" then
local unloaderTexts, unloaderValues = numberTexts(1, ADFieldModel.MAX_UNLOADERS)
self:addChoiceRow("fieldUnloaders", i18n:getText("spu_field_unloaders"), unloaderTexts, unloaderValues, plan.unloaders or 1)
+ local strawTexts = {}
+ for _, value in ipairs(SmartPickupFrame.STRAW_VALUES) do table.insert(strawTexts, i18n:getText("spu_field_straw_" .. value)) end
+ self:addChoiceRow("fieldStraw", i18n:getText("spu_field_straw"), strawTexts, SmartPickupFrame.STRAW_VALUES, plan.straw or "chop")
end
self:addTextRow(i18n:getText(info.kind == "harvest" and "spu_field_section_after" or "spu_field_section_prep"))
self:addToggleRow("fieldMulch", i18n:getText("spu_field_mulch"), yesNo, plan.mulch == true)
diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml
index 9d83182..e7f44e5 100644
--- a/FS25_ADSmartPickup/l10n/l10n_da.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_da.xml
@@ -160,6 +160,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml
index e953080..71b0a6e 100644
--- a/FS25_ADSmartPickup/l10n/l10n_en.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_en.xml
@@ -160,6 +160,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/tests/test_adFieldFleet.lua b/tests/test_adFieldFleet.lua
index dddda8a..c5b82be 100644
--- a/tests/test_adFieldFleet.lua
+++ b/tests/test_adFieldFleet.lua
@@ -88,5 +88,11 @@ check("FL11 flueben: uden flueben ude", F.isEnabled("ticked", {}, "v1"), false)
check("FL11 flueben: med flueben", F.isEnabled("ticked", {v1 = true}, "v1"), true)
check("FL11 ukendt tilstand = alle", F.isEnabled(nil, {}, "v1"), true)
+-- FL12: ballepresser og ballesamler (ballevogn/Universal Autoload som Courseplays ballesamler kan bruge)
+check("FL12 ballepresser", F.roleFromSpecs({spec_baler = true}), "baler")
+check("FL12 ballesamler", F.roleFromSpecs({canBaleCollect = true}), "baleCollector")
+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")
+
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
diff --git a/tests/test_adFieldModel.lua b/tests/test_adFieldModel.lua
index 02a5527..f458975 100644
--- a/tests/test_adFieldModel.lua
+++ b/tests/test_adFieldModel.lua
@@ -270,5 +270,24 @@ check("M21 arbejdende mark beholder trin", M.setTarget(started, "CANOLA").stage,
local cleared = M.setTarget(chosen, "")
check("M21 mål fjernet", cleared.plan.targetFruit, ""); check("M21 tilbage til manuel", cleared.plan.mode, "manual")
+-- M22: halm efter høst — hakkes (standard), samles op, eller presses til baller og samles
+local strawBase = {soil = "none", sow = false, lime = false}
+check("M22 hakkes: kun høst", table.concat(M.workStages(strawBase, "harvest", {}), ","), "harvesting")
+check("M22 samles op", table.concat(M.workStages(M.with(M.newField(1), {plan = {soil = "none", lime = false, straw = "collect"}}).plan, "harvest", {}), ","), "harvesting,strawCollecting")
+check("M22 baller", table.concat(M.workStages(M.with(M.newField(1), {plan = {soil = "none", lime = false, straw = "bale"}}).plan, "harvest", {}), ","), "harvesting,baling,baleCollecting")
+local baleChain = M.start(M.with(M.newField(2), {plan = {straw = "bale", soil = "cultivate", lime = false}}), "harvest", {})
+check("M22 høst -> presses", M.advance(baleChain, {}).stage, "baling")
+check("M22 presses -> baller samles", M.advance(M.advance(baleChain, {}), {}).stage, "baleCollecting")
+check("M22 -> kultivér", M.advance(M.advance(M.advance(baleChain, {}), {}), {}).stage, "cultivating")
+check("M22 roller", M.roleFor("baling") .. "," .. M.roleFor("baleCollecting") .. "," .. M.roleFor("strawCollecting"), "baler,baleCollector,collector")
+check("M22 halm-læssevogn følger høsterens kursus", M.courseSource(M.with(M.newField(3), {stage = "strawCollecting"}), "collector"), "harvester")
+check("M22 presser følger høsterens kursus", M.courseSource(M.with(M.newField(3), {stage = "baling"}), "baler"), "harvester")
+check("M22 græs-læssevogn følger stadig slåkurset", M.courseSource(M.with(M.newField(3), {stage = "collecting"}), "collector"), "mower")
+check("M22 halm-læssevogn uden breddekrav", M.requiredWidth(M.with(M.newField(3), {stage = "strawCollecting", mowWidth = 40}), "collector"), nil)
+check("M22 presning vejrfølsom", M.isWeatherSensitive("baling", {}, "harvest"), true)
+check("M22 ballesamling ikke vejrfølsom", M.isWeatherSensitive("baleCollecting", {}, "harvest"), false)
+check("M22 gemmes", M.fromRecord(M.toRecord(M.with(M.newField(4), {plan = {straw = "bale"}}))).plan.straw, "bale")
+check("M22 ukendt -> hakkes", M.fromRecord({id = 5, straw = "x"}).plan.straw, "chop")
+
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)