diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index 8852dc9e..e7422476 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -74,13 +74,13 @@ function CpAIJobCombineUnloader:applyCurrentState(vehicle, mission, farmId, isDi x, _, z = getWorldTranslation(vehicle.rootNode) self.cpJobParameters.fieldPosition:setPosition(x, z) end - local x, z = self.cpJobParameters.fieldUnloadPosition:getPosition() + x, z = self.cpJobParameters.fieldUnloadPosition:getPosition() local angle = self.cpJobParameters.fieldUnloadPosition:getAngle() -- no field position from the previous job, use the vehicle's current position if x == nil or z == nil or angle == nil then x, _, z = getWorldTranslation(vehicle.rootNode) local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1) - local angle = MathUtil.getYRotationFromDirection(dirX, dirZ) + angle = MathUtil.getYRotationFromDirection(dirX, dirZ) self.cpJobParameters.fieldUnloadPosition:setPosition(x, z) self.cpJobParameters.fieldUnloadPosition:setAngle(angle) end @@ -140,7 +140,7 @@ function CpAIJobCombineUnloader:validate(farmId) ------------------------------------ --- Validate selected field ------------------------------------- - local isValid, errorMessage = self:validateFieldPosition(isValid, errorMessage) + isValid, errorMessage = self:validateFieldPosition(isValid, errorMessage) if not isValid then return isValid, errorMessage diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index e8edbc14..52aff303 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -73,16 +73,6 @@ end ---@param isStartPositionInvalid boolean resets the drive to target position by giants and the field position to the vehicle position. function CpAIJobFieldWork:applyCurrentState(vehicle, mission, farmId, isDirectStart, isStartPositionInvalid) CpAIJob.applyCurrentState(self, vehicle, mission, farmId, isDirectStart) - - local _ - local x, z = self.cpJobParameters.fieldPosition:getPosition() - - if x == nil or z == nil then - x, _, z = getWorldTranslation(vehicle.rootNode) - end - - self.cpJobParameters.fieldPosition:setPosition(x, z) - if isStartPositionInvalid then local x, _, z = getWorldTranslation(vehicle.rootNode) local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1) @@ -92,12 +82,17 @@ function CpAIJobFieldWork:applyCurrentState(vehicle, mission, farmId, isDirectSt self.cpJobParameters.startPosition:setAngle(angle) self.cpJobParameters.fieldPosition:setPosition(x, z) + else + local x, z = self.cpJobParameters.fieldPosition:getPosition() + if x == nil or z == nil then + x, _, z = getWorldTranslation(vehicle.rootNode) + self.cpJobParameters.fieldPosition:setPosition(x, z) + end end end --- Checks the field position setting. function CpAIJobFieldWork:validateFieldSetup(isValid, errorMessage) - if not isValid then return isValid, errorMessage end @@ -204,7 +199,6 @@ end --- Button callback to generate a field work course. function CpAIJobFieldWork:onClickGenerateFieldWorkCourse(callback) local vehicle = self.vehicleParameter:getVehicle() - local fieldPolygon = self:getFieldPolygon() local settings = vehicle:getCourseGeneratorSettings() if self.isCustomField then CpUtil.infoVehicle(vehicle, 'disabling island bypass on custom field') diff --git a/scripts/field/CustomField.lua b/scripts/field/CustomField.lua index fa68e66a..d04b7d66 100644 --- a/scripts/field/CustomField.lua +++ b/scripts/field/CustomField.lua @@ -106,6 +106,7 @@ function CustomField:getFieldNumber() return s and tonumber(s) end +---@return [{x, z}] vertices of the field polygon function CustomField:getVertices() return self.vertices end diff --git a/scripts/field/CustomFieldManager.lua b/scripts/field/CustomFieldManager.lua index 23be04d9..051a8de5 100644 --- a/scripts/field/CustomFieldManager.lua +++ b/scripts/field/CustomFieldManager.lua @@ -203,6 +203,7 @@ function CustomFieldManager:onClickRenameDialog(newName, clickOk, fieldToRename) end end +---@return CustomField|nil first custom field found at the given x and z coordinates. function CustomFieldManager:getCustomField(x, z) for _, field in pairs(self.fields) do if field:isPointOnField(x, z) then diff --git a/scripts/field/FieldBoundaryDetector.lua b/scripts/field/FieldBoundaryDetector.lua index b7205d0b..09ddbb8b 100644 --- a/scripts/field/FieldBoundaryDetector.lua +++ b/scripts/field/FieldBoundaryDetector.lua @@ -3,40 +3,57 @@ ---@class FieldBoundaryDetector FieldBoundaryDetector = CpObject() -function FieldBoundaryDetector:debug(...) - CpUtil.debugVehicle(CpDebug.DBG_COURSES, self.vehicle, ...) -end - -function FieldBoundaryDetector:info(...) - CpUtil.infoVehicle(self.vehicle, ...) -end - --- Create a FieldBoundaryDetector instance and start the detection immediately. The detection is an asynchronous --- process and FieldBoundaryDetector:update() must be called until it returns false to get the result. +--- +--- If the user prefers custom fields, the detection will first check if there is a custom field at the given position, +--- and if so, use that as the field boundary. If not, the Giants field boundary detection is used. +--- If the Giants detection fails, the custom field is used as a fallback if exists at the position. +--- --- The result is available through getFieldPolygon(), which is a polygon representing the field boundary around ---- x, z. +--- x, z, and through getIslandPolygons() which is an array of polygons representing the islands inside the field. +--- ---@param x number world X coordinate to start the detection at ---@param z number world Z coordinate to start the detection at ---@param vehicle table vehicle, this is used to generate the field course settings the Giants detection needs. function FieldBoundaryDetector:init(x, z, vehicle) + self.logger = Logger('FieldBoundaryDetector', Logger.level.debug, CpDebug.DBG_COURSES) self.vehicle = vehicle self.updates = 0 - self:info( 'Detecting field boundary at %.1f %.1f using the Giants function', x, z) + local customField = g_customFieldManager:getCustomField(x, z) + if customField and g_Courseplay.globalSettings.preferCustomFields:getValue() then + self.logger:info( 'Found custom field %s at %.1f %.1f and custom fields are preferred', + customField:getName(), x, z) + self:_useCustomField(customField) + return + end + self.logger:info( 'Detecting field boundary at %.1f %.1f using the Giants function', x, z) local fieldCourseSettings, implementData = FieldCourseSettings.generate(vehicle) self.courseField = FieldCourseField.generateAtPosition(x, z, fieldCourseSettings, function(courseField, success) if success then - self:info('Field boundary detection successful, %d boundary points and %d islands', - #courseField.fieldRootBoundary.boundaryLine, #courseField.islands) + self.logger:info('Field boundary detection successful after %d updates, %d boundary points and %d islands', + self.updates, #courseField.fieldRootBoundary.boundaryLine, #courseField.islands) + self.fieldPolygon = self:_getAsVertices(courseField.fieldRootBoundary.boundaryLine) + self.islandPolygons = {} + for i, island in ipairs(courseField.islands) do + local islandBoundary = self:_getAsVertices(island.rootBoundary.boundaryLine) + table.insert(self.islandPolygons, islandBoundary) + end else - self:info('Field boundary detection failed') + if customField then + self.logger:info('Field boundary detection failed after %d updates, but found custom field %s at %.1f %.1f', + self.updates, customField:getName(), x, z) + self:_useCustomField(customField) + return + end end - self.success, self.result = success, courseField end) end ---@return boolean true if still in progress, false when done function FieldBoundaryDetector:update(dt) - if self.courseField:update(dt, 0.00025) then + -- when we use the custom field, we are done immediately + if not self.useCustomField and self.courseField:update(dt, 0.00025) then self.updates = self.updates + 1 return true else @@ -46,26 +63,17 @@ end ---@return table|nil [{x, y, z}] field polygon with game vertices function FieldBoundaryDetector:getFieldPolygon() - if self.success then - return self:getAsVertices(self.result.fieldRootBoundary.boundaryLine) - end + return self.fieldPolygon end ---@return table|nil [[{x, y, z}]] array of island polygons with game vertices (x, y, z) function FieldBoundaryDetector:getIslandPolygons() - local islandPolygons = {} - if self.success then - for i, island in ipairs(self.result.islands) do - local islandBoundary = self:getAsVertices(island.rootBoundary.boundaryLine) - table.insert(islandPolygons, islandBoundary) - end - end - return islandPolygons + return self.islandPolygons end ---@param boundaryLine table [[x, z]] array of arrays as the Giants functions return the field boundary ---@return table [{x, z}] array of vertices as the course generator needs it -function FieldBoundaryDetector:getAsVertices(boundaryLine) +function FieldBoundaryDetector:_getAsVertices(boundaryLine) local vertices = {} for _, point in ipairs(boundaryLine) do local x, z = point[1], point[2] @@ -73,3 +81,8 @@ function FieldBoundaryDetector:getAsVertices(boundaryLine) end return vertices end + +function FieldBoundaryDetector:_useCustomField(customField) + self.fieldPolygon = customField:getVertices() + self.useCustomField = true +end \ No newline at end of file