-- Kør: luajit tests/test_adSmartPickup.lua (fra repo-roden) -- 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 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, getFillTypeIndexByName = function(_, name) for index, candidate in pairs(FILL_TYPE_NAMES) do if candidate == name then return index end end return nil end, } local logLines = {} -- ---- FS25-mocks ---- MathUtil = {vector2Length = function(x, z) return math.sqrt(x * x + z * z) end} Logging = { info = function(fmt, ...) table.insert(logLines, string.format(fmt, ...)) end, warning = function(fmt, ...) table.insert(logLines, "WARN " .. string.format(fmt, ...)) end, } local nodePositions = {} function entityExists(node) return nodePositions[node] ~= nil end function getWorldTranslation(node) local p = nodePositions[node]; return p.x, 0, p.z end local listener function addModEventListener(l) listener = l end local function makeStation(node, x, z, levels, isBroken, ownLevels) nodePositions[node] = {x = x, z = z} local placeable = ownLevels ~= nil and {spec_silo = {storages = { {getFillLevel = function(_, fillType) return ownLevels[fillType] or 0 end}, }}} or nil return { owningPlaceable = placeable, loadTriggers = {{triggerNode = node}}, unloadTriggers = {{exactFillRootNode = node}}, getFillLevel = function(_, fillType) if isBroken then error("boom") end return levels[fillType] or 0 end, getIsFillTypeSupported = function(_, fillType) return levels[fillType] ~= nil or (ownLevels ~= nil and ownLevels[fillType] ~= nil) end, getFreeCapacity = function(_, fillType) return (levels.capacity or 200000) - (levels[fillType] or 0) end, } end -- 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, getUnloadingStations = function() return unloadStations end, }, accessHandler = {canFarmAccess = function() return true end}, getFarmId = function() return 1 end, } -- ---- AutoDrive-mocks ---- local markers, wayPoints = {}, {} -- fromX/fromZ: forrige waypoint på ruten ind til markøren (giver ankomstretningen) local function addMarker(index, name, group, x, z, fromX, fromZ) wayPoints[index] = {x = x, z = z, incoming = {}} if fromX ~= nil then wayPoints[1000 + index] = {x = fromX, z = fromZ, incoming = {}} wayPoints[index].incoming = {1000 + index} end table.insert(markers, {id = index, markerIndex = index, name = name, group = group}) end local function resetWorld() if ADSmartPickup ~= nil and ADSmartPickup.resetEnRoute ~= nil then ADSmartPickup.resetEnRoute() end stations, unloadStations, markers, wayPoints, nodePositions, logLines = {}, {}, {}, {}, {}, {} FS25_AutoDrive.ADMultipleTargetsManager.pickups = {} end local RT_NONE, RT_ONLYPICKUP = 1, 2 local testVehicles = {} FS25_AutoDrive = { AutoDrive = { RT_NONE = RT_NONE, getSetting = function(settingName) return settingName == "rotateTargets" and RT_ONLYPICKUP or "andet" end, getAllFillLevels = function(trailers) return trailers.level, trailers.capacity or 0, false, 0 end, getAllUnits = function(vehicle) return vehicle.trailers or {level = 0} end, getAllDischargeableUnits = function(vehicle) return vehicle.units or {} end, getSupportedFillTypesOfAllUnitsAlphabetically = function(vehicle) return vehicle.carriable or {} end, getAllVehicles = function() return testVehicles end, }, -- opgaven noterer hvad AutoDrive "ser" af indstillinger mens den opdaterer LoadAtDestinationTask = {update = function(self) self.seenRotate = FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", self.vehicle) self.seenOther = FS25_AutoDrive.AutoDrive.getSetting("useFolders", self.vehicle) if self.shouldFail then error("task boom") end end}, ADMultipleTargetsManager = {pickups = {}, getNextPickup = function() return "ORIGINAL" end}, -- aflæsningsopgaven: original update tæller bare kald; finished markerer UnloadAtDestinationTask = { STATE_PATHPLANNING = 1, STATE_DRIVING = 2, update = function(self) self.updates = (self.updates or 0) + 1 end, finished = function(self) self.isFinished = true end, }, ADGraphManager = { getMapMarkersInGroup = function(_, group) local result = {} for _, m in ipairs(markers) do if m.group == group then table.insert(result, m) end end return result end, getWayPointById = function(_, id) return wayPoints[id] end, getMapMarkers = function() return markers end, getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end, -- ruter: alt nåeligt medmindre testen sætter unreachable[id] (til eller fra) unreachable = {}, getPathTo = function(self, vehicle, id) if self.unreachable[id] then return {} end; return {{id = id}} end, pathFromTo = function(self, fromId, toId) if self.unreachable[fromId] or self.unreachable[toId] then return {} end; return {{id = fromId}, {id = toId}} end, }, } local function makeDriver(firstMarker, fillTypes) return { getName = function() return "Testtraktor" end, 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 -- Køretøjet er "fremme ved markøren" medmindre testen siger andet. local function withDrivePath(driver) driver.targetReached = true driver.ad.drivePathModule = {isTargetReached = function() return driver.targetReached end} return driver 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/adRuns.lua") dofile("FS25_ADSmartPickup/adRunsController.lua") dofile("FS25_ADSmartPickup/adLoadSwap.lua") dofile("FS25_ADSmartPickup/adWaitPool.lua") dofile("FS25_ADSmartPickup/adUnloadWait.lua") dofile("FS25_ADSmartPickup/adSmartPickup.lua") listener:update(16) -- installerer hook'en local manager = FS25_AutoDrive.ADMultipleTargetsManager local failures = 0 local function check(name, actual, expected) if actual == expected then print("OK " .. name) else failures = failures + 1 print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual))) end end -- 1: vælger mindst fyldte silo med varen; springer tomme og forkert vare over resetWorld() addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[WHEAT] = 900000})) addMarker(2, "Silo 2", "Siloer", 500, 0); table.insert(stations, makeStation(102, 500, 15, {[SUGARBEET] = 800000})) addMarker(3, "Silo 3", "Siloer", 1000, 0); table.insert(stations, makeStation(103, 1000, 15, {[SUGARBEET] = 12000})) addMarker(4, "Silo 4", "Siloer", 1500, 0); table.insert(stations, makeStation(104, 1500, 15, {[SUGARBEET] = 0})) addMarker(5, "Fabrik", "Andet", 1000, 5); -- anden mappe, skal ignoreres check("mindst fyldte med varen vælges", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET}), false), 3) check("AD-rotationstilstand opdateret", manager.pickups["Siloer"].lastTarget, 3) -- 2: ingen silo har varen -> AutoDrives originale rotation check("fallback når varen ikke findes", manager:getNextPickup(makeDriver(markers[1], {99}), false), "ORIGINAL") -- 3: markør uden silo i nærheden ignoreres resetWorld() addMarker(1, "Langt væk", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 200, {[SUGARBEET] = 5})) addMarker(2, "Silo 2", "Siloer", 500, 0); table.insert(stations, makeStation(102, 500, 15, {[SUGARBEET] = 700})) check("markør uden nær silo ignoreres", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET}), false), 2) -- 4: flere valgte varer lægges sammen resetWorld() addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[SUGARBEET] = 100, [WHEAT] = 5000})) addMarker(2, "Silo 2", "Siloer", 500, 0); table.insert(stations, makeStation(102, 500, 15, {[SUGARBEET] = 3000})) check("flere varetyper summeres", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET, WHEAT}), false), 2) -- 4b: klynge med delt lager — eget indhold slår nabo-sum, også når nabo-summen er mindre resetWorld() addMarker(1, "Nabo", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[SUGARBEET] = 800}, false, {})) addMarker(2, "Ejer stor", "Siloer", 14, 0); table.insert(stations, makeStation(102, 14, 15, {[SUGARBEET] = 900000}, false, {[SUGARBEET] = 500000})) addMarker(3, "Ejer lille", "Siloer", 28, 0);table.insert(stations, makeStation(103, 28, 15, {[SUGARBEET] = 900000}, false, {[SUGARBEET] = 20000})) check("eget lager foran nabo-lån, mindst først", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET}), false), 3) -- 4c: kun nabo-lån muligt -> bruges stadig resetWorld() addMarker(1, "Nabo", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[SUGARBEET] = 800}, false, {})) check("nabo-lån bruges når ingen ejer varen", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET}), false), 1) -- 5: fejl i spil-API -> fallback + advarsel, aldrig crash resetWorld() addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {}, true)) check("fejl giver fallback", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET}), false), "ORIGINAL") check("fejl logges som advarsel", logLines[#logLines]:sub(1, 4), "WARN") -- 6: gruppen "All" og manglende første markør røres ikke 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") -- S10: stationen eksponerer ikke targetStorages -> lageret læses fra produktionspunktet buildFeedWorld() local bareStation = unloadStations[1] local productionStorage = next(bareStation.targetStorages) bareStation.targetStorages = nil bareStation.owningPlaceable = {spec_productionPoint = {productionPoint = {storage = productionStorage}}} feeder = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS, SILAGE}) check("S10 lager læses fra produktionspunktet", manager:getNextPickup(feeder, false), 3) -- ==== Fabriksstyret forsyning (aflæsningsmålet er et produktionspunkt) ==== ProductionPoint = {PROD_STATUS = {INACTIVE = 0, RUNNING = 1, MISSING_INPUTS = 2, NO_OUTPUT_SPACE = 3}} local WATER = 40 FILL_TYPE_NAMES[WATER] = "WATER" -- Fabrik med TMR-opskrift: sluger ensilage, nipper til hø. Vand kan vognen ikke bære. local function buildFactoryWorld(tmrEnabled, tmrStatus) buildFeedWorld() local pp = { enabled = {tmr = tmrEnabled}, productions = { {id = "tmr", cyclesPerMinute = 1, status = tmrStatus, inputs = {{type = SILAGE, amount = 250}, {type = HAY, amount = 10}, {type = WATER, amount = 1000}}}, {id = "off", cyclesPerMinute = 1, status = ProductionPoint.PROD_STATUS.INACTIVE, inputs = {{type = GRASS, amount = 500}}}, }, getIsProductionEnabled = function(self, id) return self.enabled[id] == true end, } local factory = {spec_productionPoint = {productionPoint = pp}, getName = function() return "AIO" end} unloadStations = {makeUnloadStation(110, 5000, 10, blanderLevels, {[HAY] = 100000, [GRASS] = 100000, [SILAGE] = 100000, [WATER] = 100000}, factory)} local driver = makeSupplyDriver(markerByIndex(1), markerByIndex(10), {HAY, GRASS}) driver.carriable = {HAY, GRASS, SILAGE} return driver, pp end -- F1: tændt produktion styrer listen: ensilage (200 min til tom) vinder over hø (8000 min) -- og over græs, som er relativt tommest men ikke et input i fabrikken local factoryDriver = buildFactoryWorld(true, ProductionPoint.PROD_STATUS.RUNNING) check("F1 fabrikkens input med kortest tid til tom", manager:getNextPickup(factoryDriver, false), 4) check("F1 AD's vare skiftet til ensilage (ikke i brugerens valg)", factoryDriver.ad.stateModule.fillType, SILAGE) check("F1 log nævner minutter til tom", logLines[#logLines]:find("min til tom", 1, true) ~= nil, true) -- F2: næste tur — listen genudledes fra fabrikken, ikke fra brugerens valg blanderLevels[SILAGE] = 99500 check("F2 fuld ensilage -> hø (næste fabriks-input)", manager:getNextPickup(factoryDriver, false), 1) -- F3: AD stopper -> brugerens OPRINDELIGE valg gendannes, ikke fabrikkens liste factoryDriver.ad.stateModule.active = false listener:update(16) check("F3 brugerens valg gendannet ved stop", #factoryDriver.ad.stateModule.selectedFillTypes, 2) -- F4: manuelt slukket produktion -> fallback til brugerens valg (græs er tommest) factoryDriver = buildFactoryWorld(false, ProductionPoint.PROD_STATUS.INACTIVE) local function countLogsSince(startIndex, needle) local count = 0 for i = startIndex + 1, #logLines do if logLines[i]:find(needle, 1, true) then count = count + 1 end end return count end local logMark = #logLines check("F4 slukket fabrik -> brugerens valg", manager:getNextPickup(factoryDriver, false), 3) check("F4 loggen forklarer fallback", countLogsSince(logMark, "ingen ønskede produktioner"), 1) manager:getNextPickup(factoryDriver, false) check("F4 forklaringen logges kun én gang", countLogsSince(logMark, "ingen ønskede produktioner"), 1) -- F5: auto-slukket af kortet pga. manglende input -> stadig fabrikkens liste factoryDriver = buildFactoryWorld(false, ProductionPoint.PROD_STATUS.MISSING_INPUTS) check("F5 auto-slukket fabrik fodres stadig", manager:getNextPickup(factoryDriver, false), 4) -- F6: produktionspunkt uden getIsProductionEnabled -> activeProductions-listen bruges local fallbackDriver, fallbackPp = buildFactoryWorld(false, ProductionPoint.PROD_STATUS.RUNNING) fallbackPp.getIsProductionEnabled = nil fallbackPp.activeProductions = {fallbackPp.productions[1]} check("F6 activeProductions som fallback", manager:getNextPickup(fallbackDriver, false), 4) -- ==== En fabriks inputs stjæles aldrig (halm+TMR til kostald; TMR-fabrikken har halm som input) ==== local STRAW, TMR = 41, 42 FILL_TYPE_NAMES[STRAW], FILL_TYPE_NAMES[TMR] = "STRAW", "FORAGE" local cowLevels local function buildCowWorld() resetWorld() local tmrFactory = { getName = function() return "TMR-fabrik" end, spec_productionPoint = {productionPoint = {productions = { {id = "tmr", cyclesPerMinute = 1, inputs = {{type = STRAW, amount = 10}, {type = SILAGE, amount = 100}}, outputs = {{type = TMR, amount = 50}}}, }}}, } -- fabrikkens læssestation har BÅDE halm (input, mindst fyldt) og TMR (output) addMarker(20, "TMR Load", "Fabrication", 0, 0) table.insert(stations, makeStation(120, 0, 15, {[STRAW] = 5000, [TMR] = 30000})) stations[#stations].owningPlaceable = tmrFactory addMarker(21, "Halmlade", "Lader", 500, 0) table.insert(stations, makeStation(121, 500, 15, {[STRAW] = 50000})) addMarker(30, "Cow 1 Food", "Cow Food", 5000, 0) cowLevels = {[STRAW] = 0, [TMR] = 40000} table.insert(unloadStations, makeUnloadStation(130, 5000, 10, cowLevels, {[STRAW] = 100000, [TMR] = 100000}, {name = "kostald"})) local driver = makeSupplyDriver(markerByIndex(21), markerByIndex(30), {TMR, STRAW}) driver.carriable = {TMR, STRAW} return driver end -- P1: halm mangler mest -> hentes fra halmladen, IKKE fra fabrikken (selv om den er mindst fyldt) local cowDriver = buildCowWorld() check("P1 halm hentes fra siloen, ikke fabrikkens input", manager:getNextPickup(cowDriver, false), 21) -- P2: halm fuld -> TMR hentes gerne fra fabrikken (det er dens output) cowLevels[STRAW] = 99500 check("P2 fabrikkens output må hentes", manager:getNextPickup(cowDriver, false), 20) -- P3: mappe-logik (én vare) beskytter også fabrikkens input cowDriver = buildCowWorld() addMarker(22, "Halmlade 2", "Fabrication", 1000, 0) table.insert(stations, makeStation(122, 1000, 15, {[STRAW] = 60000})) local strawDriver = makeDriver(markerByIndex(20), {STRAW}) check("P3 mappe-logik springer fabrikkens input over", manager:getNextPickup(strawDriver, false), 22) -- P4: stald som mål — niveau/kapacitet læses fra stalden selv (getHusbandry*), ikke fra -- stationens lagre; stationen selv "understøtter" kun foder, men stalden tager også halm cowDriver = buildCowWorld() local barnLevels = {[STRAW] = 45000, [TMR] = 10000} local barnCapacity = {[STRAW] = 50000, [TMR] = 100000} local barn = { getName = function() return "Kostald" end, getHusbandryFillLevel = function(_, fillType) return barnLevels[fillType] or 0 end, getHusbandryCapacity = function(_, fillType) return barnCapacity[fillType] or 0 end, getHusbandryFreeCapacity = function(_, fillType) return (barnCapacity[fillType] or 0) - (barnLevels[fillType] or 0) end, getHusbandryIsFillTypeSupported = function(_, fillType) return barnCapacity[fillType] ~= nil end, } local barnStation = unloadStations[1] barnStation.targetStorages = nil barnStation.owningPlaceable = barn barnStation.getIsFillTypeSupported = function(_, fillType) return fillType == TMR end barnStation.getFreeCapacity = function(_, fillType) return 999999 end check("P4 stald: TMR (90 % tom) hentes før halm (10 % tom)", manager:getNextPickup(cowDriver, false), 20) barnLevels[TMR] = 95000 check("P4 stald: halm hentes når TMR er fuld", manager:getNextPickup(cowDriver, false), 21) -- ==== Wait-punkter: rest-læs + fuld stald -> vent ved nærmeste ledige Wait, tilbage når resten passer ==== local UnloadTask = FS25_AutoDrive.UnloadAtDestinationTask local waitBarnLevels, waitBarnCapacity = {}, {[STRAW] = 50000, [TMR] = 100000} local waitVehicles = {} local function buildWaitWorld() -- tidligere testkøretøjer slipper deres Wait-punkter (AD "stoppes" på dem) for _, vehicle in ipairs(waitVehicles) do vehicle.ad.stateModule.active = false end listener:update(16) waitVehicles = {} buildCowWorld() waitBarnLevels = {[STRAW] = 50000, [TMR] = 10000} -- halm FULD local barn2 = { getName = function() return "Kostald" end, getHusbandryFillLevel = function(_, fillType) return waitBarnLevels[fillType] or 0 end, getHusbandryCapacity = function(_, fillType) return waitBarnCapacity[fillType] or 0 end, getHusbandryFreeCapacity = function(_, fillType) return (waitBarnCapacity[fillType] or 0) - (waitBarnLevels[fillType] or 0) end, getHusbandryIsFillTypeSupported = function(_, fillType) return waitBarnCapacity[fillType] ~= nil end, } unloadStations[1].targetStorages = nil unloadStations[1].owningPlaceable = barn2 addMarker(40, "Wait 1", "Cow Wait", 5100, 0) addMarker(41, "Wait 2", "Cow Wait", 5300, 0) end -- traktor der holder ved stalden (markør 30) med rest-læs halm på vognen local function makeUnloadingVehicle(name, node, x, restStraw) local vehicle = makeSupplyDriver(markerByIndex(21), markerByIndex(30), {TMR, STRAW}) vehicle.ad.stateModule.secondMarkerId = 30 vehicle.ad.stateModule.setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end vehicle.ad.stateModule.getSecondMarker = function(self) return markerByIndex(self.secondMarkerId) end vehicle.getName = function() return name end vehicle.rootNode = node nodePositions[node] = {x = x, z = 10} vehicle.units = {{object = { getFillUnitFillLevel = function() return restStraw end, getFillUnitFillType = function() return STRAW end, }, fillUnitIndex = 1}} vehicle.trailers = {level = restStraw, capacity = 40000} vehicle.targetReached = true vehicle.ad.drivePathModule = { isTargetReached = function() return vehicle.targetReached end, setPathTo = function(self, id) self.pathTo = id end, } vehicle.ad.trailerModule = {isActiveAtTrigger = function() return false end} local task = {vehicle = vehicle, destinationID = 30, state = UnloadTask.STATE_DRIVING, trailers = vehicle.trailers} table.insert(waitVehicles, vehicle) return vehicle, task end -- W1: fuld stald + rest-læs -> omdirigeres til nærmeste ledige Wait; AD's original kaldes stadig buildWaitWorld() local v1, t1 = makeUnloadingVehicle("Fastrac A", 501, 5000, 35000) local waitLogMark = #logLines UnloadTask.update(t1, 16) check("W1 omdirigeret til nærmeste Wait", t1.destinationID, 40) check("W1 rute lagt til Wait", v1.ad.drivePathModule.pathTo, 40) check("W1 original update kaldt", t1.updates, 1) check("W1 log fortæller om ventning", countLogsSince(waitLogMark, "Wait 1"), 1) -- W2: anden traktor i samme situation -> Wait 1 er optaget -> Wait 2 local v2, t2 = makeUnloadingVehicle("Fastrac B", 502, 5010, 20000) UnloadTask.update(t2, 16) check("W2 optaget Wait springes over", t2.destinationID, 41) -- W3: mens A venter, skjules rotationen (ellers kører AD videre fra Wait-punktet ved "rotér aflæsning") check("W3 rotation skjult for ventende", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", v1), RT_NONE) check("W3 andre indstillinger urørt", FS25_AutoDrive.AutoDrive.getSetting("useFolders", v1), "andet") -- W4: stalden får plads til HELE resten -> A sendes hjem, Wait 1 frigives, rotation tilbage waitBarnLevels[STRAW] = 10000 -- 40000 fri >= 35000 v1.targetReached = true -- står ved Wait 1 UnloadTask.update(t1, 16) check("W4 sendt tilbage til stalden", t1.destinationID, 30) check("W4 rute lagt hjem", v1.ad.drivePathModule.pathTo, 30) check("W4 rotation gendannet", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", v1), RT_ONLYPICKUP) local v3, t3 = makeUnloadingVehicle("Fastrac C", 503, 5020, 30000) waitBarnLevels[STRAW] = 50000 UnloadTask.update(t3, 16) check("W4 Wait 1 er ledig igen", t3.destinationID, 40) -- W5: plads til noget, men ikke hele resten -> B bliver ved Wait (kun hele læs kommer af) waitBarnLevels[STRAW] = 45000 -- 5000 fri < 20000 UnloadTask.update(t2, 16) check("W5 delvis plads -> venter stadig", t2.destinationID, 41) -- W6: stalden har plads ved ankomst -> ingen omdirigering (AD læsser af som normalt) buildWaitWorld() waitBarnLevels[STRAW] = 10000 local v6, t6 = makeUnloadingVehicle("Fastrac D", 506, 5000, 35000) UnloadTask.update(t6, 16) check("W6 plads ved ankomst -> uændret mål", t6.destinationID, 30) -- W7: ingen Wait-punkter -> uændret (AD venter som i dag) + én advarsel buildCowWorld() unloadStations[1].targetStorages = nil unloadStations[1].owningPlaceable = { getHusbandryFillLevel = function() return 50000 end, getHusbandryCapacity = function() return 50000 end, getHusbandryFreeCapacity = function() return 0 end, getHusbandryIsFillTypeSupported = function() return true end } local v7, t7 = makeUnloadingVehicle("Fastrac E", 507, 5000, 35000) local warnMark = #logLines UnloadTask.update(t7, 16); UnloadTask.update(t7, 16) check("W7 uden Wait-punkter -> uændret mål", t7.destinationID, 30) check("W7 én advarsel", countLogsSince(warnMark, "WARN"), 1) -- W8: to andre holder begge punkter; stoppes de i AD, frigives punkterne buildWaitWorld() local vA, tA = makeUnloadingVehicle("Fastrac A2", 521, 5000, 35000) local vB, tB = makeUnloadingVehicle("Fastrac B2", 522, 5000, 35000) UnloadTask.update(tA, 16); UnloadTask.update(tB, 16) local v8, t8 = makeUnloadingVehicle("Fastrac F", 508, 5000, 35000) UnloadTask.update(t8, 16) check("W8 alle Wait-punkter optaget -> uændret", t8.destinationID, 30) vA.ad.stateModule.active, vB.ad.stateModule.active = false, false listener:update(16) UnloadTask.update(t8, 16) check("W8 venter ved Wait 1 efter frigivelse", t8.destinationID, 40) UnloadTask.finished(t8) check("W8 original finished kaldt", t8.isFinished, true) local v9, t9 = makeUnloadingVehicle("Fastrac G", 509, 5000, 35000) UnloadTask.update(t9, 16) check("W8 Wait 1 frigivet efter finished", t9.destinationID, 40) check("W8 rotation gendannet efter finished", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", v8), RT_ONLYPICKUP) -- W9: intet vejnet til nærmeste Wait -> næste; intet net til nogen -> uændret (AD slukker ellers sig selv) buildWaitWorld() FS25_AutoDrive.ADGraphManager.unreachable = {[40] = true} local v10, t10 = makeUnloadingVehicle("Fastrac H", 510, 5000, 35000) UnloadTask.update(t10, 16) check("W9 unåeligt Wait 1 -> Wait 2", t10.destinationID, 41) FS25_AutoDrive.ADGraphManager.unreachable = {[40] = true, [41] = true} local v11, t11 = makeUnloadingVehicle("Fastrac I", 511, 5000, 35000) local unreachableMark = #logLines UnloadTask.update(t11, 16) check("W9 intet nåeligt -> uændret mål", t11.destinationID, 30) check("W9 advarsel om manglende vejnet", countLogsSince(unreachableMark, "WARN"), 1) FS25_AutoDrive.ADGraphManager.unreachable = {} -- W10: kæmpe vogn (700.000 l rest) i 50.000 l-stald -> hjem når halvdelen af stalden er fri buildWaitWorld() local v12, t12 = makeUnloadingVehicle("Fastrac J", 512, 5000, 700000) UnloadTask.update(t12, 16) check("W10 venter", t12.destinationID, 40) waitBarnLevels[STRAW] = 30000 -- 20000 fri < 25000 UnloadTask.update(t12, 16) check("W10 under halv stald fri -> venter stadig", t12.destinationID, 40) waitBarnLevels[STRAW] = 20000 -- 30000 fri >= 25000 UnloadTask.update(t12, 16) check("W10 halv stald fri -> hjem", t12.destinationID, 30) -- L1: moden valgte siloen -> AutoDrive skal stå og læsse (rotation skjules under opgaven) resetWorld() local loadLevels = {[SUGARBEET] = 5000} addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, loadLevels)) local loader = withDrivePath(makeDriver(markers[1], {SUGARBEET})) manager:getNextPickup(loader, false) local task = {vehicle = loader, trailers = {level = 0}} FS25_AutoDrive.LoadAtDestinationTask.update(task, 16) check("L1 rotation skjult mens kilden har varen", task.seenRotate, RT_NONE) check("L1 andre indstillinger uberørt", task.seenOther, "andet") check("L1 rotation synlig igen efter opgaven", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", loader), RT_ONLYPICKUP) -- L8: PÅ VEJ til siloen ruller vognen gennem nabosiloers triggere -> dér skal -- AutoDrives rotation gælde (kør videre ved tom trigger), ikke "stå og vent" loader.targetReached = false FS25_AutoDrive.LoadAtDestinationTask.update(task, 16) check("L8 undervejs: rotation uberørt", task.seenRotate, RT_ONLYPICKUP) loader.targetReached = true FS25_AutoDrive.LoadAtDestinationTask.update(task, 16) check("L8 fremme ved markøren: står og læsser", task.seenRotate, RT_NONE) -- L2: andre køretøjer (ikke sendt af moden) beholder AutoDrives rotation local strangerTask = {vehicle = makeDriver(markers[1], {SUGARBEET})} FS25_AutoDrive.LoadAtDestinationTask.update(strangerTask, 16) check("L2 fremmed køretøj uberørt", strangerTask.seenRotate, RT_ONLYPICKUP) -- L3: kilden løber tør -> grebet slippes, AutoDrive kører videre med det den har loadLevels[SUGARBEET] = 0 FS25_AutoDrive.LoadAtDestinationTask.update(task, 16) check("L3 tom kilde -> rotation synlig igen", task.seenRotate, RT_ONLYPICKUP) -- L4: fejl i AutoDrives opgave må ikke efterlade rotationen skjult loadLevels[SUGARBEET] = 5000 task.shouldFail = true check("L4 fejl i opgaven sendes videre", pcall(FS25_AutoDrive.LoadAtDestinationTask.update, task, 16), false) check("L4 rotation synlig efter fejl", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", loader), RT_ONLYPICKUP) -- L5: AutoDrives original valgte (intet fund) -> ingen ventetid task.shouldFail = false loader.ad.stateModule.getSelectedFillTypes = function() return {WHEAT} end manager:getNextPickup(loader, false) FS25_AutoDrive.LoadAtDestinationTask.update(task, 16) check("L5 AutoDrives eget valg -> rotation uberørt", task.seenRotate, RT_ONLYPICKUP) -- G1: tæt silo-række, kørsel mod -x. Næsen stopper ved markøren, vognen holder et -- vogntog BAGVED -> markørens silo er den bag næsen, ikke den (nærmere) foran. resetWorld() addMarker(1, "1 Silo", "Siloer", 1243, 0, 1260, 0) addMarker(2, "2 Silo", "Siloer", 1228, 0, 1243, 0) table.insert(stations, makeStation(101, 1255, 1, {[HAY] = 9000}, false, {[HAY] = 9000})) -- bag markør 1 table.insert(stations, makeStation(102, 1239, 1, {[SUGARBEET] = 5000}, false, {[SUGARBEET] = 5000})) -- foran markør 1, bag markør 2 table.insert(stations, makeStation(103, 1224, 1, {}, false, {})) -- foran markør 2 check("G1 roer ligger i siloen bag markør 2", manager:getNextPickup(makeDriver(markers[1], {SUGARBEET}), false), 2) check("G1 hø ligger i siloen bag markør 1", manager:getNextPickup(makeDriver(markers[1], {HAY}), false), 1) -- G2: ingen trigger bag næsen (eller ukendt retning) -> nærmeste trigger som før resetWorld() addMarker(1, "Ensom silo", "Siloer", 100, 0, 120, 0) table.insert(stations, makeStation(101, 95, 1, {[HAY] = 9000})) check("G2 kun trigger foran -> nærmeste bruges", manager:getNextPickup(makeDriver(markers[1], {HAY}), false), 1) -- G3: tank med aflæsningsbane på den ene side og pålæsningsbane på den anden. -- Dropoff-markøren ligger 18 m ved siden af læsserøret -> ikke en pålæsningsplads, -- selv om den står først i listen og "ser" samme tank. resetWorld() addMarker(1, "1 Silage Dropof", "Ensilage", 1265, -2278, 1265, -2290) -- kører +z, røret 18 m til siden addMarker(2, "1 Silage Pickup", "Ensilage", 1247, -2278, 1247, -2266) -- kører -z, røret 7 m bag næsen 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) -- 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 resetWorld() addMarker(1, "7 Silage Pickup", "Ensilage", 100, 0, 100, 20) -- kører -z table.insert(stations, makeStation(101, 103, -8, {[SILAGE] = 4000})) -- 8 m foran, 3 m til siden check("G4 tæt trigger foran næsen bruges som nødløsning", manager:getNextPickup(makeDriver(markers[1], {SILAGE}), false), 1) -- L9: sikkerhedsventil — står den ved markøren uden at vognen fyldes, slippes grebet resetWorld() addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[SUGARBEET] = 5000})) local stuck = withDrivePath(makeDriver(markers[1], {SUGARBEET})) manager:getNextPickup(stuck, false) local stuckTask = {vehicle = stuck, trailers = {level = 0}} FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 10000) stuckTask.trailers.level = 800 -- der læsses: uret nulstilles FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 25000) FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 25000) check("L9 venter stadig mens der er læsset for nylig", stuckTask.seenRotate, RT_NONE) FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 10000) -- 35 s uden fremgang check("L9 ingen læsning i 30 s -> greb sluppet", stuckTask.seenRotate, RT_ONLYPICKUP) check("L9 sluppet greb logges som advarsel", logLines[#logLines]:sub(1, 4), "WARN") FS25_AutoDrive.LoadAtDestinationTask.update(stuckTask, 16) 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 resetWorld() addMarker(1, "Silo 1", "Andet", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[HAY] = 5000})) addMarker(2, "Blander", "Fabrik", 5000, 0) table.insert(unloadStations, makeUnloadStation(201, 5000, 15, {[HAY] = 0}, {[HAY] = 1000000, [GRASS] = 1000000}, {})) local stopper = withDrivePath(makeSupplyDriver(markers[1], markers[2], {HAY, GRASS})) manager:getNextPickup(stopper, false) local stopperTask = {vehicle = stopper, trailers = {level = 0}} FS25_AutoDrive.LoadAtDestinationTask.update(stopperTask, 16) check("L7 venter mens AD kører", stopperTask.seenRotate, RT_NONE) stopper.ad.stateModule.active = false listener:update(16) FS25_AutoDrive.LoadAtDestinationTask.update(stopperTask, 16) check("L7 greb ryddet når AD stopper", stopperTask.seenRotate, RT_ONLYPICKUP) -- W11: reservationer kan aflæses (GUI'ets Wait-puljer-visning) buildWaitWorld() local vc1, tc1 = makeUnloadingVehicle("Fastrac Claim", 531, 5000, 35000) UnloadTask.update(tc1, 16) local claims = ADUnloadWait.getClaims() check("W11 én reservation", #claims, 1) check("W11 markør", claims[1].marker.id, 40) check("W11 køretøj", claims[1].vehicle, vc1) -- W12: AD regner vognen som "aflæssende" ved et fuldt trug (isUnloading bliver hængende) — -- omdirigeres alligevel, og AD's aflæsning stoppes først buildWaitWorld() local vs, ts = makeUnloadingVehicle("Fastrac Tippet", 541, 5000, 35000) local stopped = false vs.ad.trailerModule = {isActiveAtTrigger = function() return true end, stopUnloading = function() stopped = true end, stopLoading = function() end} 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) -- E6 (bedste foder først): TMR-kilden er tom, truget halvfuldt, halm fuld -> INGEN ensilage/hø; en traktor -- i et forsynings-run holder ved Wait i stedet (in-game 24/9: Fastrac'erne fyldte truget med ensilage) e5Straw.level = 100000 e5Food[TMR], e5Food[HAY], e5Food[GRASS] = 0, 15000, 0 -- 50 % fri e5Driver.uniqueId = "vehicleE6" testVehicles = {e5Driver} ADRunsController.store = ADRuns.newStore() local e6Run = ADRunsController.createRun("Cow Food") ADRunsController.updateRun(e6Run.id, {targetWayPointId = 30, ingredientMode = "manual", ingredients = {"FORAGE", "DRYGRASS_WINDROW", "GRASS_WINDROW", "STRAW"}}) ADRunsController.assignVehicle(e6Run.id, e5Driver) local holdCalls = {} ADOutbound = { isOutboundVehicle = function() return false end, holdSupplyVehicle = function(adEnv, vehicle, run, reasons, replan) table.insert(holdCalls, {reasons = reasons, replan = replan}) return {markerIndex = 99, markerName = "Cow Wait", group = "Cow Wait", fillTypes = {}} end, releaseInactive = function() end, install = function() end, } check("E6 halvfuldt trug uden TMR: hold ved Wait", manager:getNextPickup(e5Driver, false), 99) check("E6 hold kaldt én gang", #holdCalls, 1) check("E6 replan finder intet endnu", holdCalls[1].replan(), nil) e5Food[HAY] = 3000 -- 90 % fri: nødlidende -> hø tilladt check("E6 replan: nødlidende trug -> hø", holdCalls[1].replan(), 23) check("E6 nødlidende trug: hø direkte", manager:getNextPickup(e5Driver, false), 23) ADOutbound = nil ADRunsController.store = ADRuns.newStore() testVehicles = {} g_currentMission.animalFoodSystem = nil -- W13: AD stopper vognen i triggeren FØR markøren (isTargetReached=false) — Wait udløses alligevel -- når vognen er inden for AD's trigger-afstand til aflæsningsstedet buildWaitWorld() local vn, tn = makeUnloadingVehicle("Fastrac Før markør", 581, 5000, 35000) vn.targetReached = false FS25_AutoDrive.AutoDrive.getDistanceToUnloadPosition = function() return 8.6 end FS25_AutoDrive.AutoDrive.getMaxTriggerDistance = function() return 15 end UnloadTask.update(tn, 16) check("W13 Wait selv om markøren ikke er nået", tn.destinationID, 40) FS25_AutoDrive.AutoDrive.getDistanceToUnloadPosition = nil FS25_AutoDrive.AutoDrive.getMaxTriggerDistance = nil -- W14: mens der ventes, peger AD's mål på Wait-punktet (så AD ikke tipper i småportioner); -- behovet måles stadig mod hjem-målet, og AD's mål gendannes når den kører hjem buildWaitWorld() local vm, tm = makeUnloadingVehicle("Fastrac Mål", 591, 5000, 35000) UnloadTask.update(tm, 16) check("W14 AD's mål = Wait-punkt under ventetid", vm.ad.stateModule.secondMarkerId, 40) waitBarnLevels[STRAW] = 10000 -- plads hjemme UnloadTask.update(tm, 16) check("W14 kører hjem (behov målt mod stalden, ikke Wait-punktet)", tm.destinationID, 30) check("W14 AD's mål gendannet", vm.ad.stateModule.secondMarkerId, 30) -- ==== 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() runDriver.uniqueId = "vehicleRun" runDriver.ad.stateModule.selectedFillTypes = {STRAW} runDriver.ad.stateModule.fillType = STRAW testVehicles = {runDriver} ADRunsController.store = ADRuns.newStore() local feedRun = ADRunsController.createRun("Cow Feed") ADRunsController.updateRun(feedRun.id, {targetWayPointId = 30, ingredientMode = "manual", ingredients = {"FORAGE", "STRAW"}}) ADRunsController.assignVehicle(feedRun.id, runDriver) check("I1 run-liste styrer: halm mangler -> halmladen", manager:getNextPickup(runDriver, false), 21) cowLevels[STRAW] = 99500 check("I1 run-liste styrer: TMR når halm er fuld", manager:getNextPickup(runDriver, false), 20) check("I1 getIngredientsFor = run'ets liste", table.concat(ADSmartPickup.getIngredientsFor(FS25_AutoDrive, runDriver), ","), TMR .. "," .. STRAW) -- I2: samme traktor uden run -> som før (én vare = mappe-logik i mappen "Lader") ADRunsController.store = ADRuns.newStore() runDriver.ad.stateModule.selectedFillTypes = {STRAW} cowLevels[STRAW] = 0 check("I2 uden run: mappe-logik", manager:getNextPickup(runDriver, false), 21) check("I2 uden run og én vare: ingen ingrediensliste", ADSmartPickup.getIngredientsFor(FS25_AutoDrive, runDriver), nil) -- I3: run med tom auto-liste (intet mål) -> falder tilbage til AD's valg local emptyRun = ADRunsController.createRun("Tomt") ADRunsController.assignVehicle(emptyRun.id, runDriver) check("I3 tom run-liste -> som uden run", manager:getNextPickup(runDriver, false), 21) -- I4: Wait-kandidater begrænses til run'ets pulje buildWaitWorld() addMarker(42, "Pig Wait 1", "Pig Wait", 5050, 0) -- nærmere end Cow Wait, men forkert pulje local vp, tp = makeUnloadingVehicle("Fastrac Pulje", 511, 5000, 35000) vp.uniqueId = "vehiclePool" testVehicles = {vp} ADRunsController.store = ADRuns.newStore() local poolRun = ADRunsController.createRun("Cow Feed") ADRunsController.updateRun(poolRun.id, {targetWayPointId = 30, waitPoolGroup = "Cow Wait"}) ADRunsController.assignVehicle(poolRun.id, vp) UnloadTask.update(tp, 16) check("I4 kun run'ets pulje", tp.destinationID, 40) ADRunsController.store = ADRuns.newStore() -- ==== S: læs-bytte fra Wait — halm-rest ved Wait, målet mangler TMR -> aflæs halm i silo ==== local function buildSwapWorld() buildWaitWorld() -- stald: halm FULD (50000/50000), TMR 10000/100000 -- halmladen (markør 21) er også et aflæsningssted med plads table.insert(unloadStations, stations[2]) stations[2].unloadTriggers = {{exactFillRootNode = 121}} stations[2].getIsFillTypeSupported = function(_, fillType) return fillType == STRAW end stations[2].getFreeCapacity = function() return 100000 end stations[2].owningPlaceable = {spec_silo = {storages = {{getFillLevel = function(_, fillType) return fillType == STRAW and 50000 or 0 end}}}} end -- S1: ventende traktor med halm-rest -> TMR mangler et fuldt læs (40000 l vogn, 90000 l fri) -> til halmladen buildSwapWorld() local sv, st = makeUnloadingVehicle("Fastrac Bytte", 521, 5000, 35000) UnloadTask.update(st, 16) check("S1 venter først ved Wait", st.destinationID, 40) UnloadTask.update(st, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("S1 omdirigeret til halmladen", st.destinationID, 21) check("S1 logget bytte", countLogsSince(1, "bytter læs") >= 1, true) check("S1 Wait-punkt frigivet", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", sv), RT_ONLYPICKUP) check("S1 AD's mål peger midlertidigt på siloen (ellers tipper AD ikke)", sv.ad.stateModule.secondMarkerId, 21) UnloadTask.finished(st) check("S1 mål gendannet efter aflæsning", sv.ad.stateModule.secondMarkerId, 30) -- S2: målet mangler kun lidt TMR (10 % af lageret) og et fuldt læs passer ikke -> bliv ved Wait buildSwapWorld() waitBarnLevels[TMR] = 90000 -- 10000 fri < 40000 vogn og < 25 % af 100000 local sv2, st2 = makeUnloadingVehicle("Fastrac Bliv", 522, 5000, 35000) UnloadTask.update(st2, 16) UnloadTask.update(st2, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("S2 bliver ved Wait", st2.destinationID, 40) -- S2b: vognen er større end stalden (fuldt læs passer aldrig), men TMR mangler 25 % -> byt alligevel -- (in-game 24/9: Fastrac'erne byttede aldrig, fordi vognen er større end staldens lager) buildSwapWorld() waitBarnLevels[TMR] = 75000 -- 25000 fri < 40000 vogn, men = 25 % af 100000 local sv2b, st2b = makeUnloadingVehicle("Fastrac Stor", 526, 5000, 35000) UnloadTask.update(st2b, 16) UnloadTask.update(st2b, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("S2b bytter når 25 % mangler", st2b.destinationID, 21) -- S3: ingen silo med plads/vare -> bliv ved Wait, én WARN buildSwapWorld() stations[2].getFreeCapacity = function() return 1000 end local sv3, st3 = makeUnloadingVehicle("Fastrac Ingen", 523, 5000, 35000) UnloadTask.update(st3, 16) local mark3 = #logLines UnloadTask.update(st3, ADLoadSwap.CHECK_INTERVAL_MS + 1) UnloadTask.update(st3, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("S3 bliver ved Wait", st3.destinationID, 40) check("S3 én advarsel", countLogsSince(mark3, "bliver ved Wait-punktet"), 1) -- S4: silo uden vejnet hjem springes over buildSwapWorld() FS25_AutoDrive.ADGraphManager.unreachable[21] = true local sv4, st4 = makeUnloadingVehicle("Fastrac Vej", 524, 5000, 35000) UnloadTask.update(st4, 16) UnloadTask.update(st4, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("S4 unåelig silo -> bliv", st4.destinationID, 40) FS25_AutoDrive.ADGraphManager.unreachable = {} -- S5: traktor med kun én vare i listen bytter aldrig buildSwapWorld() local sv5, st5 = makeUnloadingVehicle("Fastrac Solo", 525, 5000, 35000) sv5.ad.stateModule.selectedFillTypes = {STRAW} UnloadTask.update(st5, 16) UnloadTask.update(st5, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("S5 én vare -> bliver", st5.destinationID, 40) -- S6: efter bytte til siloen skal traktoren blive der (ikke sendes til et nyt Wait-punkt, -- fordi siloen fejlagtigt tolkes som "målet er fuldt") buildSwapWorld() local sv6, st6 = makeUnloadingVehicle("Fastrac Silo", 526, 5000, 35000) UnloadTask.update(st6, 16) UnloadTask.update(st6, ADLoadSwap.CHECK_INTERVAL_MS + 1) local mark6 = #logLines UnloadTask.update(st6, 16) UnloadTask.update(st6, 16) check("S6 bliver ved siloen efter bytte", st6.destinationID, 21) check("S6 ingen ny Wait-omdirigering", countLogsSince(mark6, "venter ved"), 0) -- I5: run'ets "kør hjem under X %" styrer hvornår den ventende kører hjem buildWaitWorld() local vt, tt = makeUnloadingVehicle("Fastrac Tærskel", 551, 5000, 60000) -- rest > stald: kun tærsklen kan sende hjem vt.uniqueId = "vehicleThreshold" testVehicles = {vt} ADRunsController.store = ADRuns.newStore() local thrRun = ADRunsController.createRun("Tærskel") ADRunsController.updateRun(thrRun.id, {targetWayPointId = 30, returnBelowPercent = 20}) ADRunsController.assignVehicle(thrRun.id, vt) UnloadTask.update(tt, 16) check("I5 venter først", tt.destinationID, 40) waitBarnLevels[STRAW] = 15000 -- 70 % fri: 50 %-reglen ville sende hjem, men tærsklen er "under 20 %" (= 80 % fri) UnloadTask.update(tt, 16) check("I5 70 % fri er ikke nok ved 'under 20 %'", tt.destinationID, 40) waitBarnLevels[STRAW] = 9000 -- 82 % fri >= 80 % UnloadTask.update(tt, 16) check("I5 hjem når målet er under 20 %", tt.destinationID, 30) ADRunsController.store = ADRuns.newStore() -- I6: run med to stalde — ved turstart vælges den stald der mangler mest, AD's mål skiftes, og -- varen hentes efter DEN stalds behov buildCowWorld() addMarker(31, "Cow 2 Food", "Cow Food", 6000, 0) local cow2Levels = {[STRAW] = 95000, [TMR] = 95000} table.insert(unloadStations, makeUnloadStation(131, 6000, 10, cow2Levels, {[STRAW] = 100000, [TMR] = 100000}, {name = "kostald 2"})) cowLevels[STRAW], cowLevels[TMR] = 90000, 20000 -- stald 1: TMR mangler local i6 = makeSupplyDriver(markerByIndex(21), markerByIndex(31), {TMR, STRAW}) -- AD peger på stald 2 (fuld) i6.uniqueId = "vehicleI6"; i6.carriable = {TMR, STRAW}; i6.trailers = {level = 0, capacity = 8000} i6.ad.stateModule.setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end i6.ad.stateModule.getSecondMarker = function(self) return markerByIndex(self.secondMarkerId or 31) end testVehicles = {i6} ADRunsController.store = ADRuns.newStore() local twoBarns = ADRunsController.createRun("Kvæg") ADRunsController.updateRun(twoBarns.id, {targetWayPointIds = {30, 31}}) ADRunsController.assignVehicle(twoBarns.id, i6) check("I6 henter TMR til stald 1", manager:getNextPickup(i6, false), 20) check("I6 AD's mål skiftet til stald 1", i6.ad.stateModule.secondMarkerId, 30) check("I6 skiftet logges", countLogsSince(1, "kører til Cow 1 Food") >= 1, true) cowLevels[STRAW], cowLevels[TMR] = 99000, 99000 cow2Levels[STRAW] = 10000 -- nu mangler stald 2 halm check("I6 næste tur: halm til stald 2", manager:getNextPickup(i6, false), 21) check("I6 mål skiftet til stald 2", i6.ad.stateModule.secondMarkerId, 31) ADRunsController.store = ADRuns.newStore() -- ==== V: forkert last på vognen ==== -- V1: pålæsning starter med anden vare på vognen -> opgaven afsluttes straks (AD leverer det den har) buildCowWorld() cowLevels[STRAW], cowLevels[TMR] = 90000, 0 -- TMR mangler local wv = makeSupplyDriver(markerByIndex(21), markerByIndex(30), {TMR, STRAW}) wv.carriable = {TMR, STRAW}; wv.trailers = {level = 8000, capacity = 8000} wv.units = {{object = {getFillUnitFillLevel = function() return 8000 end, getFillUnitFillType = function() return STRAW end}, fillUnitIndex = 1}} withDrivePath(wv); wv.targetReached = false check("V1 TMR vælges som behov", manager:getNextPickup(wv, false), 20) local loadTask = {vehicle = wv, trailers = wv.trailers, finished = function(self) self.isFinished = true end} FS25_AutoDrive.LoadAtDestinationTask.update(loadTask, 16) check("V1 pålæsningsopgave afsluttet pga. halm på vognen", loadTask.isFinished, true) check("V1 logget", countLogsSince(1, "leverer først") >= 1, true) -- samme vare på vognen som den der hentes -> opgaven kører normalt wv.units[1].object.getFillUnitFillType = function() return TMR end local loadTask2 = {vehicle = wv, trailers = wv.trailers, finished = function(self) self.isFinished = true end} manager:getNextPickup(wv, false) FS25_AutoDrive.LoadAtDestinationTask.update(loadTask2, 16) check("V1 samme vare -> ikke afsluttet", loadTask2.isFinished, nil) -- V2: ved målet med en vare målet ikke tager -> Wait; ved Wait -> silo uanset "manglende" vare buildWaitWorld() table.insert(unloadStations, stations[2]) -- halmladen tager halm stations[2].unloadTriggers = {{exactFillRootNode = 121}} stations[2].getIsFillTypeSupported = function(_, fillType) return fillType == STRAW end stations[2].getFreeCapacity = function() return 100000 end stations[2].owningPlaceable = {spec_silo = {storages = {{getFillLevel = function() return 0 end}}}} waitBarnCapacity[STRAW] = nil -- stalden tager IKKE halm local vw, tw = makeUnloadingVehicle("Fastrac Forkert", 561, 5000, 35000) UnloadTask.update(tw, 16) check("V2 målet tager ikke varen -> Wait", tw.destinationID, 40) UnloadTask.update(tw, ADLoadSwap.CHECK_INTERVAL_MS + 1) check("V2 fra Wait til silo selv uden manglende vare", tw.destinationID, 21) waitBarnCapacity[STRAW] = 50000 -- V3: Start med forkert last -> AD's mål = silo; første getNextPickup må IKKE sætte run-målet tilbage, -- og efter aflæsningen (finished) gendannes run-målet buildCowWorld() table.insert(unloadStations, stations[2]) -- halmladen tager halm stations[2].unloadTriggers = {{exactFillRootNode = 121}} stations[2].getIsFillTypeSupported = function(_, fillType) return fillType == STRAW end stations[2].getFreeCapacity = function() return 100000 end stations[2].owningPlaceable = {spec_silo = {storages = {{getFillLevel = function() return 0 end}}}} local v3 = makeSupplyDriver(markerByIndex(21), markerByIndex(30), {TMR, STRAW}) v3.uniqueId = "vehicleV3"; v3.carriable = {TMR}; v3.trailers = {level = 4000, capacity = 8000}; v3.rootNode = 571; nodePositions[571] = {x = 0, z = 0} v3.units = {{object = {getFillUnitFillLevel = function() return 4000 end, getFillUnitFillType = function() return STRAW end}, fillUnitIndex = 1}} v3.ad.stateModule.secondMarkerId = 30 v3.ad.stateModule.setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end v3.ad.stateModule.getSecondMarker = function(self) return markerByIndex(self.secondMarkerId) end cowLevels[STRAW] = nil -- stald 1 tager i denne test kun TMR unloadStations[1].getIsFillTypeSupported = function(_, fillType) return fillType == TMR end testVehicles = {v3} ADRunsController.store = ADRuns.newStore() local v3Run = ADRunsController.createRun("TMR only") ADRunsController.updateRun(v3Run.id, {targetWayPointId = 30, ingredientMode = "manual", ingredients = {"FORAGE"}}) ADRunsController.assignVehicle(v3Run.id, v3) ADUnloadWait.setDumpTarget(v3, 21) -- som Start gør ved forkert last check("V3 AD's mål = silo", v3.ad.stateModule.secondMarkerId, 21) manager:getNextPickup(v3, false) check("V3 getNextPickup rører ikke målet mens silo-aflæsning er undervejs", v3.ad.stateModule.secondMarkerId, 21) UnloadTask.finished({vehicle = v3}) check("V3 mål gendannet efter aflæsning", v3.ad.stateModule.secondMarkerId, 30) ADRunsController.store = ADRuns.newStore() -- ==== E: eksponerede opslag (bruges af runs-controller og læs-bytte) ==== local eDriver = buildCowWorld() check("E1 aflæsningsstation ved waypoint", ADSmartPickup.getUnloadStationAtWayPointId(FS25_AutoDrive, 30, 1), unloadStations[1]) check("E1 ukendt waypoint -> nil", ADSmartPickup.getUnloadStationAtWayPointId(FS25_AutoDrive, 9999, 1), nil) check("E2 læssestation ved waypoint", ADSmartPickup.getLoadStationAtWayPointId(FS25_AutoDrive, 21, 1), stations[2]) local eLevel, eCapacity = ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], TMR, 1) check("E3 niveau i målet", eLevel, 40000) check("E3 kapacitet i målet", eCapacity, 100000) check("E3 vare målet ikke tager -> nil", ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], MILK, 1), nil) -- E4: FS25-stald med foder-spec: foder læses fra spec_husbandryFood (fælles kapacitet), -- fodertyper fra animalFoodSystem, halm fra spec_husbandryStraw; outputs (mælk) tages ikke buildCowWorld() local foodLevels = {[TMR] = 10000, [HAY] = 5000} local fs25Barn = { getName = function() return "FS25-kostald" end, spec_husbandryFood = {fillLevels = foodLevels, capacity = 100000, animalTypeIndex = 7}, spec_husbandryStraw = {fillLevel = 20000, capacity = 300000}, -- staldens egen "understøtter" siger også ja til mælk (output) — må ikke blive et mål getHusbandryIsFillTypeSupported = function(_, fillType) return fillType == MILK or fillType == STRAW or fillType == TMR end, getHusbandryFillLevel = function(_, fillType) if fillType == STRAW then return 20000 end return 0 end, getHusbandryCapacity = function(_, fillType) if fillType == STRAW then return 300000 end return 0 end, } g_currentMission.animalFoodSystem = { getAnimalFood = function(_, animalTypeIndex) if animalTypeIndex == 7 then return {groups = {{fillTypes = {TMR}}, {fillTypes = {HAY, GRASS}}}} end end, getMixturesByAnimalTypeIndex = function() return {} end, } unloadStations[1].targetStorages = nil unloadStations[1].owningPlaceable = fs25Barn local tmrLevel, tmrCapacity = ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], TMR, 1) check("E4 TMR-niveau fra foder-spec", tmrLevel, 10000) check("E4 TMR-kapacitet = eget + fælles fri plads", tmrCapacity, 10000 + (100000 - 15000)) local grassLevel, grassCapacity = ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], GRASS, 1) check("E4 græs (fodergruppe uden lager) niveau 0", grassLevel, 0) check("E4 græs kapacitet = fælles fri plads", grassCapacity, 85000) local strawLevel, strawCapacity = ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], STRAW, 1) check("E4 halm via husbandry-opslag", strawLevel .. "/" .. strawCapacity, "20000/300000") check("E4 mælk (output) er ikke et mål", ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], MILK, 1), nil) check("E4 sukkerroer (ikke foder) er ikke et mål", ADSmartPickup.getStationLevelAndCapacity(unloadStations[1], SUGARBEET, 1), nil) g_currentMission.animalFoodSystem = nil check("E3 farmId", ADSmartPickup.getFarmId(eDriver), 1) -- L6: samme valg logges kun én gang resetWorld() addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[SUGARBEET] = 5000}, false, {[SUGARBEET] = 5000})) local chatty = makeDriver(markers[1], {SUGARBEET}) manager:getNextPickup(chatty, false); manager:getNextPickup(chatty, false) check("L6 gentaget valg logges én gang", #logLines, 1) check("L6 loggen viser eget lager", logLines[1]:find("eget lager", 1, true) ~= nil, true) -- W15: efter genindlæsning er ventetilstanden væk, men AD's mål peger stadig på Wait-punktet og -- traktoren står der med rest-læs -> ventningen genoptages (hjem = run'ets mål) og den kører hjem -- når der er plads buildWaitWorld() local orphan, orphanTask = makeUnloadingVehicle("Genindlæst", 515, 5090, 20000) orphan.uniqueId = "vehicleOrphan" orphan.spec_motorized = {} orphan.ad.stateModule.secondMarkerId = 40 -- AD's gemte mål = Wait 1 orphanTask.destinationID = 40 orphan.ad.taskModule = {getActiveTask = function() return orphanTask end} testVehicles = {orphan} ADRunsController.store = ADRuns.newStore() local orphanRun = ADRunsController.createRun("Cow Feed") ADRunsController.updateRun(orphanRun.id, {targetWayPointId = 30}) ADRunsController.assignVehicle(orphanRun.id, orphan) check("W15 ingen ventning før", ADUnloadWait.getWaitFor(orphan), nil) ADUnloadWait.adoptOrphans(FS25_AutoDrive) local adopted = ADUnloadWait.getWaitFor(orphan) check("W15 ventning genoptaget", adopted ~= nil and adopted.markerName, "Wait 1") check("W15 Wait-punktet reserveret", ADWaitPool.getOwner(40), orphan) check("W15 hjem-mål husket", ADUnloadWait.getPendingTargetRestore(orphan), 30) check("W15 logget", logLines[#logLines]:find("genoptager", 1, true) ~= nil, true) ADUnloadWait.adoptOrphans(FS25_AutoDrive) check("W15 ikke dobbelt", ADWaitPool.getOwner(40), orphan) waitBarnLevels[STRAW] = 0 -- plads til hele resten UnloadTask.update(orphanTask, 16) check("W15 kører hjem", orphanTask.destinationID, 30) check("W15 AD's mål gendannet", orphan.ad.stateModule.secondMarkerId, 30) testVehicles = {} -- N1: løst opslag (stald/fabrik): nærmeste læssestation inden for maxDistance, uanset retning og "bag næsen" -- (in-game 23/9: mælketankens trigger lå ved siden af markøren, 20+ m væk -> "ingen læssestation") resetWorld() addMarker(1, "cow 1 milk", "Milk", 0, 0, -30, 0) -- kører mod +x; tanken ligger 25 m til siden table.insert(stations, makeStation(101, 0, 25, {[WHEAT] = 1})) check("N1 strengt opslag finder intet", ADSmartPickup.getLoadStationAtWayPointId(FS25_AutoDrive, 1, 1), nil) 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) -- 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 resetWorld() addMarker(1, "Suger Load", "Pickup points", 0, 0); table.insert(stations, makeStation(101, 0, 15, {[WHEAT] = 1000})) addMarker(2, "Wait 3", "General Wait", 100, 0) local outboundDriver = makeDriver(markers[1], {WHEAT}) local outboundCalls, outboundChoice, outboundError = 0, nil, false ADOutbound = { isOutboundVehicle = function(vehicle) return vehicle == outboundDriver end, choosePickup = function(adEnv, vehicle) outboundCalls = outboundCalls + 1; if outboundError then error("boom") end return outboundChoice end, releaseInactive = function() end, install = function() end, } outboundChoice = {markerIndex = markers[1].markerIndex, markerName = "Suger Load", group = "Pickup points", station = stations[1], fillTypes = {WHEAT}, fillType = WHEAT} check("X1 udkørsel: kilden vælges af ADOutbound", manager:getNextPickup(outboundDriver, false), markers[1].markerIndex) check("X1 ADOutbound spurgt", outboundCalls, 1) check("X1 greb sat (station kendt)", ADSmartPickup.getActivePickup(outboundDriver) ~= nil, true) outboundChoice = {markerIndex = markers[2].markerIndex, markerName = "Wait 3", group = "General Wait", station = nil, fillTypes = {}, fillType = nil} check("X1 Wait-markør uden station", manager:getNextPickup(outboundDriver, false), markers[2].markerIndex) check("X1 intet greb uden station", ADSmartPickup.getActivePickup(outboundDriver), nil) outboundChoice = nil check("X1 nil -> AutoDrives original", manager:getNextPickup(outboundDriver, false), "ORIGINAL") outboundError = true check("X1 fejl -> AutoDrives original", manager:getNextPickup(outboundDriver, false), "ORIGINAL") outboundError = false -- setActivePickup (bruges af opsamlingsrunden) opdaterer grebet direkte ADSmartPickup.setActivePickup(outboundDriver, {station = stations[1], fillTypes = {WHEAT}, markerName = "Suger Load", fillType = WHEAT}) check("X2 setActivePickup", ADSmartPickup.getActivePickup(outboundDriver).markerName, "Suger Load") ADSmartPickup.setActivePickup(outboundDriver, nil) check("X2 setActivePickup nil rydder", ADSmartPickup.getActivePickup(outboundDriver), nil) -- Wait-logikken springer udkørsels-traktorer over (rører ikke vognens indhold) local originalUnits = FS25_AutoDrive.AutoDrive.getAllDischargeableUnits FS25_AutoDrive.AutoDrive.getAllDischargeableUnits = function() error("må ikke kaldes for udkørsel") end outboundDriver.ad.drivePathModule = {isTargetReached = function() return true end} local okWait = pcall(ADUnloadWait.beforeUpdate, FS25_AutoDrive, {vehicle = outboundDriver, destinationID = 1}, 16) check("X3 Wait-logik springer udkørsel over", okWait, true) FS25_AutoDrive.AutoDrive.getAllDischargeableUnits = originalUnits ADOutbound = nil print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)