feat: læs på vej tælles med — flere traktorer til samme stald fordeler sig på varer og stalde (v1.14.1.0)
This commit is contained in:
@@ -252,7 +252,9 @@ function ADRunsController.chooseTarget(adEnv, run, vehicle)
|
|||||||
local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle)
|
local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle)
|
||||||
local bestId, bestNeed = nil, nil
|
local bestId, bestNeed = nil, nil
|
||||||
for _, entry in ipairs(getTargetStations(adEnv, run, farmId)) do
|
for _, entry in ipairs(getTargetStations(adEnv, run, farmId)) do
|
||||||
local ranked = ADSmartPickup.getTargetNeedsRanked(entry.station, ingredients, farmId)
|
-- læs andre traktorer er på vej med til stalden, tæller med — så fordeler de sig på staldene
|
||||||
|
local pending = ADSmartPickup.getPending ~= nil and ADSmartPickup.getPending(entry.wayPointId, vehicle) or nil
|
||||||
|
local ranked = ADSmartPickup.getTargetNeedsRanked(entry.station, ingredients, farmId, pending)
|
||||||
local need = ranked[1]
|
local need = ranked[1]
|
||||||
if need ~= nil and need.freeSpace >= (ADSupplyPlanner.MIN_FREE_LITERS or 1000) then
|
if need ~= nil and need.freeSpace >= (ADSupplyPlanner.MIN_FREE_LITERS or 1000) then
|
||||||
local isBetter = bestNeed == nil
|
local isBetter = bestNeed == nil
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ local supplyState = setmetatable({}, {__mode = "k"})
|
|||||||
-- køretøj -> {station, fillTypes}: siloen moden sendte køretøjet til på denne tur.
|
-- køretøj -> {station, fillTypes}: siloen moden sendte køretøjet til på denne tur.
|
||||||
local activePickups = setmetatable({}, {__mode = "k"})
|
local activePickups = setmetatable({}, {__mode = "k"})
|
||||||
-- køretøj -> senest loggede valg (AutoDrive spørger flere gange pr. start).
|
-- køretøj -> senest loggede valg (AutoDrive spørger flere gange pr. start).
|
||||||
|
-- køretøj -> {wayPointId, fillType, liters}: læs traktoren er på vej med (fordeler flere traktorer til
|
||||||
|
-- samme mål; ryddes ved aflæsning, stop eller ny tur)
|
||||||
|
local enRoute = setmetatable({}, {__mode = "k"})
|
||||||
local lastLogged = setmetatable({}, {__mode = "k"})
|
local lastLogged = setmetatable({}, {__mode = "k"})
|
||||||
|
|
||||||
-- Cross-mod adgang: FS25-mods er sandboxede; AutoDrives globals ligger
|
-- Cross-mod adgang: FS25-mods er sandboxede; AutoDrives globals ligger
|
||||||
@@ -436,7 +439,7 @@ local function getFoodPriority(station, fillType)
|
|||||||
end
|
end
|
||||||
local capacity = foodSpec.capacity or 0
|
local capacity = foodSpec.capacity or 0
|
||||||
local troughScore = capacity > 0 and math.max(0, capacity - total) / capacity or 0
|
local troughScore = capacity > 0 and math.max(0, capacity - total) / capacity or 0
|
||||||
return troughScore, foodFillTypes[fillType]
|
return troughScore, foodFillTypes[fillType], capacity
|
||||||
end
|
end
|
||||||
|
|
||||||
-- En stald kan MODTAGE foder, halm og vand — ikke sine egne outputs (mælk, gødning, gylle),
|
-- En stald kan MODTAGE foder, halm og vand — ikke sine egne outputs (mælk, gødning, gylle),
|
||||||
@@ -508,16 +511,49 @@ local function getTargetNeeds(station, ingredients, farmId, demand)
|
|||||||
for _, fillType in ipairs(ingredients) do
|
for _, fillType in ipairs(ingredients) do
|
||||||
local level, capacity = getTargetLevelAndCapacity(station, fillType, farmId)
|
local level, capacity = getTargetLevelAndCapacity(station, fillType, farmId)
|
||||||
if level ~= nil then
|
if level ~= nil then
|
||||||
local troughScore, priority = getFoodPriority(station, fillType)
|
local troughScore, priority, troughCapacity = getFoodPriority(station, fillType)
|
||||||
table.insert(needs, {fillType = fillType, level = level, capacity = capacity, litersPerMinute = demand[fillType], scoreOverride = troughScore, priority = priority})
|
table.insert(needs, {fillType = fillType, level = level, capacity = capacity, litersPerMinute = demand[fillType], scoreOverride = troughScore, priority = priority, troughCapacity = troughCapacity})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return needs
|
return needs
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Til fanen: samme rangering som valget bruger (uden vogn-størrelse). Fulde varer sidst.
|
-- Til fanen: samme rangering som valget bruger (uden vogn-størrelse). Fulde varer sidst.
|
||||||
function ADSmartPickup.getTargetNeedsRanked(station, fillTypes, farmId)
|
-- ---- læs på vej ----
|
||||||
|
|
||||||
|
function ADSmartPickup.setEnRoute(vehicle, wayPointId, fillType, liters)
|
||||||
|
if wayPointId == nil or fillType == nil or liters == nil or liters <= 0 then
|
||||||
|
enRoute[vehicle] = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
enRoute[vehicle] = {wayPointId = wayPointId, fillType = fillType, liters = liters}
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADSmartPickup.clearEnRoute(vehicle)
|
||||||
|
enRoute[vehicle] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function ADSmartPickup.resetEnRoute()
|
||||||
|
enRoute = setmetatable({}, {__mode = "k"})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- {[fillType] = liter} som ANDRE (aktive) traktorer er på vej med til målet.
|
||||||
|
function ADSmartPickup.getPending(wayPointId, excludeVehicle)
|
||||||
|
local pending = {}
|
||||||
|
for vehicle, entry in pairs(enRoute) do
|
||||||
|
if vehicle ~= excludeVehicle and entry.wayPointId == wayPointId then
|
||||||
|
pending[entry.fillType] = (pending[entry.fillType] or 0) + entry.liters
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return pending
|
||||||
|
end
|
||||||
|
|
||||||
|
-- pending (valgfri): læs på vej til målet, lægges oven i (fanen og mål-valget mellem stalde).
|
||||||
|
function ADSmartPickup.getTargetNeedsRanked(station, fillTypes, farmId, pending)
|
||||||
local needs = getTargetNeeds(station, fillTypes, farmId, {})
|
local needs = getTargetNeeds(station, fillTypes, farmId, {})
|
||||||
|
if pending ~= nil and next(pending) ~= nil then
|
||||||
|
needs = ADSupplyPlanner.applyPending(needs, pending)
|
||||||
|
end
|
||||||
local ranked = ADSupplyPlanner.rankNeeds(needs)
|
local ranked = ADSupplyPlanner.rankNeeds(needs)
|
||||||
local seen = {}
|
local seen = {}
|
||||||
for _, need in ipairs(ranked) do seen[need.fillType] = true end
|
for _, need in ipairs(ranked) do seen[need.fillType] = true end
|
||||||
@@ -593,7 +629,8 @@ function ADSmartPickup.findSupplyPickup(adEnv, driver, ingredients, targetWayPoi
|
|||||||
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)
|
||||||
local loadCapacity = getLoadCapacity(adEnv, driver)
|
local loadCapacity = getLoadCapacity(adEnv, driver)
|
||||||
local ranked = ADSupplyPlanner.rankNeeds(getTargetNeeds(target, ingredients, farmId, demand), loadCapacity)
|
local needs = ADSupplyPlanner.applyPending(getTargetNeeds(target, ingredients, farmId, demand), ADSmartPickup.getPending(wayPointId, driver))
|
||||||
|
local ranked = ADSupplyPlanner.rankNeeds(needs, loadCapacity)
|
||||||
-- bedste foder først: lavere foder kun når truget er nødlidende (ADSupplyPlanner.isFoodAllowed)
|
-- bedste foder først: lavere foder kun når truget er nødlidende (ADSupplyPlanner.isFoodAllowed)
|
||||||
local bestFood = ADSupplyPlanner.bestFoodPriority ~= nil and ADSupplyPlanner.bestFoodPriority(ranked) or nil
|
local bestFood = ADSupplyPlanner.bestFoodPriority ~= nil and ADSupplyPlanner.bestFoodPriority(ranked) or nil
|
||||||
for _, need in ipairs(ranked) do
|
for _, need in ipairs(ranked) do
|
||||||
@@ -602,6 +639,9 @@ function ADSmartPickup.findSupplyPickup(adEnv, driver, ingredients, targetWayPoi
|
|||||||
return {
|
return {
|
||||||
markerIndex = choice.markerIndex,
|
markerIndex = choice.markerIndex,
|
||||||
markerId = choice.markerId,
|
markerId = choice.markerId,
|
||||||
|
targetWayPointId = wayPointId,
|
||||||
|
-- det der reelt kommer af: vognen, kildens beholdning og målets fri plads sætter grænsen
|
||||||
|
pendingLiters = math.min(loadCapacity or math.huge, choice.level or math.huge, need.freeSpace or math.huge),
|
||||||
markerName = choice.markerName,
|
markerName = choice.markerName,
|
||||||
group = choice.group,
|
group = choice.group,
|
||||||
level = choice.level,
|
level = choice.level,
|
||||||
@@ -619,6 +659,7 @@ end
|
|||||||
|
|
||||||
local function applySupplyChoice(driver, ingredients, choice)
|
local function applySupplyChoice(driver, ingredients, choice)
|
||||||
local stateModule = driver.ad.stateModule
|
local stateModule = driver.ad.stateModule
|
||||||
|
ADSmartPickup.setEnRoute(driver, choice.targetWayPointId, choice.fillType, choice.pendingLiters)
|
||||||
stateModule:setFillType(choice.fillType)
|
stateModule:setFillType(choice.fillType)
|
||||||
stateModule.selectedFillTypes = {choice.fillType}
|
stateModule.selectedFillTypes = {choice.fillType}
|
||||||
stateModule:raiseDirtyFlag()
|
stateModule:raiseDirtyFlag()
|
||||||
@@ -687,6 +728,7 @@ end
|
|||||||
|
|
||||||
-- Samlet valg: behovsstyret med ingrediensliste, ellers mappe-logik. nil = AutoDrives original.
|
-- Samlet valg: behovsstyret med ingrediensliste, ellers mappe-logik. nil = AutoDrives original.
|
||||||
function ADSmartPickup.choosePickup(adEnv, driver)
|
function ADSmartPickup.choosePickup(adEnv, driver)
|
||||||
|
enRoute[driver] = nil -- ny tur: forrige læs er leveret (eller opgivet)
|
||||||
chooseRunTarget(adEnv, driver)
|
chooseRunTarget(adEnv, driver)
|
||||||
local ingredients = ADSmartPickup.getIngredientsFor(adEnv, driver)
|
local ingredients = ADSmartPickup.getIngredientsFor(adEnv, driver)
|
||||||
if ingredients == nil then
|
if ingredients == nil then
|
||||||
@@ -913,6 +955,12 @@ local function restoreStoppedVehicles()
|
|||||||
activePickups[vehicle] = nil
|
activePickups[vehicle] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
for vehicle in pairs(enRoute) do
|
||||||
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
||||||
|
if stateModule == nil or stateModule.isActive == nil or not stateModule:isActive() then
|
||||||
|
enRoute[vehicle] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
for vehicle, state in pairs(supplyState) do
|
for vehicle, state in pairs(supplyState) do
|
||||||
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
|
||||||
if stateModule == nil or not stateModule:isActive() then
|
if stateModule == nil or not stateModule:isActive() then
|
||||||
|
|||||||
@@ -210,6 +210,37 @@ function ADSupplyPlanner.rankNeeds(needs, loadCapacity)
|
|||||||
return ranked
|
return ranked
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- ---- læs på vej ----
|
||||||
|
-- pending = {[fillType] = liter} som ANDRE traktorer allerede er på vej med til samme mål.
|
||||||
|
-- Almindelige lagre: niveau + på vej (højst kapaciteten). Foder i et fælles trug (scoreOverride +
|
||||||
|
-- troughCapacity): varens niveau stiger, og trugets fri plads falder med ALT foder på vej.
|
||||||
|
-- Returnerer NY liste; input røres ikke.
|
||||||
|
function ADSupplyPlanner.applyPending(needs, pending)
|
||||||
|
local troughPending = 0
|
||||||
|
for _, need in ipairs(needs) do
|
||||||
|
if need.scoreOverride ~= nil and pending[need.fillType] ~= nil then
|
||||||
|
troughPending = troughPending + pending[need.fillType]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local result = {}
|
||||||
|
for index, need in ipairs(needs) do
|
||||||
|
local copy = {}
|
||||||
|
for key, value in pairs(need) do copy[key] = value end
|
||||||
|
local extra = pending[need.fillType] or 0
|
||||||
|
if need.scoreOverride ~= nil and troughPending > 0 then
|
||||||
|
local free = math.max(0, (need.capacity - need.level) - troughPending)
|
||||||
|
copy.level = need.level + extra
|
||||||
|
copy.capacity = copy.level + free
|
||||||
|
local troughCapacity = need.troughCapacity or need.capacity
|
||||||
|
copy.scoreOverride = troughCapacity > 0 and math.max(0, need.scoreOverride - troughPending / troughCapacity) or 0
|
||||||
|
elseif extra > 0 then
|
||||||
|
copy.level = math.min(need.capacity, need.level + extra)
|
||||||
|
end
|
||||||
|
result[index] = copy
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
-- ---- bedste foder først ----
|
-- ---- bedste foder først ----
|
||||||
-- Foder i et stald-trug deler plads. Lavere foder (ensilage/hø, vægt 0,8) må kun hentes når der er
|
-- Foder i et stald-trug deler plads. Lavere foder (ensilage/hø, vægt 0,8) må kun hentes når der er
|
||||||
-- bedre foder (TMR, 1,0) på listen, hvis truget er nødlidende: mindst så stor en andel fri.
|
-- bedre foder (TMR, 1,0) på listen, hvis truget er nødlidende: mindst så stor en andel fri.
|
||||||
|
|||||||
@@ -392,6 +392,10 @@ function ADUnloadWait.install(adEnv)
|
|||||||
task.finished = function(self)
|
task.finished = function(self)
|
||||||
release(self.vehicle)
|
release(self.vehicle)
|
||||||
restoreTarget(self.vehicle)
|
restoreTarget(self.vehicle)
|
||||||
|
-- læsset er afleveret: tæller ikke længere som "på vej" for de andre traktorer
|
||||||
|
if ADSmartPickup ~= nil and ADSmartPickup.clearEnRoute ~= nil then
|
||||||
|
ADSmartPickup.clearEnRoute(self.vehicle)
|
||||||
|
end
|
||||||
return originalFinished(self)
|
return originalFinished(self)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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.14.0.0</version>
|
<version>1.14.1.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ local function addMarker(index, name, group, x, z, fromX, fromZ)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function resetWorld()
|
local function resetWorld()
|
||||||
|
if ADSmartPickup ~= nil and ADSmartPickup.resetEnRoute ~= nil then ADSmartPickup.resetEnRoute() end
|
||||||
stations, unloadStations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}, {}
|
stations, unloadStations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}, {}
|
||||||
FS25_AutoDrive.ADMultipleTargetsManager.pickups = {}
|
FS25_AutoDrive.ADMultipleTargetsManager.pickups = {}
|
||||||
end
|
end
|
||||||
@@ -1140,6 +1141,20 @@ check("N1 strengt opslag finder intet", ADSmartPickup.getLoadStationAtWayPointId
|
|||||||
check("N1 løst opslag finder tanken", ADSmartPickup.getLoadStationNearWayPointId(FS25_AutoDrive, 1, 1, 40), stations[1])
|
check("N1 løst opslag finder tanken", ADSmartPickup.getLoadStationNearWayPointId(FS25_AutoDrive, 1, 1, 40), stations[1])
|
||||||
check("N1 uden for afstanden -> nil", ADSmartPickup.getLoadStationNearWayPointId(FS25_AutoDrive, 1, 1, 20), nil)
|
check("N1 uden for afstanden -> nil", ADSmartPickup.getLoadStationNearWayPointId(FS25_AutoDrive, 1, 1, 20), nil)
|
||||||
|
|
||||||
|
-- V: flere traktorer til samme mål fordeler sig — læs på vej tælles med (in-game 24/9: alle 4 Fastrac'er
|
||||||
|
-- hentede det samme). Græs mangler mest (20000/100000), ensilage næst (50000/100000).
|
||||||
|
buildFeedWorld()
|
||||||
|
local feederA = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE})
|
||||||
|
feederA.trailers = {level = 0, capacity = 70000}
|
||||||
|
local feederB = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE})
|
||||||
|
feederB.trailers = {level = 0, capacity = 70000}
|
||||||
|
check("V1 A henter græs (mangler mest)", manager:getNextPickup(feederA, false), 3)
|
||||||
|
check("V1 B henter ensilage (græs er på vej med A)", manager:getNextPickup(feederB, false), 4)
|
||||||
|
check("V1 A's læs registreret på vej", ADSmartPickup.getPending(10, feederB)[GRASS], 40000)
|
||||||
|
ADSmartPickup.clearEnRoute(feederA)
|
||||||
|
check("V2 A læsset af -> intet på vej", ADSmartPickup.getPending(10, feederB)[GRASS], nil)
|
||||||
|
check("V2 egne læs tæller ikke", ADSmartPickup.getPending(10, feederB)[SILAGE], nil)
|
||||||
|
|
||||||
-- X: udkørsel koblet på getNextPickup-wrap'en og Wait-logikken
|
-- X: udkørsel koblet på getNextPickup-wrap'en og Wait-logikken
|
||||||
resetWorld()
|
resetWorld()
|
||||||
addMarker(1, "Suger Load", "Pickup points", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[WHEAT] = 1000}))
|
addMarker(1, "Suger Load", "Pickup points", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[WHEAT] = 1000}))
|
||||||
|
|||||||
@@ -206,5 +206,22 @@ check("NF1 ikke-foder (halm) påvirkes ikke", ADSupplyPlanner.isFoodAllowed(stra
|
|||||||
check("NF1 intet bedre foder på listen -> tilladt", ADSupplyPlanner.isFoodAllowed({fillType = 7, priority = 0.8, score = 0.1, trough = true}, 0.8), true)
|
check("NF1 intet bedre foder på listen -> tilladt", ADSupplyPlanner.isFoodAllowed({fillType = 7, priority = 0.8, score = 0.1, trough = true}, 0.8), true)
|
||||||
check("NF1 bedste foderprioritet", ADSupplyPlanner.bestFoodPriority({tmrNeed, silageNeed, strawNeed}), 1.0)
|
check("NF1 bedste foderprioritet", ADSupplyPlanner.bestFoodPriority({tmrNeed, silageNeed, strawNeed}), 1.0)
|
||||||
|
|
||||||
|
-- Læs på vej (andre traktorer): lægges oven i målets niveau; foder deler trug -> hele trugets fri plads falder
|
||||||
|
local pendingNeeds = {
|
||||||
|
{fillType = 31, level = 5000, capacity = 20000}, -- halm
|
||||||
|
{fillType = 116, level = 0, capacity = 30000, scoreOverride = 1.0, troughCapacity = 30000}, -- TMR, tomt trug
|
||||||
|
{fillType = 7, level = 0, capacity = 30000, scoreOverride = 1.0, troughCapacity = 30000}, -- ensilage
|
||||||
|
}
|
||||||
|
local adjusted = ADSupplyPlanner.applyPending(pendingNeeds, {[116] = 20000, [31] = 20000})
|
||||||
|
check("AP halm: niveau + på vej, højst kapacitet", adjusted[1].level, 20000)
|
||||||
|
check("AP TMR: niveau + på vej", adjusted[2].level, 20000)
|
||||||
|
check("AP TMR: kapacitet = niveau + resterende fri", adjusted[2].capacity, 30000)
|
||||||
|
check("AP ensilage: truget har kun 10000 fri tilbage", adjusted[3].capacity - adjusted[3].level, 10000)
|
||||||
|
check("AP trug-score falder", math.floor(adjusted[3].scoreOverride * 100 + 0.5), 33)
|
||||||
|
check("AP input urørt", pendingNeeds[2].level, 0)
|
||||||
|
check("AP intet på vej -> samme værdier", ADSupplyPlanner.applyPending(pendingNeeds, {})[1].level, 5000)
|
||||||
|
local ranked = ADSupplyPlanner.rankNeeds(adjusted, 8000)
|
||||||
|
check("AP halm fuld (på vej) -> kun trug-varer tilbage", #ranked, 2)
|
||||||
|
|
||||||
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