fix(wait): fysisk optagede Wait-punkter springes over; feat(udkørsel): fodring — med 'Lager til' køres ved >= 10.000 l i kilden (v1.18.3.0)

This commit is contained in:
2026-09-24 12:51:12 +02:00
parent 889689cfce
commit 925163758a
9 changed files with 88 additions and 7 deletions
+2
View File
@@ -306,7 +306,9 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
product.store = findStoreMarker(adEnv, vehicle, product, capacity, sources, nil, run)
end
end
local feedMode = ADRunsController ~= nil and ADRunsController.getStoreMarkers ~= nil and #ADRunsController.getStoreMarkers(adEnv, run) > 0
local input = {
feedMode = feedMode,
capacity = capacity,
hasAutoload = hasAutoload,
sellAtPercent = run.sellAtPercent,
+5 -1
View File
@@ -44,6 +44,10 @@ local function sumSources(product)
return total, percent, pallets
end
-- Fodring: flowet har "Lager til" (et bestemt anlæg der bruger varen, fx gylle til en biogas-fermentor).
-- Så køres der så snart kilden har så meget — at vente på fuldt læs/80 % sulter anlægget.
ADOutboundPlanner.FEED_MIN_LITERS = 10000
function ADOutboundPlanner.evaluate(product, input)
local available, storagePercent, palletCount = sumSources(product)
local percent = product.quote ~= nil and product.quote.percent or nil
@@ -119,7 +123,7 @@ local function collectCandidates(input)
if wantsSell(product.policy) and e.priceOk and product.quote ~= nil and canSellWithoutReserve(e, input) then
table.insert(sell, {product = product, e = e, kind = "sell", targetWayPointId = product.quote.wayPointId, targetName = product.quote.stationName, percent = e.percent})
added = true
elseif wantsStore(product.policy) and (e.fullLoad or e.storageHigh) and product.store ~= nil then
elseif wantsStore(product.policy) and (e.fullLoad or e.storageHigh or (input.feedMode and e.available >= ADOutboundPlanner.FEED_MIN_LITERS)) and product.store ~= nil then
table.insert(store, {product = product, e = e, kind = "store", targetWayPointId = product.store.wayPointId, targetName = product.store.name})
added = true
end
+21 -2
View File
@@ -135,11 +135,30 @@ end
-- ledigt (eller allerede reserveret af dette køretøj) og som isReachable(candidate)
-- godkender (valgfri; AutoDrive slukker sig selv, hvis den sendes mod et punkt uden rute).
-- 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)
-- Wait-punkter hvor et ANDET køretøj fysisk holder (positions = {{x, z}}): hvert køretøj optager
-- højst ét punkt — det nærmeste inden for radius — så et nabopunkt 6 m væk ikke blokeres ved en fejl.
function ADSupplyPlanner.markOccupiedWaitPoints(candidates, positions, radius)
local occupied = {}
local limit = (radius or 6) * (radius or 6)
for _, position in ipairs(positions or {}) do
local best, bestDistance = nil, limit
for _, candidate in ipairs(candidates or {}) do
local dx, dz = candidate.x - position.x, candidate.z - position.z
local distance = dx * dx + dz * dz
if distance <= bestDistance then best, bestDistance = candidate, distance end
end
if best ~= nil then occupied[best.marker.id] = true end
end
return occupied
end
-- occupied (valgfri) = {[markerId] = true} fysisk optaget af andre; springes over medmindre man selv har reservationen.
function ADSupplyPlanner.pickNearestFreeWaitPoint(candidates, claims, x, z, vehicle, isReachable, occupied)
local free = {}
for _, candidate in ipairs(candidates) do
local owner = claims[candidate.marker.id]
if owner == nil or owner == vehicle then
local isTaken = occupied ~= nil and occupied[candidate.marker.id] == true and owner ~= vehicle
if (owner == nil or owner == vehicle) and not isTaken then
local dx, dz = candidate.x - x, candidate.z - z
table.insert(free, {candidate = candidate, distance = dx * dx + dz * dz})
end
+25 -1
View File
@@ -53,12 +53,36 @@ end
-- Nærmeste ledige (eller eget reserverede) Wait-punkt fra vognens position; isReachable(candidate)
-- valgfri. nil uden position eller uden brugbart punkt. Reserverer IKKE selv.
ADWaitPool.OCCUPIED_RADIUS = 6
-- Positioner for ANDRE motoriserede køretøjer (ikke vognen selv eller dens redskaber) — til at se om et
-- Wait-punkt fysisk er optaget, også uden reservation (fx lige efter en omplanlægning).
local function otherVehiclePositions(vehicle)
local positions = {}
local system = g_currentMission ~= nil and g_currentMission.vehicleSystem or nil
local own = {}
if vehicle.getChildVehicles ~= nil then
local ok, children = pcall(vehicle.getChildVehicles, vehicle)
for _, child in pairs(ok and children or {}) do own[child] = true end
end
own[vehicle] = true
for _, other in pairs(system ~= nil and system.vehicles or {}) do
if not own[other] and other.spec_motorized ~= nil and other.rootNode ~= nil then
local ok, x, _, z = pcall(getWorldTranslation, other.rootNode)
if ok and x ~= nil then table.insert(positions, {x = x, z = z}) end
end
end
return positions
end
function ADWaitPool.pickFree(adEnv, vehicle, poolGroup, isReachable)
local x, z = getVehiclePosition(vehicle)
if x == nil then
return nil
end
return ADSupplyPlanner.pickNearestFreeWaitPoint(ADWaitPool.getCandidates(adEnv, poolGroup), ADWaitPool.claims, x, z, vehicle, isReachable)
local candidates = ADWaitPool.getCandidates(adEnv, poolGroup)
local occupied = ADSupplyPlanner.markOccupiedWaitPoints(candidates, otherVehiclePositions(vehicle), ADWaitPool.OCCUPIED_RADIUS)
return ADSupplyPlanner.pickNearestFreeWaitPoint(candidates, ADWaitPool.claims, x, z, vehicle, isReachable, occupied)
end
-- Ender ruten i målet? (AutoDrive returnerer tom/afkortet liste når der ingen vej er.)
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107">
<author>masterdraco</author>
<version>1.18.2.0</version>
<version>1.18.3.0</version>
<title>
<en>AD Smart Pickup</en>
</title>
+7 -1
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — brugermanual
Version 1.18.2 · Farming Simulator 25 · tillæg til AutoDrive
Version 1.18.3 · Farming Simulator 25 · tillæg til AutoDrive
*English version: [USER_MANUAL.md](USER_MANUAL.md)*
@@ -102,6 +102,8 @@ Markøren skal højst være **40 m** fra bygningens trigger.
3. Forbind hvert Wait-punkt til vejnettet **i begge retninger**, både fra bygningerne og tilbage.
Wait-markører bliver aldrig brugt som kilde eller mål, heller ikke når de står tæt på en bygning.
Et Wait-punkt regnes som optaget, når det er reserveret, eller når et andet køretøj fysisk holder
højst 6 m fra det.
Markører i markernes mapper, fx *F91 Wait*, bliver ikke rørt, fordi det er **mappens** navn, der
tæller.
@@ -309,6 +311,10 @@ de valgte tage læsset, venter traktoren ved Wait, og loggen skriver hvorfor, fx
fuld*. Vælger du ingen, finder moden selv den nærmeste silo eller egne fabrik, der bruger varen.
Paller følger stadig reglen om nærmeste palleskur.
Med **Lager til** valgt regnes flowet som **fodring**: traktoren kører, så snart kilden har mindst
10.000 l af varen. Den venter ikke på fuldt læs eller *Kør til lager over*, for et anlæg som en
biogas-fermentor går i stå, hvis det kun får læs en gang imellem.
### Sådan vælges hver tur
Før hver pålæsning, og hvert femte sekund mens traktoren holder, vurderer moden:
+7 -1
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — user manual
Version 1.18.2 · Farming Simulator 25 · add-on for AutoDrive
Version 1.18.3 · Farming Simulator 25 · add-on for AutoDrive
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
@@ -103,6 +103,8 @@ The marker must be at most **40 m** from the building's trigger.
3. Connect every Wait point to the road network **in both directions**, from the buildings and back.
Wait markers are never used as a source or target, even when they stand close to a building.
A Wait point counts as taken when it is reserved, or when another vehicle physically stands within
6 m of it.
Markers in field folders, such as *F91 Wait*, are left alone, because it is the **folder** name
that counts.
@@ -313,6 +315,10 @@ never to the AIO factory. If none of the chosen buildings can take the load, the
Wait, and the log says why, such as *valgt lager: BGA500kw: fuld*. If you pick none, the mod finds the
nearest silo or own factory that uses the good by itself. Pallets still follow the nearest pallet shed rule.
With **Store to** chosen, the flow counts as **feeding**: the tractor drives as soon as the source has
at least 10,000 l of the good. It does not wait for a full load or *Drive to storage above*, because a
plant such as a biogas fermenter stalls when it only gets a load now and then.
### How each trip is chosen
Before each loading, and every five seconds while the tractor waits, the mod checks:
+11
View File
@@ -156,5 +156,16 @@ check("P15 kilde uden lagre beholdes", kept[3].wayPointId, 5)
local none = P.dedupeSharedSources({}, function() return nil end)
check("P15 tom liste", #none, 0)
-- P16: fodring ("Lager til" valgt = feedMode): kør så snart kilden har FEED_MIN_LITERS — ikke fuldt læs / 80 %
local feedStore = {wayPointId = 50, name = "Slurry Drop 1", free = 100000}
local feedProduct = product("LIQUIDMANURE", FORAGE, "store", {{wayPointId = 2, available = 20000, level = 20000, capacity = 500000}}, nil, feedStore)
local tripNoFeed = P.plan(input({feedProduct}, {capacity = 100000}))
check("P16 uden fodring: 4 % og intet fuldt læs -> ingen tur", tripNoFeed, nil)
local tripFeed = P.plan(input({feedProduct}, {capacity = 100000, feedMode = true}))
check("P16 fodring: 20k >= minimum -> lagertur", tripFeed and tripFeed.kind, "store")
check("P16 fodring: mål = valgt drop", tripFeed and tripFeed.targetWayPointId, 50)
local tinyProduct = product("LIQUIDMANURE", FORAGE, "store", {{wayPointId = 2, available = 3000, level = 3000, capacity = 500000}}, nil, feedStore)
check("P16 fodring: under minimum -> ingen tur", P.plan(input({tinyProduct}, {capacity = 100000, feedMode = true})), nil)
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1)
+9
View File
@@ -159,6 +159,15 @@ 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("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)
-- fysisk optaget: et køretøj holder på punktet uden reservation (fx efter omplanlægning) -> spring over
local occ = ADSupplyPlanner.markOccupiedWaitPoints(waitPoints, {{x = waitPoints[1].x + 2, z = waitPoints[1].z}}, 6)
check("optaget punkt markeres", occ[waitPoints[1].marker.id], true)
check("optaget springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, nil, occ).marker.id, 2)
check("egen reservation vinder over optaget (det er en selv)", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {[1] = V1}, 10, 0, V1, nil, occ).marker.id, 1)
check("køretøj langt væk optager intet", next(ADSupplyPlanner.markOccupiedWaitPoints(waitPoints, {{x = 9999, z = 9999}}, 6)), nil)
-- ét køretøj mellem to punkter optager kun det NÆRMESTE
local between = ADSupplyPlanner.markOccupiedWaitPoints({{marker = {id = 7}, x = 0, z = 0}, {marker = {id = 8}, x = 6, z = 0}}, {{x = 2, z = 0}}, 6)
check("kun nærmeste punkt optages", (between[7] == true and between[8] == nil), true)
-- 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)