From 1a6de2d1885f1a7a1db3c312b1d88de87c73eb96 Mon Sep 17 00:00:00 2001 From: Helge Neumann <117832631+helgehelge123@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:56:19 +0200 Subject: [PATCH 1/2] Fix non deterministic order of the map context buttons getNumberOfItemsInSection() built contextActionMapping by iterating the CONTEXT_ACTIONS table with pairs(). That table has integer keys assigned in the constructor, so pairs() returns them in Lua hash order, not in index order, and the buttons in the lower right corner could show up in any order. Use ipairs() so the buttons always appear in the order the actions are defined in. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/gui/pages/CpCourseGeneratorFrame.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index cac6840d1..3edf2a407 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -1153,7 +1153,7 @@ function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) end if list == self.contextButtonList then self.contextActionMapping = {} - for index, action in pairs(self.contextActions) do + for index, action in ipairs(self.contextActions) do if action.isActive and not action.actionOnly then table.insert(self.contextActionMapping, index) end @@ -1165,7 +1165,7 @@ function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) end if list == self.contextButtonCustomFieldList then self.contextActionMapping = {} - for index, action in pairs(self.contextActions) do + for index, action in ipairs(self.contextActions) do if action.isActive and not action.actionOnly then table.insert(self.contextActionMapping, index) end From 1c0b1ede3b72324532a9d016dbb92177f7e77c7d Mon Sep 17 00:00:00 2001 From: Helge Neumann <117832631+helgehelge123@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:56:44 +0200 Subject: [PATCH 2/2] Add a button to set the job positions to the vehicle position When creating a job while already standing on the field, the target position and the field/silo position still have to be picked on the map, which is fiddly and easy to get wrong. Add a "Target & field to vehicle" button to the job parameter list that sets both to the vehicle's current position in one click, the target position also gets the direction the vehicle is facing. It is placed in a group of its own right below the position parameters, so it is not clicked by accident, and only shows up for jobs that actually have such a position. The same action is available as a context button in the lower right corner and bound to a new, rebindable input action CP_SET_POSITIONS_TO_VEHICLE (KEY_v by default, that key is unused in the menu), so the usual work flow can be done with the keyboard alone: V set the positions, C generate the course, X start the job. Co-Authored-By: Claude Opus 5 (1M context) --- modDesc.xml | 5 + scripts/gui/pages/CpCourseGeneratorFrame.lua | 109 +++++++++++++++++-- translations/translation_de.xml | 3 + translations/translation_en.xml | 3 + 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/modDesc.xml b/modDesc.xml index a24cfce9b..c31156b7e 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -404,6 +404,10 @@ Changelog 8.1.0.3 + + + + @@ -451,6 +455,7 @@ Changelog 8.1.0.3 + diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 3edf2a407..036be5ddf 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -35,14 +35,17 @@ CpCourseGeneratorFrame = { CONTEXT_ACTIONS = { ENTER_VEHICLE = 1, CREATE_JOB = 2, - START_JOB = 3, - STOP_JOB = 4, - GENERATE_COURSE = 5, - DELETE_CUSTOM_FIELD = 6, - RENAME_CUSTOM_FIELD = 7, - EDIT_CUSTOM_FIELD = 8, - DRAW_CUSTOM_FIELD = 9, - HOTSPOT_SELECT_ALL = 10 + --- These three show up in the create job button list in this order, which is also + --- the usual work flow: set the positions, generate the course, start the job. + SET_POSITIONS_TO_VEHICLE = 3, + GENERATE_COURSE = 4, + START_JOB = 5, + STOP_JOB = 6, + DELETE_CUSTOM_FIELD = 7, + RENAME_CUSTOM_FIELD = 8, + EDIT_CUSTOM_FIELD = 9, + DRAW_CUSTOM_FIELD = 10, + HOTSPOT_SELECT_ALL = 11 }, AI_MODE_OVERVIEW = 1, AI_MODE_CREATE = 2, @@ -945,6 +948,63 @@ function CpCourseGeneratorFrame:getIsPicking() return self.isPickingRotation or self.isPickingLocation end +--- Sets the target position (the position the helper drives to first, with the vehicle's direction) +--- and the field/silo position of the job currently being created to the vehicle's position, so +--- these don't have to be picked on the map when standing on the field already. +function CpCourseGeneratorFrame:setJobPositionsToVehiclePosition() + local vehicle = self.currentJobVehicle + if self.currentJob == nil or vehicle == nil or vehicle.rootNode == nil or self:getIsPicking() then + return + end + local x, _, z = getWorldTranslation(vehicle.rootNode) + local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1) + local angle = MathUtil.getYRotationFromDirection(dirX, dirZ) + local changed = false + for _, element in ipairs(self.currentJobElements) do + local parameter = element.aiParameter + if parameter and parameter.is_a and parameter:is_a(CpAIParameterPosition) and parameter:getCanBeChanged() then + local positionType = parameter:getPositionType() + if positionType == CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO then + parameter:setPosition(x, z) + if parameter.setAngle then + --- the target of the drive to task also has a direction, use the one the vehicle is facing + parameter:setAngle(angle) + end + changed = true + elseif positionType == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then + parameter:setPosition(x, z) + changed = true + end + end + end + if changed then + --- this also restarts the field boundary detection for the new field position + self:validateParameters() + self:updateParameterValueTexts() + end +end + +--- Adds a button to the job parameter list, which sets the target and the field position +--- to the vehicle's current position. It gets a group of its own so it is separated from +--- the position rows above and isn't clicked by accident. +function CpCourseGeneratorFrame:addSetPositionsToVehicleButton() + local titleElement = self.createTitleTemplate:clone(self.jobMenuLayout) + titleElement:setText(g_i18n:getText("CP_ai_page_positions_to_vehicle_title")) + --- Using the position parameter template, so the button looks and behaves exactly + --- like the position rows above it. + local element = self.createPositionTemplate:clone(self.jobMenuLayout) + FocusManager:loadElementFromCustomValues(element) + element:setText(g_i18n:getText("CP_ai_page_positions_to_vehicle")) + local invalidElement = element:getDescendantByName("invalid") + if invalidElement then + invalidElement:setVisible(false) + end + element.onClickCallback = function() + self:setJobPositionsToVehiclePosition() + end + return element +end + function CpCourseGeneratorFrame:validateParameters() local isValid = true local errorText = "" @@ -1455,6 +1515,12 @@ function CpCourseGeneratorFrame:initializeContextActions() callback = self.onCreateJob, isActive = false }, + [self.CONTEXT_ACTIONS.SET_POSITIONS_TO_VEHICLE] = { + text = g_i18n:getText("CP_ai_page_positions_to_vehicle"), + action = InputAction.CP_SET_POSITIONS_TO_VEHICLE, + callback = self.setJobPositionsToVehiclePosition, + isActive = false + }, [self.CONTEXT_ACTIONS.START_JOB] = { text = g_i18n:getText("button_startJob"), action = InputAction.MENU_EXTRA_1, @@ -1547,6 +1613,9 @@ function CpCourseGeneratorFrame:updateContextActions() end end self.contextActions[self.CONTEXT_ACTIONS.CREATE_JOB].isActive = self.canCreateJob and self.mode ~= self.AI_MODE_CREATE + self.contextActions[self.CONTEXT_ACTIONS.SET_POSITIONS_TO_VEHICLE].isActive = + self.mode == self.AI_MODE_CREATE and self.currentJob ~= nil and + self.currentJobVehicle ~= nil and not self:getIsPicking() self.contextActions[self.CONTEXT_ACTIONS.START_JOB].isActive = self:getCanStartJob() self.contextActions[self.CONTEXT_ACTIONS.STOP_JOB].isActive = self:getCanCancelJob() and self.mode ~= self.AI_MODE_CREATE self.contextActions[self.CONTEXT_ACTIONS.GENERATE_COURSE].isActive = self:getCanGenerateFieldWorkCourse() @@ -1765,11 +1834,27 @@ function CpCourseGeneratorFrame:setActiveJobTypeSelection(jobTypeIndex) end self.currentJob:applyCurrentState(self.currentJobVehicle, g_currentMission, farmId, false) self.currentJobElements = {} + --- The button setting the positions to the vehicle position goes right below the field/silo + --- position, or below the target position for jobs without a field position (silo loader). + local buttonAnchorPositionType = nil + for _, group in ipairs(self.currentJob:getGroupedParameters()) do + for _, item in ipairs(group:getParameters()) do + local positionType = item.getPositionType and item:getPositionType() + if positionType == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then + buttonAnchorPositionType = positionType + elseif positionType == CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO and + buttonAnchorPositionType == nil then + buttonAnchorPositionType = positionType + end + end + end + local isButtonAdded = false for _, group in ipairs(self.currentJob:getGroupedParameters()) do local titleElement = self.createTitleTemplate:clone(self.jobMenuLayout) titleElement:setText(group:getTitle()) + local isButtonAnchorGroup = false for _, item in ipairs(group:getParameters()) do local element = nil local parameterType = item:getType() @@ -1797,8 +1882,16 @@ function CpCourseGeneratorFrame:setActiveJobTypeSelection(jobTypeIndex) end element:setDisabled(not item:getCanBeChanged()) table.insert(self.currentJobElements, element) + if buttonAnchorPositionType ~= nil and item.getPositionType and + item:getPositionType() == buttonAnchorPositionType then + isButtonAnchorGroup = true + end end end + if isButtonAnchorGroup and not isButtonAdded then + self:addSetPositionsToVehicleButton() + isButtonAdded = true + end end self:validateParameters() self:updateParameterValueTexts() diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 6d6a9364e..e13796b80 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -108,6 +108,8 @@ + + @@ -1076,6 +1078,7 @@ Das Kreuz sollte jetzt, wie im Bild dargestellt, gelb sein. + diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 29744c145..f4096d35e 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -108,6 +108,8 @@ + + @@ -1092,6 +1094,7 @@ Now your selection should look similar to the image. +