Skip to content

Commit 6cec16e

Browse files
antler22claude
andcommitted
Address pvaiko review round 3: simplify proxy, revert unrelated files, clean up guards
- CpManualCombineProxy: replace callUnloader() stub with isActiveCpCombine() returning true (correct approach per reviewer) - AIDriveStrategyCombineCourse: revert to upstream — combine strategy not installed when manually driving - PurePursuitController: revert to upstream — disableStopWhenOffTrack(5000) in the strategy is sufficient, grace period is redundant - PathfinderUtil: revert to upstream — swath detection unconfirmed, defer to future iteration - AIDriveStrategyUnloadCombine: remove do/end wrapper and self.ppc guard from disableStopWhenOffTrack call, fold into isManualProxy check; revert fill-level block to upstream; remove nil guards from harvesterStrategy calls in handleChopperTurn; remove redundant or-6 fallback on getWorkWidth(); handle manual blocker inline at blocking vehicle check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent de4c4a6 commit 6cec16e

5 files changed

Lines changed: 24 additions & 80 deletions

File tree

scripts/ai/CpManualCombineProxy.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ function CpManualCombineProxy:isWaitingInPocket()
380380
return false
381381
end
382382

383-
--- Needed so isActiveCpCombine() recognizes this as a valid combine strategy.
384-
function CpManualCombineProxy:callUnloader()
383+
function CpManualCombineProxy:isActiveCpCombine()
384+
return true
385385
end
386386

387387
function CpManualCombineProxy:getUnloadTargetType()

scripts/ai/PurePursuitController.lua

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,6 @@ PurePursuitController = CpObject()
5858
--- if the vehicle is more than cutOutDistanceLimit meters from the current segment's endpoints, cut-out the
5959
--- controller to stop. Some error must have caused us to wander way off-track, unlikely to recover.
6060
PurePursuitController.cutOutDistanceLimit = 50
61-
--- Only shut off after being off track for this long (ms). Reduces false shutdowns on brief excursions (turns, terrain).
62-
PurePursuitController.offTrackGracePeriodMs = 10000
6361

6462
-- constructor
6563
function PurePursuitController:init(vehicle)
@@ -94,8 +92,6 @@ function PurePursuitController:init(vehicle)
9492
self.waypointChangeListeners = {}
9593
-- enable/disable stopping the vehicle when it is off-track (too far away from any waypoint)
9694
self.stopWhenOffTrack = CpTemporaryObject(true)
97-
-- when we first entered the off-track shutdown zone (nil = not in zone or just left it)
98-
self.offTrackShutdownSince = nil
9995
end
10096

10197
-- destructor
@@ -468,7 +464,6 @@ function PurePursuitController:findGoalPoint()
468464
-- case i (first node outside virtual circle but not yet reached) or (not the first node but we are way off the track)
469465
if (ix == self.firstIx and ix ~= self.lastPassedWaypointIx) and
470466
q1 >= self.lookAheadDistance and q2 >= self.lookAheadDistance then
471-
self.offTrackShutdownSince = nil
472467
-- If we weren't on track yet (after initialization, on our way to the first/initialized waypoint)
473468
-- set the goal to the relevant WP
474469
self.goalWpNode:setToWaypoint(self.course, self.relevantWpNode.ix)
@@ -481,7 +476,6 @@ function PurePursuitController:findGoalPoint()
481476

