feat: Wait-punkter for rest-læs + læs der passer foretrækkes (v1.7.0.0)

Rest-læs ved fuldt mål: AutoDrive ventede ved aflæsningspunktet 'til tømt' og
spærrede køen. Nu omdirigeres den aktuelle UnloadAtDestinationTask (destinationID +
drivePathModule:setPathTo) til nærmeste ledige markør med 'Wait' i navnet / mappen
Wait (fælles pulje, reservationer pr. køretøj); tilbage når målet har plads til
hele resten. Ventende ser rotateTargets=RT_NONE så 'rotér aflæsning' ikke kører
videre fra Wait-punktet. Ingen ledige punkter = som i dag + én advarsel.

Rangering: varer hvor et fuldt læs (AutoDrive.getAllFillLevels-kapacitet) passer i
målet går forud; passer intet, mest trængende. Nyt modul adUnloadWait.lua;
ADSmartPickup.getTargetFreeCapacity delt hjælper.
This commit is contained in:
2026-09-22 21:12:55 +02:00
parent 2ff0b641b0
commit 39fccefa40
7 changed files with 469 additions and 22 deletions
+119 -1
View File
@@ -84,7 +84,9 @@ 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 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,
},
-- opgaven noterer hvad AutoDrive "ser" af indstillinger mens den opdaterer
LoadAtDestinationTask = {update = function(self)
@@ -93,6 +95,12 @@ FS25_AutoDrive = {
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 = {}
@@ -142,6 +150,7 @@ local function makeSupplyDriver(firstMarker, secondMarker, fillTypes)
end
dofile("FS25_ADSmartPickup/adSupplyPlanner.lua")
dofile("FS25_ADSmartPickup/adUnloadWait.lua")
dofile("FS25_ADSmartPickup/adSmartPickup.lua")
listener:update(16) -- installerer hook'en
local manager = FS25_AutoDrive.ADMultipleTargetsManager
@@ -417,6 +426,115 @@ check("P4 stald: TMR (90 % tom) hentes før halm (10 % tom)", manager:getNextPic
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 function buildWaitWorld()
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 Food", 5100, 0)
addMarker(41, "Wait 2", "Cow Food", 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.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}
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: B og C venter stadig og holder begge punkter; stoppes de i AD, frigives punkterne
buildWaitWorld()
local v8, t8 = makeUnloadingVehicle("Fastrac F", 508, 5000, 35000)
UnloadTask.update(t8, 16)
check("W8 alle Wait-punkter optaget -> uændret", t8.destinationID, 30)
v2.ad.stateModule.active, v3.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)
-- L1: moden valgte siloen -> AutoDrive skal stå og læsse (rotation skjules under opgaven)
resetWorld()
local loadLevels = {[SUGARBEET] = 5000}
+36
View File
@@ -122,5 +122,41 @@ check("vare fabrikken ikke kender -> ikke beskyttet", ADSupplyPlanner.isProducti
check("ingen produktioner -> ikke beskyttet", ADSupplyPlanner.isProductionInput({}, STRAW), false)
check("produktion uden inputs/outputs tåles", ADSupplyPlanner.isProductionInput({{id = "x"}}, STRAW), false)
-- rankNeeds med læs-størrelse: varer hvor et FULDT læs passer går forud, ellers mest trængende
local sized = ADSupplyPlanner.rankNeeds({
{fillType = STRAW, level = 40000, capacity = 50000, litersPerMinute = 500}, -- 80 min, kun 10000 fri
{fillType = TMR, level = 20000, capacity = 100000, litersPerMinute = 50}, -- 400 min, 80000 fri
}, 40000)
check("fuldt læs der passer vinder over mere trængende der ikke passer", sized[1].fillType, TMR)
check("den der ikke passer bevares som reserve", sized[2].fillType, STRAW)
check("fits markeres", sized[1].fits, true)
check("ikke-fits markeres", sized[2].fits, false)
local noneFit = ADSupplyPlanner.rankNeeds({
{fillType = STRAW, level = 40000, capacity = 50000, litersPerMinute = 500},
{fillType = TMR, level = 90000, capacity = 100000, litersPerMinute = 50},
}, 40000)
check("passer intet -> mest trængende først", noneFit[1].fillType, STRAW)
check("uden læs-størrelse -> som før", ADSupplyPlanner.rankNeeds({{fillType = STRAW, level = 40000, capacity = 50000}})[1].fits, nil)
-- Wait-punkter: navn med ordet "Wait" (uanset store/små) eller mappen "Wait"
check("'Cow 1 Food Wait' er wait-punkt", ADSupplyPlanner.isWaitMarker({name = "Cow 1 Food Wait", group = "Cow Food"}), true)
check("'wait 3' er wait-punkt", ADSupplyPlanner.isWaitMarker({name = "wait 3", group = "Andet"}), true)
check("mappen Wait tæller", ADSupplyPlanner.isWaitMarker({name = "P2", group = "Wait"}), true)
check("'Waiter' er ikke wait-punkt", ADSupplyPlanner.isWaitMarker({name = "Waiter", group = "X"}), false)
check("'Cow 1 Food' er ikke wait-punkt", ADSupplyPlanner.isWaitMarker({name = "Cow 1 Food", group = "Cow Food"}), false)
-- nærmeste ledige wait-punkt; candidates = {{marker, x, z}}, claims = {[markerId] = vehicle}
local V1, V2 = {}, {}
local waitPoints = {
{marker = {id = 1, name = "Wait A"}, x = 0, z = 0},
{marker = {id = 2, name = "Wait B"}, x = 100, z = 0},
{marker = {id = 3, name = "Wait C"}, x = 300, z = 0},
}
check("nærmeste ledige vælges", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1).marker.id, 1)
check("reserveret springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V2}, 10, 0, V1).marker.id, 2)
check("egen reservation tæller som ledig", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V1}, 10, 0, V1).marker.id, 1)
check("alle optaget -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V2, [2] = V2, [3] = V2}, 10, 0, V1), nil)
check("ingen punkter -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint({}, {}, 0, 0, V1), nil)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)