fix: rute-tjek begge veje før Wait-omdirigering; hjem ved halv-tomt mål (v1.7.2.0)
In-game 2026-09-22 21:37: Fastrac omdirigeret til 'Cow Wait' uden vejnet dertil -> AutoDrive 'cannot reach' + StopAndDisableADTask (AD slukket i stalden). Nu: ADGraphManager:getPathTo + pathFromTo (rene opslag) skal ende i hhv. Wait-punkt og mål, ellers næste punkt; intet nåeligt = som i dag + advarsel. 700.000 l-vogn i 300.000 l-stald kunne aldrig opfylde 'hele resten passer': ADSupplyPlanner.canUnloadRest = fri >= min(rest, 50 % af kapacitet).
This commit is contained in:
@@ -401,7 +401,7 @@ local function getLoadCapacity(adEnv, driver)
|
|||||||
return capacity ~= nil and capacity > 0 and capacity or nil
|
return capacity ~= nil and capacity > 0 and capacity or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Fri plads til varen i køretøjets aflæsningsmål (bruges af Wait-punkt-logikken);
|
-- Fri plads + kapacitet for varen i køretøjets aflæsningsmål (Wait-punkt-logikken);
|
||||||
-- nil hvis målet ikke kendes eller ikke tager varen.
|
-- nil hvis målet ikke kendes eller ikke tager varen.
|
||||||
function ADSmartPickup.getTargetFreeCapacity(adEnv, driver, fillType)
|
function ADSmartPickup.getTargetFreeCapacity(adEnv, driver, fillType)
|
||||||
local secondMarker = driver.ad.stateModule:getSecondMarker()
|
local secondMarker = driver.ad.stateModule:getSecondMarker()
|
||||||
@@ -418,7 +418,7 @@ function ADSmartPickup.getTargetFreeCapacity(adEnv, driver, fillType)
|
|||||||
if level == nil then
|
if level == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
return capacity - level
|
return capacity - level, capacity
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Behovsstyret: den ingrediens aflæsningsmålet mangler mest, som også findes i en silo.
|
-- Behovsstyret: den ingrediens aflæsningsmålet mangler mest, som også findes i en silo.
|
||||||
|
|||||||
@@ -132,20 +132,39 @@ function ADSupplyPlanner.isWaitMarker(marker)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- candidates: {{marker, x, z}}; claims: {[marker.id] = køretøj}. Nærmeste punkt der er
|
-- candidates: {{marker, x, z}}; claims: {[marker.id] = køretøj}. Nærmeste punkt der er
|
||||||
-- ledigt eller allerede reserveret af dette køretøj. nil hvis alle er optaget.
|
-- ledigt (eller allerede reserveret af dette køretøj) og som isReachable(candidate)
|
||||||
function ADSupplyPlanner.pickNearestFreeWaitPoint(candidates, claims, x, z, vehicle)
|
-- godkender (valgfri; AutoDrive slukker sig selv, hvis den sendes mod et punkt uden rute).
|
||||||
local best, bestDistance = nil, nil
|
-- nil hvis intet punkt duer. Nåelighed tjekkes kun for de punkter der er i spil, nærmeste først.
|
||||||
|
function ADSupplyPlanner.pickNearestFreeWaitPoint(candidates, claims, x, z, vehicle, isReachable)
|
||||||
|
local free = {}
|
||||||
for _, candidate in ipairs(candidates) do
|
for _, candidate in ipairs(candidates) do
|
||||||
local owner = claims[candidate.marker.id]
|
local owner = claims[candidate.marker.id]
|
||||||
if owner == nil or owner == vehicle then
|
if owner == nil or owner == vehicle then
|
||||||
local dx, dz = candidate.x - x, candidate.z - z
|
local dx, dz = candidate.x - x, candidate.z - z
|
||||||
local distance = dx * dx + dz * dz
|
table.insert(free, {candidate = candidate, distance = dx * dx + dz * dz})
|
||||||
if bestDistance == nil or distance < bestDistance then
|
|
||||||
best, bestDistance = candidate, distance
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
table.sort(free, function(a, b) return a.distance < b.distance end)
|
||||||
|
for _, entry in ipairs(free) do
|
||||||
|
if isReachable == nil or isReachable(entry.candidate) then
|
||||||
|
return entry.candidate
|
||||||
end
|
end
|
||||||
return best
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Så stor en andel af målets kapacitet skal være fri, før en rest der er større end
|
||||||
|
-- det, kan afleveres (en 700.000 l-vogn kan aldrig tømme HELE resten i en 300.000 l-stald).
|
||||||
|
ADSupplyPlanner.WAIT_RETURN_FREE_FRACTION = 0.5
|
||||||
|
|
||||||
|
-- Kan den ventende køre hjem? Ja når hele resten passer, eller når målet er mindst
|
||||||
|
-- WAIT_RETURN_FREE_FRACTION tomt. capacity nil = kun hele resten tæller.
|
||||||
|
function ADSupplyPlanner.canUnloadRest(freeSpace, rest, capacity)
|
||||||
|
local needed = rest
|
||||||
|
if capacity ~= nil and capacity > 0 then
|
||||||
|
needed = math.min(rest, capacity * ADSupplyPlanner.WAIT_RETURN_FREE_FRACTION)
|
||||||
|
end
|
||||||
|
return freeSpace >= needed
|
||||||
end
|
end
|
||||||
|
|
||||||
-- needs: {{fillType, level, capacity[, litersPerMinute]}, ...}. Returnerer NY liste,
|
-- needs: {{fillType, level, capacity[, litersPerMinute]}, ...}. Returnerer NY liste,
|
||||||
|
|||||||
@@ -60,6 +60,23 @@ local function getVehiclePosition(vehicle)
|
|||||||
return x, z
|
return x, z
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Ender ruten i målet? (AutoDrive returnerer tom/afkortet liste når der ingen vej er.)
|
||||||
|
local function pathReaches(path, wayPointId)
|
||||||
|
return path ~= nil and #path > 0 and path[#path].id == wayPointId
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Findes der vejnet fra køretøjet til Wait-punktet OG fra Wait-punktet hjem til målet?
|
||||||
|
-- Sendes AutoDrive mod et punkt uden rute, viser den "cannot reach" og slukker sig selv.
|
||||||
|
-- getPathTo/pathFromTo er rene opslag uden bivirkninger.
|
||||||
|
local function hasRouteBothWays(adEnv, vehicle, waitWayPointId, homeWayPointId)
|
||||||
|
local graph = adEnv.ADGraphManager
|
||||||
|
if graph.getPathTo == nil or graph.pathFromTo == nil then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return pathReaches(graph:getPathTo(vehicle, waitWayPointId), waitWayPointId)
|
||||||
|
and pathReaches(graph:pathFromTo(waitWayPointId, homeWayPointId), homeWayPointId)
|
||||||
|
end
|
||||||
|
|
||||||
local function redirect(task, destinationID)
|
local function redirect(task, destinationID)
|
||||||
task.destinationID = destinationID
|
task.destinationID = destinationID
|
||||||
task.state = ADUnloadWait.STATE_DRIVING
|
task.state = ADUnloadWait.STATE_DRIVING
|
||||||
@@ -90,11 +107,14 @@ local function startWaiting(adEnv, task)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local x, z = getVehiclePosition(vehicle)
|
local x, z = getVehiclePosition(vehicle)
|
||||||
local candidate = x ~= nil and ADSupplyPlanner.pickNearestFreeWaitPoint(getWaitCandidates(adEnv), claims, x, z, vehicle) or nil
|
local isReachable = function(candidate)
|
||||||
|
return hasRouteBothWays(adEnv, vehicle, candidate.marker.id, task.destinationID)
|
||||||
|
end
|
||||||
|
local candidate = x ~= nil and ADSupplyPlanner.pickNearestFreeWaitPoint(getWaitCandidates(adEnv), claims, x, z, vehicle, isReachable) or nil
|
||||||
if candidate == nil then
|
if candidate == nil then
|
||||||
if not warnedNoWaitPoint[vehicle] then
|
if not warnedNoWaitPoint[vehicle] then
|
||||||
warnedNoWaitPoint[vehicle] = true
|
warnedNoWaitPoint[vehicle] = true
|
||||||
Logging.warning(ADUnloadWait.LOG_PREFIX .. "'%s' har %.0f l rest, og målet er fuldt — intet ledigt Wait-punkt (markør i en mappe med 'Wait' i navnet), så AutoDrive venter ved målet", tostring(vehicle:getName()), rest)
|
Logging.warning(ADUnloadWait.LOG_PREFIX .. "'%s' har %.0f l rest, og målet er fuldt — intet ledigt Wait-punkt med vejnet til OG fra målet (markør i en mappe med 'Wait' i navnet), så AutoDrive venter ved målet", tostring(vehicle:getName()), rest)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -118,8 +138,8 @@ local function updateWaiting(adEnv, task, wait)
|
|||||||
release(vehicle)
|
release(vehicle)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local free = ADSmartPickup.getTargetFreeCapacity(adEnv, vehicle, fillType)
|
local free, capacity = ADSmartPickup.getTargetFreeCapacity(adEnv, vehicle, fillType)
|
||||||
if free == nil or free < rest then
|
if free == nil or not ADSupplyPlanner.canUnloadRest(free, rest, capacity) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local home = wait.homeDestinationID
|
local home = wait.homeDestinationID
|
||||||
|
|||||||
@@ -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.7.1.0</version>
|
<version>1.7.2.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -59,7 +59,11 @@ spærrer for de næste i køen. Med Wait-punkter:
|
|||||||
dine mark-ventepladser ("F91 Wait" i mark-mapperne) rører den ikke.
|
dine mark-ventepladser ("F91 Wait" i mark-mapperne) rører den ikke.
|
||||||
2. Kan målet ikke tage mere af vognens vare, kører traktoren til det **nærmeste ledige** Wait-punkt
|
2. Kan målet ikke tage mere af vognens vare, kører traktoren til det **nærmeste ledige** Wait-punkt
|
||||||
(modden holder styr på reservationer) og venter dér.
|
(modden holder styr på reservationer) og venter dér.
|
||||||
3. Når målet igen har plads til **hele** resten, kører den tilbage og læsser af. Derefter normal tur.
|
3. Når målet igen har plads til **hele** resten — eller er mindst **halvt tomt** (kæmpe vogne kan aldrig
|
||||||
|
tømme hele resten i en lille stald) — kører den tilbage og læsser af. Derefter normal tur.
|
||||||
|
4. Wait-punkterne SKAL være forbundet til vejnettet både fra målets udkørsel og tilbage til målet.
|
||||||
|
Modden tjekker ruten begge veje før den omdirigerer; uden rute prøves næste punkt (AutoDrive
|
||||||
|
ville ellers vise "cannot reach" og slukke sig selv).
|
||||||
|
|
||||||
Kun den aktuelle aflæsningsopgaves destination ændres — mål, rotation og loop-tæller røres ikke.
|
Kun den aktuelle aflæsningsopgaves destination ændres — mål, rotation og loop-tæller røres ikke.
|
||||||
Ventende traktorer ser rotationen som slået fra, så AutoDrive ikke kører videre fra Wait-punktet.
|
Ventende traktorer ser rotationen som slået fra, så AutoDrive ikke kører videre fra Wait-punktet.
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ FS25_AutoDrive = {
|
|||||||
end,
|
end,
|
||||||
getWayPointById = function(_, id) return wayPoints[id] end,
|
getWayPointById = function(_, id) return wayPoints[id] end,
|
||||||
getMapMarkers = function() return markers end,
|
getMapMarkers = function() return markers 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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +433,12 @@ check("P4 stald: halm hentes når TMR er fuld", manager:getNextPickup(cowDriver,
|
|||||||
-- ==== Wait-punkter: rest-læs + fuld stald -> vent ved nærmeste ledige Wait, tilbage når resten passer ====
|
-- ==== Wait-punkter: rest-læs + fuld stald -> vent ved nærmeste ledige Wait, tilbage når resten passer ====
|
||||||
local UnloadTask = FS25_AutoDrive.UnloadAtDestinationTask
|
local UnloadTask = FS25_AutoDrive.UnloadAtDestinationTask
|
||||||
local waitBarnLevels, waitBarnCapacity = {}, {[STRAW] = 50000, [TMR] = 100000}
|
local waitBarnLevels, waitBarnCapacity = {}, {[STRAW] = 50000, [TMR] = 100000}
|
||||||
|
local waitVehicles = {}
|
||||||
local function buildWaitWorld()
|
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()
|
buildCowWorld()
|
||||||
waitBarnLevels = {[STRAW] = 50000, [TMR] = 10000} -- halm FULD
|
waitBarnLevels = {[STRAW] = 50000, [TMR] = 10000} -- halm FULD
|
||||||
local barn2 = {
|
local barn2 = {
|
||||||
@@ -462,6 +471,7 @@ local function makeUnloadingVehicle(name, node, x, restStraw)
|
|||||||
}
|
}
|
||||||
vehicle.ad.trailerModule = {isActiveAtTrigger = function() return false end}
|
vehicle.ad.trailerModule = {isActiveAtTrigger = function() return false end}
|
||||||
local task = {vehicle = vehicle, destinationID = 30, state = UnloadTask.STATE_DRIVING, trailers = vehicle.trailers}
|
local task = {vehicle = vehicle, destinationID = 30, state = UnloadTask.STATE_DRIVING, trailers = vehicle.trailers}
|
||||||
|
table.insert(waitVehicles, vehicle)
|
||||||
return vehicle, task
|
return vehicle, task
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -519,12 +529,15 @@ UnloadTask.update(t7, 16); UnloadTask.update(t7, 16)
|
|||||||
check("W7 uden Wait-punkter -> uændret mål", t7.destinationID, 30)
|
check("W7 uden Wait-punkter -> uændret mål", t7.destinationID, 30)
|
||||||
check("W7 én advarsel", countLogsSince(warnMark, "WARN"), 1)
|
check("W7 én advarsel", countLogsSince(warnMark, "WARN"), 1)
|
||||||
|
|
||||||
-- W8: B og C venter stadig og holder begge punkter; stoppes de i AD, frigives punkterne
|
-- W8: to andre holder begge punkter; stoppes de i AD, frigives punkterne
|
||||||
buildWaitWorld()
|
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)
|
local v8, t8 = makeUnloadingVehicle("Fastrac F", 508, 5000, 35000)
|
||||||
UnloadTask.update(t8, 16)
|
UnloadTask.update(t8, 16)
|
||||||
check("W8 alle Wait-punkter optaget -> uændret", t8.destinationID, 30)
|
check("W8 alle Wait-punkter optaget -> uændret", t8.destinationID, 30)
|
||||||
v2.ad.stateModule.active, v3.ad.stateModule.active = false, false
|
vA.ad.stateModule.active, vB.ad.stateModule.active = false, false
|
||||||
listener:update(16)
|
listener:update(16)
|
||||||
UnloadTask.update(t8, 16)
|
UnloadTask.update(t8, 16)
|
||||||
check("W8 venter ved Wait 1 efter frigivelse", t8.destinationID, 40)
|
check("W8 venter ved Wait 1 efter frigivelse", t8.destinationID, 40)
|
||||||
@@ -535,6 +548,32 @@ UnloadTask.update(t9, 16)
|
|||||||
check("W8 Wait 1 frigivet efter finished", t9.destinationID, 40)
|
check("W8 Wait 1 frigivet efter finished", t9.destinationID, 40)
|
||||||
check("W8 rotation gendannet efter finished", FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", v8), RT_ONLYPICKUP)
|
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)
|
-- L1: moden valgte siloen -> AutoDrive skal stå og læsse (rotation skjules under opgaven)
|
||||||
resetWorld()
|
resetWorld()
|
||||||
local loadLevels = {[SUGARBEET] = 5000}
|
local loadLevels = {[SUGARBEET] = 5000}
|
||||||
|
|||||||
@@ -159,6 +159,18 @@ check("reserveret springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitP
|
|||||||
check("egen reservation tæller som ledig", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V1}, 10, 0, V1).marker.id, 1)
|
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("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)
|
check("ingen punkter -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint({}, {}, 0, 0, V1), nil)
|
||||||
|
-- nåelighed: et punkt AD ikke kan finde vej til/fra springes over (ellers slukker AD sig selv)
|
||||||
|
local unreachableA = function(candidate) return candidate.marker.id ~= 1 end
|
||||||
|
check("unåeligt nærmeste springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, unreachableA).marker.id, 2)
|
||||||
|
check("intet nåeligt -> nil", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, function() return false end), nil)
|
||||||
|
|
||||||
|
-- Hjem fra Wait: fri plads >= min(rest, halvdelen af målets kapacitet)
|
||||||
|
-- (en 700.000 l-vogn kan aldrig tømme HELE resten i en 300.000 l-stald)
|
||||||
|
check("hele resten passer -> hjem", ADSupplyPlanner.canUnloadRest(35000, 35000, 300000), true)
|
||||||
|
check("resten passer ikke endnu -> vent", ADSupplyPlanner.canUnloadRest(5000, 20000, 50000), false)
|
||||||
|
check("kæmpe rest: halv stald fri -> hjem", ADSupplyPlanner.canUnloadRest(150000, 700000, 300000), true)
|
||||||
|
check("kæmpe rest: under halv stald fri -> vent", ADSupplyPlanner.canUnloadRest(100000, 700000, 300000), false)
|
||||||
|
check("ukendt kapacitet -> kun hele resten tæller", ADSupplyPlanner.canUnloadRest(100000, 700000, nil), false)
|
||||||
|
|
||||||
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