Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); WIP rewrite the job stopping logic by schwiti6190 · Pull Request #800 · Courseplay/Courseplay_FS25 · GitHub
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions scripts/ai/controllers/BaleLoaderController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,4 +124,8 @@ end
--- This one is not used for the giants baleloaders
function BaleLoaderController:isReadyToLoadNextBale()
return false
end

function BaleLoaderController:onPreFinished()
return self:canBeFolded()
end
20 changes: 15 additions & 5 deletions scripts/ai/controllers/BalerController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,6 +26,7 @@ function BalerController:init(vehicle, baler)
self.slowDownStartSpeed = 20
self.balerSpec = self.baler.spec_baler
self.baleWrapperSpec = self.baler.spec_baleWrapper
self.baleLoaderSpec = self.baler.spec_baleLoader
self.lastDroppedBale = CpTemporaryObject()
self:debug('Baler controller initialized')
local additives = self.balerSpec.additives
Expand DownExpand Up@@ -124,11 +125,20 @@ function BalerController:onStart()
end
end

function BalerController:onFinished(hasFinished)
-- TODO: not working, as this probably needs to be called, before the drive is released.
-- if hasFinished and not self.balerSpec.automaticDrop or not self.balerSpec.platformAutomaticDrop then
-- Baler.actionEventUnloading(self.implement)
-- end
function BalerController:onPreFinished(hasFinished)
if hasFinished then
Baler.actionEventUnloading(self.baler)
if self.balerSpec.platformDropInProgress then
return
end
if self.balerSpec.isBaleUnloading then
return
end
if self.balerSpec.unloadingState ~= Baler.UNLOADING_CLOSED then
return
end
end
return true
end

function BalerController:isThisMyBale(baleObject)
Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/controllers/ImplementController.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,10 @@ function ImplementController:onFinished(hasFinished)
--- override
end

function ImplementController:onPreFinished()
return true
end

function ImplementController:onFinishRow(isHeadlandTurn)
end

Expand Down
15 changes: 0 additions & 15 deletions scripts/ai/jobs/CpAIJob.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,14 +58,6 @@ function CpAIJob:setupCpJobParameters(jobParameters)
self.cpJobParameters:validateSettings()
end

--- Is the ai job allowed to finish ?
--- This entry point allowes us to catch giants stop conditions.
---@param message table Stop reason can be used to reverse engineer the cause.
---@return boolean
function CpAIJob:isFinishingAllowed(message)
return true
end

--- Gets the first task to start with.
function CpAIJob:getStartTaskIndex()
if self.currentTaskIndex ~= 0 or self.isDirectStart or self:isTargetReached() then
Expand DownExpand Up@@ -137,12 +129,8 @@ function CpAIJob:stop(aiMessage)
vehicle:deleteAgent()
vehicle:aiJobFinished()
vehicle:resetCpAllActiveInfoTexts()
local driveStrategy = vehicle:getCpDriveStrategy()
if not aiMessage then
self:debug("No valid ai message given!")
if driveStrategy then
driveStrategy:onFinished()
end
AIJob.stop(self, aiMessage)
return
end
Expand All@@ -160,9 +148,6 @@ function CpAIJob:stop(aiMessage)
if event then
SpecializationUtil.raiseEvent(vehicle, event)
end
if driveStrategy then
driveStrategy:onFinished(hasFinished)
end
g_messageCenter:unsubscribeAll(self)
end

Expand Down
23 changes: 0 additions & 23 deletions scripts/ai/jobs/CpAIJobFieldWork.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,29 +47,6 @@ function CpAIJobFieldWork:setupJobParameters()
self:setupCpJobParameters(CpFieldWorkJobParameters(self))
end

function CpAIJobFieldWork:isFinishingAllowed(message)
local nextTaskIndex = self:getNextTaskIndex()
if message:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.

local vehicle = self:getVehicle()
local setting = vehicle:getCpSettings().refillOnTheField

if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_DISABLED then
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
if self.currentTaskIndex == self.fieldWorkTask.taskIndex then
self.fieldWorkTask:setWaitingForRefillingActive()
end
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self.fieldWorkTask:skip()
end
return false
end
return CpAIJob.isFinishingAllowed(self, message)
end

---@param vehicle table
---@param mission table
---@param farmId number
Expand Down
94 changes: 88 additions & 6 deletions scripts/ai/strategies/AIDriveStrategyCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -26,12 +26,16 @@ AIDriveStrategyCourse.myStates = {
INITIAL = {},
WAITING_FOR_PATHFINDER = {},
WAITING_FOR_FIELD_BOUNDARY_DETECTION = {},
PRE_FINISHED = {},
WAITING_FOR_FINISHED = {},
FINISHED = {}
}