482477
-- case ii (common case)
483478
if q1 <= self.lookAheadDistance and q2 >= self.lookAheadDistance then
484-
self.offTrackShutdownSince = nil
485479
-- in some weird cases q1 may be 0 (when we calculate a course based on the vehicle position) so fix that
486480
-- to avoid a nan
487481
if q1 < 0.0001 then
@@ -525,17 +519,10 @@ function PurePursuitController:findGoalPoint()
525519
-- current waypoint is the waypoint at the end of the path segment
526520
self:setCurrentWaypoint(ix + 1)
527521
end
528-
-- Only shut off after being continuously off track for a grace period (reduces false shutdowns).
529-
-- Strategies that legitimately expect to be off track should call disableStopWhenOffTrack().
530522
if (q1 > self.cutOutDistanceLimit) and (q2 > self.cutOutDistanceLimit) and self.stopWhenOffTrack:get() then
531-
self.offTrackShutdownSince = self.offTrackShutdownSince or g_time
532-
if (g_time - self.offTrackShutdownSince) >= self.offTrackGracePeriodMs then
533-
CpUtil.infoVehicle(self.vehicle, 'vehicle off track, shutting off Courseplay now.')
534-
self.vehicle:stopCurrentAIJob(AIMessageCpError.new())
535-
return
536-
end
537-
else
538-
self.offTrackShutdownSince = nil
523+
CpUtil.infoVehicle(self.vehicle, 'vehicle off track, shutting off Courseplay now.')
524+
self.vehicle:stopCurrentAIJob(AIMessageCpError.new())
525+
return
539526
end
540527
break
541528
end
@@ -545,7 +532,6 @@ function PurePursuitController:findGoalPoint()
545532
-- the reference node is already beyond the direction switch waypoint. We should not skip that being
546533
-- the current waypoint otherwise the relevant waypoint won't be moved over the direction switch
547534
if self.course:switchingDirectionAt(ix) then
548-
self.offTrackShutdownSince = nil
549535
-- force waypoint change
550536
self:showGoalpointDiag(100, 'switching direction while looking for goal point, ix=%d', ix)
551537
self.wpBeforeGoalPointIx = ix - 1

