diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua
index 6e806a5..5136390 100644
--- a/FS25_ADSmartPickup/adFieldJobs.lua
+++ b/FS25_ADSmartPickup/adFieldJobs.lua
@@ -213,9 +213,48 @@ end
-- ---- silo ----
+-- Gårdens gæringssiloer for markens skår, nærmest marken først (produktioner skår -> SILAGE).
+function ADFieldJobs.getFermenters(adEnv, farmId, windrowFillType, x, z)
+ local silage = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeIndexByName("SILAGE") or nil
+ local result = {}
+ for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
+ local spec = building.kind == "factory" and building.placeable ~= nil and building.placeable.spec_productionPoint or nil
+ local point = spec ~= nil and spec.productionPoint or nil
+ if point ~= nil and ADFieldModel.isFermenter(point.productions, windrowFillType, silage) then
+ local bx, _, bz = getWorldTranslation(building.placeable.rootNode)
+ table.insert(result, {building = building, distance = (bx - (x or bx)) ^ 2 + (bz - (z or bz)) ^ 2})
+ end
+ end
+ table.sort(result, function(a, b) return a.distance < b.distance end)
+ local buildings = {}
+ for _, entry in ipairs(result) do table.insert(buildings, entry.building) end
+ return buildings
+end
+
+local function unloadMarkerIds(buildings)
+ local markerIds = {}
+ for _, building in ipairs(buildings) do
+ for _, marker in ipairs(ADBuildings.getMarkers(building, "unload") or {}) do
+ table.insert(markerIds, marker.id)
+ end
+ end
+ return markerIds
+end
+
+-- Tvunget lager -> de valgte; ensilage -> gæringssiloer (produktion skår -> ensilage) før almindelige
+-- siloer (in-game 25/9: græs endte i en Farma-silo); hø -> kun siloer, aldrig gæringssiloer (flere af dem
+-- laver også hø om til ensilage).
local function chooseStore(adEnv, field, info, vehicle)
local fillType = field.plan.product == "hay" and info.hay or info.windrow
local isReachable = function(markerId) return ADUnloadWait.hasRouteTo(adEnv, vehicle, markerId) end
+ if #field.plan.storeBuildingIds == 0 and field.plan.product ~= "hay" then
+ local fermenters = ADFieldJobs.getFermenters(adEnv, vehicle:getOwnerFarmId(), fillType, info.x, info.z)
+ if #fermenters > 0 then
+ local store, why = ADLoadSwap.findForcedDumpMarker(adEnv, vehicle, fillType, 1, unloadMarkerIds(fermenters), isReachable)
+ if store ~= nil then return store end
+ log(field.id, "ingen gæringssilo kan tage græsset (%s) — bruger almindelig silo", tostring(why))
+ end
+ end
if #field.plan.storeBuildingIds > 0 then
local markerIds = {}
for _, buildingId in ipairs(field.plan.storeBuildingIds) do
diff --git a/FS25_ADSmartPickup/adFieldModel.lua b/FS25_ADSmartPickup/adFieldModel.lua
index 74c4462..0a0c274 100644
--- a/FS25_ADSmartPickup/adFieldModel.lua
+++ b/FS25_ADSmartPickup/adFieldModel.lua
@@ -178,6 +178,23 @@ function ADFieldModel.collectorOffset(ownOffset, windrowOffset)
return math.max(-ADFieldModel.CP_MAX_TOOL_OFFSET, math.min(ADFieldModel.CP_MAX_TOOL_OFFSET, offset))
end
+-- Gæringssilo: en produktion der har markens skår som input og ensilage som output (FermentingSiloPack,
+-- GrassSilageSilo m.fl. er produktioner, ikke siloer). productions = {{inputs = {{type}}, outputs = {{type}}}}.
+function ADFieldModel.isFermenter(productions, windrowFillType, silageFillType)
+ if silageFillType == nil then return false end
+ for _, production in ipairs(productions or {}) do
+ local takesWindrow, makesSilage = false, false
+ for _, input in ipairs(production.inputs or {}) do
+ if input.type == windrowFillType then takesWindrow = true end
+ end
+ for _, output in ipairs(production.outputs or {}) do
+ if output.type == silageFillType then makesSilage = true end
+ end
+ if takesWindrow and makesSilage then return true end
+ end
+ return false
+end
+
function ADFieldModel.toRecord(field)
return {
id = field.id, stage = field.stage, mode = field.plan.mode, product = field.plan.product,
diff --git a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
index d036d4a..af3adb0 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupFieldTab.lua
@@ -129,11 +129,17 @@ function SmartPickupFrame:fillFieldStoreRows(adEnv, info, field)
local fieldSilos = {}
for _, building in ipairs(ADFields.fieldSilos(adEnv, farmId, info)) do fieldSilos[building.id] = building end
local count = 0
+ local fermenterIds = {}
+ if field.plan.product ~= "hay" then
+ for _, building in ipairs(ADFieldJobs.getFermenters(adEnv, farmId, fillType, info.x, info.z)) do fermenterIds[building.id] = true end
+ end
for _, building in ipairs(ADBuildings.list(adEnv, farmId)) do
- if building.kind == "silo" and (chosen[building.id] or ADRunsController.canStoreAt(adEnv, building, {{fillType = fillType, isPallet = false}})) then
+ local isSilo = building.kind == "silo" and ADRunsController.canStoreAt(adEnv, building, {{fillType = fillType, isPallet = false}})
+ if chosen[building.id] or isSilo or fermenterIds[building.id] then
count = count + 1
local row = self:addRow(self.ingredientPrefab, {kind = "fieldStore", buildingId = building.id})
local suffix = fieldSilos[building.id] ~= nil and (" (" .. i18n:getText("spu_field_fieldSilo") .. ")") or ""
+ if fermenterIds[building.id] then suffix = suffix .. " (" .. i18n:getText("spu_field_fermenter") .. ")" end
row:getDescendantByName("title"):setText(" " .. tostring(building.name) .. suffix)
row:getDescendantByName("option"):setState(chosen[building.id] and SmartPickupFrame.STATE_ON or SmartPickupFrame.STATE_OFF, false)
end
@@ -141,7 +147,7 @@ function SmartPickupFrame:fillFieldStoreRows(adEnv, info, field)
if count == 0 then
self:addTextRow(" " .. i18n:getText("spu_field_noStore"))
elseif #field.plan.storeBuildingIds == 0 then
- self:addTextRow(" " .. i18n:getText("spu_field_storeAuto"))
+ self:addTextRow(" " .. i18n:getText(field.plan.product == "hay" and "spu_field_storeAuto" or "spu_field_storeAutoSilage"))
end
-- marksiloer skal tømmes hjem af et udkørselsflow
for id, building in pairs(fieldSilos) do
diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml
index 05a6261..95d5d83 100644
--- a/FS25_ADSmartPickup/l10n/l10n_da.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_da.xml
@@ -91,6 +91,8 @@
+
+
diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml
index 4b2b4d5..bc0b336 100644
--- a/FS25_ADSmartPickup/l10n/l10n_en.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_en.xml
@@ -91,6 +91,8 @@
+
+
diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml
index 671ce6f..a74ef37 100644
--- a/FS25_ADSmartPickup/modDesc.xml
+++ b/FS25_ADSmartPickup/modDesc.xml
@@ -1,7 +1,7 @@
masterdraco
- 1.24.0.0
+ 1.25.0.0
AD Smart Pickup
diff --git a/docs/BRUGERMANUAL.md b/docs/BRUGERMANUAL.md
index 6f175f0..957dfca 100644
--- a/docs/BRUGERMANUAL.md
+++ b/docs/BRUGERMANUAL.md
@@ -1,6 +1,6 @@
# AD Smart Pickup — brugermanual
-Version 1.24.0 · Farming Simulator 25 · tillæg til AutoDrive
+Version 1.25.0 · Farming Simulator 25 · tillæg til AutoDrive
*English version: [USER_MANUAL.md](USER_MANUAL.md)*
@@ -570,7 +570,7 @@ Klik på en mark i tabellen for at vælge den. Under tabellen står:
| **Plan** | Ensilage eller Hø |
| **Kørsel** | *Manuel*: marken venter på **Start nu**. *Auto*: marken starter selv, når den er klar |
| **Riv** | Riv skårene sammen før opsamling (hø og ensilage). Standard fra. Slå til ved smalle læssevogne |
-| **Tving lager** | Vælg siloer. Uden valg bruges nærmeste silo med plads, der tager varen |
+| **Tving lager** | Vælg lagre. Uden valg: **ensilage** går til nærmeste **gæringssilo** (en produktion der laver skår om til ensilage, fx FermentingSiloPack) med plads, ellers nærmeste silo. **Hø** går kun i almindelige siloer, aldrig i en gæringssilo |
En mark er **klar**, når mindst 90 % af den står i høstvinduet. Status viser andelen, fx `Venter 72 %`.
**Start nu** virker fra 50 %. **Stop mark** stopper maskinerne på marken, og marken står klar igen.
diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md
index 19582d5..d764f57 100644
--- a/docs/USER_MANUAL.md
+++ b/docs/USER_MANUAL.md
@@ -1,6 +1,6 @@
# AD Smart Pickup — user manual
-Version 1.24.0 · Farming Simulator 25 · add-on for AutoDrive
+Version 1.25.0 · Farming Simulator 25 · add-on for AutoDrive
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
@@ -577,7 +577,7 @@ Click a field in the table to select it. Below the table:
| **Plan** | Silage or Hay |
| **Mode** | *Manual*: the field waits for **Start now**. *Auto*: the field starts by itself when ready |
| **Rake** | Rake the swaths before collecting (hay and silage). Off by default. Turn on for narrow loading wagons |
-| **Force storage** | Pick silos. Without a choice the nearest silo with room that takes the goods is used |
+| **Force storage** | Pick stores. Without a choice: **silage** goes to the nearest **fermenting silo** (a production that turns swaths into silage, such as FermentingSiloPack) with room, else the nearest silo. **Hay** only goes to ordinary silos, never a fermenting silo |
A field is **ready** when at least 90 % of it is in the harvest window. The status shows the share,
such as `Waiting 72 %`. **Start now** works from 50 %. **Stop field** stops the machines on the field,
diff --git a/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md b/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md
index d8a64e7..d7bcec7 100644
--- a/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md
+++ b/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md
@@ -52,8 +52,10 @@ Ikke med i etape 1: mejetærskerafgrøder (etape 2), roer (etape 3), jordbehandl
`dropWindrowWorkAreaIndex`, ellers riverens XML) i traktorens ramme minus rivens CP-forskydning =
skårets sideforskydning (gemt pr. mark). Læssevognen på rivens kursus får `toolOffsetX = egen −
skårets` (±10 m) og får sin egen værdi tilbage bagefter.
-7. **Silovalg** genbruger Smart Pickups motor (`ADLoadSwap`): nærmeste silo med plads, der tager varen,
- med vej fra `F`-markøren. *Tving lager* pr. mark (som "Lager til" i flows). Fabrikker er ikke mål.
+7. **Silovalg** genbruger Smart Pickups motor (`ADLoadSwap`). *Tving lager* pr. mark (som "Lager til" i
+ flows). **Revideret 25/9:** ensilage går først til egne **gæringssiloer** (produktion skår -> SILAGE,
+ fx FermentingSiloPack/GrassSilageSilo), nærmest marken, ellers nærmeste silo med plads; hø kun til
+ almindelige siloer (gæringssiloer laver også hø om til ensilage).
8. **Marksilo.** En silo, hvis afstand til markens polygon er ≤ 150 m, er et mellemlager. Den må gerne
vælges (korte ture). Oversigten viser "!" ved marken, hvis intet Smart Pickup-udkørselsflow har
siloen som kilde — hjemkørslen klares af de eksisterende flows, ikke af markdelen.
diff --git a/tests/test_adFieldModel.lua b/tests/test_adFieldModel.lua
index fea948d..a762b01 100644
--- a/tests/test_adFieldModel.lua
+++ b/tests/test_adFieldModel.lua
@@ -133,5 +133,14 @@ check("M12 skår gemt", M.fromRecord(M.toRecord(side)).windrowOffset, 3.2)
check("M12 intet skår gemt", M.fromRecord(M.toRecord(M.newField(12))).windrowOffset, nil)
check("M12 start glemmer skår", M.start(M.with(M.onReadiness(M.newField(13), 1), {windrowOffset = 1})).windrowOffset, nil)
+-- M13: gæringssilo = produktion der laver markens skår om til ensilage (FermentingSiloPack m.fl.)
+local GRASS_W, DRY_W, SILAGE, CHAFF, BREAD = 10, 11, 20, 30, 40
+local fermenter = {{inputs = {{type = CHAFF}}, outputs = {{type = SILAGE}}}, {inputs = {{type = GRASS_W}}, outputs = {{type = SILAGE}}}}
+check("M13 græs -> ensilage", M.isFermenter(fermenter, GRASS_W, SILAGE), true)
+check("M13 hø ikke i denne", M.isFermenter(fermenter, DRY_W, SILAGE), false)
+check("M13 bageri ikke", M.isFermenter({{inputs = {{type = GRASS_W}}, outputs = {{type = BREAD}}}}, GRASS_W, SILAGE), false)
+check("M13 ingen produktioner", M.isFermenter(nil, GRASS_W, SILAGE), false)
+check("M13 ukendt ensilage-type", M.isFermenter(fermenter, GRASS_W, nil), false)
+
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)