--- Implement controller events.
--- TODO_25 a more generic implementation
AIDriveStrategyCourse.onRaisingEvent = "onRaising"
AIDriveStrategyCourse.onLoweringEvent = "onLowering"
AIDriveStrategyCourse.onPreFinishedEvent = "onPreFinished"
AIDriveStrategyCourse.onFinishedEvent = "onFinished"
AIDriveStrategyCourse.onStartEvent = "onStart"
AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling"
Expand DownExpand Up@@ -59,6 +63,11 @@ function AIDriveStrategyCourse:init(task, job)

self.currentTask = task
self.job = job
self.stopRequestData = {
reason = nil,
waitForFolding = false,
prepareTimeout = CpTemporaryObject(true)
}
end

function AIDriveStrategyCourse:setCurrentTaskFinished()
Expand DownExpand Up@@ -279,6 +288,10 @@ end

--- Called in the low frequency function for the helper.
function AIDriveStrategyCourse:updateLowFrequencyImplementControllers()
if self:hasFinished() then
--- Small hack so every ai drive strategy waits during finished.
self:setMaxSpeed(0)
end
for _, controller in pairs(self.controllers) do
---@type ImplementController
if controller:isEnabled() then
Expand DownExpand Up@@ -435,8 +448,57 @@ function AIDriveStrategyCourse:update(dt)
self.pathfinderController:update(dt)
self:updatePathfinding()
self:updateInfoTexts()
self:updateFinishing()
end

function AIDriveStrategyCourse:updateFinishing()
local function finishStrategy()
if self.stopRequestData.stopReason then
g_currentMission.aiSystem:stopJob(self.job, self.stopRequestData.stopReason)
return
end
self.currentTask:skip()
end
if self.state == self.states.PRE_FINISHED then
--- Every implement controller gets the chance to
--- prepare for the driver release.
--- For example balers can unload their bales
--- before we can fold them and so on.
local finished = true
for _, controller in ipairs(self.controllers) do
finished = finished and controller:onPreFinished()
end
if finished then
self:debug("Precondition for stopping the strategy are reached.")
self.state = self.states.FINISHED
end
elseif self.state == self.states.FINISHED then
self:raiseControllerEvent(self.onFinishedEvent, self.stopRequestData.waitForFolding)
if self.stopRequestData.waitForFolding then
self:debug("Starting to fold implements and so on...")
self.vehicle:prepareForAIDriving()
self.stopRequestData.prepareTimeout:set(false, 15000)
self.state = self.states.WAITING_FOR_FINISHED
else
finishStrategy()
end
elseif self.state == self.states.WAITING_FOR_FINISHED then
if not self.vehicle:getIsAIPreparingToDrive() or self.stopRequestData.prepareTimeout:get() then
if self.stopRequestData.prepareTimeout:get() then
self:debug("Failed to prepare ai drive, aborting ..")
end
finishStrategy()
end
end
end

--- Job has finished and we are now waiting to release the driver.
---@return boolean
function AIDriveStrategyCourse:hasFinished()
return self.state == self.states.PRE_FINISHED or
self.state == self.states.WAITING_FOR_FINISHED or
self.state == self.states.FINISHED
end

function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ)
local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -664,15 +726,35 @@ function AIDriveStrategyCourse:isCloseToCourseStart(distance)
return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance
end

--- Possiblity to override the vehicle:stopCurrentAIJob(),
--- if for example refilling on the field is active.
function AIDriveStrategyCourse:handleFinishedRequest(stopReason)
--- override
return false
end

--- Event raised when the driver was stopped.
---@param hasFinished boolean|nil flag passed by the info text
function AIDriveStrategyCourse:onFinished(hasFinished)
self:raiseControllerEvent(self.onFinishedEvent, hasFinished)
if hasFinished and self.settings.foldImplementAtEnd:getValue() then
--- Folds implements at the end if the setting is active.
self:debug("Finished with folding implements of the implements.")
self.vehicle:prepareForAIDriving()
---@param stopReason table
function AIDriveStrategyCourse:onFinished(hasFinished, stopReason)
if self:handleFinishedRequest(stopReason) then
--- Stop request ignored
return
end
if self:hasFinished() then
--- Driver is already stopping and still waiting for folding and so on ...
return
end
self:setFinished(hasFinished and self.settings.foldImplementAtEnd:getValue(), stopReason)
end

