fix: spor-regel, vent kun ved målet, sikkerhedsventil (v1.4.0.0)
In-game-fund 2026-09-21: - 'Dropof'-markøren vandt over 'Pickup' ved ensilage-tankene (samme tank, modsat side): triggeren skal nu ligge i trækkerens spor (<= 6 m til siden); nødløsning uden spor-træf kun inden for 12 m når retningen kendes. - Rotationen blev skjult under hele pålæsningsopgaven, så traktoren ventede ved nabosiloens tomme trigger på vej til målet: skjules nu først når målet er nået. - Sikkerhedsventil: 30 s ved markøren uden læsning -> grebet slippes. Verificeret mod savegame: 40/40 silo-markører, 7 pickup koblet, 0 dropof.
This commit is contained in:
@@ -16,8 +16,17 @@ ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE = 40
|
||||
-- I tætte silo-rækker ligger nabosiloens trigger FORAN næsen og er ofte nærmere
|
||||
-- end markørens egen. Triggere mere end dette foran næsen hører til naboen.
|
||||
ADSmartPickup.MAX_TRIGGER_AHEAD_OF_MARKER = 2
|
||||
-- Vognen følger trækkerens spor: en trigger længere til siden end dette er en
|
||||
-- anden bane (fx aflæsningssiden af samme tank), ikke markørens læsseplads.
|
||||
ADSmartPickup.MAX_TRIGGER_LATERAL_OFFSET = 6
|
||||
-- Kendes køreretningen, men ligger ingen trigger i sporet bag næsen (kurve ind til
|
||||
-- markøren), accepteres kun en trigger helt tæt på.
|
||||
ADSmartPickup.MAX_FALLBACK_TRIGGER_DISTANCE = 12
|
||||
-- Under dette niveau regnes siloen som tom (undgår ture efter støv-rester).
|
||||
ADSmartPickup.MIN_USEFUL_LITERS = 1
|
||||
-- Sikkerhedsventil: står køretøjet ved markøren så længe uden at vognen fyldes
|
||||
-- (forkert markør↔silo-kobling, røret når ikke vognen), slippes grebet.
|
||||
ADSmartPickup.MAX_WAIT_WITHOUT_LOADING_MS = 30000
|
||||
ADSmartPickup.LOG_PREFIX = "ADSmartPickup: "
|
||||
|
||||
local LOAD_TRIGGER_NODE_FIELDS = {"triggerNode"}
|
||||
@@ -96,22 +105,26 @@ local function getDistancesToTriggers(triggers, nodeFields, wayPoint, direction)
|
||||
local tx, _, tz = getWorldTranslation(node)
|
||||
local distance = MathUtil.vector2Length(wayPoint.x - tx, wayPoint.z - tz)
|
||||
closest = math.min(closest, distance)
|
||||
-- > 0: triggeren ligger bag næsen set i køreretningen
|
||||
local behind = direction ~= nil and ((wayPoint.x - tx) * direction.x + (wayPoint.z - tz) * direction.z) or nil
|
||||
if behind ~= nil and behind >= -ADSmartPickup.MAX_TRIGGER_AHEAD_OF_MARKER then
|
||||
if direction ~= nil then
|
||||
-- behind > 0: bag næsen set i køreretningen; lateral: afstand fra sporet
|
||||
local behind = (wayPoint.x - tx) * direction.x + (wayPoint.z - tz) * direction.z
|
||||
local lateral = math.abs((wayPoint.x - tx) * direction.z - (wayPoint.z - tz) * direction.x)
|
||||
if behind >= -ADSmartPickup.MAX_TRIGGER_AHEAD_OF_MARKER and lateral <= ADSmartPickup.MAX_TRIGGER_LATERAL_OFFSET then
|
||||
closestBehind = math.min(closestBehind, distance)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return closest, closestBehind
|
||||
end
|
||||
|
||||
-- Markørens station: nærmeste trigger bag næsen; findes ingen (eller er
|
||||
-- retningen ukendt), nærmeste trigger overhovedet.
|
||||
-- Markørens station: nærmeste trigger i sporet bag næsen. Findes ingen, bruges
|
||||
-- nærmeste trigger overhovedet — helt tæt på hvis retningen kendes, ellers som før.
|
||||
local function getClosestStation(adEnv, candidateStations, triggersField, nodeFields, wayPoint, farmId)
|
||||
local direction = getArrivalDirection(adEnv, wayPoint)
|
||||
local best = {any = {distance = ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE}, behind = {distance = ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE}}
|
||||
local maxFallbackDistance = direction ~= nil and ADSmartPickup.MAX_FALLBACK_TRIGGER_DISTANCE or ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE
|
||||
local best = {any = {distance = maxFallbackDistance}, behind = {distance = ADSmartPickup.MAX_MARKER_TO_TRIGGER_DISTANCE}}
|
||||
for _, station in pairs(candidateStations) do
|
||||
if g_currentMission.accessHandler:canFarmAccess(farmId, station) then
|
||||
local closest, closestBehind = getDistancesToTriggers(station[triggersField], nodeFields, wayPoint, direction)
|
||||
@@ -339,6 +352,12 @@ function ADSmartPickup.shouldWaitForLoading(vehicle)
|
||||
if pickup == nil then
|
||||
return false
|
||||
end
|
||||
-- kun FREMME ved markøren: undervejs ruller vognen gennem nabosiloers triggere,
|
||||
-- og dér skal AutoDrives rotation gælde (kør videre ved tom trigger).
|
||||
local drivePathModule = vehicle.ad ~= nil and vehicle.ad.drivePathModule or nil
|
||||
if drivePathModule == nil or not drivePathModule:isTargetReached() then
|
||||
return false
|
||||
end
|
||||
local level = getStationFillLevel(pickup.station, pickup.fillTypes, getFarmId(vehicle))
|
||||
return level >= ADSmartPickup.MIN_USEFUL_LITERS
|
||||
end
|
||||
@@ -364,9 +383,30 @@ local function installLoadWaitHook(adEnv)
|
||||
return originalGetSetting(settingName, vehicle)
|
||||
end
|
||||
|
||||
-- true når vognen ikke er blevet fyldt i MAX_WAIT_WITHOUT_LOADING_MS ved markøren
|
||||
local function hasLoadingStalled(loadTask, dt)
|
||||
local pickup = activePickups[loadTask.vehicle]
|
||||
local fillLevel = autoDrive.getAllFillLevels ~= nil and autoDrive.getAllFillLevels(loadTask.trailers) or 0
|
||||
if pickup.lastFillLevel == nil or fillLevel > pickup.lastFillLevel then
|
||||
pickup.lastFillLevel, pickup.stalledMs = fillLevel, 0
|
||||
return false
|
||||
end
|
||||
pickup.stalledMs = pickup.stalledMs + dt
|
||||
return pickup.stalledMs > ADSmartPickup.MAX_WAIT_WITHOUT_LOADING_MS
|
||||
end
|
||||
|
||||
task.update = function(self, dt)
|
||||
local ok, shouldWait = pcall(ADSmartPickup.shouldWaitForLoading, self.vehicle)
|
||||
waitingVehicle = ok and shouldWait and self.vehicle or nil
|
||||
shouldWait = ok and shouldWait
|
||||
if shouldWait then
|
||||
local stalledOk, isStalled = pcall(hasLoadingStalled, self, dt)
|
||||
if not stalledOk or isStalled then
|
||||
Logging.warning(ADSmartPickup.LOG_PREFIX .. "'%s' læsser ikke ved markøren; slipper grebet så AutoDrive kører videre", tostring(self.vehicle:getName()))
|
||||
activePickups[self.vehicle] = nil
|
||||
shouldWait = false
|
||||
end
|
||||
end
|
||||
waitingVehicle = shouldWait and self.vehicle or nil
|
||||
local updateOk, err = pcall(originalUpdate, self, dt)
|
||||
waitingVehicle = nil
|
||||
if not updateOk then
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<modDesc descVersion="107">
|
||||
<author>masterdraco</author>
|
||||
<version>1.3.0.0</version>
|
||||
<version>1.4.0.0</version>
|
||||
<title>
|
||||
<en>AD Smart Pickup</en>
|
||||
</title>
|
||||
|
||||
@@ -34,6 +34,14 @@ Slå fra ved at vælge en anden enkelt vare.
|
||||
bagved. Markørens silo er derfor nærmeste trigger **bag næsen** set i ankomstretningen (forrige
|
||||
waypoint), højst 2 m foran. I tætte rækker (15 m mellem siloerne) er nabosiloens trigger ellers
|
||||
nærmere end markørens egen. Ingen trigger bag næsen/ukendt retning → nærmeste trigger som før.
|
||||
- **Spor-regel (v1.4):** triggeren skal ligge i trækkerens spor (højst 6 m til siden). En tank med
|
||||
aflæsningsbane på den ene side og pålæsningsbane på den anden giver ellers to markører til samme
|
||||
tank, og "Dropof"-markøren kunne vinde. Kendes retningen, men ligger intet i sporet (kurve ind til
|
||||
markøren), accepteres kun en trigger inden for 12 m.
|
||||
- **Kun fremme ved markøren (v1.4):** rotationen skjules først når målet er nået — undervejs ruller
|
||||
vognen gennem nabosiloers triggere, og dér skal AutoDrive køre videre ved tom trigger.
|
||||
- **Sikkerhedsventil (v1.4):** 30 s ved markøren uden at vognen fyldes → grebet slippes (advarsel i
|
||||
log), og AutoDrive kører videre som med almindelig rotation.
|
||||
- **Står og læsser (v1.2):** med rotation + mapper afslutter AutoDrive normalt pålæsningen i det øjeblik
|
||||
markøren nås (læsning skal være startet på vej ind, ellers kører den tom videre). Når moden har valgt
|
||||
siloen, ser AutoDrive under `LoadAtDestinationTask.update` rotationen som slået fra for netop det
|
||||
|
||||
@@ -78,7 +78,11 @@ end
|
||||
|
||||
local RT_NONE, RT_ONLYPICKUP = 1, 2
|
||||
FS25_AutoDrive = {
|
||||
AutoDrive = {RT_NONE = RT_NONE, getSetting = function(settingName) return settingName == "rotateTargets" and RT_ONLYPICKUP or "andet" end},
|
||||
AutoDrive = {
|
||||
RT_NONE = RT_NONE,
|
||||
getSetting = function(settingName) return settingName == "rotateTargets" and RT_ONLYPICKUP or "andet" end,
|
||||
getAllFillLevels = function(trailers) return trailers.level end,
|
||||
},
|
||||
-- opgaven noterer hvad AutoDrive "ser" af indstillinger mens den opdaterer
|
||||
LoadAtDestinationTask = {update = function(self)
|
||||
self.seenRotate = FS25_AutoDrive.AutoDrive.getSetting("rotateTargets", self.vehicle)
|
||||
@@ -110,6 +114,13 @@ local function makeDriver(firstMarker, fillTypes)
|
||||
}
|
||||
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}
|
||||
@@ -280,14 +291,23 @@ check("S10 lager læses fra produktionspunktet", manager:getNextPickup(feeder, f
|
||||
resetWorld()
|
||||
local loadLevels = {[SUGARBEET] = 5000}
|
||||
addMarker(1, "Silo 1", "Siloer", 0, 0); table.insert(stations, makeStation(101, 0, 15, loadLevels))
|
||||
local loader = makeDriver(markers[1], {SUGARBEET})
|
||||
local loader = withDrivePath(makeDriver(markers[1], {SUGARBEET}))
|
||||
manager:getNextPickup(loader, false)
|
||||
local task = {vehicle = loader}
|
||||
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)
|
||||
@@ -328,14 +348,46 @@ 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)
|
||||
|
||||
-- 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)
|
||||
|
||||
-- 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 = makeSupplyDriver(markers[1], markers[2], {HAY, GRASS})
|
||||
local stopper = withDrivePath(makeSupplyDriver(markers[1], markers[2], {HAY, GRASS}))
|
||||
manager:getNextPickup(stopper, false)
|
||||
local stopperTask = {vehicle = stopper}
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user