feat: fyldeste kilde først for mælk/gylle/digestat (v1.5.0.0)
Lagervarer tømmes stadig mindst-fyldte-først; varer hvis tanke løber fulde og stopper produktionen hentes fyldest-først (ADSupplyPlanner.FULLEST_FIRST_FILL_TYPES). Strategien vises i loggen. fix: en trigger foran næsen tæller aldrig som markørens egen (tolerance 2 -> 0). Markør-drift i ensilage-rækken lod næste tanks rør (1,9 m foran) vinde. Verificeret mod savegame: 40/40 siloer, 7/7 ensilage-tanke, 0 dropof.
This commit is contained in:
@@ -14,8 +14,9 @@ ADSmartPickup = {}
|
|||||||
-- vogntog bagved. Siloer tættere end dette regnes som "markørens silo".
|
-- vogntog bagved. Siloer tættere end dette regnes som "markørens silo".
|
||||||
ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE = 40
|
ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE = 40
|
||||||
-- I tætte silo-rækker ligger nabosiloens trigger FORAN næsen og er ofte nærmere
|
-- I tætte silo-rækker ligger nabosiloens trigger FORAN næsen og er ofte nærmere
|
||||||
-- end markørens egen. Triggere mere end dette foran næsen hører til naboen.
|
-- end markørens egen. Vognen holder altid BAG næsen, så en trigger foran næsen
|
||||||
ADSmartPickup.MAX_TRIGGER_AHEAD_OF_MARKER = 2
|
-- hører til naboen (en ensom silo med markøren ved røret fanges af nødløsningen).
|
||||||
|
ADSmartPickup.MAX_TRIGGER_AHEAD_OF_MARKER = 0
|
||||||
-- Vognen følger trækkerens spor: en trigger længere til siden end dette er en
|
-- Vognen følger trækkerens spor: en trigger længere til siden end dette er en
|
||||||
-- anden bane (fx aflæsningssiden af samme tank), ikke markørens læsseplads.
|
-- anden bane (fx aflæsningssiden af samme tank), ikke markørens læsseplads.
|
||||||
ADSmartPickup.MAX_TRIGGER_LATERAL_OFFSET = 6
|
ADSmartPickup.MAX_TRIGGER_LATERAL_OFFSET = 6
|
||||||
@@ -194,13 +195,26 @@ local function collectMarkerStations(adEnv, candidateMarkers, farmId, excludedPl
|
|||||||
return markerStations
|
return markerStations
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Mindst fyldte silo der har varen; siloer med varen i EGET lager går altid
|
local function getFillTypeNames(fillTypes)
|
||||||
-- forud for dem der kun kan låne fra naboer. Returnerer et valg eller nil.
|
local names = {}
|
||||||
local function findLeastFilled(markerStations, fillTypes, farmId)
|
for _, fillType in ipairs(fillTypes) do
|
||||||
local best = {[true] = {level = math.huge}, [false] = {level = math.huge}}
|
local name = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeNameByIndex(fillType) or nil
|
||||||
|
if name ~= nil then
|
||||||
|
table.insert(names, name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Bedste kilde der har varen: mindst fyldte først for lagervarer, fyldeste først for
|
||||||
|
-- varer hvis tanke løber fulde (ADSupplyPlanner). Siloer med varen i EGET lager går
|
||||||
|
-- altid forud for dem der kun kan låne fra naboer. Returnerer et valg eller nil.
|
||||||
|
local function findBestSource(markerStations, fillTypes, farmId)
|
||||||
|
local prefersFullest = ADSupplyPlanner.prefersFullestSource(getFillTypeNames(fillTypes))
|
||||||
|
local best = {[true] = {}, [false] = {}}
|
||||||
for _, entry in ipairs(markerStations) do
|
for _, entry in ipairs(markerStations) do
|
||||||
local level, isOwn = getStationFillLevel(entry.station, fillTypes, farmId)
|
local level, isOwn = getStationFillLevel(entry.station, fillTypes, farmId)
|
||||||
if level >= ADSmartPickup.MIN_USEFUL_LITERS and level < best[isOwn].level then
|
if level >= ADSmartPickup.MIN_USEFUL_LITERS and ADSupplyPlanner.isBetterSource(level, best[isOwn].level, prefersFullest) then
|
||||||
best[isOwn] = {level = level, marker = entry.marker, station = entry.station, isOwn = isOwn}
|
best[isOwn] = {level = level, marker = entry.marker, station = entry.station, isOwn = isOwn}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -217,6 +231,7 @@ local function findLeastFilled(markerStations, fillTypes, farmId)
|
|||||||
station = choice.station,
|
station = choice.station,
|
||||||
isOwn = choice.isOwn,
|
isOwn = choice.isOwn,
|
||||||
fillTypes = fillTypes,
|
fillTypes = fillTypes,
|
||||||
|
prefersFullest = prefersFullest,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -232,7 +247,7 @@ function ADSmartPickup.findLeastFilledPickup(adEnv, driver)
|
|||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local groupMarkers = adEnv.ADGraphManager:getMapMarkersInGroup(firstMarker.group)
|
local groupMarkers = adEnv.ADGraphManager:getMapMarkersInGroup(firstMarker.group)
|
||||||
return findLeastFilled(collectMarkerStations(adEnv, groupMarkers, farmId, nil), fillTypes, farmId)
|
return findBestSource(collectMarkerStations(adEnv, groupMarkers, farmId, nil), fillTypes, farmId)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Lagrene bag aflæsningsstationen; produktionspunkter uden targetStorages læses direkte.
|
-- Lagrene bag aflæsningsstationen; produktionspunkter uden targetStorages læses direkte.
|
||||||
@@ -285,7 +300,7 @@ function ADSmartPickup.findSupplyPickup(adEnv, driver, ingredients)
|
|||||||
local allMarkers = adEnv.ADGraphManager:getMapMarkers()
|
local allMarkers = adEnv.ADGraphManager:getMapMarkers()
|
||||||
local markerStations = collectMarkerStations(adEnv, allMarkers, farmId, target.owningPlaceable)
|
local markerStations = collectMarkerStations(adEnv, allMarkers, farmId, target.owningPlaceable)
|
||||||
for _, need in ipairs(ADSupplyPlanner.rankNeeds(getTargetNeeds(target, ingredients, farmId))) do
|
for _, need in ipairs(ADSupplyPlanner.rankNeeds(getTargetNeeds(target, ingredients, farmId))) do
|
||||||
local choice = findLeastFilled(markerStations, {need.fillType}, farmId)
|
local choice = findBestSource(markerStations, {need.fillType}, farmId)
|
||||||
if choice ~= nil then
|
if choice ~= nil then
|
||||||
return {
|
return {
|
||||||
markerIndex = choice.markerIndex,
|
markerIndex = choice.markerIndex,
|
||||||
@@ -295,6 +310,7 @@ function ADSmartPickup.findSupplyPickup(adEnv, driver, ingredients)
|
|||||||
station = choice.station,
|
station = choice.station,
|
||||||
isOwn = choice.isOwn,
|
isOwn = choice.isOwn,
|
||||||
fillTypes = choice.fillTypes,
|
fillTypes = choice.fillTypes,
|
||||||
|
prefersFullest = choice.prefersFullest,
|
||||||
fillType = need.fillType,
|
fillType = need.fillType,
|
||||||
need = need,
|
need = need,
|
||||||
}
|
}
|
||||||
@@ -330,6 +346,7 @@ end
|
|||||||
|
|
||||||
local function describeChoice(choice)
|
local function describeChoice(choice)
|
||||||
local source = choice.isOwn and "eget lager" or "nabo-lager"
|
local source = choice.isOwn and "eget lager" or "nabo-lager"
|
||||||
|
source = source .. (choice.prefersFullest and ", fyldeste først" or ", mindst fyldte først")
|
||||||
if choice.fillType == nil then
|
if choice.fillType == nil then
|
||||||
return string.format("%.0f l, %s", choice.level, source)
|
return string.format("%.0f l, %s", choice.level, source)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,6 +7,39 @@ ADSupplyPlanner = {}
|
|||||||
-- Mindre fri plads end dette i målet regnes som "fuldt" (undgår ture med læs der ikke kan komme af).
|
-- Mindre fri plads end dette i målet regnes som "fuldt" (undgår ture med læs der ikke kan komme af).
|
||||||
ADSupplyPlanner.MIN_FREE_LITERS = 1000
|
ADSupplyPlanner.MIN_FREE_LITERS = 1000
|
||||||
|
|
||||||
|
-- Varer hvis kilder LØBER FULDE (dyr/produktion fylder tanken løbende og går i stå
|
||||||
|
-- når den er fuld): her hentes den fyldeste kilde først. Alt andet er lagervarer,
|
||||||
|
-- hvor den mindst fyldte silo tømmes først så rester ryddes og siloer frigives.
|
||||||
|
ADSupplyPlanner.FULLEST_FIRST_FILL_TYPES = {
|
||||||
|
MILK = true, GOATMILK = true, BUFFALOMILK = true, SHEEPMILK = true,
|
||||||
|
LIQUIDMANURE = true, DIGESTATE = true,
|
||||||
|
}
|
||||||
|
|
||||||
|
-- fillTypeNames: navnene på de varer der hentes på turen. Kun når ALLE er
|
||||||
|
-- "løber fuld"-varer vendes strategien; blandet eller ukendt valg = mindst først.
|
||||||
|
function ADSupplyPlanner.prefersFullestSource(fillTypeNames)
|
||||||
|
if #fillTypeNames == 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
for _, name in ipairs(fillTypeNames) do
|
||||||
|
if not ADSupplyPlanner.FULLEST_FIRST_FILL_TYPES[name] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Er en kilde med dette niveau bedre end den hidtil bedste (nil = ingen endnu)?
|
||||||
|
function ADSupplyPlanner.isBetterSource(level, bestLevel, prefersFullest)
|
||||||
|
if bestLevel == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
if prefersFullest then
|
||||||
|
return level > bestLevel
|
||||||
|
end
|
||||||
|
return level < bestLevel
|
||||||
|
end
|
||||||
|
|
||||||
function ADSupplyPlanner.copyList(list)
|
function ADSupplyPlanner.copyList(list)
|
||||||
local copy = {}
|
local copy = {}
|
||||||
for index, value in ipairs(list) do
|
for index, value in ipairs(list) do
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||||
<modDesc descVersion="107">
|
<modDesc descVersion="107">
|
||||||
<author>masterdraco</author>
|
<author>masterdraco</author>
|
||||||
<version>1.4.0.0</version>
|
<version>1.5.0.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ AutoDrive-tillæg til Farming Simulator 25 med to funktioner oven på Pickup&Del
|
|||||||
## 1. Vare-bevidst mappe-rotation (én vare valgt)
|
## 1. Vare-bevidst mappe-rotation (én vare valgt)
|
||||||
|
|
||||||
Når pålæsningsmål roteres i en AD-mappe, køres der direkte til den silo i mappen, der har den
|
Når pålæsningsmål roteres i en AD-mappe, køres der direkte til den silo i mappen, der har den
|
||||||
valgte vare — **mindst fyldte først** — i stedet for at prøve markørerne én efter én.
|
valgte vare — mindst fyldte først (mælk/gylle: fyldeste først) — i stedet for at prøve markørerne én efter én.
|
||||||
|
|
||||||
## 2. Behovsstyret forsyning (flere varer valgt)
|
## 2. Behovsstyret forsyning (flere varer valgt)
|
||||||
|
|
||||||
@@ -34,6 +34,11 @@ Slå fra ved at vælge en anden enkelt vare.
|
|||||||
bagved. Markørens silo er derfor nærmeste trigger **bag næsen** set i ankomstretningen (forrige
|
bagved. Markørens silo er derfor nærmeste trigger **bag næsen** set i ankomstretningen (forrige
|
||||||
waypoint), højst 2 m foran. I tætte rækker (15 m mellem siloerne) er nabosiloens trigger ellers
|
waypoint), højst 2 m foran. I tætte rækker (15 m mellem siloerne) er nabosiloens trigger ellers
|
||||||
nærmere end markørens egen. Ingen trigger bag næsen/ukendt retning → nærmeste trigger som før.
|
nærmere end markørens egen. Ingen trigger bag næsen/ukendt retning → nærmeste trigger som før.
|
||||||
|
- **Kildestrategi (v1.5):** lagervarer hentes fra den **mindst fyldte** silo først (rester ryddes,
|
||||||
|
siloer frigives). Varer hvis tanke *løber fulde* — mælk, gylle, digestat — hentes fra den **fyldeste**
|
||||||
|
kilde først, så produktionen ikke går i stå. Listen er `ADSupplyPlanner.FULLEST_FIRST_FILL_TYPES`
|
||||||
|
(`adSupplyPlanner.lua`); tilføj fx `MANURE = true` hvis møddinger også skal tømmes fyldest-først.
|
||||||
|
Loggen viser den brugte strategi. En trigger foran næsen tæller aldrig som markørens egen (v1.5).
|
||||||
- **Spor-regel (v1.4):** triggeren skal ligge i trækkerens spor (højst 6 m til siden). En tank med
|
- **Spor-regel (v1.4):** triggeren skal ligge i trækkerens spor (højst 6 m til siden). En tank med
|
||||||
aflæsningsbane på den ene side og pålæsningsbane på den anden giver ellers to markører til samme
|
aflæsningsbane på den ene side og pålæsningsbane på den anden giver ellers to markører til samme
|
||||||
tank, og "Dropof"-markøren kunne vinde. Kendes retningen, men ligger intet i sporet (kurve ind til
|
tank, og "Dropof"-markøren kunne vinde. Kendes retningen, men ligger intet i sporet (kurve ind til
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
local SUGARBEET, WHEAT = 10, 11
|
local SUGARBEET, WHEAT = 10, 11
|
||||||
local HAY, GRASS, SILAGE = 20, 21, 22
|
local HAY, GRASS, SILAGE = 20, 21, 22
|
||||||
|
local MILK = 30
|
||||||
|
local FILL_TYPE_NAMES = {[SUGARBEET] = "SUGARBEET", [WHEAT] = "WHEAT", [HAY] = "DRYGRASS_WINDROW", [GRASS] = "GRASS_WINDROW", [SILAGE] = "SILAGE", [MILK] = "MILK"}
|
||||||
|
g_fillTypeManager = {getFillTypeNameByIndex = function(_, index) return FILL_TYPE_NAMES[index] end}
|
||||||
local logLines = {}
|
local logLines = {}
|
||||||
|
|
||||||
-- ---- FS25-mocks ----
|
-- ---- FS25-mocks ----
|
||||||
@@ -357,6 +360,15 @@ addMarker(2, "1 Silage Pickup", "Ensilage", 1247, -2278, 1247, -2266) -- køre
|
|||||||
table.insert(stations, makeStation(101, 1247, -2271, {[SILAGE] = 4000}))
|
table.insert(stations, makeStation(101, 1247, -2271, {[SILAGE] = 4000}))
|
||||||
check("G3 pickup-markøren vælges, ikke dropoff", manager:getNextPickup(makeDriver(markers[1], {SILAGE}), false), 2)
|
check("G3 pickup-markøren vælges, ikke dropoff", manager:getNextPickup(makeDriver(markers[1], {SILAGE}), false), 2)
|
||||||
|
|
||||||
|
-- G5: markør-drift i en tank-række — næste tanks rør ligger 2 m FORAN næsen og er
|
||||||
|
-- nærmest, men vognen holder under røret 11 m bagved
|
||||||
|
resetWorld()
|
||||||
|
addMarker(1, "5 Silage Pickup", "Ensilage", 1246, -2218, 1246, -2230) -- kører +z
|
||||||
|
table.insert(stations, makeStation(101, 1246, -2216, {[SILAGE] = 9000})) -- 2 m foran næsen
|
||||||
|
table.insert(stations, makeStation(102, 1246, -2229, {[HAY] = 4000})) -- 11 m bag næsen: kun den har hø
|
||||||
|
addMarker(2, "6 Silage Pickup", "Ensilage", 1246, -2208, 1246, -2218)
|
||||||
|
check("G5 markør 5 hører til tanken bag næsen", manager:getNextPickup(makeDriver(markers[1], {HAY}), false), 1)
|
||||||
|
|
||||||
-- G4: kurve ind til markøren -> retningen snyder og røret ligger "foran"; tæt på = ok
|
-- G4: kurve ind til markøren -> retningen snyder og røret ligger "foran"; tæt på = ok
|
||||||
resetWorld()
|
resetWorld()
|
||||||
addMarker(1, "7 Silage Pickup", "Ensilage", 100, 0, 100, 20) -- kører -z
|
addMarker(1, "7 Silage Pickup", "Ensilage", 100, 0, 100, 20) -- kører -z
|
||||||
@@ -380,6 +392,14 @@ check("L9 sluppet greb logges som advarsel", logLines[#logLines]:sub(1, 4), "WAR
|
|||||||
FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 16)
|
FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 16)
|
||||||
check("L9 grebet tages ikke igen på samme tur", stuckTask.seenRotate, RT_ONLYPICKUP)
|
check("L9 grebet tages ikke igen på samme tur", stuckTask.seenRotate, RT_ONLYPICKUP)
|
||||||
|
|
||||||
|
-- M1: mælketanke løber fulde -> den FYLDESTE hentes først (siloer: mindst fyldte)
|
||||||
|
resetWorld()
|
||||||
|
addMarker(1, "Kostald 1", "Mælk", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[MILK] = 2000}))
|
||||||
|
addMarker(2, "Kostald 2", "Mælk", 500, 0); table.insert(stations, makeStation(102, 500, 15, {[MILK] = 48000}))
|
||||||
|
addMarker(3, "Kostald 3", "Mælk", 1000, 0); table.insert(stations, makeStation(103, 1000, 15, {[MILK] = 30000}))
|
||||||
|
check("M1 fyldeste mælketank først", manager:getNextPickup(makeDriver(markers[1], {MILK}), false), 2)
|
||||||
|
check("M1 loggen nævner strategien", logLines[#logLines]:find("fyldeste først", 1, true) ~= nil, true)
|
||||||
|
|
||||||
-- L7: AD stopper -> grebet ryddes, næste opgave venter ikke på en gammel silo
|
-- L7: AD stopper -> grebet ryddes, næste opgave venter ikke på en gammel silo
|
||||||
resetWorld()
|
resetWorld()
|
||||||
addMarker(1, "Silo 1", "Andet", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[HAY] = 5000}))
|
addMarker(1, "Silo 1", "Andet", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[HAY] = 5000}))
|
||||||
|
|||||||
@@ -51,5 +51,16 @@ check("bruger valgte andet -> nil", ADSupplyPlanner.resolveIngredients(state, {H
|
|||||||
check("nyt multi-valg erstatter gemt liste", #ADSupplyPlanner.resolveIngredients(state, {HAY, SILAGE}), 2)
|
check("nyt multi-valg erstatter gemt liste", #ADSupplyPlanner.resolveIngredients(state, {HAY, SILAGE}), 2)
|
||||||
check("ugyldigt valg -> nil", ADSupplyPlanner.resolveIngredients(state, nil), nil)
|
check("ugyldigt valg -> nil", ADSupplyPlanner.resolveIngredients(state, nil), nil)
|
||||||
|
|
||||||
|
-- Kildestrategi: lager-siloer tømmes mindst-først; tanke der løber fulde (mælk, gylle) fyldest-først
|
||||||
|
check("mælk hentes fyldest-først", ADSupplyPlanner.prefersFullestSource({"MILK"}), true)
|
||||||
|
check("gylle hentes fyldest-først", ADSupplyPlanner.prefersFullestSource({"LIQUIDMANURE"}), true)
|
||||||
|
check("roer hentes mindst-først", ADSupplyPlanner.prefersFullestSource({"SUGARBEET"}), false)
|
||||||
|
check("blandet valg -> mindst-først", ADSupplyPlanner.prefersFullestSource({"MILK", "WHEAT"}), false)
|
||||||
|
check("ukendt/tomt valg -> mindst-først", ADSupplyPlanner.prefersFullestSource({}), false)
|
||||||
|
check("mindst-først: lavere niveau er bedre", ADSupplyPlanner.isBetterSource(100, 500, false), true)
|
||||||
|
check("mindst-først: højere niveau er ikke bedre", ADSupplyPlanner.isBetterSource(900, 500, false), false)
|
||||||
|
check("fyldest-først: højere niveau er bedre", ADSupplyPlanner.isBetterSource(900, 500, true), true)
|
||||||
|
check("første kandidat er altid bedre end ingen", ADSupplyPlanner.isBetterSource(900, nil, false), true)
|
||||||
|
|
||||||
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
|
||||||
os.exit(failures == 0 and 0 or 1)
|
os.exit(failures == 0 and 0 or 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user