--- Internal stop request
---@param waitForFolding boolean|nil wait until for the folding and so on ..
---@param stopReason table|nil if nil is given, then the task will be skipped
function AIDriveStrategyCourse:setFinished(waitForFolding, stopReason)
self.stopRequestData.stopReason = stopReason
self.stopRequestData.waitForFolding = waitForFolding
self.state = self.states.PRE_FINISHED
end

--- This is to set the offsets on the course at start, or update those values
Expand Down
21 changes: 19 additions & 2 deletions scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,11 +108,28 @@ function AIDriveStrategyFieldWorkCourse:prepareForFieldWork()
end

--- Event raised when the driver has finished.
function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished)
AIDriveStrategyCourse.onFinished(self, hasFinished)
function AIDriveStrategyFieldWorkCourse:onFinished(...)
AIDriveStrategyCourse.onFinished(self, ...)
self.remainingTime:reset()
end

function AIDriveStrategyFieldWorkCourse:handleFinishedRequest(stopReason)
--- TODO consolidate this logic in the future ...
if stopReason:isa(AIMessageErrorOutOfFill) then
--- At least one implement type needs to be refilled.
local setting = self.settings.refillOnTheField
if setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_WAITING then
self.currentTask:setWaitingForRefillingActive()
return true
elseif setting:getValue() == CpVehicleSettings.REFILL_ON_FIELD_ACTIVE then
--- TODO_25 Add driving to trailer for refilling here and so on ..
self:setFinished(true, nil)
return true
end
end
return false
end

function AIDriveStrategyFieldWorkCourse:update(dt)
AIDriveStrategyCourse.update(self, dt)
if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then
Expand Down
41 changes: 9 additions & 32 deletions scripts/ai/strategies/AIDriveStrategyFindBales.lua
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,8 +31,7 @@ AIDriveStrategyFindBales.myStates = {
WORKING_ON_BALE = {},
REVERSING_AFTER_PATHFINDER_FAILURE = {},
REVERSING_DUE_TO_OBSTACLE_AHEAD = {},
DRIVING_TO_START_MARKER = {},
WAITING_FOR_IMPLEMENTS_TO_FOLD = {}
DRIVING_TO_START_MARKER = {}
}
--- Offset to apply at the goal marker, so we don't crash with an empty unloader waiting there with the same position.
AIDriveStrategyFindBales.invertedGoalPositionOffset = -4.5
Expand DownExpand Up@@ -85,7 +84,7 @@ function AIDriveStrategyFindBales:collectNextBale()
self:findPathToNextBale()
return
end
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
self.state = self.states.DRIVING_TO_START_MARKER
end
end

Expand DownExpand Up@@ -314,29 +313,6 @@ function AIDriveStrategyFindBales:getBaleTarget(bale)
return State3D(xb, -zb, CpMathUtil.angleFromGame(yRot))
end

--- Sets the driver as finished, so either a path
--- to the start marker as a park position can be used
--- or the driver stops directly.
function AIDriveStrategyFindBales:setFinished()
if not self:isReadyToFoldImplements() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
self.vehicle:prepareForAIDriving()
if not self.vehicle:getIsAIReadyToDrive() then
-- Waiting until the folding has finished..
self:debugSparse("Waiting until an animation has finish, so the driver can be released ..")
return
end
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end

--- Finishes the job with the correct stop reason, as
--- the correct reason is needed for a possible AD takeover.
function AIDriveStrategyFindBales:finishJob()
Expand DownExpand Up@@ -566,9 +542,6 @@ function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ)
self:setMaxSpeed(self.settings.reverseSpeed:getValue())
elseif self.state == self.states.DRIVING_TO_START_MARKER then
self:setMaxSpeed(self.settings.fieldSpeed:getValue())
elseif self.state == self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
self:setFinished()
self:setMaxSpeed(0) --- folding
end

local moveForwards = not self.ppc:isReversing()
Expand DownExpand Up@@ -685,10 +658,14 @@ function AIDriveStrategyFindBales:update(dt)
self.ppc:getCourse():draw()
end
end
if self.state ~= self.states.DRIVING_TO_START_MARKER and
self.state ~= self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD then
if self.state ~= self.states.DRIVING_TO_START_MARKER then
if self:areBaleLoadersFull() then
self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD
if self.invertedStartPositionMarkerNode then
self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node")
self:startPathfindingToStartMarker()
else
self:finishJob()
end
end
end
--- Ignores the loaded auto loader bales.
Expand Down
Loading