feat: behovsstyret forsyning — én traktor holder alle input fyldt

This commit is contained in:
2026-09-21 12:52:46 +02:00
parent a7d7d7815f
commit fb87dc4d37
2 changed files with 306 additions and 40 deletions
+123 -3
View File
@@ -2,6 +2,7 @@
-- Mock'er de få FS25-/AutoDrive-API'er moden rører og tester valg-logikken.
local SUGARBEET, WHEAT = 10, 11
local HAY, GRASS, SILAGE = 20, 21, 22
local logLines = {}
-- ---- FS25-mocks ----
@@ -31,9 +32,30 @@ local function makeStation(node, x, z, levels, isBroken, ownLevels)
}
end
local stations = {}
-- Aflæsningsmål (fx TMR-blander): ét lager med niveau + kapacitet pr. vare.
local function makeUnloadStation(node, x, z, levels, capacities, placeable, isBroken)
nodePositions[node] = {x = x, z = z}
local storage = {
getFillLevel = function(_, fillType)
if isBroken then error("boom") end
return levels[fillType] or 0
end,
getFreeCapacity = function(_, fillType) return (capacities[fillType] or 0) - (levels[fillType] or 0) end,
}
return {
owningPlaceable = placeable,
unloadTriggers = {{exactFillRootNode = node}},
targetStorages = {[storage] = storage},
getIsFillTypeSupported = function(_, fillType) return capacities[fillType] ~= nil end,
}
end
local stations, unloadStations = {}, {}
g_currentMission = {
storageSystem = {getLoadingStations = function() return stations end},
storageSystem = {
getLoadingStations = function() return stations end,
getUnloadingStations = function() return unloadStations end,
},
accessHandler = {canFarmAccess = function() return true end},
}
@@ -45,7 +67,7 @@ local function addMarker(index, name, group, x, z)
end
local function resetWorld()
stations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}
stations, unloadStations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}, {}
FS25_AutoDrive.ADMultipleTargetsManager.pickups = {}
end
@@ -58,6 +80,7 @@ FS25_AutoDrive = {
return result
end,
getWayPointById = function(_, id) return wayPoints[id] end,
getMapMarkers = function() return markers end,
},
}
@@ -67,12 +90,31 @@ local function makeDriver(firstMarker, fillTypes)
getOwnerFarmId = function() return 1 end,
ad = {stateModule = {
getFirstMarker = function() return firstMarker end,
getSecondMarker = function() return nil end,
getSelectedFillTypes = function() return fillTypes end,
getFillType = function() return fillTypes[1] end,
}},
}
end
-- Muterbar stateModule-mock: behovsstyringen skifter vare og indsnævrer AD's valg.
local function makeSupplyDriver(firstMarker, secondMarker, fillTypes)
local stateModule = {selectedFillTypes = fillTypes, fillType = fillTypes[1], active = true, dirtyCount = 0}
stateModule.getFirstMarker = function() return firstMarker end
stateModule.getSecondMarker = function() return secondMarker end
stateModule.getSelectedFillTypes = function(self) return self.selectedFillTypes end
stateModule.getFillType = function(self) return self.fillType end
stateModule.setFillType = function(self, fillType) self.fillType = fillType end
stateModule.raiseDirtyFlag = function(self) self.dirtyCount = self.dirtyCount + 1 end
stateModule.isActive = function(self) return self.active end
return {
getName = function() return "Fodertraktor" end,
getOwnerFarmId = function() return 1 end,
ad = {stateModule = stateModule},
}
end
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
dofile("FS25_ADSmartPickup/adSmartPickup.lua")
listener:update(16) -- installerer hook'en
local manager = FS25_AutoDrive.ADMultipleTargetsManager
@@ -134,5 +176,83 @@ check("fejl logges som advarsel", logLines[#logLines]:sub(1, 4), "WARN")
check("gruppe All -> fallback", manager:getNextPickup(makeDriver({group = "All", markerIndex = 1}, {SUGARBEET}), false), "ORIGINAL")
check("ingen første markør -> fallback", manager:getNextPickup(makeDriver(nil, {SUGARBEET}), false), "ORIGINAL")
-- ==== Behovsstyret forsyning (flere varer valgt + aflæsningsmål med lager) ====
local BLANDER = {name = "TMR-blander placeable"}
local FULL = {[HAY] = 100000, [GRASS] = 100000, [SILAGE] = 100000}
local blanderLevels
local function buildFeedWorld()
resetWorld()
blanderLevels = {[HAY] = 80000, [GRASS] = 20000, [SILAGE] = 50000}
addMarker(1, "Hølade", "Lader", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[HAY] = 50000}))
addMarker(2, "Græssilo stor", "Siloer", 500, 0); table.insert(stations, makeStation(102, 500, 15, {[GRASS] = 300000}))
addMarker(3, "Græssilo lille", "Andet", 1000, 0);table.insert(stations, makeStation(103, 1000, 15, {[GRASS] = 40000}))
addMarker(4, "Ensilagesilo", "Siloer", 1500, 0); table.insert(stations, makeStation(104, 1500, 15, {[SILAGE] = 200000}))
addMarker(10, "TMR-blander", "Produktion", 5000, 0)
table.insert(unloadStations, makeUnloadStation(110, 5000, 10, blanderLevels, FULL, BLANDER))
end
local function markerByIndex(index)
for _, m in ipairs(markers) do if m.markerIndex == index then return m end end
end
-- S1: mest trængende ingrediens vælges, kilde findes på tværs af mapper, AD's vare skiftes
buildFeedWorld()
local feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE})
check("S1 mest trængende vare, mindst fyldte kilde", manager:getNextPickup(feeder, false), 3)
check("S1 AD's vare skiftet til græs", feeder.ad.stateModule.fillType, GRASS)
check("S1 AD's valg indsnævret til én vare", #feeder.ad.stateModule.selectedFillTypes, 1)
check("S1 ændring synkroniseres (dirty-flag)", feeder.ad.stateModule.dirtyCount > 0, true)
check("S1 rotationstilstand følger valgt markørs mappe", manager.pickups["Andet"].lastTarget, 3)
-- S2: næste tur — gemt liste genbruges; græs er nu fuldt og springes over -> ensilage
blanderLevels[GRASS] = 99500
check("S2 fuld ingrediens springes over", manager:getNextPickup(feeder, false), 4)
check("S2 vare skiftet til ensilage", feeder.ad.stateModule.fillType, SILAGE)
-- S3: ingrediens uden kilde springes over -> næste i behovs-rækkefølgen
blanderLevels[SILAGE] = 10000
stations[4] = makeStation(104, 1500, 15, {[SILAGE] = 0})
check("S3 ingen kilde -> næste ingrediens", manager:getNextPickup(feeder, false), 1)
check("S3 vare skiftet til hø", feeder.ad.stateModule.fillType, HAY)
-- S4: AD stopper -> hele ingredienslisten gendannes i AD's varevalg
feeder.ad.stateModule.active = false
listener:update(16)
check("S4 valg gendannet ved stop", #feeder.ad.stateModule.selectedFillTypes, 3)
-- S5: aflæsningsmålets egen læssestation bruges ikke som kilde
buildFeedWorld()
addMarker(11, "Blander udtag", "Produktion", 5030, 0)
table.insert(stations, makeStation(111, 5030, 15, {[GRASS] = 10}, false, nil))
stations[#stations].owningPlaceable = BLANDER
feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS})
check("S5 målets egen station udelukkes som kilde", manager:getNextPickup(feeder, false), 3)
-- S6: brugeren vælger selv en anden enkelt vare -> behovsstyring fra (mappe-logik)
feeder.ad.stateModule.selectedFillTypes = {HAY}
feeder.ad.stateModule.fillType = HAY
check("S6 brugerens enkelt-valg -> mappe-logik", manager:getNextPickup(feeder, false), 1)
check("S6 vare røres ikke", feeder.ad.stateModule.fillType, HAY)
-- S7: intet behov (alt fuldt) -> mappe-logik, valg indsnævres ikke
buildFeedWorld()
blanderLevels[HAY], blanderLevels[GRASS], blanderLevels[SILAGE] = 99900, 99900, 99900
feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE})
check("S7 intet behov -> mappe-logik", manager:getNextPickup(feeder, false), 1)
check("S7 valg ikke indsnævret", #feeder.ad.stateModule.selectedFillTypes, 3)
-- S8: ingen aflæsningsstation ved målet -> mappe-logik
buildFeedWorld()
unloadStations = {}
feeder = makeSupplyDriver(markerByIndex(2), markerByIndex(10), {GRASS, SILAGE})
check("S8 ingen station ved mål -> mappe-logik", manager:getNextPickup(feeder, false), 4)
-- S9: fejl i målets lager-API -> AutoDrives original + advarsel
buildFeedWorld()
unloadStations = {makeUnloadStation(110, 5000, 10, blanderLevels, FULL, BLANDER, true)}
feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS})
check("S9 fejl giver fallback", manager:getNextPickup(feeder, false), "ORIGINAL")
check("S9 fejl logges som advarsel", logLines[#logLines]:sub(1, 4), "WARN")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)