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:
@@ -306,7 +306,9 @@ function ADOutbound.buildPlanInput(adEnv, run, vehicle)
|
|||||||
product.store = findStoreMarker(adEnv, vehicle, product, capacity, sources, nil, run)
|
product.store = findStoreMarker(adEnv, vehicle, product, capacity, sources, nil, run)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
local feedMode = ADRunsController ~= nil and ADRunsController.getStoreMarkers ~= nil and #ADRunsController.getStoreMarkers(adEnv, run) > 0
|
||||||
local input = {
|
local input = {
|
||||||
|
feedMode = feedMode,
|
||||||
capacity = capacity,
|
capacity = capacity,
|
||||||
hasAutoload = hasAutoload,
|
hasAutoload = hasAutoload,
|
||||||
sellAtPercent = run.sellAtPercent,
|
sellAtPercent = run.sellAtPercent,
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ local function sumSources(product)
|
|||||||
return total, percent, pallets
|
return total, percent, pallets
|
||||||
end
|
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)
|
function ADOutboundPlanner.evaluate(product, input)
|
||||||
local available, storagePercent, palletCount = sumSources(product)
|
local available, storagePercent, palletCount = sumSources(product)
|
||||||
local percent = product.quote ~= nil and product.quote.percent or nil
|
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
|
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})
|
table.insert(sell, {product = product, e = e, kind = "sell", targetWayPointId = product.quote.wayPointId, targetName = product.quote.stationName, percent = e.percent})
|
||||||
added = true
|
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})
|
table.insert(store, {product = product, e = e, kind = "store", targetWayPointId = product.store.wayPointId, targetName = product.store.name})
|
||||||
added = true
|
added = true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -135,11 +135,30 @@ end
|
|||||||
-- ledigt (eller allerede reserveret af dette køretøj) og som isReachable(candidate)
|
-- 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).
|
-- 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.
|
-- 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 = {}
|
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
|
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
|
local dx, dz = candidate.x - x, candidate.z - z
|
||||||
table.insert(free, {candidate = candidate, distance = dx * dx + dz * dz})
|
table.insert(free, {candidate = candidate, distance = dx * dx + dz * dz})
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,12 +53,36 @@ end
|
|||||||
|
|
||||||
-- Nærmeste ledige (eller eget reserverede) Wait-punkt fra vognens position; isReachable(candidate)
|
-- Nærmeste ledige (eller eget reserverede) Wait-punkt fra vognens position; isReachable(candidate)
|
||||||
-- valgfri. nil uden position eller uden brugbart punkt. Reserverer IKKE selv.
|
-- 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)
|
function ADWaitPool.pickFree(adEnv, vehicle, poolGroup, isReachable)
|
||||||
local x, z = getVehiclePosition(vehicle)
|
local x, z = getVehiclePosition(vehicle)
|
||||||
if x == nil then
|
if x == nil then
|
||||||
return nil
|
return nil
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
-- Ender ruten i målet? (AutoDrive returnerer tom/afkortet liste når der ingen vej er.)
|
-- Ender ruten i målet? (AutoDrive returnerer tom/afkortet liste når der ingen vej er.)
|
||||||
|
|||||||
@@ -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.18.2.0</version>
|
<version>1.18.3.0</version>
|
||||||
<title>
|
<title>
|
||||||
<en>AD Smart Pickup</en>
|
<en>AD Smart Pickup</en>
|
||||||
</title>
|
</title>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# AD Smart Pickup — brugermanual
|
# 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)*
|
*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.
|
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.
|
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
|
Markører i markernes mapper, fx *F91 Wait*, bliver ikke rørt, fordi det er **mappens** navn, der
|
||||||
tæller.
|
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.
|
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.
|
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
|
### Sådan vælges hver tur
|
||||||
|
|
||||||
Før hver pålæsning, og hvert femte sekund mens traktoren holder, vurderer moden:
|
Før hver pålæsning, og hvert femte sekund mens traktoren holder, vurderer moden:
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,6 @@
|
|||||||
# AD Smart Pickup — user manual
|
# 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)*
|
*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.
|
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.
|
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
|
Markers in field folders, such as *F91 Wait*, are left alone, because it is the **folder** name
|
||||||
that counts.
|
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
|
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.
|
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
|
### How each trip is chosen
|
||||||
|
|
||||||
Before each loading, and every five seconds while the tractor waits, the mod checks:
|
Before each loading, and every five seconds while the tractor waits, the mod checks:
|
||||||
|
|||||||
@@ -156,5 +156,16 @@ check("P15 kilde uden lagre beholdes", kept[3].wayPointId, 5)
|
|||||||
local none = P.dedupeSharedSources({}, function() return nil end)
|
local none = P.dedupeSharedSources({}, function() return nil end)
|
||||||
check("P15 tom liste", #none, 0)
|
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"))
|
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)
|
||||||
|
|||||||
@@ -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("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)
|
||||||
|
-- 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)
|
-- 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
|
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("unåeligt nærmeste springes over", ADSupplyPlanner.pickNearestFreeWaitPoint(waitPoints, {}, 10, 0, V1, unreachableA).marker.id, 2)
|
||||||
|
|||||||
Reference in New Issue
Block a user