From 69a98984bbfa7eca479983eaae1ad9410b595166 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Wed, 23 Sep 2026 01:49:19 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20foderprioritering=20=E2=80=94=20trug=20?= =?UTF-8?q?som=20=C3=A9n=20trang,=20spillets=20foderv=C3=A6gt=20(TMR=201,0?= =?UTF-8?q?=20>=20h=C3=B8/ensilage=200,8=20>=20gr=C3=A6s=200,4)=20afg?= =?UTF-8?q?=C3=B8r=20r=C3=A6kkef=C3=B8lgen=20i=20truget;=20fanen=20viser?= =?UTF-8?q?=20samme=20rangering=20(v1.10.3.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adRunsController.lua | 8 +++++ FS25_ADSmartPickup/adSmartPickup.lua | 41 +++++++++++++++++++++++-- FS25_ADSmartPickup/adSupplyPlanner.lua | 13 ++++++-- FS25_ADSmartPickup/modDesc.xml | 2 +- tests/test_adSmartPickup.lua | 36 ++++++++++++++++++++++ tests/test_adSupplyPlanner.lua | 15 +++++++++ 6 files changed, 108 insertions(+), 7 deletions(-) diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index 2926ba6..92c4a87 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -450,6 +450,14 @@ function ADRunsController.getTargetLevels(adEnv, run) table.insert(fillTypes, fillType.index) end end + -- samme rangering som valget bruger (trug som én trang, fodervægt som prioritet); fulde sidst + if ADSmartPickup.getTargetNeedsRanked ~= nil then + local levels = {} + for _, need in ipairs(ADSmartPickup.getTargetNeedsRanked(target, fillTypes, farmId)) do + table.insert(levels, {fillType = need.fillType, level = need.level, capacity = need.capacity, free = need.freeSpace}) + end + return levels + end local levels = {} for _, fillType in ipairs(fillTypes) do local level, capacity = ADSmartPickup.getStationLevelAndCapacity(target, fillType, farmId) diff --git a/FS25_ADSmartPickup/adSmartPickup.lua b/FS25_ADSmartPickup/adSmartPickup.lua index 10a69bd..d8ace0a 100644 --- a/FS25_ADSmartPickup/adSmartPickup.lua +++ b/FS25_ADSmartPickup/adSmartPickup.lua @@ -377,23 +377,43 @@ local function getHusbandryFoodFillTypes(husbandry) if foodSpec == nil or foodSystem == nil or foodSpec.animalTypeIndex == nil then return nil end + -- værdien er foderets vægt (spillets productionWeight/eatWeight: TMR 1,0, hø/ensilage + -- 0,8, græs 0,4) — bruges som prioritet inden for truget; ukendt = 1 local fillTypes = {} local animalFood = foodSystem.getAnimalFood ~= nil and foodSystem:getAnimalFood(foodSpec.animalTypeIndex) or nil for _, group in pairs(animalFood ~= nil and animalFood.groups or {}) do + local weight = tonumber(group.productionWeight) or tonumber(group.eatWeight) or 1 for _, fillType in pairs(group.fillTypes or {}) do - fillTypes[fillType] = true + fillTypes[fillType] = math.max(fillTypes[fillType] or 0, weight) end end local mixtures = foodSystem.getMixturesByAnimalTypeIndex ~= nil and foodSystem:getMixturesByAnimalTypeIndex(foodSpec.animalTypeIndex) or nil for _, mixture in pairs(mixtures or {}) do local fillType = type(mixture) == "table" and (mixture.fillType or mixture.fillTypeIndex) or mixture if type(fillType) == "number" then - fillTypes[fillType] = true + fillTypes[fillType] = math.max(fillTypes[fillType] or 0, 1) end end return fillTypes end +-- Trugets fælles trang (fri/kapacitet) og foderets vægt for en vare; nil når varen ikke er foder. +local function getFoodPriority(station, fillType) + local husbandry = getHusbandry(station) + local foodFillTypes = husbandry ~= nil and getHusbandryFoodFillTypes(husbandry) or nil + if foodFillTypes == nil or foodFillTypes[fillType] == nil then + return nil + end + local foodSpec = husbandry.spec_husbandryFood + local total = 0 + for _, level in pairs(foodSpec.fillLevels or {}) do + total = total + (level or 0) + end + local capacity = foodSpec.capacity or 0 + local troughScore = capacity > 0 and math.max(0, capacity - total) / capacity or 0 + return troughScore, foodFillTypes[fillType] +end + -- En stald kan MODTAGE foder, halm og vand — ikke sine egne outputs (mælk, gødning, gylle), -- selv om placeable'n "understøtter" dem som lager. Uden foder-spec (ældre/mod-stalde) -- spørges staldens egen understøttelse. @@ -463,12 +483,27 @@ local function getTargetNeeds(station, ingredients, farmId, demand) for _, fillType in ipairs(ingredients) do local level, capacity = getTargetLevelAndCapacity(station, fillType, farmId) if level ~= nil then - table.insert(needs, {fillType = fillType, level = level, capacity = capacity, litersPerMinute = demand[fillType]}) + local troughScore, priority = getFoodPriority(station, fillType) + table.insert(needs, {fillType = fillType, level = level, capacity = capacity, litersPerMinute = demand[fillType], scoreOverride = troughScore, priority = priority}) end end return needs end +-- Til fanen: samme rangering som valget bruger (uden vogn-størrelse). Fulde varer sidst. +function ADSmartPickup.getTargetNeedsRanked(station, fillTypes, farmId) + local needs = getTargetNeeds(station, fillTypes, farmId, {}) + local ranked = ADSupplyPlanner.rankNeeds(needs) + local seen = {} + for _, need in ipairs(ranked) do seen[need.fillType] = true end + for _, need in ipairs(needs) do + if not seen[need.fillType] then + table.insert(ranked, {fillType = need.fillType, level = need.level, capacity = need.capacity, freeSpace = need.capacity - need.level, fits = false, score = 0, priority = need.priority or 0}) + end + end + return ranked +end + -- Vognens samlede kapacitet (liter); nil hvis AutoDrive ikke kan opgøre den. local function getLoadCapacity(adEnv, driver) if adEnv.AutoDrive.getAllUnits == nil or adEnv.AutoDrive.getAllFillLevels == nil then diff --git a/FS25_ADSmartPickup/adSupplyPlanner.lua b/FS25_ADSmartPickup/adSupplyPlanner.lua index 784e5b4..a14bf78 100644 --- a/FS25_ADSmartPickup/adSupplyPlanner.lua +++ b/FS25_ADSmartPickup/adSupplyPlanner.lua @@ -168,10 +168,13 @@ function ADSupplyPlanner.canUnloadRest(freeSpace, rest, capacity, freeFraction) return freeSpace >= needed end --- needs: {{fillType, level, capacity[, litersPerMinute]}, ...}. Returnerer NY liste, --- mest trængende først; ingredienser uden plads i målet er sorteret fra. +-- needs: {{fillType, level, capacity[, litersPerMinute][, scoreOverride][, priority]}, ...}. +-- Returnerer NY liste, mest trængende først; ingredienser uden plads i målet er sorteret fra. -- loadCapacity (valgfri): vognens kapacitet — varer hvor et FULDT læs kan komme af -- går forud (så vognen ikke ender med rest-læs); passer intet, gælder ren trang. +-- scoreOverride: varer der deler ét lager (staldens fodertrug) får trugets fælles score, +-- så truget konkurrerer som ÉN trang; priority (fx spillets fodervægt: TMR 1,0 > hø 0,8 > +-- græs 0,4) afgør så rækkefølgen inden for lageret. function ADSupplyPlanner.rankNeeds(needs, loadCapacity) local ranked = {} for _, need in ipairs(needs) do @@ -186,7 +189,8 @@ function ADSupplyPlanner.rankNeeds(needs, loadCapacity) litersPerMinute = need.litersPerMinute, minutesToEmpty = minutesToEmpty, fits = ADSupplyPlanner.fullLoadFits(freeSpace, loadCapacity), - score = ADSupplyPlanner.scoreNeed(need.level, need.capacity, freeSpace, minutesToEmpty), + score = need.scoreOverride or ADSupplyPlanner.scoreNeed(need.level, need.capacity, freeSpace, minutesToEmpty), + priority = need.priority or 0, }) end end @@ -197,6 +201,9 @@ function ADSupplyPlanner.rankNeeds(needs, loadCapacity) if a.score ~= b.score then return a.score > b.score end + if a.priority ~= b.priority then + return a.priority > b.priority + end return a.fillType < b.fillType end) return ranked diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 6bec9fb..aab726e 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.10.2.0 + 1.10.3.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adSmartPickup.lua b/tests/test_adSmartPickup.lua index 0473423..defa67c 100644 --- a/tests/test_adSmartPickup.lua +++ b/tests/test_adSmartPickup.lua @@ -731,6 +731,42 @@ UnloadTask.update(ts, 16) check("W12 omdirigeret trods isActiveAtTrigger", ts.destinationID, 40) check("W12 AD's aflæsning stoppet før kørsel", stopped, true) +-- E5: fodertrug = én trang; inden for truget vælges efter spillets fodervægt (TMR 1,0 > hø 0,8 > græs 0,4); +-- et tommere halmlager vinder over truget +buildCowWorld() +addMarker(23, "Høsilo", "Lader", 700, 0); table.insert(stations, makeStation(123, 700, 15, {[HAY] = 80000}, false, {[HAY] = 80000})) +addMarker(24, "Græssilo", "Lader", 800, 0); table.insert(stations, makeStation(124, 800, 15, {[GRASS] = 80000}, false, {[GRASS] = 80000})) +local e5Food = {[TMR] = 0, [HAY] = 0, [GRASS] = 0} +local e5Straw = {level = 20000} +local e5Barn = { + getName = function() return "Prioritets-stald" end, + spec_husbandryFood = {fillLevels = e5Food, capacity = 30000, animalTypeIndex = 7}, + spec_husbandryStraw = {}, + getHusbandryIsFillTypeSupported = function(_, fillType) return fillType == STRAW end, + getHusbandryFillLevel = function(_, fillType) if fillType == STRAW then return e5Straw.level end return 0 end, + getHusbandryCapacity = function(_, fillType) if fillType == STRAW then return 100000 end return 0 end, +} +g_currentMission.animalFoodSystem = { + getAnimalFood = function(_, animalTypeIndex) if animalTypeIndex == 7 then return {groups = { + {fillTypes = {TMR}, productionWeight = 1.0}, {fillTypes = {HAY}, productionWeight = 0.8}, {fillTypes = {GRASS}, productionWeight = 0.4}}} end end, + getMixturesByAnimalTypeIndex = function() return {} end, +} +unloadStations[1].targetStorages = nil +unloadStations[1].owningPlaceable = e5Barn +local e5Driver = makeSupplyDriver(markerByIndex(21), markerByIndex(30), {TMR, HAY, GRASS, STRAW}) +e5Driver.carriable = {TMR, HAY, GRASS, STRAW} +e5Driver.trailers = {level = 0, capacity = 8000} +-- trug tomt (100 % trang) vs. halm 80 % tomt -> truget først, og TMR før hø/græs +check("E5 tomt trug: TMR først", manager:getNextPickup(e5Driver, false), 20) +-- ingen TMR-kilde -> hø (0,8) før græs (0,4) +stations[1].getFillLevel = function() return 0 end +stations[1].owningPlaceable = nil +check("E5 uden TMR-kilde: hø før græs", manager:getNextPickup(e5Driver, false), 23) +-- trug 70 % fuldt (30 % trang) vs. halm 80 % tomt -> halm +e5Food[HAY] = 21000 +check("E5 halmlager tommere end truget -> halm", manager:getNextPickup(e5Driver, false), 21) +g_currentMission.animalFoodSystem = nil + -- ==== I: run-integration — ingredienser fra run i stedet for multi-valg; Wait-pulje pr. run ==== -- I1: traktor i et run med ÉN valgt vare i AD får alligevel behovsstyring (run'ets liste) local runDriver = buildCowWorld() diff --git a/tests/test_adSupplyPlanner.lua b/tests/test_adSupplyPlanner.lua index 13d55e6..4f6da2b 100644 --- a/tests/test_adSupplyPlanner.lua +++ b/tests/test_adSupplyPlanner.lua @@ -178,5 +178,20 @@ check("tærskel 30 %: 70 % fri er nok", ADSupplyPlanner.canUnloadRest(70000, 900 check("hele resten passer vinder altid", ADSupplyPlanner.canUnloadRest(5000, 5000, 100000, 0.7), true) check("uden tærskel = 50 %-reglen", ADSupplyPlanner.canUnloadRest(50000, 90000, 100000), true) +-- rankNeeds: fælles trug (scoreOverride) + fodervægt (priority): TMR før hø før græs, +-- men et separat halmlager der er tommere vinder over truget +local TMR, HAYX = 40, 41 +local trough = ADSupplyPlanner.rankNeeds({ + {fillType = GRASS, level = 0, capacity = 30000, scoreOverride = 0.3, priority = 0.4}, + {fillType = TMR, level = 0, capacity = 30000, scoreOverride = 0.3, priority = 1.0}, + {fillType = HAYX, level = 0, capacity = 30000, scoreOverride = 0.3, priority = 0.8}, + {fillType = HAY, level = 20000, capacity = 100000}, -- halm-agtigt separat lager, 80 % tomt +}) +check("separat lager 80 % tomt vinder over trug 30 % tomt", trough[1].fillType, HAY) +check("i truget: TMR (1.0) først", trough[2].fillType, TMR) +check("så hø (0.8)", trough[3].fillType, HAYX) +check("så græs (0.4)", trough[4].fillType, GRASS) +check("scoreOverride bruges som score", trough[2].score, 0.3) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)