scripts/ai/strategies/AIDriveStrategyCombineCourse.lua

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -966,17 +966,14 @@ function AIDriveStrategyCombineCourse:callUnloader(bestUnloader, tentativeRendez
966966
end
967967

968968
---@param vehicle table
969-
---@return boolean true if vehicle is an active Courseplay controlled combine/harvester,
970-
--- or a manually-driven combine with an active "Call Unloader" request
969+
---@return boolean true if vehicle is an active Courseplay controlled combine/harvester
971970
function AIDriveStrategyCombineCourse.isActiveCpCombine(vehicle)
972-
if vehicle.getIsCpActive and vehicle:getIsCpActive() then
973-
local driveStrategy = vehicle.getCpDriveStrategy and vehicle:getCpDriveStrategy()
974-
return driveStrategy and driveStrategy.callUnloader ~= nil
975-
end
976-
if vehicle.cpIsManualCombineCallingUnloader and vehicle:cpIsManualCombineCallingUnloader() then
977-
return true
971+
if not (vehicle.getIsCpActive and vehicle:getIsCpActive()) then
972+
-- not driven by CP
973+
return false
978974
end
979-
return false
975+
local driveStrategy = vehicle.getCpDriveStrategy and vehicle:getCpDriveStrategy()
976+
return driveStrategy and driveStrategy.callUnloader ~= nil
980977
end
981978

982979
--- Find an unloader to drive to the target, which may either be the combine itself (when stopped and waiting for unload)

scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ function AIDriveStrategyUnloadCombine:handleChopperTurn(harvester)
928928

929929
-- since we are taking care of staying away, ask the chopper to ignore us
930930
local harvesterStrategy = self:getCombineStrategy()
931-
if harvesterStrategy then harvesterStrategy:requestToIgnoreProximity(self.vehicle) end
931+
harvesterStrategy:requestToIgnoreProximity(self.vehicle)
932932

933933
local d, dx, dz = self:getDistanceFromCombine(harvester)
934934
local combineSpeed = harvester.lastSpeedReal * 3600
@@ -946,12 +946,12 @@ function AIDriveStrategyUnloadCombine:handleChopperTurn(harvester)
946946
-- stay closer when still discharging
947947
if sameDirection then
948948
-- reverse speed is controlled around combine's speed
949-
dReference = (harvesterStrategy and harvesterStrategy:isDischarging()) and dz or dz - 3
949+
dReference = harvesterStrategy:isDischarging() and dz or dz - 3
950950
speed = combineSpeed + CpMathUtil.clamp(self.targetDistanceBehindChopper - dReference, -combineSpeed,
951951
self.settings.reverseSpeed:getValue() * 1.5)
952952
else
953953
-- reverse speed only depends on distance from the combine, stop when at working width
954-
speed = CpMathUtil.clamp((harvesterStrategy and harvesterStrategy:getWorkWidth() or 6) - d, 0,
954+
speed = CpMathUtil.clamp(harvesterStrategy:getWorkWidth() - d, 0,
955955
self.settings.reverseSpeed:getValue() * 1.5)
956956
end
957957
else
@@ -2166,27 +2166,6 @@ function AIDriveStrategyUnloadCombine:unloadMovingCombine()
21662166
self.followingCourseOffset = self:getFollowingCourseOffset(self.combineToUnload)
21672167
self.followCourse:setOffset(self.followingCourseOffset, 0)
21682168

2169-
-- NOTE: For manually-driven combines we do NOT refresh the follow course.
2170-
-- driveBesideCombine() computes the steering goal point directly from the pipe reference
2171-
-- node every frame (see the isManual branch there), so the course is never consulted for
2172-
-- steering. This prevents any possibility of angle lock, stale courses, or backward
2173-
-- courses produced by synthesising a follow course from the combine's live position.
2174-
--
2175-
-- Because the placeholder follow course is deliberately static while steering happens
2176-
-- off a live pipe reference, the cart WILL drift far away from the placeholder course as
2177-
-- the combine curves or S-turns. The PPC's off-track shutdown would see that drift and
2178-
-- kill the CP helper. Keep the check continuously disabled while a manual combine is the
2179-
-- unload target. We use 5000 ms (much longer than any realistic frame interval) so there
2180-
-- is no risk of a brief re-enable window between ticks.
2181-
do
2182-
local combineStrategy = self:getCombineStrategy()
2183-
if combineStrategy and combineStrategy.isManualProxy and combineStrategy:isManualProxy() then
2184-
if self.ppc and self.ppc.disableStopWhenOffTrack then
2185-
self.ppc:disableStopWhenOffTrack(5000)
2186-
end
2187-
end
2188-
end
2189-
21902169
if self:changeToUnloadWhenTrailerFull() then
21912170
return
21922171
end
@@ -2198,22 +2177,18 @@ function AIDriveStrategyUnloadCombine:unloadMovingCombine()
21982177
-- The farmer is in full control: ignore fill level, alignment, turning state, etc.
21992178
-- Stay under the pipe until the proxy's isUnloadFinished() fires (pipe closed for 2s)
22002179
-- or the grain cart's own trailer fills up (handled by changeToUnloadWhenTrailerFull above).
2201-
if combineStrategy.isManualProxy and combineStrategy:isManualProxy() then
2180+
-- The cart will drift from the static placeholder course as the combine curves — disable the
2181+
-- PPC off-track check for the duration (5000 ms >> any realistic frame interval).
2182+
if combineStrategy.isManualProxy then
2183+
self.ppc:disableStopWhenOffTrack(5000)
22022184
self:debugSparse('unloadMovingCombine (manual): isDischarging=%s',
2203-
tostring(combineStrategy.isDischarging and combineStrategy:isDischarging()))
2185+
tostring(combineStrategy:isDischarging()))
22042186
return gx, gz
22052187
end
22062188

22072189
--when the combine is empty, stop and wait for next combine (unless this can't work without an unloader nearby)
2208-
local fillPct = combineStrategy:getFillLevelPercentage()
2209-
local isDischarging = combineStrategy.isDischarging and combineStrategy:isDischarging()
2210-
local isUnloadFinished = combineStrategy.isUnloadFinished and combineStrategy:isUnloadFinished()
2211-
self:debugSparse('unloadMovingCombine: fillPct=%.2f isDischarging=%s isUnloadFinished=%s',
2212-
fillPct, tostring(isDischarging), tostring(isUnloadFinished))
2213-
-- Don't exit on fill level while the combine is still actively discharging — the pipe hasn't
2214-
-- closed yet and we'd leave with grain still flowing. Wait for discharge to stop first.
2215-
if fillPct <= 0.1 and not isDischarging and not combineStrategy:alwaysNeedsUnloader() then
2216-
self:debug('unloadMovingCombine: EXIT - combine empty (fillPct=%.2f) and not discharging, finishing unloading.', fillPct)
2190+
if combineStrategy:getFillLevelPercentage() <= 0.1 and not combineStrategy:alwaysNeedsUnloader() then
2191+
self:debug('Combine empty, finish unloading.')
22172192
self:onUnloadingMovingCombineFinished(combineStrategy)
22182193
return
22192194
end
@@ -2376,13 +2351,14 @@ function AIDriveStrategyUnloadCombine:onBlockingVehicle(blockingVehicle, isBack)
23762351
-- TODO: maybe a generic getTrailer() ?
23772352
local referenceObject = AIUtil.getImplementOrVehicleWithSpecialization(self.vehicle, Trailer) or
23782353
AIUtil.getImplementOrVehicleWithSpecialization(self.vehicle, HookLiftTrailer) or self.vehicle
2379-
if AIDriveStrategyCombineCourse.isActiveCpCombine(blockingVehicle) then
2354+
local isManualBlocker = blockingVehicle.cpIsManualCombineCallingUnloader and blockingVehicle:cpIsManualCombineCallingUnloader()
2355+
if AIDriveStrategyCombineCourse.isActiveCpCombine(blockingVehicle) or isManualBlocker then
23802356
-- except we are blocking our buddy, so set up a course parallel to the combine's direction,
23812357
-- with an offset from the combine that makes sure we are clear. Use the trailer's root node (and not
23822358
-- the tractor's) as when we reversing, it is easier when the trailer remains on the same side of the combine
23832359
local dx, _, _ = localToLocal(referenceObject.rootNode, blockingVehicle:getAIDirectionNode(), 0, 0, 0)
23842360
local blockingStrategy = blockingVehicle:getCpDriveStrategy() or (blockingVehicle.cpGetManualCombineProxy and blockingVehicle:cpGetManualCombineProxy())
2385-
local blockingWorkWidth = blockingStrategy and blockingStrategy:getWorkWidth() or 6
2361+
local blockingWorkWidth = blockingStrategy:getWorkWidth()
23862362
local xOffset = self.vehicle.size.width / 2 + blockingWorkWidth / 2 + 2
23872363
xOffset = dx > 0 and xOffset or -xOffset
23882364
self:setNewState(self.states.MOVING_AWAY_FROM_OTHER_VEHICLE)

scripts/pathfinder/PathfinderUtil.lua

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -269,21 +269,6 @@ function PathfinderUtil.hasFruit(x, z, length, width, areaToIgnoreFruit)
269269
break
270270
end
271271
end
272-
-- Ignore windrow/swath types (cut material lying on the ground). Use the game API first:
273-
-- isFillTypeWindrow covers all registered windrow fill types including DLC additions.
274-
-- Name-based fallback catches any types not in the windrow registry (e.g. mod types).
275-
if not ignoreThis then
276-
local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fruitType.name)
277-
if fillTypeIndex and g_fruitTypeManager:isFillTypeWindrow(fillTypeIndex) then
278-
ignoreThis = true
279-
end
280-
end
281-
if not ignoreThis then
282-
local name = string.lower(fruitType.name or '')
283-
if string.find(name, 'windrow') or string.find(name, 'swath') or name == 'straw' or name == 'chaff' then
284-
ignoreThis = true
285-
end
286-
end
287272
if not ignoreThis then
288273
-- if the last boolean parameter is true then it returns fruitValue > 0 for fruits/states ready for forage also
289274
local fruitValue, numPixels, totalNumPixels, c = FSDensityMapUtil.getFruitArea(fruitType.index, x - width / 2, z - length / 2, x + width / 2, z, x, z + length / 2, true, true)

0 commit comments

Comments
 (0)