From a11c8e46df156893170ad6dfa631cc78092319b8 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Thu, 24 Sep 2026 02:11:45 +0200 Subject: [PATCH] =?UTF-8?q?feat(lagre):=20tabel=20med=20kolonner=20for=20b?= =?UTF-8?q?ygningsstatistik=20+=20gitter=20for=20g=C3=A5rdens=20beholdning?= =?UTF-8?q?,=20egen=20tooltip;=20brugermanual=20p=C3=A5=20dansk=20og=20eng?= =?UTF-8?q?elsk=20(v1.15.2.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adStorageStats.lua | 25 + FS25_ADSmartPickup/gui/SmartPickupFrame.lua | 2 +- FS25_ADSmartPickup/gui/SmartPickupFrame.xml | 17 + .../gui/SmartPickupStorageTab.lua | 113 ++-- FS25_ADSmartPickup/gui/guiProfiles.xml | 8 + FS25_ADSmartPickup/l10n/l10n_da.xml | 10 +- FS25_ADSmartPickup/l10n/l10n_en.xml | 10 +- FS25_ADSmartPickup/modDesc.xml | 2 +- README.md | 2 +- docs/BRUGERMANUAL.md | 43 +- docs/USER_MANUAL.md | 525 ++++++++++++++++++ tests/test_adStorageStats.lua | 14 + 12 files changed, 714 insertions(+), 57 deletions(-) create mode 100644 docs/USER_MANUAL.md diff --git a/FS25_ADSmartPickup/adStorageStats.lua b/FS25_ADSmartPickup/adStorageStats.lua index a457a07..c7e6d63 100644 --- a/FS25_ADSmartPickup/adStorageStats.lua +++ b/FS25_ADSmartPickup/adStorageStats.lua @@ -65,3 +65,28 @@ function ADStorageStats.splitTroughConsumption(perDay, levels, weights) if best ~= nil then result[best] = perDay end return result end + +-- Deler en liste i rækker á `size` celler (gitter i fanen Lagre). Tom liste giver én tom række, +-- så bygningen stadig får sin navnerække. +function ADStorageStats.chunk(list, size) + local rows, current = {}, {} + for _, value in ipairs(list or {}) do + table.insert(current, value) + if #current == size then + table.insert(rows, current) + current = {} + end + end + if #current > 0 or #rows == 0 then + table.insert(rows, current) + end + return rows +end + +-- "-14.4k +3.6k": forbrug med minus, produktion med plus pr. døgn; intet = "-". +function ADStorageStats.rateText(consumption, production, format) + local parts = {} + if (consumption or 0) > 0 then table.insert(parts, "-" .. format(consumption)) end + if (production or 0) > 0 then table.insert(parts, "+" .. format(production)) end + return #parts > 0 and table.concat(parts, " ") or "-" +end diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua index 7f1d091..870fa85 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua @@ -56,7 +56,7 @@ end -- Prefabs skal ud af layoutet før de klones, ellers vises de selv som rækker. function SmartPickupFrame:initialize() - for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab", "productPrefab"}) do + for _, name in ipairs({"ingredientPrefab", "vehiclePrefab", "textPrefab", "productPrefab", "statPrefab", "gridPrefab"}) do local prefab = self[name] if prefab ~= nil then prefab:unlinkElement() diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.xml b/FS25_ADSmartPickup/gui/SmartPickupFrame.xml index f433f2a..beac163 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.xml +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.xml @@ -72,6 +72,23 @@ + + + + + + + + + + + + + + + + + diff --git a/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua b/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua index 8a6b018..860facf 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua @@ -3,20 +3,48 @@ -- Kun bygninger med indhold vises; teksterne opdateres hvert sekund uden genopbygning. -- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua). --- "Sugar mill (fabrik): *Sukker 150.0k/180.0k · Roer 356.8k/360.0k" (* = output) -function SmartPickupFrame:describeInventory(entry) - local parts = {} - for _, item in ipairs(entry.entries) do - local amount - if item.count ~= nil then - amount = string.format(self.i18n:getText("spu_storage_pallets"), item.count) - else - amount = string.format("%s/%s", SmartPickupFrame.formatCompact(item.level or 0), SmartPickupFrame.formatCompact(item.capacity or 0)) - end - table.insert(parts, string.format("%s%s %s", item.isOutput and "*" or "", tostring(item.title), amount)) +SmartPickupFrame.GRID_CELLS = 3 + +-- Én celle i gitteret: "*Ensilage 1.7M/20.0M" eller "Kurve 12 paller" (* = fabrikkens output). +function SmartPickupFrame:describeInventoryItem(item) + local amount + if item.count ~= nil then + amount = string.format(self.i18n:getText("spu_storage_pallets"), item.count) + else + amount = string.format("%s/%s", SmartPickupFrame.formatCompact(item.level or 0), SmartPickupFrame.formatCompact(item.capacity or 0)) end + return string.format("%s%s %s", item.isOutput and "*" or "", tostring(item.title), amount) +end + +-- Gitterlinjer for en bygning: {{navn, celle, celle, celle}, {"", celle, ...}, ...}. +function SmartPickupFrame:inventoryLines(entry) + local cells = {} + for _, item in ipairs(entry.entries) do table.insert(cells, self:describeInventoryItem(item)) end local kindText = self.i18n:getText("spu_source_kind_" .. tostring(entry.kind)) - return string.format("%s (%s): %s", tostring(entry.name), kindText, table.concat(parts, " · ")) + local lines = {} + for index, chunk in ipairs(ADStorageStats.chunk(cells, SmartPickupFrame.GRID_CELLS)) do + local line = {index == 1 and string.format("%s (%s)", tostring(entry.name), kindText) or ""} + for cell = 1, SmartPickupFrame.GRID_CELLS do line[cell + 1] = chunk[cell] or "" end + table.insert(lines, line) + end + return lines +end + +-- Kloner en tabelrække og sætter cellerne c1..cN; returnerer celle-elementerne. +function SmartPickupFrame:addCellRow(prefab, texts) + local row = self:addRow(prefab, {kind = "text"}) + local cells = {} + for index = 1, #texts do + cells[index] = row:getDescendantByName("c" .. index) + end + SmartPickupFrame.setCells(cells, texts) + return cells +end + +function SmartPickupFrame.setCells(cells, texts) + for index, cell in ipairs(cells) do + if cell ~= nil then cell:setText(tostring(texts[index] or "")) end + end end function SmartPickupFrame:getInventory(adEnv) @@ -55,27 +83,29 @@ function SmartPickupFrame:computeBuildingStats(adEnv, entry, inventory) return stats end --- "Sukker — 150.0k/180.0k (83 %) · nu 240.300 kr · top 300.400 kr · gården 412.0k = 3.2 døgn · forbrug 130.0k/døgn · prod. 48.0k/døgn" -function SmartPickupFrame:describeStat(stat) +-- Tabelkolonner: vare · i bygningen · værdi nu · ved toppris · gården · rækker til · forbrug/prod. pr. døgn +function SmartPickupFrame:statHeader() local i18n = self.i18n - local fc = SmartPickupFrame.formatCompact - local parts = {string.format("%s — %s/%s%s", self:fillTypeTitle(stat.fillType), fc(stat.level), fc(stat.capacity), stat.percent ~= nil and string.format(" (%d %%)", stat.percent) or "")} - if stat.valueNow ~= nil then - table.insert(parts, string.format(i18n:getText("spu_stat_now"), SmartPickupFrame.formatMoney(stat.valueNow))) - table.insert(parts, string.format(i18n:getText("spu_stat_top"), SmartPickupFrame.formatMoney(stat.valueTop))) - else - table.insert(parts, i18n:getText("spu_stat_noSell")) - end - local days = stat.days ~= nil and string.format(i18n:getText("spu_stat_days"), stat.days) or i18n:getText("spu_stat_noUse") - table.insert(parts, string.format(i18n:getText("spu_stat_farm"), fc(stat.farmTotal), days)) - if stat.consumption > 0 then table.insert(parts, string.format(i18n:getText("spu_stat_use"), fc(stat.consumption))) end - if stat.production > 0 then table.insert(parts, string.format(i18n:getText("spu_stat_prod"), fc(stat.production))) end - return " " .. table.concat(parts, " · ") + return {i18n:getText("spu_col_item"), i18n:getText("spu_col_here"), i18n:getText("spu_col_now"), i18n:getText("spu_col_top"), + i18n:getText("spu_col_farm"), i18n:getText("spu_col_lasts"), i18n:getText("spu_col_rate")} end -function SmartPickupFrame:describeStatTotals(stats) +function SmartPickupFrame:statCells(stat) + local i18n = self.i18n + local fc = SmartPickupFrame.formatCompact + local here = string.format("%s/%s", fc(stat.level), fc(stat.capacity)) + if stat.percent ~= nil then here = here .. string.format(" %d %%", stat.percent) end + local now, top = i18n:getText("spu_stat_noSell"), "" + if stat.valueNow ~= nil then + now, top = SmartPickupFrame.formatMoney(stat.valueNow), SmartPickupFrame.formatMoney(stat.valueTop) + end + local days = stat.days ~= nil and string.format(i18n:getText("spu_stat_days"), stat.days) or "-" + return {self:fillTypeTitle(stat.fillType), here, now, top, fc(stat.farmTotal), days, ADStorageStats.rateText(stat.consumption, stat.production, fc)} +end + +function SmartPickupFrame:statTotalCells(stats) local totals = ADStorageStats.buildingTotals(stats) - return string.format(" " .. self.i18n:getText("spu_stat_total"), SmartPickupFrame.formatMoney(totals.valueNow), SmartPickupFrame.formatMoney(totals.valueTop)) + return {self.i18n:getText("spu_stat_total"), "", SmartPickupFrame.formatMoney(totals.valueNow), SmartPickupFrame.formatMoney(totals.valueTop), "", "", ""} end -- Navne til vælgeren; ens navne (40 Farma-siloer) får løbenummer. @@ -102,11 +132,16 @@ function SmartPickupFrame:fillStorageDetail(adEnv, inventory) row:getDescendantByName("title"):setText(self.i18n:getText("spu_storage_select")) local entry = inventory[self.storageIndex] self.storageDetailPlaceable = entry.placeable + local tooltip = option:getDescendantByName("ignore") + local tooltipText = self.i18n:getText("spu_storage_select_tooltip") + if tooltip ~= nil then tooltip:setText(tooltipText) end + option.toolTipText = tooltipText local stats = self:computeBuildingStats(adEnv, entry, inventory) + self:addCellRow(self.statPrefab, self:statHeader()) for _, stat in ipairs(stats) do - table.insert(self.storageDetailRows, self:addTextRow(self:describeStat(stat))) + table.insert(self.storageDetailRows, self:addCellRow(self.statPrefab, self:statCells(stat))) end - table.insert(self.storageDetailRows, self:addTextRow(self:describeStatTotals(stats))) + table.insert(self.storageDetailRows, self:addCellRow(self.statPrefab, self:statTotalCells(stats))) end function SmartPickupFrame:refreshStorageDetail(adEnv, inventory) @@ -119,9 +154,9 @@ function SmartPickupFrame:refreshStorageDetail(adEnv, inventory) end local stats = self:computeBuildingStats(adEnv, entry, inventory) for index, stat in ipairs(stats) do - self.storageDetailRows[index]:setText(self:describeStat(stat)) + SmartPickupFrame.setCells(self.storageDetailRows[index], self:statCells(stat)) end - self.storageDetailRows[#stats + 1]:setText(self:describeStatTotals(stats)) + SmartPickupFrame.setCells(self.storageDetailRows[#stats + 1], self:statTotalCells(stats)) end function SmartPickupFrame:onClickStorageBuilding(state) @@ -135,7 +170,11 @@ function SmartPickupFrame:fillStorageRows(adEnv) self:fillStorageDetail(adEnv, inventory) self:addTextRow(self.i18n:getText("spu_section_storage")) for _, entry in ipairs(inventory) do - table.insert(self.storageRows, {placeable = entry.placeable, title = self:addTextRow(self:describeInventory(entry))}) + local rows = {} + for _, line in ipairs(self:inventoryLines(entry)) do + table.insert(rows, self:addCellRow(self.gridPrefab, line)) + end + table.insert(self.storageRows, {placeable = entry.placeable, rows = rows}) end if #inventory == 0 then self:addTextRow(self.i18n:getText("spu_storage_empty")) @@ -154,8 +193,10 @@ function SmartPickupFrame:refreshStorageRows(adEnv) pcall(self.refreshStorageDetail, self, adEnv, inventory) for _, row in ipairs(self.storageRows) do local entry = byPlaceable[row.placeable] - if entry ~= nil then - row.title:setText(self:describeInventory(entry)) + local lines = entry ~= nil and self:inventoryLines(entry) or nil + -- flere/færre linjer end ved opbygningen (ny vare) vises ved næste genopbygning + if lines ~= nil and #lines == #row.rows then + for index, line in ipairs(lines) do SmartPickupFrame.setCells(row.rows[index], line) end end end end diff --git a/FS25_ADSmartPickup/gui/guiProfiles.xml b/FS25_ADSmartPickup/gui/guiProfiles.xml index f9571f7..89af44c 100644 --- a/FS25_ADSmartPickup/gui/guiProfiles.xml +++ b/FS25_ADSmartPickup/gui/guiProfiles.xml @@ -11,4 +11,12 @@ + + + + + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index b1a1387..e3b7fa8 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -133,6 +133,14 @@ - + + + + + + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index 4a4c3e8..ddf4c9f 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -133,6 +133,14 @@ - + + + + + + + + + diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 4fe0f7e..51ea5c2 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.15.1.0 + 1.15.2.0 <en>AD Smart Pickup</en> diff --git a/README.md b/README.md index 6a67962..d7eaa16 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # FS25_ADSmartPickup -**Brugermanual for spillere:** [docs/BRUGERMANUAL.md](docs/BRUGERMANUAL.md). Resten af denne fil er udviklernoter pr. version. +**Brugermanual for spillere:** [dansk](docs/BRUGERMANUAL.md) · [English](docs/USER_MANUAL.md). Resten af denne fil er udviklernoter pr. version. AutoDrive-tillæg til Farming Simulator 25 med to funktioner oven på Pickup&Deliver: diff --git a/docs/BRUGERMANUAL.md b/docs/BRUGERMANUAL.md index 3caf608..0865930 100644 --- a/docs/BRUGERMANUAL.md +++ b/docs/BRUGERMANUAL.md @@ -1,6 +1,8 @@ # AD Smart Pickup — brugermanual -Version 1.15.1 · Farming Simulator 25 · tillæg til AutoDrive +Version 1.15.2 · Farming Simulator 25 · tillæg til AutoDrive + +*English version: [USER_MANUAL.md](USER_MANUAL.md)* AD Smart Pickup får dine AutoDrive-traktorer til at tænke selv. Du fortæller, hvad der skal køres hvorhen, og moden vælger tur for tur, hvilken vare der skal hentes, fra hvilken silo, og hvor den @@ -393,26 +395,35 @@ Flows fra ældre versioner indlæses automatisk. Gamle markørvalg oversættes t ## 9. Fanen Lagre: beholdning og statistik -Fanen **Lagre** viser gårdens beholdning pr. bygning og opdateres hvert sekund. Fabrikkens outputs -er markeret med `*`, og palleskure viser antal paller. +Fanen **Lagre** viser gårdens beholdning og statistik pr. bygning. Tallene opdateres hvert sekund. ### Statistik for en bygning -Vælg en bygning i **Vis bygning**. For hver vare får du en linje: +Vælg en bygning i **Vis bygning**. Under vælgeren står en tabel med én række pr. vare og +kolonneoverskrifter øverst: -``` -Mælk 42.000 l — nu 58.800 kr · ved toppris 71.400 kr · gården 180.000 l = 12,5 døgn · prod. 14.400 l/døgn -``` +| Vare | I bygningen | Værdi nu | Ved toppris | Gården | Rækker til | Forbrug/prod. døgn | +|---|---|---|---|---|---|---| +| Ensilage | 483.5k/935.5k 52 % | 173.921 kr | 193.400 kr | 1.7M | 34,0 døgn | -50.0k | +| Mælk | 1.3k/10.3M 0 % | intet salgssted | | 233.1k | - | +14.4k | +| **Bygningen i alt** | | 425.885 kr | 429.039 kr | | | | -| Felt | Betydning | +| Kolonne | Betydning | |---|---| -| **nu** | Varens værdi i bygningen ved den bedste pris lige nu, blandt alle salgssteder på kortet | -| **ved toppris** | Værdien, hvis du sælger ved årets højeste pris | -| **gården … = … døgn** | Gårdens samlede beholdning og hvor mange døgns forbrug den svarer til | -| **forbrug …/døgn** | Hvad gårdens stalde og fabrikker bruger pr. døgn | -| **prod. …/døgn** | Hvad gårdens fabrikker og dyr producerer pr. døgn | +| **I bygningen** | Mængde og kapacitet i den valgte bygning samt fyldningsgrad | +| **Værdi nu** | Værdien ved den bedste pris lige nu, blandt alle salgssteder på kortet | +| **Ved toppris** | Værdien, hvis du sælger ved årets højeste pris | +| **Gården** | Gårdens samlede beholdning af varen i alle bygninger | +| **Rækker til** | Hvor mange døgn gårdens beholdning holder ved det nuværende forbrug | +| **Forbrug/prod. døgn** | Forbrug pr. døgn med minus og produktion pr. døgn med plus | -Nederst står **bygningens samlede værdi** nu og ved toppris. +Nederst står **Bygningen i alt** med bygningens samlede værdi nu og ved toppris. + +### Gårdens beholdning + +Under statistikken står alle bygninger med indhold som et gitter. Bygningens navn står til venstre, +og varerne står med op til tre pr. række. Fabrikkens outputs er markeret med `*`, og palleskure +viser antal paller. **Hvad tælles med** @@ -422,8 +433,8 @@ Nederst står **bygningens samlede værdi** nu og ved toppris. - **Stalde, produktion:** hvert dyrs produktion efter alder gange antal dyr. Det dækker mælk, bøffelmælk, gødning, gylle, æg, uld og gedemælk. Unge dyr under produktionsalderen tæller ikke. -Varer, der ikke bruges på gården, står som *forbruges ikke*. Varer uden salgssted står som *intet -salgssted*. +Varer, der ikke bruges på gården, har en streg under **Rækker til**. Varer uden salgssted står som +*intet salgssted*. --- diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md new file mode 100644 index 0000000..7850cf3 --- /dev/null +++ b/docs/USER_MANUAL.md @@ -0,0 +1,525 @@ +# AD Smart Pickup — user manual + +Version 1.15.2 · Farming Simulator 25 · add-on for AutoDrive + +*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)* + +AD Smart Pickup makes your AutoDrive tractors think for themselves. You say what should go where, +and the mod decides trip by trip which good to fetch, from which silo, and where to unload it. +Everything is set up in one place in the ESC menu, and all tractors in a flow start and stop together. + +The mod does two things: + +- **Supply (into building):** keeps barns and factories filled. Each trip, the tractor fetches the + good the target needs most, from a silo anywhere on the map. +- **Delivery (out of building):** empties factories, silos, barns and pallet sheds. Goods are taken + to storage or sold when the price is close to the yearly top. + +--- + +## Contents + +1. [Requirements and installation](#1-requirements-and-installation) +2. [Key concepts](#2-key-concepts) +3. [Prepare the map: markers and Wait points](#3-prepare-the-map-markers-and-wait-points) +4. [The Smart Pickup menu](#4-the-smart-pickup-menu) +5. [Supply: keep barns and factories filled](#5-supply-keep-barns-and-factories-filled) +6. [Delivery: empty, store and sell](#6-delivery-empty-store-and-sell) +7. [Tractors and trailers](#7-tractors-and-trailers) +8. [Start, stop and saving](#8-start-stop-and-saving) +9. [The Storage tab: inventory and statistics](#9-the-storage-tab-inventory-and-statistics) +10. [Recipes](#10-recipes) +11. [Troubleshooting](#11-troubleshooting) +12. [Glossary](#12-glossary) + +--- + +## 1. Requirements and installation + +**Required** + +- **FS25_AutoDrive.** The mod builds on AutoDrive's Pickup & Deliver and does not change AutoDrive. +- An **AutoDrive road network** connecting silos, barns, factories, selling points and Wait points. + +**Recommended** + +- **Universal Autoload (UAL)** on trailers that should carry pallets. Without UAL the mod can only + move bulk goods such as grain, milk, silage and TMR. + +**Installation** + +1. Put `FS25_ADSmartPickup.zip` in the mods folder: + `Documents/My Games/FarmingSimulator2025/mods/` +2. Enable the mod together with AutoDrive when you choose your savegame. +3. The AutoDrive setting **"Use folders"** must be allowed on. The mod switches it on when a flow + starts. The setting is global, so it applies to the whole game. + +--- + +## 2. Key concepts + +**Flow.** A flow is a named recipe. It has a direction, buildings, goods, rules and a list of +tractors. Older versions called it a *run*, and the word is still used on some buttons. + +**Building.** A barn, factory, silo, object storage (pallet shed) or selling point. You pick +buildings, not markers. The mod finds the AutoDrive marker that belongs to the building's trigger. + +**Marker.** An AutoDrive destination. Every building needs a marker within 40 m of its loading or +unloading trigger. A barn often has several, such as one at the milk tank and one at the slurry tank. + +**Wait point.** A marker in an AutoDrive folder with the word **Wait** in its name. Tractors park +here while they wait for space, for a good price or for demand. Wait points are one shared pool for +all tractors. + +**Need.** How much of a good a barn or factory is missing. For factories it is measured as *time +until empty*: the good that runs out first is fetched first. + +**Reserve.** What the farm's own barns and factories will use during the next day. The reserve is +never sold. + +--- + +## 3. Prepare the map: markers and Wait points + +### Markers at the buildings + +Place an AutoDrive marker where the **tractor's nose** stops when the trailer is in the trigger. +The marker must be at most **40 m** from the building's trigger. + +- **Silos in tight rows:** the mod picks the silo whose trigger is *behind* the tractor's nose in + the driving direction. This stops the neighbouring silo from getting the load. +- **Barns with several loading points:** give each point its own marker, such as *Cow 1 Milk* and + *Cow 1 Slurry*. The mod shows all of them next to the barn. +- **Object storages (pallet sheds):** place the marker so the trailer's autoload zone covers the + storage's output area, just like when you load by hand. +- **Marker names:** names are free. The menu shows the building's own name and the marker's name. + +### Wait points + +1. Create an AutoDrive folder with **Wait** in its name, such as *Cow Wait* or *Wait*. +2. Put as many markers in the folder as you have room for. One per tractor is plenty. +3. Connect every Wait point to the road network **in both directions**, from the buildings and back. + +Wait markers are never used as a source or target, even when they stand close to a building. +Markers in field folders, such as *F91 Wait*, are left alone, because it is the **folder** name +that counts. + +### Tight silos and AutoDrive's trigger distance + +AutoDrive tips at the first unloading trigger within its global **"Max trigger distance"**, which is +25 m by default. If your silos stand closer than that, the neighbouring silo can get the load. Set +the option to **10 m** in that case. The mod writes a warning to the log the first time it sees a +storage target with a neighbouring trigger. + +--- + +## 4. The Smart Pickup menu + +Press **ESC** and open the **Smart Pickup** tab. It has two sub-tabs: + +| Tab | Contents | +|---|---| +| **Flows** | The board with all flows, editing of the selected flow and the Wait pools | +| **Storage** | The farm's inventory per building with value and statistics | + +### The flow board + +At the top there is one line per flow. The selected flow is marked with `>`. + +``` +> Milk sale: Milk · Cow Barn 1 + Cow Barn 2 → Sell · price ≥ 90 %, storage > 80 % · Virtus · Running (1/1) + Barn feed: by need · all silos → Cow Barn 1 · need · JCB 1 + JCB 2 · Stopped (2 tractors) +``` + +Read the line as **name: goods · from → to · rule · tractors · status**. + +- A `!` after a building name means the building has no marker. +- `!!` in the status is a warning, such as a selling point that cannot be reached. +- The status also says why a tractor is waiting, such as `SUGAR 78 % of top (needs 90)`. + +### Buttons + +| Button | What it does | +|---|---| +| **New run** | Creates a new flow. It starts as *Into building* | +| **Rename** | Renames the selected flow | +| **Delete run** | Deletes the flow and stops its running tractors. Cannot be undone | +| **Start run / Stop run** | Starts or stops all tractors in the flow | + +Pick a flow in the **Run** field at the top. The details of the selected flow are shown below the board. + +### Direction + +The **Direction** field switches between two kinds of flow: + +- **Into building** is supply. See [section 5](#5-supply-keep-barns-and-factories-filled). +- **Out of building** is delivery. See [section 6](#6-delivery-empty-store-and-sell). + +### The building list + +Each building is shown on one line with its markers and their distance to the trigger: + +``` +Cow Barn (large) — Cow 1 Milk 6 m · Cow 1 Slurry 4 m +Silo 7 (silo) — NO MARKER within 40 m — place an AD marker at the trigger +``` + +Set **Yes** on the buildings the flow should use. The list is sorted by name, so *Silo 9* comes +before *Silo 10*. A building you have sold shows as *building no longer exists*. + +--- + +## 5. Supply: keep barns and factories filled + +A supply flow keeps one or more buildings filled with everything they use. + +### Setup + +1. **New run**, and leave Direction on **Into building**. +2. Under **To**: set Yes on the barns and factories to fill. +3. Under **Goods**: choose **Auto** or **Select**. + - **Auto** fetches everything the building accepts. For factories, that is the inputs of the + productions that are switched on. + - **Select** lets you tick specific goods. +4. Set **Loops**, **Return from Wait below** and **Wait pool**. See the rules below. +5. Set **Yes** on the tractors that should drive. +6. Press **Start run**. + +**From** always shows *all silos*. The tractor fetches from any silo on the map. + +### How each trip is chosen + +Before each loading, the mod looks at the targets and picks: + +1. **The neediest good.** For factories, that is the good that runs out first. For barns, it is the + lowest fill level compared with capacity. +2. **Full loads first.** Goods where a whole load fits come before goods where only part fits. +3. **The barn that needs it most.** With several targets, the neediest one gets the trip. +4. **Which silo.** Your own silos are used before borrowing from neighbours. The least-filled silo + is emptied first, so leftovers get cleared. Milk, slurry and digestate are taken from the + *fullest* source, so production never stops. + +A factory's own **inputs** are never taken from the factory itself. Straw in a TMR factory stays +where it is. + +**Factories follow their productions.** Switch TMR on in a factory, and the tractor starts fetching +silage, hay, straw and mineral feed by itself. Switch it off, and the tractor stops. Productions +the map switched off because an input ran out still count as wanted, because those are exactly the +ones that need filling. + +### Best feed first + +Feed in a barn trough shares space. If better feed such as **TMR** is on the list, lower feed such +as silage and hay is only fetched when the trough is **at least 75 % empty**. That way silage does +not fill the trough while TMR is available. + +### Several tractors on the same barn + +The mod remembers what is already **on the way** to each barn. With four trailers on the same barn +they spread out. One fetches TMR, another straw, and a third drives to the next barn, instead of all +of them fetching the same thing. + +### Leftover loads and Wait + +When the target cannot take the whole load, this happens: + +1. The tractor unloads what fits. +2. The rest is taken to the **nearest free Wait point**, so the trigger is not blocked for the next + tractors. +3. It drives back when the target can take **the whole rest**, or has dropped below **Return from + Wait below**. + +A rest of less than 100 l that the target cannot take counts as empty. The tractor drives on instead +of blocking the trigger. + +### Load swap + +When a tractor waits with a good the target is full of, while the target needs another good on the +list, it swaps loads: + +1. The rest is unloaded in an ordinary storage silo that accepts the good and has room. +2. The tractor then fetches the good the target is missing. + +This only happens when the target is missing at least a quarter of its capacity of the other good, +and there is a road to the silo and on to the target. With no suitable silo, the tractor stays at Wait. + +### Supply rules + +| Rule | Meaning | Values | +|---|---|---| +| **Loops** | Number of trips per tractor before it parks | 0 = endless, otherwise 1–99 | +| **Return from Wait below** | A waiting tractor returns when the target is below this fill level | 10–90 %, default 50 % | +| **Wait pool** | Which Wait folder the tractors wait in | *All (nearest free)* or a folder | + +Below the rules, **To fetch** lists the goods the flow is fetching now, neediest first. + +--- + +## 6. Delivery: empty, store and sell + +A delivery flow takes goods out of buildings and drives them to storage or to a selling point. + +### Sources + +Under **From** you can pick: + +| Source | What is taken | +|---|---| +| **Factory** | The factory's outputs, both bulk and pallets from the pallet spawner | +| **Silo** | The silo's contents | +| **Barn** | Milk, slurry and manure from the barn's loading points | +| **Object storage** | Pallets and bales. They are taken out one at a time | + +### Policy per good + +Every good in the sources is listed with its amount and current price. Pick a policy: + +| Policy | Meaning | +|---|---| +| **Off** | The good is left alone | +| **Store** | Taken to a silo or pallet shed when there is a full load, or the source is nearly full | +| **Sell** | Sold when the price is close enough to the yearly top | +| **Sell, else store** | Sold at a good price. Otherwise taken to storage when the source is nearly full | + +Bulk goods need a tipper or tank. Pallet goods need a trailer with Universal Autoload. + +### Delivery rules + +| Rule | Meaning | Values | +|---|---|---| +| **Sell from** | Goods are sold when the price is at least this share of the yearly top price | 50–100 %, default 90 % | +| **Drive to storage above** | Store goods are moved when the source is filled above this share | 50–95 %, default 80 % | +| **Collect in one trip** | Collection round, see below | Yes/No | + +### How each trip is chosen + +Before each loading, and every five seconds while the tractor waits, the mod checks: + +1. **Sell.** If a Sell good's best *reachable* selling point pays at least the threshold, the tractor + drives to sell **regardless of amount**. This makes sure you get the money when the price peaks. + The highest price share goes first. +2. **Store.** If a Store good has a full load, or a source is above its threshold, it is taken to the + nearest silo with room for the whole load. The fullest source goes first. +3. **No trip.** The tractor parks at a Wait point and tries again. The status says why, such as + `holding at Wait 3: SUGAR 78 % of top (needs 90), storage 41 %`. + +### The price + +The price share is the good's current price compared with the yearly high. The mod uses the same +seasonal curve as the game's price graph. Goods with a flat curve always count as 100 %. The selling +point with the best price wins, as long as the road network reaches it. + +### Reserve: never sell what the farm needs + +Before selling, the mod subtracts what the farm's own factories and barns will use during the next +day. It only sells when a whole load can be taken without touching the reserve. The status shows, +for example, `SUGARBEET: 196.0k reserved for TMR factory`. The Store policy does not touch the +reserve, because the good stays on the farm. + +### Collection round + +With **Collect in one trip** switched on: + +- **Tipper:** the same good is collected from several sources until the trailer is full. The next + source is only visited when at least 10 % of the space is left. +- **Autoload trailer:** several pallet goods with the same destination go on one trip. + +### Pallets and Universal Autoload + +- The mod switches AutoDrive's **AL unload** on for the trailer when the flow starts. +- **Straps** are tightened after loading and released **25 m before** the unloading trigger. +- A full load of pallets is at least four pallets. +- Pallets for storage go to the nearest pallet shed that accepts the good and has room. +- From an object storage, pallets are taken out **one at a time**, so none are left on the ground. +- If the storage gets stuck, loading ends after 30 seconds with what is on board. If an autoload + source makes no progress for 15 seconds, the tractor leaves with what it has. + +### On-screen warnings + +When a selling point cannot be reached, or no silo has room, the mod shows a red AutoDrive message, +for example: + +``` +'MT635' cannot reach selling point 'Grain Elevator' for SUGAR – check the road network +``` + +The message also goes to AutoDrive's message history, to `log.txt` and to the flow's status. The +tractor waits at Wait and drives by itself once the road is connected. The warning is only repeated +when the tractor, station or good changes. + +--- + +## 7. Tractors and trailers + +At the bottom of the flow, the farm's vehicles are listed with their capacity. Set **Yes** on the +ones that should drive. + +- Capacity is shown in litres for bulk trailers and as *autoload (pallets)* for UAL trailers. +- **Vehicles that cannot be used are locked** and show the reason. The reasons are in the table. +- A vehicle can only be in one flow. If it is moved, it shows *moved from 'other flow'*. +- If you remove a running tractor from a flow, it is stopped. + +| Reason | Meaning | +|---|---| +| no trailer | The tractor has no trailer attached | +| trailer cannot carry the run's goods | The trailer accepts none of the flow's goods | +| pallet goods need Universal Autoload on the trailer | The flow has pallets, but the trailer has no UAL | +| too far from the road network | The tractor is too far from AutoDrive's network | +| no target / no recognised source | The flow has no buildings | +| no good selected | A delivery flow with no good set to Store or Sell | +| no Wait point in the pool | Delivery needs at least one Wait point | + +--- + +## 8. Start, stop and saving + +**Start run** does this on every approved tractor: + +- Puts AutoDrive in **Pickup & Deliver** with the flow's goods and loops. +- Switches **Use folders** on globally and sets rotation to **loading only**. +- Sets the flow's markers. They are picked again for every trip. + +This **overwrites** the tractor's own AutoDrive settings. Tractors outside flows drive as before. + +A flow starts even if one of the chosen buildings has no marker. The status then names the +building. If **none** of the buildings has a marker, start is refused with `'' has no AD +marker at its trigger`. + +**Stop run** stops AutoDrive on the flow's tractors. The tractors stay in the flow, so it can be +started again. + +**Saving.** Flows are saved in `savegameN/ADSmartPickup_runs.xml` when the game is saved, and on +every Start and Stop. After loading, tractors that AutoDrive saved as running carry on. Tractors that +were waiting at a Wait point resume waiting by themselves. If a flow is stopped after loading, press +**Start run** again. + +Flows from older versions load automatically. Old marker choices are translated into buildings. + +--- + +## 9. The Storage tab: inventory and statistics + +The **Storage** tab shows the farm's inventory and statistics per building. Figures update every +second. + +### Statistics for a building + +Pick a building in **Show building**. Below the selector there is a table with column headings and +one row per good: + +| Item | In building | Value now | At top price | Farm | Lasts | Use/prod. per day | +|---|---|---|---|---|---|---| +| Silage | 483.5k/935.5k 52 % | € 173,921 | € 193,400 | 1.7M | 34.0 days | -50.0k | +| Milk | 1.3k/10.3M 0 % | no selling point | | 233.1k | - | +14.4k | +| **Building total** | | € 425,885 | € 429,039 | | | | + +| Column | Meaning | +|---|---| +| **In building** | Amount and capacity in the selected building, plus fill level | +| **Value now** | The value at the best price right now, across all selling points on the map | +| **At top price** | The value if you sell at the year's highest price | +| **Farm** | The farm's total amount of the good across all buildings | +| **Lasts** | How many days the farm's stock lasts at the current use | +| **Use/prod. per day** | Daily use with a minus sign and daily production with a plus sign | + +At the bottom, **Building total** shows the building's total value now and at top price. + +### Farm inventory + +Below the statistics, all buildings with contents are shown as a grid. The building name is on the +left, with up to three goods per row. Factory outputs are marked with `*`, and pallet sheds show the +number of pallets. + +**What is counted** + +- **Factories:** use and production of the wanted productions. +- **Barns, use:** feed, straw and water. Water is not counted when the barn has automatic water. + Feed use is split across the feed types in the trough. +- **Barns, production:** each animal's output by age times the number of animals. This covers milk, + buffalo milk, manure, slurry, eggs, wool and goat milk. Young animals below production age do not count. + +Goods the farm does not use show a dash under **Lasts**. Goods with no selling point show *no +selling point*. + +--- + +## 10. Recipes + +### Keep a cow barn fed + +1. Wait folder *Cow Wait* with 2–4 markers close to the barn. +2. New flow, **Into building**, To = the barn, Goods = **Auto**. +3. Wait pool = *Cow Wait*, Loops = 0. +4. Two tractors with tippers, Yes, Start. + +The tractors share feed, straw and water by need and pick TMR over silage. + +### A TMR factory feeding several barns + +1. Flow A: **Into building**, To = the TMR factory, Goods = Auto. The tractor fetches what the + switched-on productions are missing. +2. Flow B: **Out of building**, From = the TMR factory, TMR = **Store**. TMR goes to a silo. +3. Flow C: **Into building**, To = all cow barns. The neediest barn gets the trip. + +### Sell milk at a high price + +1. New flow, **Out of building**, From = barn(s) or milk tanks. +2. Milk = **Sell, else store**, Sell from = 90 %, Drive to storage above = 80 %. +3. One tanker, Yes, Start. + +The tanker waits at Wait until the price is high. If the tank fills up first, the milk goes to storage. + +### Empty a pallet shed for sale + +1. New flow, **Out of building**, From = the object storage. +2. Goods = **Sell**, **Collect in one trip** = Yes. +3. One UAL truck, Yes, Start. + +--- + +## 11. Troubleshooting + +The mod writes every decision to `log.txt` with the prefix `ADSmartPickup:`. The log is in +`Documents/My Games/FarmingSimulator2025/`. + +| Problem | Fix | +|---|---| +| The building shows *NO MARKER* | Place an AutoDrive marker within 40 m of the trigger. Wait markers do not count | +| A barn's slurry or milk point is missing | The points are often at opposite ends of the barn. Give each point its own marker | +| The tractor is locked in the list | Read the reason next to it in [section 7](#7-tractors-and-trailers) | +| The tractor just sits at Wait | Read the status. It is usually waiting for price, space or demand | +| The neighbouring silo gets the load | Set AutoDrive's *Max trigger distance* to 10 m | +| *cannot reach selling point* | Connect the selling point to the network in both directions | +| The pallet shed does not hand out pallets | Move the marker so the trailer's autoload zone covers the output | +| Pallets fall off | Check that the trailer has UAL. Straps are tightened after loading | +| The tractor does not fill at the silo | After 30 seconds without filling, the mod lets go and AutoDrive drives on | +| The flow does not run after loading | Press **Start run** again | +| All tractors fetch the same good | Check that they are in the same flow. Only tractors in one flow share the work | + +The log lines are written in Danish. Typical examples: + +``` +ADSmartPickup: 'JCB 1' bytter læs: 2400 l STRAW tilbage i Silo 3, målet mangler FORAGE +ADSmartPickup: 'Virtus' holder ved Wait 3: MILK 82 % af top (kræver 90), lager 41 % +ADSmartPickup: 'Fastrac' genoptager ventning ved Cow Wait efter genindlæsning +``` + +These mean: a load swap, a tractor holding for a better price, and a tractor resuming its wait +after the savegame was loaded. + +--- + +## 12. Glossary + +| Term | Meaning | +|---|---| +| **AD** | AutoDrive | +| **Flow / run** | A named recipe with buildings, goods, rules and tractors | +| **Marker** | AutoDrive destination at a building | +| **Trigger** | The area at a building where goods are loaded or unloaded | +| **Wait point** | A marker in a folder with *Wait* in its name, where tractors wait | +| **UAL** | Universal Autoload, a mod that loads pallets automatically | +| **Object storage** | Pallet and bale storage, such as the one in the HVF pack | +| **Top price** | The good's highest price during the year according to the game's seasonal curve | +| **Reserve** | What the farm's own barns and factories use during the next day | +| **Time until empty** | How long a factory's stock of a good lasts at the current use | diff --git a/tests/test_adStorageStats.lua b/tests/test_adStorageStats.lua index d0b48e8..45d6810 100644 --- a/tests/test_adStorageStats.lua +++ b/tests/test_adStorageStats.lua @@ -42,5 +42,19 @@ local empty = S.splitTroughConsumption(2400, {}, {[116] = 1.0, [7] = 0.8}) check("ST5 tomt trug -> bedste foder", empty[116], 2400) check("ST5 tomt trug -> intet på ensilage", empty[7], nil) +-- ST6: gitter — varer i rækker á n celler, tom liste giver én tom række +local rows = S.chunk({"a", "b", "c", "d"}, 3) +check("ST6 to rækker", #rows, 2) +check("ST6 første række 3", #rows[1], 3) +check("ST6 anden række 1", rows[2][1], "d") +check("ST6 tom liste -> én tom række", #S.chunk({}, 3), 1) + +-- ST7: rate-kolonne: forbrug med minus, produktion med plus, intet -> streg +local fmt = function(v) return string.format("%.1fk", v / 1000) end +check("ST7 begge", S.rateText(14400, 3600, fmt), "-14.4k +3.6k") +check("ST7 kun prod", S.rateText(0, 3600, fmt), "+3.6k") +check("ST7 kun forbrug", S.rateText(2000, 0, fmt), "-2.0k") +check("ST7 intet", S.rateText(0, 0, fmt), "-") + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)