From 0d68848155400f2c2b4bdc3063b57562d9280b0b Mon Sep 17 00:00:00 2001 From: Peter Vaiko Date: Tue, 12 Nov 2024 18:48:09 -0500 Subject: [PATCH 001/158] chore: FS25 compatibility updates Looks like now some GUI elements can't be created as the 22 profiles we used do not exist. --- Courseplay.lua | 2 +- modDesc.xml | 2 +- scripts/Waypoint.lua | 2 +- scripts/ai/controllers/ShovelController.lua | 4 ++-- .../ai/parameters/AIParameterSettingList.lua | 18 +++++++++--------- scripts/gui/CpAIFrameExtended.lua | 2 +- scripts/gui/CpCourseManagerFrame.lua | 2 +- scripts/gui/CpGamePadHudScreen.lua | 2 +- scripts/gui/CpGlobalSettingsFrame.lua | 2 +- scripts/gui/CpGuiUtil.lua | 2 +- scripts/gui/CpVehicleSettingsFrame.lua | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index 528015fe4..9964ec83d 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -74,7 +74,7 @@ function Courseplay:showUserInformation(xmlFile, key) self.MOD_NAME, self.currentVersion, lastLoadedVersion) if showInfoDialog then - g_gui:showInfoDialog({ + g_gui:showDialog({ text = string.format(g_i18n:getText("CP_infoText"), self.currentVersion) }) if xmlFile then diff --git a/modDesc.xml b/modDesc.xml index 61f776880..b694a0714 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -1,5 +1,5 @@ - + 7.5.0.1 diff --git a/scripts/Waypoint.lua b/scripts/Waypoint.lua index bea476c72..542838f68 100644 --- a/scripts/Waypoint.lua +++ b/scripts/Waypoint.lua @@ -39,7 +39,7 @@ function Waypoint:init(wp) self.rev = self.rev or wp.gear and wp.gear == Gear.Backward -- dynamically added/calculated properties self.useTightTurnOffset = wp.useTightTurnOffset - self.turnControls = table.copy(wp.turnControls) + self.turnControls = table.clone(wp.turnControls) self.dToNext = wp.dToNext self.yRot = wp.yRot --- Set, when generated for a multi tool course diff --git a/scripts/ai/controllers/ShovelController.lua b/scripts/ai/controllers/ShovelController.lua index 8f5d03329..bddee17e7 100644 --- a/scripts/ai/controllers/ShovelController.lua +++ b/scripts/ai/controllers/ShovelController.lua @@ -10,8 +10,8 @@ ShovelController.POSITIONS = { } ShovelController.MAX_TRIGGER_HEIGHT = 8 ShovelController.MIN_TRIGGER_HEIGHT = 1 -ShovelController.TRIGGER_HEIGHT_RAYCAST_COLLISION_MASK = CollisionFlag.STATIC_WORLD + CollisionFlag.STATIC_OBJECTS + - CollisionFlag.STATIC_OBJECT + CollisionFlag.VEHICLE +ShovelController.TRIGGER_HEIGHT_RAYCAST_COLLISION_MASK = + CollisionFlag.DEFAULT + CollisionFlag.STATIC_OBJECT + CollisionFlag.VEHICLE function ShovelController:init(vehicle, implement, isConsoleCommand) ImplementController.init(self, vehicle, implement) diff --git a/scripts/ai/parameters/AIParameterSettingList.lua b/scripts/ai/parameters/AIParameterSettingList.lua index 1e183a833..8edb0dcdb 100644 --- a/scripts/ai/parameters/AIParameterSettingList.lua +++ b/scripts/ai/parameters/AIParameterSettingList.lua @@ -15,8 +15,8 @@ function AIParameterSettingList:init(data, vehicle, class) if next(data.values) ~=nil then --- The setting has values defined in the data, so we copy these here. --- This saves the unmodified values in the data table. - self.values = table.copy(data.values) - self.texts = table.copy(data.texts) + self.values = table.clone(data.values) + self.texts = table.clone(data.texts) elseif data.min ~= nil and data.max ~=nil then --- The setting has a min and max value, --- so we generate a series of float values and texts here. @@ -25,17 +25,17 @@ function AIParameterSettingList:init(data, vehicle, class) AIParameterSettingList.generateValues(self, self.data.values, self.data.texts, data.min, data.max, data.incremental, data.unit, data.precision) --- Same as above, make sure the values are copied. - self.values = table.copy(self.data.values) + self.values = table.clone(self.data.values) if self.data.texts ~= nil then - self.texts = table.copy(self.data.texts) + self.texts = table.clone(self.data.texts) end data.textInputAllowed = true elseif data.generateValuesFunction then --- A generation function by the parent class is used --- to enrich/create the setting values/texts. self.data.values, self.data.texts = self:getCallback(data.generateValuesFunction) - self.values = table.copy(self.data.values) - self.texts = table.copy(self.data.texts) + self.values = table.clone(self.data.values) + self.texts = table.clone(self.data.texts) self:validateTexts() end --- Text input is only allowed, when the settings values are numeric. @@ -50,7 +50,7 @@ function AIParameterSettingList:init(data, vehicle, class) --- Fallback text generation based on the numeric values and a optional given unit. self.data.texts = {} AIParameterSettingList.enrichTexts(self, self.data.texts, data.unit) - self.texts = table.copy(self.data.texts) + self.texts = table.clone(self.data.texts) end --- Lastly apply the default values here. if data.default ~=nil then @@ -474,8 +474,8 @@ end --- Resets the disabled values back to the orignal value table function AIParameterSettingList:resetValuesBackToSetupValues() - self.values = table.copy(self.data.values) - self.texts = table.copy(self.data.texts) + self.values = table.clone(self.data.values) + self.texts = table.clone(self.data.texts) end --- Gets a specific value. diff --git a/scripts/gui/CpAIFrameExtended.lua b/scripts/gui/CpAIFrameExtended.lua index b6696f41e..8406e27e9 100644 --- a/scripts/gui/CpAIFrameExtended.lua +++ b/scripts/gui/CpAIFrameExtended.lua @@ -47,7 +47,7 @@ function CpInGameMenuAIFrameExtended:onAIFrameLoadMapFinished() CpInGameMenuAIFrameExtended.setupButtons(self) - self:registerControls({"multiTextOptionPrefab","subTitlePrefab","courseGeneratorLayoutElements", + self:exposeControlsAsFields({"multiTextOptionPrefab","subTitlePrefab","courseGeneratorLayoutElements", "courseGeneratorLayout","courseGeneratorHeader","drawingCustomFieldHeader", "drawingCustomFieldSubHeader", "courseGeneratorFrame", "createCpMultiOptionTemplate", "createCpTextTemplate"}) diff --git a/scripts/gui/CpCourseManagerFrame.lua b/scripts/gui/CpCourseManagerFrame.lua index 2763e3238..ab3b7a452 100644 --- a/scripts/gui/CpCourseManagerFrame.lua +++ b/scripts/gui/CpCourseManagerFrame.lua @@ -74,7 +74,7 @@ local CpCourseManagerFrame_mt = Class(CpCourseManagerFrame, TabbedMenuFrameEleme function CpCourseManagerFrame.new(courseStorage, target, custom_mt) local self = TabbedMenuFrameElement.new(target, custom_mt or CpCourseManagerFrame_mt) - self:registerControls(CpCourseManagerFrame.CONTROLS) + self:exposeControlsAsFields(CpCourseManagerFrame.CONTROLS) self.courseStorage = courseStorage return self end diff --git a/scripts/gui/CpGamePadHudScreen.lua b/scripts/gui/CpGamePadHudScreen.lua index 83ae723bb..3f757c42b 100644 --- a/scripts/gui/CpGamePadHudScreen.lua +++ b/scripts/gui/CpGamePadHudScreen.lua @@ -22,7 +22,7 @@ local CpGamePadHudScreen_mt = Class(CpGamePadHudScreen, ScreenElement) function CpGamePadHudScreen.new(settings, target, custom_mt) local self = ScreenElement.new(target, custom_mt or CpGamePadHudScreen_mt) - self:registerControls(CpGamePadHudScreen.CONTROLS) + self:exposeControlsAsFields(CpGamePadHudScreen.CONTROLS) self.settings = settings return self end diff --git a/scripts/gui/CpGlobalSettingsFrame.lua b/scripts/gui/CpGlobalSettingsFrame.lua index 1c87077ee..7eb1112b5 100644 --- a/scripts/gui/CpGlobalSettingsFrame.lua +++ b/scripts/gui/CpGlobalSettingsFrame.lua @@ -17,7 +17,7 @@ local CpGlobalSettingsFrame_mt = Class(CpGlobalSettingsFrame, TabbedMenuFrameEle function CpGlobalSettingsFrame.new(target, custom_mt) local self = TabbedMenuFrameElement.new(target, custom_mt or CpGlobalSettingsFrame_mt) - self:registerControls(CpGlobalSettingsFrame.CONTROLS) + self:exposeControlsAsFields(CpGlobalSettingsFrame.CONTROLS) return self end diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua index 59e56cdaa..9c29a274a 100644 --- a/scripts/gui/CpGuiUtil.lua +++ b/scripts/gui/CpGuiUtil.lua @@ -11,7 +11,7 @@ function CpGuiUtil.fixInGameMenuPage(frame, pageName, uvs, position, predicateFu inGameMenu.controlIDs[v] = nil end - inGameMenu:registerControls({pageName}) + inGameMenu:exposeControlsAsFields({pageName}) inGameMenu[pageName] = frame inGameMenu.pagingElement:addElement(inGameMenu[pageName]) diff --git a/scripts/gui/CpVehicleSettingsFrame.lua b/scripts/gui/CpVehicleSettingsFrame.lua index 68219697a..53028b49d 100644 --- a/scripts/gui/CpVehicleSettingsFrame.lua +++ b/scripts/gui/CpVehicleSettingsFrame.lua @@ -21,7 +21,7 @@ local CpVehicleSettingsFrame_mt = Class(CpVehicleSettingsFrame, TabbedMenuFrameE function CpVehicleSettingsFrame.new(target, custom_mt) local self = TabbedMenuFrameElement.new(target, custom_mt or CpVehicleSettingsFrame_mt) - self:registerControls(CpVehicleSettingsFrame.CONTROLS) + self:exposeControlsAsFields(CpVehicleSettingsFrame.CONTROLS) return self end From ff4b28a0f780787f6ffd077d48a3a5c67ad2a953 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Wed, 13 Nov 2024 08:24:14 -0500 Subject: [PATCH 002/158] chore: FS25 compatibility updates Commented out lots of GUI stuff with TODO 25, game now starts. --- Courseplay.lua | 8 ++++-- config/gui/CourseManagerFrame.xml | 14 +++++----- scripts/ai/jobs/CpAIJobCombineUnloader.lua | 14 +++++----- scripts/ai/jobs/CpAIJobSiloLoader.lua | 28 +++++++++---------- scripts/gui/CpGamePadHudScreen.lua | 2 +- scripts/gui/hud/CpBaseHud.lua | 2 +- .../CpCourseGeneratorSettings.lua | 6 ++-- scripts/specializations/CpCourseManager.lua | 2 +- 8 files changed, 40 insertions(+), 36 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index 9964ec83d..150b7a0ac 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -133,8 +133,8 @@ function Courseplay:setupGui() "CpVehicleSettingsFrame", vehicleSettingsFrame, true) g_gui:loadGui(Utils.getFilename("config/gui/GlobalSettingsFrame.xml", Courseplay.BASE_DIRECTORY), "CpGlobalSettingsFrame", globalSettingsFrame, true) - g_gui:loadGui(Utils.getFilename("config/gui/CourseManagerFrame.xml", Courseplay.BASE_DIRECTORY), - "CpCourseManagerFrame", courseManagerFrame, true) + --g_gui:loadGui(Utils.getFilename("config/gui/CourseManagerFrame.xml", Courseplay.BASE_DIRECTORY), + -- "CpCourseManagerFrame", courseManagerFrame, true) local function predicateFunc() -- Only allow the vehicle bound pages, when a vehicle with cp functionality is chosen/entered. local vehicle = CpInGameMenuAIFrameExtended.getVehicle() @@ -145,12 +145,14 @@ function Courseplay:setupGui() --- we move it down one position. local pos = g_modIsLoaded["FS22_precisionFarming"] and 4 or 3 +--[[ TODO 25 CpGuiUtil.fixInGameMenuPage(vehicleSettingsFrame, "pageCpVehicleSettings", {896, 0, 128, 128}, pos + 1, predicateFunc) CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings", {768, 0, 128, 128}, pos + 1, function () return true end) CpGuiUtil.fixInGameMenuPage(courseManagerFrame, "pageCpCourseManager", {256, 0, 128, 128}, pos + 1, predicateFunc) +]] self.infoTextsHud = CpHudInfoTexts() g_currentMission.hud.ingameMap.drawFields = Utils.appendedFunction(g_currentMission.hud.ingameMap.drawFields, Courseplay.drawHudMap) @@ -352,7 +354,7 @@ function Courseplay.register(typeManager) CpAICombineUnloader.register(typeManager, typeName, typeEntry.specializations) CpAISiloLoaderWorker.register(typeManager, typeName, typeEntry.specializations) CpAIBunkerSiloWorker.register(typeManager, typeName, typeEntry.specializations) - CpGamePadHud.register(typeManager, typeName,typeEntry.specializations) + -- TODO 25 CpGamePadHud.register(typeManager, typeName,typeEntry.specializations) CpHud.register(typeManager, typeName, typeEntry.specializations) CpInfoTexts.register(typeManager, typeName, typeEntry.specializations) CpShovelPositions.register(typeManager, typeName, typeEntry.specializations) diff --git a/config/gui/CourseManagerFrame.xml b/config/gui/CourseManagerFrame.xml index 14b0c81d1..faa11c024 100644 --- a/config/gui/CourseManagerFrame.xml +++ b/config/gui/CourseManagerFrame.xml @@ -9,12 +9,12 @@ <GuiElement type="empty" profile="ingameMenuSettingsBox" id="mainBox"> <GuiElement type="empty" profile="ingameMenuPriceLeftColumn" id="leftColumn"> - <GuiElement type="boxLayout" profile="ingameCalendarHeaderBox" id="tableHeaderBox" size="680px 104px"> + <GuiElement type="boxLayout" profile="fs25_calendarHeaderBox" id="tableHeaderBox" size="680px 104px"> <GuiElement type="text" profile="ingameMenuPriceHeader" size="340px 104px" id="leftColumnHeader" /> </GuiElement> - <GuiElement type="smoothList" profile="ingameMenuPriceList" id="leftList" focusChangeTop="nil" focusChangeBottom="nil" selectedWithoutFocus="true"> - <GuiElement type="listItem" profile="ingameMenuPriceListItem"> + <GuiElement type="smoothList" profile="fs25_pricesPriceList" id="leftList" focusChangeTop="nil" focusChangeBottom="nil" selectedWithoutFocus="true"> + <GuiElement type="listItem" profile="fs25_pricesPriceListItem"> <GuiElement type="bitmap" name="icon" profile="ingameMenuPriceGoodsIcon" /> <GuiElement type="text" name="title" profile="ingameMenuPriceItemTitle" /> </GuiElement> @@ -26,13 +26,13 @@ </GuiElement> <GuiElement type="empty" profile="ingameMenuPriceRightColumn" id="rightColumn"> - <GuiElement type="boxLayout" profile="ingameCalendarHeaderBox" id="tableHeaderBox" size="680px 104px"> + <GuiElement type="boxLayout" profile="fs25_calendarHeaderBox" id="tableHeaderBox" size="680px 104px"> <GuiElement type="text" profile="ingameMenuPriceHeader" size="330px 104px" id="rightColumnHeader" /> </GuiElement> - <GuiElement type="smoothList" profile="ingameMenuPriceList" id="rightList" focusChangeTop="nil" focusChangeBottom="nil"> - <GuiElement type="listItem" profile="ingameMenuPriceListItem"> - <GuiElement type="bitmap" name="icon" profile="ingameMenuPriceItemHotspot" /> + <GuiElement type="smoothList" profile="fs25_pricesPriceList" id="rightList" focusChangeTop="nil" focusChangeBottom="nil"> + <GuiElement type="listItem" profile="fs25_pricesPriceListItem"> + <GuiElement type="bitmap" name="icon" profile="fs25_pricesPriceListItemHotspot" /> <GuiElement type="text" name="title" profile="ingameMenuPriceSellpointTitle" /> </GuiElement> </GuiElement> diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index 7aa5afc0e..d4821ec41 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -19,8 +19,8 @@ function CpAIJobCombineUnloader.new(isServer, customMt) self.selectedFieldPlot:setVisible(false) self.selectedFieldPlot:setBrightColor(true) - self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) - self.heapPlot:setVisible(false) + --TODO 25 self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) + --TODO 25 self.heapPlot:setVisible(false) self.heapNode = CpUtil.createNode("siloNode", 0, 0, 0, nil) --- Giants unload @@ -138,7 +138,7 @@ end --- Called when parameters change, scan field function CpAIJobCombineUnloader:validate(farmId) self.selectedFieldPlot:setVisible(false) - self.heapPlot:setVisible(false) + --TODO 25 self.heapPlot:setVisible(false) local isValid, errorMessage = CpAIJob.validate(self, farmId) if not isValid then return isValid, errorMessage @@ -220,9 +220,9 @@ function CpAIJobCombineUnloader:validate(farmId) setTranslation(self.heapNode, x, 0, z) setRotation(self.heapNode, 0, angle, 0) local found, heapSilo = BunkerSiloManagerUtil.createHeapBunkerSilo(vehicle, self.heapNode, 0, self.maxHeapLength, -10) - if found then - self.heapPlot:setArea(heapSilo:getArea()) - self.heapPlot:setVisible(true) + if found then + --TODO 25 self.heapPlot:setArea(heapSilo:getArea()) + --TODO 25 self.heapPlot:setVisible(true) end end @@ -233,7 +233,7 @@ function CpAIJobCombineUnloader:draw(map, isOverviewMap) CpAIJob.draw(self, map, isOverviewMap) if not isOverviewMap then self.selectedFieldPlot:draw(map) - self.heapPlot:draw(map) + --TODO 25 self.heapPlot:draw(map) end end diff --git a/scripts/ai/jobs/CpAIJobSiloLoader.lua b/scripts/ai/jobs/CpAIJobSiloLoader.lua index 8ae7dd319..5584b7874 100644 --- a/scripts/ai/jobs/CpAIJobSiloLoader.lua +++ b/scripts/ai/jobs/CpAIJobSiloLoader.lua @@ -20,10 +20,10 @@ CpAIJobSiloLoader.MAX_UNLOAD_TARGET_DISTANCE_FROM_SILO = 180 function CpAIJobSiloLoader.new(isServer, customMt) local self = CpAIJob.new(isServer, customMt or AIJobCombineUnloaderCp_mt) - self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) - self.heapPlot:setVisible(false) + --TODO 25 self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) + --TODO 25 self.heapPlot:setVisible(false) - self.trailerAreaPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) + --TODO 25 self.trailerAreaPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) self.heapNode = CpUtil.createNode("siloNode", 0, 0, 0, nil) @@ -101,8 +101,8 @@ function CpAIJobSiloLoader:setValues() if bunkerSilo then self.bunkerSilo = bunkerSilo elseif heapSilo then - self.heapPlot:setArea(heapSilo:getArea()) - self.heapPlot:setVisible(true) + --TODO 25 self.heapPlot:setArea(heapSilo:getArea()) + --TODO 25 self.heapPlot:setVisible(true) self.heap = heapSilo end self.siloLoaderTask:setSiloAndHeap(self.bunkerSilo, self.heap) @@ -112,8 +112,8 @@ end --- Called when parameters change, scan field function CpAIJobSiloLoader:validate(farmId) - self.heapPlot:setVisible(false) - self.trailerAreaPlot:setVisible(false) + --TODO 25 self.heapPlot:setVisible(false) + --TODO 25 self.trailerAreaPlot:setVisible(false) self.heap = nil self.bunkerSilo = nil self.unloadStation = nil @@ -136,8 +136,8 @@ function CpAIJobSiloLoader:validate(farmId) if bunkerSilo then self.bunkerSilo = bunkerSilo elseif heapSilo then - self.heapPlot:setArea(heapSilo:getArea()) - self.heapPlot:setVisible(true) + --TODO 25 self.heapPlot:setArea(heapSilo:getArea()) + --TODO 25 self.heapPlot:setVisible(true) self.heap = heapSilo end self.siloLoaderTask:setSiloAndHeap(self.bunkerSilo, self.heap) @@ -175,9 +175,9 @@ function CpAIJobSiloLoader:validate(farmId) else local found, area, validDistanceToSilo = CpAIJobSiloLoader.getTrailerUnloadArea( self.cpJobParameters.unloadPosition, self.bunkerSilo or self.heap) - if found then - self.trailerAreaPlot:setVisible(true) - self.trailerAreaPlot:setArea(area) + if found then + --TODO 25 self.trailerAreaPlot:setVisible(true) + --TODO 25 self.trailerAreaPlot:setArea(area) end if not validDistanceToSilo then return false, g_i18n:getText("CP_error_unload_target_to_far_away_from_silo") @@ -324,7 +324,7 @@ end function CpAIJobSiloLoader:draw(map, isOverviewMap) CpAIJob.draw(self, map, isOverviewMap) if not isOverviewMap then - self.heapPlot:draw(map) + --TODO 25 self.heapPlot:draw(map) g_bunkerSiloManager:drawSilos(map, self.bunkerSilo) if self.cpJobParameters.unloadAt:getValue() == CpSiloLoaderJobParameters.UNLOAD_TRIGGER then local fillTypes = self:getConvertedFillTypes() @@ -335,7 +335,7 @@ function CpAIJobSiloLoader:draw(map, isOverviewMap) g_triggerManager:drawDischargeableTriggers(map, self.unloadTrigger, fillTypes) else --- Drawing trailer area - self.trailerAreaPlot:draw(map) + --TODO 25 self.trailerAreaPlot:draw(map) end end end diff --git a/scripts/gui/CpGamePadHudScreen.lua b/scripts/gui/CpGamePadHudScreen.lua index 3f757c42b..2e1562ae6 100644 --- a/scripts/gui/CpGamePadHudScreen.lua +++ b/scripts/gui/CpGamePadHudScreen.lua @@ -39,7 +39,7 @@ function CpGamePadHudScreen:onGuiSetupFinished() self.clearButton:unlinkElement() FocusManager:removeElement(self.clearButton) - self.clearButton:setText(g_i18n:getText("CP_courseManager_clear_current_courses")) + -- TODO 25 (didn't we just remove this button above?) self.clearButton:setText(g_i18n:getText("CP_courseManager_clear_current_courses")) self.backButton:unlinkElement() FocusManager:removeElement(self.backButton) diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 6d26e45ef..59b6469b6 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -278,7 +278,7 @@ function CpBaseHud:init(vehicle) --- Create start/stop button local onOffBtnWidth, height = getNormalizedScreenValues(20, 20) local onOffIndicatorOverlay = CpGuiUtil.createOverlay({onOffBtnWidth, height}, - {g_baseUIFilename, GuiUtils.getUVs(MixerWagonHUDExtension.UV.RANGE_MARKER_ARROW)}, + {g_baseUIFilename, GuiUtils.getUVs(self.uvs.streetDriveToSymbol)}, --TODO 25 self.OFF_COLOR, self.alignments.bottomRight) self.onOffButton = CpHudButtonElement.new(onOffIndicatorOverlay, self.baseHud) diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua index 84c8430aa..9b5dffa16 100644 --- a/scripts/specializations/CpCourseGeneratorSettings.lua +++ b/scripts/specializations/CpCourseGeneratorSettings.lua @@ -92,7 +92,8 @@ function CpCourseGeneratorSettings:onLoad(savegame) --- Register the spec: spec_cpCourseGeneratorSettings self.spec_cpCourseGeneratorSettings = self["spec_" .. CpCourseGeneratorSettings.SPEC_NAME] local spec = self.spec_cpCourseGeneratorSettings - spec.gui = g_currentMission.inGameMenu.pageAI + -- TODO 25 + -- spec.gui = g_currentMission.inGameMenu.pageAI --- Clones the generic settings to create different settings containers for each vehicle. CpSettingsUtil.cloneSettingsTable(spec,CpCourseGeneratorSettings.settings,self,CpCourseGeneratorSettings) @@ -246,7 +247,8 @@ end ---@param callbackStr string event to be raised ---@param setting AIParameterSettingList setting that raised the callback. function CpCourseGeneratorSettings:raiseCallback(callbackStr, setting, ...) - SpecializationUtil.raiseEvent(self, callbackStr, setting, ...) + -- TODO 25 event is registered by the HUD which is now disabled + --TODO 25 SpecializationUtil.raiseEvent(self, callbackStr, setting, ...) end function CpCourseGeneratorSettings:raiseDirtyFlag(setting) diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index 5a5c38568..6ac796bb4 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -125,7 +125,7 @@ function CpCourseManager:onLoad(savegame) --- Register the spec: spec_cpCourseManager self.spec_cpCourseManager = self["spec_" .. CpCourseManager.SPEC_NAME] local spec = self.spec_cpCourseManager - spec.coursePlot = CoursePlot(g_currentMission.inGameMenu.ingameMap) + -- TODO 25 spec.coursePlot = CoursePlot(g_currentMission.inGameMenu.ingameMap) spec.courses = {} From 00379a0fb0d4d77c79036d92ece22cebdc84169d Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 13 Nov 2024 19:10:44 +0100 Subject: [PATCH 003/158] Some more compability updates --- modDesc.xml | 4 ++-- scripts/gui/CoursePlot.lua | 2 +- scripts/gui/UnloadingTriggerPlot.lua | 2 +- scripts/gui/hud/CpBaseHud.lua | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modDesc.xml b/modDesc.xml index b694a0714..ff0591c89 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -489,8 +489,8 @@ Changelog 7.1.0.0: <sourceFile filename="scripts/gui/HeapPlot.lua"/> <sourceFile filename="scripts/gui/UnloadingTriggerPlot.lua"/> <sourceFile filename="scripts/gui/CpStatus.lua"/> - <sourceFile filename="scripts/gui/CpAIHotspots.lua"/> - <sourceFile filename="scripts/gui/CpAIFrameExtended.lua"/> + <!-- <sourceFile filename="scripts/gui/CpAIHotspots.lua"/> + <sourceFile filename="scripts/gui/CpAIFrameExtended.lua"/> --> <sourceFile filename="scripts/gui/CpVehicleSettingsFrame.lua"/> <sourceFile filename="scripts/gui/CpGlobalSettingsFrame.lua"/> <sourceFile filename="scripts/gui/CpCourseManagerFrame.lua"/> diff --git a/scripts/gui/CoursePlot.lua b/scripts/gui/CoursePlot.lua index e42f67ac4..d28c770c8 100644 --- a/scripts/gui/CoursePlot.lua +++ b/scripts/gui/CoursePlot.lua @@ -30,7 +30,7 @@ function CoursePlot:init() self.lightColor = {CpGuiUtil.getNormalizedRgb(45, 207, 255)} -- a darker shade of the same color self.darkColor = {CpGuiUtil.getNormalizedRgb(19, 87, 107)} - self.courseOverlayId = createImageOverlay('dataS/scripts/shared/graph_pixel.dds') + self.courseOverlayId = createImageOverlay('dataS/menu/base/graph_pixel.dds') self.startSignOverlayId = createImageOverlay(Utils.getFilename('img/signs/start_noMM.dds', Courseplay.BASE_DIRECTORY)) self.stopSignOverlayId = createImageOverlay(Utils.getFilename('img/signs/stop_noMM.dds', Courseplay.BASE_DIRECTORY)) self.arrowOverlayId = createImageOverlay(Utils.getFilename('img/iconSprite.dds', Courseplay.BASE_DIRECTORY)) diff --git a/scripts/gui/UnloadingTriggerPlot.lua b/scripts/gui/UnloadingTriggerPlot.lua index 18e69421d..2e2e18294 100644 --- a/scripts/gui/UnloadingTriggerPlot.lua +++ b/scripts/gui/UnloadingTriggerPlot.lua @@ -3,7 +3,7 @@ UnloadingTriggerPlot = CpObject() function UnloadingTriggerPlot:init(node) - self.courseOverlayId = createImageOverlay('dataS/scripts/shared/graph_pixel.dds') + self.courseOverlayId = createImageOverlay('dataS/menu/base/graph_pixel.dds') self.isVisible = false -- the normal FS22 blue -- 0.9900 0.4640 0.0010 1 --self.color = {CpGuiUtil.getNormalizedRgb(42, 193, 237)} diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 59b6469b6..51f488321 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -249,7 +249,7 @@ function CpBaseHud:init(vehicle) end, self.vehicle) local x, y = unpack(self.lines[6].left) - local spacerLineOverlay = Overlay.new("dataS/scripts/shared/graph_pixel.png", + local spacerLineOverlay = Overlay.new('dataS/menu/base/graph_pixel.dds', x, y, self.width - 2 * self.wMargin, self.hMargin/8) spacerLineOverlay:setColor(unpack(self.OFF_COLOR)) spacerLineOverlay:setAlignment(Overlay.ALIGN_VERTICAL_MIDDLE) From 3d5e524c7fb37ae33f7d7a7d131a27f9ae2f8f1f Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 13 Nov 2024 19:18:34 +0100 Subject: [PATCH 004/158] Some more error messages --- Courseplay.lua | 2 +- scripts/silo/BunkerSiloWrapper.lua | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index 150b7a0ac..a7cff8be3 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -210,7 +210,7 @@ function Courseplay:update(dt) local factor = 2*mapElement.terrainSize/2048 mapElement.zoomMax = mapElement.zoomMax * factor end - setIngameMapFix(g_currentMission.inGameMenu.pageAI.ingameMap) + -- setIngameMapFix(g_currentMission.inGameMenu.pageAI.ingameMap) setIngameMapFix(g_currentMission.inGameMenu.pageMapOverview.ingameMap) end end diff --git a/scripts/silo/BunkerSiloWrapper.lua b/scripts/silo/BunkerSiloWrapper.lua index dc7d6e626..4455b54fe 100644 --- a/scripts/silo/BunkerSiloWrapper.lua +++ b/scripts/silo/BunkerSiloWrapper.lua @@ -287,15 +287,16 @@ end --- Checks if the silo has a back wall and sets the plot area afterwards. function CpBunkerSilo:initialize() - local x, z = self.sx + self.dirXWidth * self.width/2 + self.dirXLength * 2, self.sz + self.dirZWidth * self.width/2 + self.dirZLength * 2 - local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 + --- TODO_25 + -- local x, z = self.sx + self.dirXWidth * self.width/2 + self.dirXLength * 2, self.sz + self.dirZWidth * self.width/2 + self.dirZLength * 2 + -- local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 - raycastAll(x, y, z, self.dirXLength, 0, self.dirZLength, 'rayCastCallbackOneSidedSilo', self.length + 2, self) + -- raycastAll(x, y, z, self.dirXLength, 0, self.dirZLength, 'rayCastCallbackOneSidedSilo', self.length + 2, self) - local x, z = self.hx + self.dirXWidth * self.width/2 - self.dirXLength * 2, self.hz + self.dirZWidth * self.width/2 - self.dirZLength * 2 - local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 + -- local x, z = self.hx + self.dirXWidth * self.width/2 - self.dirXLength * 2, self.hz + self.dirZWidth * self.width/2 - self.dirZLength * 2 + -- local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 - raycastAll(x, y, z, -self.dirXLength, 0, -self.dirZLength, 'rayCastCallbackOneSidedSiloInverted', self.length + 2, self) + -- raycastAll(x, y, z, -self.dirXLength, 0, -self.dirZLength, 'rayCastCallbackOneSidedSiloInverted', self.length + 2, self) self.plot:setAreas(self:getPlotAreas()) From 35d2701b106c3b7d0562550f00dc9c374acb6dd0 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 13 Nov 2024 20:10:39 +0100 Subject: [PATCH 005/158] Some more changes .. --- Courseplay.lua | 6 ++-- scripts/gui/CpAIFrameExtended.lua | 2 +- scripts/specializations/CpCourseManager.lua | 5 +-- scripts/specializations/CpHud.lua | 34 +++++++++++---------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index a7cff8be3..ab91ab64f 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -155,7 +155,8 @@ function Courseplay:setupGui() ]] self.infoTextsHud = CpHudInfoTexts() - g_currentMission.hud.ingameMap.drawFields = Utils.appendedFunction(g_currentMission.hud.ingameMap.drawFields, Courseplay.drawHudMap) + -- TODO_25 + -- g_currentMission.hud.ingameMap.drawFields = Utils.appendedFunction(g_currentMission.hud.ingameMap.drawFields, Courseplay.drawHudMap) end @@ -210,8 +211,9 @@ function Courseplay:update(dt) local factor = 2*mapElement.terrainSize/2048 mapElement.zoomMax = mapElement.zoomMax * factor end + --- TODO_25 -- setIngameMapFix(g_currentMission.inGameMenu.pageAI.ingameMap) - setIngameMapFix(g_currentMission.inGameMenu.pageMapOverview.ingameMap) + -- setIngameMapFix(g_currentMission.inGameMenu.pageMapOverview.ingameMap) end end diff --git a/scripts/gui/CpAIFrameExtended.lua b/scripts/gui/CpAIFrameExtended.lua index 8406e27e9..c4657f9c3 100644 --- a/scripts/gui/CpAIFrameExtended.lua +++ b/scripts/gui/CpAIFrameExtended.lua @@ -8,7 +8,7 @@ CpInGameMenuAIFrameExtended.positionUvs = GuiUtils.getUVs({ 4, 100, 100 -}, AITargetHotspot.FILE_RESOLUTION) +}, AIPlaceableMarkerHotspot.FILE_RESOLUTION) CpInGameMenuAIFrameExtended.curDrawPositions={} CpInGameMenuAIFrameExtended.drawDelay = g_updateLoopIndex diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index 6ac796bb4..80786fc2b 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -358,8 +358,9 @@ function CpCourseManager:onWriteStream(streamId,connection) end function CpCourseManager:onPreDelete() - g_assignedCoursesManager:unregisterVehicle(self,self.id) - CpCourseManager.resetCourses(self) + g_assignedCoursesManager:unregisterVehicle(self, self.id) + --- TODO_25 + -- CpCourseManager.resetCourses(self) local spec = self.spec_cpCourseManager spec.courseDisplay:delete() end diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index 25a7cab48..19dea50e1 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -341,22 +341,24 @@ function CpHud:onDraw() if not self:getIsEntered() then return end - local spec = self.spec_cpHud - spec.hud:draw(spec.status) - if spec.hud:getIsOpen() then - if spec.lastShownWorkWidthTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then - if spec.hud:isBunkerSiloLayoutActive() or spec.hud:isSiloLoaderLayoutActive() then - CpHud.showCpBunkerSiloWorkWidth(self) - elseif spec.hud:isCombineUnloaderLayoutActive() then - CpHud.showCpCombineUnloaderWorkWidth(self) - else - CpHud.showCpCourseWorkWidth(self) - end - end - if spec.lastShownBaleCollectorOffsetTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then - ImplementUtil.showBaleCollectorOffset(self, self:getCpSettings().baleCollectorOffset:getValue()) - end - end + --- TODO_25 + -- local spec = self.spec_cpHud + + -- spec.hud:draw(spec.status) + -- if spec.hud:getIsOpen() then + -- if spec.lastShownWorkWidthTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then + -- if spec.hud:isBunkerSiloLayoutActive() or spec.hud:isSiloLoaderLayoutActive() then + -- CpHud.showCpBunkerSiloWorkWidth(self) + -- elseif spec.hud:isCombineUnloaderLayoutActive() then + -- CpHud.showCpCombineUnloaderWorkWidth(self) + -- else + -- CpHud.showCpCourseWorkWidth(self) + -- end + -- end + -- if spec.lastShownBaleCollectorOffsetTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then + -- ImplementUtil.showBaleCollectorOffset(self, self:getCpSettings().baleCollectorOffset:getValue()) + -- end + -- end end function CpHud:showCpBunkerSiloWorkWidth() From 8ed8a80385a4bf82094cab6cf89a8edda6f4054c Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Wed, 13 Nov 2024 16:35:34 -0500 Subject: [PATCH 006/158] chore: DevHelper works Even the field scanner... --- scripts/ai/AIDriveStrategyCombineCourse.lua | 2 +- scripts/ai/AIDriveStrategyShovelSiloLoader.lua | 2 +- scripts/ai/AIDriveStrategySiloLoader.lua | 2 +- scripts/ai/AIDriveStrategyUnloadCombine.lua | 4 ++-- scripts/ai/CollisionAvoidanceController.lua | 2 +- scripts/ai/FieldWorkerProximityController.lua | 2 +- scripts/ai/SelfUnloadHelper.lua | 2 +- scripts/dev/ConsoleCommands.lua | 6 +++--- scripts/dev/DevHelper.lua | 6 +++--- scripts/pathfinder/PathfinderUtil.lua | 2 +- scripts/silo/BunkerSiloWrapper.lua | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index 4798037eb..a997993e6 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -967,7 +967,7 @@ end function AIDriveStrategyCombineCourse:findUnloader(combine, waypoint) local bestScore = -math.huge local bestUnloader, bestEte - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if AIDriveStrategyUnloadCombine.isActiveCpCombineUnloader(vehicle) then local x, _, z = getWorldTranslation(self.vehicle.rootNode) ---@type AIDriveStrategyUnloadCombine diff --git a/scripts/ai/AIDriveStrategyShovelSiloLoader.lua b/scripts/ai/AIDriveStrategyShovelSiloLoader.lua index 2c2ea6ca6..1ebcd4e29 100644 --- a/scripts/ai/AIDriveStrategyShovelSiloLoader.lua +++ b/scripts/ai/AIDriveStrategyShovelSiloLoader.lua @@ -492,7 +492,7 @@ end function AIDriveStrategyShovelSiloLoader:getClosestTrailerAndDistance(trailerToIgnore) local closestDistance = math.huge local closestTrailerData = nil - for i, vehicle in pairs(g_currentMission.vehicles) do + for i, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do local x, _, z = getWorldTranslation(vehicle.rootNode) if CpMathUtil.isPointInPolygon(self.trailerSearchArea, x, z) then local dist = calcDistanceFrom(vehicle.rootNode, self.siloFrontNode) diff --git a/scripts/ai/AIDriveStrategySiloLoader.lua b/scripts/ai/AIDriveStrategySiloLoader.lua index 17f0cc7b7..2c831ffb6 100644 --- a/scripts/ai/AIDriveStrategySiloLoader.lua +++ b/scripts/ai/AIDriveStrategySiloLoader.lua @@ -413,7 +413,7 @@ end function AIDriveStrategySiloLoader:findUnloader() local bestScore = -math.huge local bestUnloader, bestEte - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if AIDriveStrategyUnloadCombine.isActiveCpSiloLoader(vehicle) then local x, _, z = getWorldTranslation(self.vehicle.rootNode) ---@type AIDriveStrategyUnloadCombine diff --git a/scripts/ai/AIDriveStrategyUnloadCombine.lua b/scripts/ai/AIDriveStrategyUnloadCombine.lua index eba3cb610..957b9c489 100644 --- a/scripts/ai/AIDriveStrategyUnloadCombine.lua +++ b/scripts/ai/AIDriveStrategyUnloadCombine.lua @@ -565,7 +565,7 @@ end ---@param combine table ---@param combineDriver AIDriveStrategyCombineCourse function AIDriveStrategyUnloadCombine:areThereAnyCombinesOrLoaderLeftoverOnTheField(combine, combineDriver) - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if vehicle ~= combine and AIDriveStrategyCombineCourse.isActiveCpCombine(vehicle) then local x, _, z = getWorldTranslation(combine.rootNode) if self:isServingPosition(x, z, 10) then @@ -2375,7 +2375,7 @@ function AIDriveStrategyUnloadCombine:findOtherUnloaderAroundCombine(combine, co return nil end if g_currentMission then - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if vehicle ~= self.vehicle and vehicle.cp.driver and vehicle.cp.driver:is_a(AIDriveStrategyUnloadCombine) then local dx, _, dz = localToLocal(vehicle.rootNode, combine:getAIDirectionNode(), 0, 0, 0) if math.abs(dz) < 30 and math.abs(dx) <= (combineOffset + 3) then diff --git a/scripts/ai/CollisionAvoidanceController.lua b/scripts/ai/CollisionAvoidanceController.lua index ef3653a2b..097704273 100644 --- a/scripts/ai/CollisionAvoidanceController.lua +++ b/scripts/ai/CollisionAvoidanceController.lua @@ -57,7 +57,7 @@ function CollisionAvoidanceController:isCollisionWarningActive() end function CollisionAvoidanceController:findPotentialCollisions() - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if AIDriveStrategyCombineCourse.isActiveCpCombine(vehicle) then local d = calcDistanceFrom(self.vehicle.rootNode, vehicle.rootNode) if d < self.range then diff --git a/scripts/ai/FieldWorkerProximityController.lua b/scripts/ai/FieldWorkerProximityController.lua index 3fb1c22c4..a3297e097 100644 --- a/scripts/ai/FieldWorkerProximityController.lua +++ b/scripts/ai/FieldWorkerProximityController.lua @@ -133,7 +133,7 @@ function FieldWorkerProximityController:getMaxSpeed(distanceLimit, currentMaxSpe -- our trail should be long enough for everyone on the field, that is, at least as long as their -- convoy distance setting. local maxConvoyDistance = distanceLimit - for _, otherVehicle in pairs(g_currentMission.vehicles) do + for _, otherVehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if otherVehicle ~= self.vehicle and self:hasSameCourse(otherVehicle) and otherVehicle.getIsCpFieldWorkActive and otherVehicle:getIsCpFieldWorkActive() then local otherStrategy = otherVehicle:getCpDriveStrategy() diff --git a/scripts/ai/SelfUnloadHelper.lua b/scripts/ai/SelfUnloadHelper.lua index 92e9a6cd8..ae944a957 100644 --- a/scripts/ai/SelfUnloadHelper.lua +++ b/scripts/ai/SelfUnloadHelper.lua @@ -37,7 +37,7 @@ SelfUnloadHelper.maxDistanceFromField = 20 function SelfUnloadHelper:findBestTrailer(fieldPolygon, myVehicle, implementWithPipe, pipeOffsetX) local bestTrailer, bestFillUnitIndex, bestFillType local minDistance = math.huge - for _, otherVehicle in pairs(g_currentMission.vehicles) do + for _, otherVehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if SpecializationUtil.hasSpecialization(Trailer, otherVehicle.specializations) then local rootVehicle = otherVehicle:getRootVehicle() local attacherVehicle diff --git a/scripts/dev/ConsoleCommands.lua b/scripts/dev/ConsoleCommands.lua index bc85392f2..66740d9fc 100644 --- a/scripts/dev/ConsoleCommands.lua +++ b/scripts/dev/ConsoleCommands.lua @@ -218,7 +218,7 @@ function CpConsoleCommands:cpSaveAllFields() end function CpConsoleCommands:cpSaveAllVehiclePositions() - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if SpecializationUtil.hasSpecialization(CpAIWorker, vehicle.specializations) then vehicle.vehiclePositionData = {} CpConsoleCommands.saveVehiclePosition(vehicle, vehicle.vehiclePositionData) @@ -227,7 +227,7 @@ function CpConsoleCommands:cpSaveAllVehiclePositions() end function CpConsoleCommands:cpRestoreAllVehiclePositions() - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if vehicle.vehiclePositionData then CpConsoleCommands.restoreVehiclePosition(vehicle) end @@ -247,7 +247,7 @@ function CpConsoleCommands:cpUnfreeze() end function CpConsoleCommands:cpStopAll() - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if vehicle.getIsAIActive and vehicle:getIsAIActive() then vehicle:stopCurrentAIJob(AIMessageErrorUnknown.new()) end diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 06dd34e2e..341876370 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -64,7 +64,7 @@ function DevHelper:update() else -- camera node looks backwards so need to flip everything by 180 degrees - self.node = g_currentMission.player.cameraNode + self.node = g_currentMission.playerSystem:getLocalPlayer():getCurrentCameraNode() lx, _, lz = localDirectionToWorld(self.node, 0, 0, -1) end @@ -90,7 +90,7 @@ function DevHelper:update() self.data.isOnFieldArea, self.data.onFieldArea, self.data.totalOnFieldArea = CpFieldUtil.isOnFieldArea(self.data.x, self.data.z) self.data.nx, self.data.ny, self.data.nz = getTerrainNormalAtWorldPos(g_currentMission.terrainRootNode, self.data.x, y, self.data.z) - local collisionMask = CollisionFlag.STATIC_WORLD + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA self.data.collidingShapes = '' overlapBox(self.data.x, self.data.y + 0.2, self.data.z, 0, self.yRot, 0, 1.6, 1, 8, "overlapBoxCallback", self, collisionMask, true, true, true) @@ -215,7 +215,7 @@ function DevHelper:draw() end function DevHelper:showFillNodes() - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if SpecializationUtil.hasSpecialization(Trailer, vehicle.specializations) then DebugUtil.drawDebugNode(vehicle.rootNode, 'Root node') local fillUnits = vehicle:getFillUnits() diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index 22cc11fed..e6f3ed09b 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -343,7 +343,7 @@ function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, self.collidingShapes = 0 self.collidingShapesText = 'unknown' - local collisionMask = CollisionFlag.STATIC_WORLD + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA overlapBox(x, y + 0.2, z, xRot, yRot, zRot, width, 1, length, 'overlapBoxCallback', self, collisionMask, true, true, true) diff --git a/scripts/silo/BunkerSiloWrapper.lua b/scripts/silo/BunkerSiloWrapper.lua index 4455b54fe..18558d71e 100644 --- a/scripts/silo/BunkerSiloWrapper.lua +++ b/scripts/silo/BunkerSiloWrapper.lua @@ -650,7 +650,7 @@ end function CpBunkerSilo:updateUnloaders(dt) --- Searches for new unloaders in the unloader area and remove unloaders, that left. if self.numControllers > 0 and g_updateLoopIndex % 7 == 0 then - for _, vehicle in pairs(g_currentMission.vehicles) do + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do local isValid = false if self:isValidUnloader(vehicle) then for _, v in pairs(vehicle:getChildVehicles()) do From 22b9f5212da089444ac2e94086ae75b2ba07e1d5 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 14 Nov 2024 09:38:29 -0500 Subject: [PATCH 007/158] refactor: replace g_currentMission.controlledVehicle --- Courseplay.lua | 4 ++-- scripts/Course.lua | 4 ++-- scripts/CpGlobalSettings.lua | 4 ++-- scripts/CpUtil.lua | 5 +++++ scripts/Logger.lua | 2 +- scripts/ai/Markers.lua | 4 ++-- scripts/config/VehicleConfigurations.lua | 6 +++--- scripts/dev/ConsoleCommands.lua | 15 ++++++++------- scripts/dev/DevHelper.lua | 16 ++++++++-------- scripts/field/FieldScanner.lua | 2 +- scripts/field/VineScanner.lua | 4 ++-- scripts/gui/CpAIFrameExtended.lua | 2 +- scripts/gui/hud/CpHudInfoTexts.lua | 2 +- scripts/silo/BunkerSiloVehicleController.lua | 2 +- scripts/specializations/CpAICombineUnloader.lua | 2 +- scripts/specializations/CpAIWorker.lua | 8 ++++---- .../CpCourseGeneratorSettings.lua | 2 +- scripts/specializations/CpGamePadHud.lua | 4 ++-- scripts/specializations/CpHud.lua | 4 ++-- scripts/specializations/CpInfoTexts.lua | 2 +- scripts/specializations/CpShovelPositions.lua | 6 +++--- scripts/specializations/CpVehicleSettings.lua | 2 +- scripts/util/CpRemainingTime.lua | 2 +- 23 files changed, 55 insertions(+), 49 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index ab91ab64f..a340ce441 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -163,7 +163,7 @@ end --- Enables drawing onto the hud map. function Courseplay.drawHudMap(map) if g_Courseplay.globalSettings.drawOntoTheHudMap:getValue() then - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if vehicle and vehicle:getIsEntered() and not g_gui:getIsGuiVisible() and vehicle.spec_cpAIWorker and not vehicle.spec_locomotive then SpecializationUtil.raiseEvent(vehicle, "onCpDrawHudMap", map) end @@ -238,7 +238,7 @@ end ---@param button number function Courseplay:mouseEvent(posX, posY, isDown, isUp, button) if not g_gui:getIsGuiVisible() then - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() local hud = vehicle and vehicle.getCpHud and vehicle:getCpHud() if hud then hud:mouseEvent(posX, posY, isDown, isUp, button) diff --git a/scripts/Course.lua b/scripts/Course.lua index 08d9a165d..fd84ecfa8 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -270,7 +270,7 @@ function Course:enrichWaypointData(startIx) directionChangeFound = true end end - CpUtil.debugVehicle(CpDebug.DBG_COURSES, self.vehicle or g_currentMission.controlledVehicle, + CpUtil.debugVehicle(CpDebug.DBG_COURSES, self.vehicle or CpUtil.getCurrentVehicle(), 'Course with %d waypoints created/updated, %.1f meters, %d turns', #self.waypoints, self.length, self.totalTurns) end @@ -1680,7 +1680,7 @@ end function Course.createFromGeneratedCourse(vehicle, generatedCourse, workWidth, numberOfHeadlands, nVehicles, headlandClockwise, islandHeadlandClockwise, straightRows) local waypoints = createWaypointsFromGeneratedPath(generatedCourse:getPath()) - local course = Course(vehicle or g_currentMission.controlledVehicle, waypoints) + local course = Course(vehicle or CpUtil.getCurrentVehicle(), waypoints) course.workWidth = workWidth course.numberOfHeadlands = numberOfHeadlands course.nVehicles = nVehicles diff --git a/scripts/CpGlobalSettings.lua b/scripts/CpGlobalSettings.lua index f4e406047..6836a64de 100644 --- a/scripts/CpGlobalSettings.lua +++ b/scripts/CpGlobalSettings.lua @@ -74,7 +74,7 @@ function CpGlobalSettings:onCpUserSettingChanged() end function CpGlobalSettings:onHudSelectionChanged() - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if vehicle then self:debug("reset action events for %s",vehicle:getName()) -- g_inputBinding:setShowMouseCursor(false) @@ -84,7 +84,7 @@ function CpGlobalSettings:onHudSelectionChanged() end function CpGlobalSettings:onActionEventTextVisibilityChanged() - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if vehicle then vehicle:requestActionEventUpdate() end diff --git a/scripts/CpUtil.lua b/scripts/CpUtil.lua index 4bd35d3d2..f2e46fb79 100644 --- a/scripts/CpUtil.lua +++ b/scripts/CpUtil.lua @@ -472,3 +472,8 @@ function CpUtil.getAllRootVegetables() return rootVegetables end + +---@return Vehicle the currently selected/controlled vehicle, formerly known as g_currentMission.controlledVehicle +function CpUtil.getCurrentVehicle() + return g_currentMission.playerSystem:getLocalPlayer():getCurrentVehicle() +end \ No newline at end of file diff --git a/scripts/Logger.lua b/scripts/Logger.lua index f7558a0a8..c9926b385 100644 --- a/scripts/Logger.lua +++ b/scripts/Logger.lua @@ -67,7 +67,7 @@ end -- or use the CP debug channel when running in the game. function Logger:log(...) if CourseGenerator.isRunningInGame() then - CpUtil.debugVehicle(CpDebug.DBG_COURSES, g_currentMission.controlledVehicle, ...) + CpUtil.debugVehicle(CpDebug.DBG_COURSES, CpUtil.getCurrentVehicle(), ...) else local message = self:_getCurrentTimeStr() .. ' ' .. string.format(...) print(message) diff --git a/scripts/ai/Markers.lua b/scripts/ai/Markers.lua index 217a04583..9f5918833 100644 --- a/scripts/ai/Markers.lua +++ b/scripts/ai/Markers.lua @@ -133,7 +133,7 @@ end -------------------------------------------- function Markers:consoleCommandReload(backDistance) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("No valid vehicle entered!") return @@ -146,7 +146,7 @@ function Markers:consoleCommandReload(backDistance) end function Markers:consoleCommandPrintDebug() - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("No valid vehicle entered!") return diff --git a/scripts/config/VehicleConfigurations.lua b/scripts/config/VehicleConfigurations.lua index cef9e48fe..9072bd0d4 100644 --- a/scripts/config/VehicleConfigurations.lua +++ b/scripts/config/VehicleConfigurations.lua @@ -241,7 +241,7 @@ function VehicleConfigurations:consoleCommandReload() end function VehicleConfigurations:consoleCommandPrintConfigFileNames() - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("No vehicle entered!") return @@ -253,7 +253,7 @@ function VehicleConfigurations:consoleCommandPrintConfigFileNames() end function VehicleConfigurations:consoleCommandPrintSingleAttributeValuesForVehicleAndImplements(attribute) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("No vehicle entered!") return @@ -277,7 +277,7 @@ function VehicleConfigurations:consoleCommandPrintSingleAttributeValuesForVehicl end function VehicleConfigurations:consoleCommandPrintAllAttributeValuesForVehicleAndImplements() - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("No vehicle entered!") return diff --git a/scripts/dev/ConsoleCommands.lua b/scripts/dev/ConsoleCommands.lua index 66740d9fc..ffebb2721 100644 --- a/scripts/dev/ConsoleCommands.lua +++ b/scripts/dev/ConsoleCommands.lua @@ -8,7 +8,7 @@ CpConsoleCommands.commands = { { 'cpReturnToSaveGameSelect', 'Returns to the menu', 'returnToSaveGameSelect' }, { 'print', 'Print a variable', 'printVariable' }, { 'printGlobalCpVariable', 'Print a global cp variable', 'printGlobalCpVariable' }, - { 'printVehicleVariable', 'Print g_currentMission.controlledVehicle.variable', 'printVehicleVariable' }, + { 'printVehicleVariable', 'Print CpUtil.getCurrentVehicle().variable', 'printVehicleVariable' }, { 'printImplementVariable', 'printImplementVariable <implement index> <variable>', 'printImplementVariable' }, { 'printStrategyVariable', 'Print a CP drive strategy variable', 'printStrategyVariable' }, { 'printAiPageVariable', 'Print a in game menu ai page variable.', 'printAiPageVariable' }, @@ -125,15 +125,16 @@ end --- Print the variable in the selected vehicle's namespace -- You can omit the dot for data members but if you want to call a function, you must start the variable name with a colon function CpConsoleCommands:printVehicleVariable(variableName, maxDepth, printToXML, printToSeparateXmlFiles) - local prefix = variableName and 'g_currentMission.controlledVehicle' or 'g_currentMission' - variableName = variableName or 'controlledVehicle' + local prefix = variableName and 'CpUtil.getCurrentVehicle()' or 'CpUtil' + -- if no variable name given, print the whole vehicle + variableName = variableName or '.getCurrentVehicle()' self:printVariableInternal( prefix, variableName, maxDepth, printToXML, printToSeparateXmlFiles) end --- Print an implement variable. If implement.object.variable exists, print that, otherwise implement.variable ---@param implementIndex number index in getAttachedImplements() function CpConsoleCommands:printImplementVariable(implementIndex, variableName, maxDepth, printToXML, printToSeparateXmlFiles) - local prefix = string.format('g_currentMission.controlledVehicle:getAttachedImplements()[%d]', implementIndex) + local prefix = string.format('CpUtil.getCurrentVehicle():getAttachedImplements()[%d]', implementIndex) local objectVariableName = string.format('%s.object%s', prefix, self:ensureVariableNameSyntax(variableName)) local var = CpUtil.getVariable(objectVariableName) if var then @@ -145,7 +146,7 @@ function CpConsoleCommands:printImplementVariable(implementIndex, variableName, end function CpConsoleCommands:printStrategyVariable(variableName, maxDepth, printToXML, printToSeparateXmlFiles) - local prefix = 'g_currentMission.controlledVehicle:getCpDriveStrategy()' + local prefix = 'CpUtil.getCurrentVehicle():getCpDriveStrategy()' self:printVariableInternal( prefix, variableName, maxDepth, printToXML, printToSeparateXmlFiles) end @@ -239,11 +240,11 @@ function CpConsoleCommands:cpSetPathfinderDebug(d) end function CpConsoleCommands:cpFreeze() - g_currentMission.controlledVehicle:freezeCp() + CpUtil.getCurrentVehicle():freezeCp() end function CpConsoleCommands:cpUnfreeze() - g_currentMission.controlledVehicle:unfreezeCp() + CpUtil.getCurrentVehicle():unfreezeCp() end function CpConsoleCommands:cpStopAll() diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 341876370..14d71dfd0 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -50,16 +50,16 @@ function DevHelper:update() local lx, lz, hasCollision, vehicle -- make sure not calling this for something which does not have courseplay installed (only ones with spec_aiVehicle) - if g_currentMission.controlledVehicle and g_currentMission.controlledVehicle.spec_cpAIWorker then - if self.vehicle ~= g_currentMission.controlledVehicle then + if CpUtil.getCurrentVehicle() and CpUtil.getCurrentVehicle().spec_cpAIWorker then + if self.vehicle ~= CpUtil.getCurrentVehicle() then if self.vehicle then self.vehicle:removeDeleteListener(self, "removedSelectedVehicle") end - --self.vehicleData = PathfinderUtil.VehicleData(g_currentMission.controlledVehicle, true) + --self.vehicleData = PathfinderUtil.VehicleData(CpUtil.getCurrentVehicle(), true) end - self.vehicle = g_currentMission.controlledVehicle + self.vehicle = CpUtil.getCurrentVehicle() self.vehicle:addDeleteListener(self, "removedSelectedVehicle") - self.node = g_currentMission.controlledVehicle:getAIDirectionNode() + self.node = CpUtil.getCurrentVehicle():getAIDirectionNode() lx, _, lz = localDirectionToWorld(self.node, 0, 0, 1) else @@ -155,11 +155,11 @@ function DevHelper:keyEvent(unicode, sym, modifier, isDown) self:debug('Set field %d for pathfinding', self.fieldNumForPathfinding) elseif bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_space then -- save vehicle position - g_currentMission.controlledVehicle.vehiclePositionData = {} - DevHelper.saveVehiclePosition(g_currentMission.controlledVehicle, g_currentMission.controlledVehicle.vehiclePositionData) + CpUtil.getCurrentVehicle().vehiclePositionData = {} + DevHelper.saveVehiclePosition(CpUtil.getCurrentVehicle(), CpUtil.getCurrentVehicle().vehiclePositionData) elseif bitAND(modifier, Input.MOD_LCTRL) ~= 0 and isDown and sym == Input.KEY_space then -- restore vehicle position - DevHelper.restoreVehiclePosition(g_currentMission.controlledVehicle) + DevHelper.restoreVehiclePosition(CpUtil.getCurrentVehicle()) elseif bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_c then self:debug('Finding contour of current field') local valid, points = g_fieldScanner:findContour(self.data.x, self.data.z) diff --git a/scripts/field/FieldScanner.lua b/scripts/field/FieldScanner.lua index c346116fc..9db016dcd 100644 --- a/scripts/field/FieldScanner.lua +++ b/scripts/field/FieldScanner.lua @@ -20,7 +20,7 @@ function FieldScanner:init(resolution) end function FieldScanner:debug(...) - CpUtil.debugVehicle(CpDebug.DBG_COURSES, g_currentMission.controlledVehicle, 'FieldScanner: ' .. string.format(...)) + CpUtil.debugVehicle(CpDebug.DBG_COURSES, CpUtil.getCurrentVehicle(), 'FieldScanner: ' .. string.format(...)) end function FieldScanner:info(...) diff --git a/scripts/field/VineScanner.lua b/scripts/field/VineScanner.lua index 844d48ff7..77750972a 100644 --- a/scripts/field/VineScanner.lua +++ b/scripts/field/VineScanner.lua @@ -419,7 +419,7 @@ function VineScanner:getVineSegmentIxForNode(node, vineSegments) end function VineScanner:debug(...) - CpUtil.debugVehicle(CpDebug.DBG_COURSES, g_currentMission.controlledVehicle, 'VineScanner: ' .. string.format(...)) + CpUtil.debugVehicle(CpDebug.DBG_COURSES, CpUtil.getCurrentVehicle(), 'VineScanner: ' .. string.format(...)) end function VineScanner:debugSparse(...) @@ -429,7 +429,7 @@ function VineScanner:debugSparse(...) end function VineScanner:draw() - if g_currentMission.controlledVehicle and CpDebug:isChannelActive(CpDebug.DBG_COURSES, g_currentMission.controlledVehicle) and self:foundVines() then + if CpUtil.getCurrentVehicle() and CpDebug:isChannelActive(CpDebug.DBG_COURSES, CpUtil.getCurrentVehicle()) and self:foundVines() then self:drawSegments(self.lines) self:drawFieldBorder() end diff --git a/scripts/gui/CpAIFrameExtended.lua b/scripts/gui/CpAIFrameExtended.lua index c4657f9c3..d8178cd65 100644 --- a/scripts/gui/CpAIFrameExtended.lua +++ b/scripts/gui/CpAIFrameExtended.lua @@ -113,7 +113,7 @@ function CpInGameMenuAIFrameExtended:onAIFrameLoadMapFinished() local function onOpenInGameMenu(inGameMenu) local pageAI = inGameMenu.pageAI if CpInGameMenuAIFrameExtended.getVehicle() == nil then - pageAI.lastVehicle = g_currentMission.controlledVehicle + pageAI.lastVehicle = CpUtil.getCurrentVehicle() end end g_messageCenter:subscribe(MessageType.GUI_AFTER_CLOSE, onCloseInGameMenu, g_currentMission.inGameMenu) diff --git a/scripts/gui/hud/CpHudInfoTexts.lua b/scripts/gui/hud/CpHudInfoTexts.lua index fd1967e4a..e3adf6b38 100644 --- a/scripts/gui/hud/CpHudInfoTexts.lua +++ b/scripts/gui/hud/CpHudInfoTexts.lua @@ -163,7 +163,7 @@ function CpHudInfoTexts:update() elements.lastInfo = info end --- Makes sure the current entered vehicle is highlighted. - if info.vehicle == g_currentMission.controlledVehicle then + if info.vehicle == CpUtil.getCurrentVehicle() then elements.vehicleBtn:setColor(unpack(self.SELECTED_COLOR)) else elements.vehicleBtn:setColor(unpack(self.OFF_COLOR)) diff --git a/scripts/silo/BunkerSiloVehicleController.lua b/scripts/silo/BunkerSiloVehicleController.lua index 288832efb..3e83f83a8 100644 --- a/scripts/silo/BunkerSiloVehicleController.lua +++ b/scripts/silo/BunkerSiloVehicleController.lua @@ -325,7 +325,7 @@ function CpBunkerSiloLevelerController:draw() if self:isDebugEnabled() then self.silo:drawDebug() self.silo:drawUnloaderArea() - if g_currentMission.controlledVehicle == self.vehicle then + if CpUtil.getCurrentVehicle() == self.vehicle then local debugData = self.silo:getDebugData() table.insert(debugData, 1, { name = "is waiting", value = self:isWaitingAtParkPosition() diff --git a/scripts/specializations/CpAICombineUnloader.lua b/scripts/specializations/CpAICombineUnloader.lua index ee8b3901b..e1a35757e 100644 --- a/scripts/specializations/CpAICombineUnloader.lua +++ b/scripts/specializations/CpAICombineUnloader.lua @@ -32,7 +32,7 @@ function CpAICombineUnloader.initSpecialization() end local function executePipeControllerCommand(lambdaFunc, ...) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("Could not measure pipe properties without entering a vehicle!") return diff --git a/scripts/specializations/CpAIWorker.lua b/scripts/specializations/CpAIWorker.lua index cc07f774b..f66bf1f43 100644 --- a/scripts/specializations/CpAIWorker.lua +++ b/scripts/specializations/CpAIWorker.lua @@ -533,11 +533,11 @@ function CpAIWorker:onStartAutoDrive() --- Use last job parameters. --- Only the start point needs to be forced back! SpecializationUtil.raiseEvent(self, "onCpADRestarted") - elseif g_currentMission.controlledVehicle == self then + elseif CpUtil.getCurrentVehicle() == self then --- Apply hud variables SpecializationUtil.raiseEvent(self, "onCpADStartedByPlayer") end - elseif g_currentMission.controlledVehicle == self then + elseif CpUtil.getCurrentVehicle() == self then --- Apply hud variables SpecializationUtil.raiseEvent(self, "onCpADStartedByPlayer") CpJobStartAtLastWpSyncRequestEvent.sendEvent(self) @@ -562,7 +562,7 @@ end --- as these might turn on implements, that otherwise aren't turned on or --- disables the unfolding of a given implement. function CpAIWorker:consoleCommandRaiseWorkStart() - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("Not entered a valid vehicle!") return @@ -583,7 +583,7 @@ end --- Either prints all settings or a desired setting by the name or index in the setting table. ---@param name any function CpAIWorker:consoleCommandPrintCurrentSelectedJobParameters(name) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle or vehicle.getCpStartableJob == nil then CpUtil.info("Not entered a valid vehicle!") return diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua index 9b5dffa16..c3f5f157b 100644 --- a/scripts/specializations/CpCourseGeneratorSettings.lua +++ b/scripts/specializations/CpCourseGeneratorSettings.lua @@ -325,7 +325,7 @@ end --- Either prints all settings or a desired setting by the name or index in the setting table. ---@param name any function CpCourseGeneratorSettings:consoleCommandPrintSetting(name) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("Not entered a valid vehicle!") return diff --git a/scripts/specializations/CpGamePadHud.lua b/scripts/specializations/CpGamePadHud.lua index c067cde53..ddb1e5363 100644 --- a/scripts/specializations/CpGamePadHud.lua +++ b/scripts/specializations/CpGamePadHud.lua @@ -71,8 +71,8 @@ function CpGamePadHud.loadFromXMLFile() Gui.getIsOverlayGuiVisible = Utils.overwrittenFunction(Gui.getIsOverlayGuiVisible, getIsOverlayGuiVisible) local function isHudPopupMessageVisible(hud, superFunc, ...) - print(tostring(g_currentMission.controlledVehicle and g_currentMission.controlledVehicle.isCpGamePadHudActive and g_currentMission.controlledVehicle:isCpGamePadHudActive())) - return superFunc(hud, ...) or g_currentMission.controlledVehicle and g_currentMission.controlledVehicle.isCpGamePadHudActive and g_currentMission.controlledVehicle:isCpGamePadHudActive() + print(tostring(CpUtil.getCurrentVehicle() and CpUtil.getCurrentVehicle().isCpGamePadHudActive and CpUtil.getCurrentVehicle():isCpGamePadHudActive())) + return superFunc(hud, ...) or CpUtil.getCurrentVehicle() and CpUtil.getCurrentVehicle().isCpGamePadHudActive and CpUtil.getCurrentVehicle():isCpGamePadHudActive() end g_currentMission.hud.popupMessage.getIsVisible = Utils.overwrittenFunction(g_currentMission.hud.popupMessage.getIsVisible, isHudPopupMessageVisible) end diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index 19dea50e1..920ca61e6 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -127,7 +127,7 @@ function CpHud:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSe end function CpHud:actionEventMouse(isMouseEvent) - if self ~= g_currentMission.controlledVehicle then + if self ~= CpUtil.getCurrentVehicle() then ---Player has entered a child vehicle, so don't open the hud. return end @@ -293,7 +293,7 @@ end function CpHud:onEnterVehicle(isControlling) -- if the mouse cursor is shown when we enter the vehicle, disable camera rotations - if isControlling and self == g_currentMission.controlledVehicle then + if isControlling and self == CpUtil.getCurrentVehicle() then CpGuiUtil.setCameraRotation(self, not g_inputBinding:getShowMouseCursor(), self.spec_cpHud.savedCameraRotatableInfo) local spec = self.spec_cpHud diff --git a/scripts/specializations/CpInfoTexts.lua b/scripts/specializations/CpInfoTexts.lua index 8b78daecc..2acc25d17 100644 --- a/scripts/specializations/CpInfoTexts.lua +++ b/scripts/specializations/CpInfoTexts.lua @@ -182,7 +182,7 @@ end --- Debug test function ---@param ix number|nil optional bit mask to set. function CpInfoTexts.debug(ix) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if vehicle and vehicle.spec_cpInfoTexts then local spec = vehicle.spec_cpInfoTexts CpUtil.infoVehicle(vehicle, "Current bit mask: %s", CpInfoTexts.getBitMask(vehicle)) diff --git a/scripts/specializations/CpShovelPositions.lua b/scripts/specializations/CpShovelPositions.lua index 39e3ea183..e8a9f4b7c 100644 --- a/scripts/specializations/CpShovelPositions.lua +++ b/scripts/specializations/CpShovelPositions.lua @@ -360,7 +360,7 @@ function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, height local function draw(x1, y1, z1, x2, y2, z2, r, g, b) - if g_currentMission.controlledVehicle == shovelVehicle.rootVehicle and + if CpUtil.getCurrentVehicle() == shovelVehicle.rootVehicle and CpDebug:isChannelActive(CpDebug.DBG_SILO, shovelVehicle.rootVehicle) then DebugUtil.drawDebugLine(x1, y1, z1, x2, y2, z2, r, g, b) end @@ -469,7 +469,7 @@ function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, height end --- Debug information - if g_currentMission.controlledVehicle == shovelVehicle.rootVehicle and + if CpUtil.getCurrentVehicle() == shovelVehicle.rootVehicle and CpDebug:isChannelActive(CpDebug.DBG_SILO, shovelVehicle.rootVehicle) then DebugUtil.drawDebugLine(wsx, wsy, wsz, wex, wey, wez) DebugUtil.drawDebugLine(wsx, terrainHeight + minimalTargetHeight , wsz, @@ -661,7 +661,7 @@ function CpShovelPositions.initConsoleCommands() end local function executeConsoleCommand(func, ...) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("Not entered a valid vehicle!") return false diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index 57bb448c2..dda6b848f 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -486,7 +486,7 @@ end --- Either prints all settings or a desired setting by the name or index in the setting table. ---@param name any function CpVehicleSettings:consoleCommandPrintSetting(name) - local vehicle = g_currentMission.controlledVehicle + local vehicle = CpUtil.getCurrentVehicle() if not vehicle then CpUtil.info("Not entered a valid vehicle!") return diff --git a/scripts/util/CpRemainingTime.lua b/scripts/util/CpRemainingTime.lua index c6e2c6a53..f1a14c312 100644 --- a/scripts/util/CpRemainingTime.lua +++ b/scripts/util/CpRemainingTime.lua @@ -51,7 +51,7 @@ function CpRemainingTime:start() end function CpRemainingTime:update(dt) - if g_currentMission.controlledVehicle == self.vehicle and self.DEBUG_ACTIVE and CpDebug:isChannelActive(self.debugChannel, self.vehicle) then + if CpUtil.getCurrentVehicle() == self.vehicle and self.DEBUG_ACTIVE and CpDebug:isChannelActive(self.debugChannel, self.vehicle) then DebugUtil.renderTable(0.2, 0.2, 0.018, { {name = "time", value = CpGuiUtil.getFormatTimeText(self.time)}, {name = "optimal speed", value = MathUtil.mpsToKmh(self:getOptimalSpeed())}, From 67c9bd79c020cf53476ff1c536cc7f6007e00361 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 15 Nov 2024 16:22:18 +0100 Subject: [PATCH 008/158] Fixed hud --- scripts/gui/CpGuiUtil.lua | 112 ++++++++++++++++-------------- scripts/gui/hud/CpBaseHud.lua | 14 ++-- scripts/specializations/CpHud.lua | 35 +++++----- 3 files changed, 81 insertions(+), 80 deletions(-) diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua index 9c29a274a..99ce197c3 100644 --- a/scripts/gui/CpGuiUtil.lua +++ b/scripts/gui/CpGuiUtil.lua @@ -458,69 +458,73 @@ function CpGuiUtil.preOpeningInGameMenu(vehicle) end function CpGuiUtil.openCourseManagerGui(vehicle) - local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - inGameMenu:goToPage(inGameMenu.pageCpCourseManager) + --- TODO_25 + -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) + -- inGameMenu:goToPage(inGameMenu.pageCpCourseManager) end function CpGuiUtil.openCourseGeneratorGui(vehicle) - local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - local pageAI = inGameMenu.pageAI - --- Opens the ai inGame menu - inGameMenu:goToPage(pageAI) - local hotspot = vehicle:getMapHotspot() - pageAI:setMapSelectionItem(hotspot) - CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame menu.") - if vehicle:getIsCpActive() or not g_currentMission:getHasPlayerPermission("hireAssistant") then - pageAI:updateParameterValueTexts() - return - end - CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame job creation.") - vehicle:updateAIFieldWorkerImplementData() - pageAI.currentJobTypes = {} - local currentJobTypesTexts = {} - local currentJobTypeIndex, currentIndex = nil, nil - for index, jobType in ipairs(g_currentMission.aiJobTypeManager.jobTypes) do - if pageAI.jobTypeInstances[index]:getIsAvailableForVehicle(vehicle) then - table.insert(pageAI.currentJobTypes, index) - table.insert(currentJobTypesTexts, jobType.title) - if pageAI.jobTypeInstances[index]:isa(CpAIJob) then - currentJobTypeIndex = currentJobTypeIndex or index - currentIndex = currentIndex or #pageAI.currentJobTypes - end - end - end - if #pageAI.currentJobTypes == 0 then - return - end - - pageAI.jobTypeElement:setTexts(currentJobTypesTexts) - pageAI.jobTypeElement:setState(currentIndex or 1) - - pageAI.mode = InGameMenuAIFrame.MODE_CREATE - pageAI.currentJobVehicle = vehicle - pageAI.currentJob = nil - - pageAI:setJobMenuVisible(true) - pageAI:setActiveJobTypeSelection(currentJobTypeIndex or 1) - if not vehicle:hasCpCourse() then - if pageAI.currentJob:getCanGenerateFieldWorkCourse() then - CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame menu course generator.") - pageAI:onClickOpenCloseCourseGenerator() - end - end - --- Moves the map, so the selected vehicle is directly visible. - local worldX, _, worldZ = getWorldTranslation(vehicle.rootNode) - CpGuiUtil.movesMapCenterTo(pageAI.ingameMap, worldX, worldZ) + --- TODO_25 + -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) + -- local pageAI = inGameMenu.pageAI + -- --- Opens the ai inGame menu + -- inGameMenu:goToPage(pageAI) + -- local hotspot = vehicle:getMapHotspot() + -- pageAI:setMapSelectionItem(hotspot) + -- CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame menu.") + -- if vehicle:getIsCpActive() or not g_currentMission:getHasPlayerPermission("hireAssistant") then + -- pageAI:updateParameterValueTexts() + -- return + -- end + -- CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame job creation.") + -- vehicle:updateAIFieldWorkerImplementData() + -- pageAI.currentJobTypes = {} + -- local currentJobTypesTexts = {} + -- local currentJobTypeIndex, currentIndex = nil, nil + -- for index, jobType in ipairs(g_currentMission.aiJobTypeManager.jobTypes) do + -- if pageAI.jobTypeInstances[index]:getIsAvailableForVehicle(vehicle) then + -- table.insert(pageAI.currentJobTypes, index) + -- table.insert(currentJobTypesTexts, jobType.title) + -- if pageAI.jobTypeInstances[index]:isa(CpAIJob) then + -- currentJobTypeIndex = currentJobTypeIndex or index + -- currentIndex = currentIndex or #pageAI.currentJobTypes + -- end + -- end + -- end + -- if #pageAI.currentJobTypes == 0 then + -- return + -- end + + -- pageAI.jobTypeElement:setTexts(currentJobTypesTexts) + -- pageAI.jobTypeElement:setState(currentIndex or 1) + + -- pageAI.mode = InGameMenuAIFrame.MODE_CREATE + -- pageAI.currentJobVehicle = vehicle + -- pageAI.currentJob = nil + + -- pageAI:setJobMenuVisible(true) + -- pageAI:setActiveJobTypeSelection(currentJobTypeIndex or 1) + -- if not vehicle:hasCpCourse() then + -- if pageAI.currentJob:getCanGenerateFieldWorkCourse() then + -- CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame menu course generator.") + -- pageAI:onClickOpenCloseCourseGenerator() + -- end + -- end + -- --- Moves the map, so the selected vehicle is directly visible. + -- local worldX, _, worldZ = getWorldTranslation(vehicle.rootNode) + -- CpGuiUtil.movesMapCenterTo(pageAI.ingameMap, worldX, worldZ) end function CpGuiUtil.openVehicleSettingsGui(vehicle) - local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - inGameMenu:goToPage(inGameMenu.pageCpVehicleSettings) + --- TODO_25 + -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) + -- inGameMenu:goToPage(inGameMenu.pageCpVehicleSettings) end function CpGuiUtil.openGlobalSettingsGui(vehicle) - local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - inGameMenu:goToPage(inGameMenu.pageCpGlobalSettings) + --- TODO_25 + -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) + -- inGameMenu:goToPage(inGameMenu.pageCpGlobalSettings) end CpGuiUtil.UNIT_EXTENSIONS = { diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 51f488321..2d13bae2d 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -62,7 +62,7 @@ CpBaseHud.uvs = { 30, 44, 44 - }, AITargetHotspot.FILE_RESOLUTION), + }, AIPlaceableMarkerHotspot.FILE_RESOLUTION), exitSymbol = { {148, 184, 32, 32}, {256, 512} @@ -108,7 +108,6 @@ CpBaseHud.uvs = { }, } - --- Vertical + horizontal overlay alignment CpBaseHud.alignments = { bottomLeft = {Overlay.ALIGN_VERTICAL_BOTTOM, Overlay.ALIGN_HORIZONTAL_LEFT}, @@ -174,7 +173,6 @@ function CpBaseHud:init(vehicle) self.lines[self.numLines-1].left[2] = self.lines[self.numLines-1].left[2] - self.hMargin/2 self.lines[self.numLines-1].right[2] = self.lines[self.numLines-1].right[2] - self.hMargin/2 self.lines[self.numLines].right[2] = self.lines[self.numLines].right[2] - self.hMargin/4 - local background = CpGuiUtil.createOverlay({self.width, self.height}, {g_baseUIFilename, g_colorBgUVs}, self.BACKGROUND_COLOR, @@ -223,7 +221,8 @@ function CpBaseHud:init(vehicle) --- Cp icon local cpIconWidth, height = getNormalizedScreenValues(22, 22) local cpIconOverlay = CpGuiUtil.createOverlay({cpIconWidth, height}, - {Utils.getFilename("img/courseplayIconHud.dds", Courseplay.BASE_DIRECTORY), GuiUtils.getUVs(unpack(self.uvs.cpIcon))}, + {Utils.getFilename("img/courseplayIconHud.dds", Courseplay.BASE_DIRECTORY), + GuiUtils.getUVs(unpack(self.uvs.cpIcon))}, self.BASE_COLOR, self.alignments.bottomLeft) self.cpIcon = CpHudButtonElement.new(cpIconOverlay, self.baseHud) @@ -258,7 +257,6 @@ function CpBaseHud:init(vehicle) -------------------------------------- --- Right side -------------------------------------- - --- Exit button local width, height = getNormalizedScreenValues(18, 18) local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) @@ -278,7 +276,7 @@ function CpBaseHud:init(vehicle) --- Create start/stop button local onOffBtnWidth, height = getNormalizedScreenValues(20, 20) local onOffIndicatorOverlay = CpGuiUtil.createOverlay({onOffBtnWidth, height}, - {g_baseUIFilename, GuiUtils.getUVs(self.uvs.streetDriveToSymbol)}, --TODO 25 + {g_baseUIFilename, GuiUtils.getUVs(unpack(self.uvs.streetDriveToSymbol))}, --TODO 25 self.OFF_COLOR, self.alignments.bottomRight) self.onOffButton = CpHudButtonElement.new(onOffIndicatorOverlay, self.baseHud) @@ -287,7 +285,7 @@ function CpBaseHud:init(vehicle) self.onOffButton:setCallback("onClickPrimary", self.vehicle, function(vehicle) vehicle:cpStartStopDriver(true) end) - + --- Create start/stop field boarder record button local recordingBtnWidth, height = getNormalizedScreenValues(18, 18) local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) @@ -341,7 +339,7 @@ function CpBaseHud:init(vehicle) --- Goal button. local width, height = getNormalizedScreenValues(34, 34) local goalOverlay = CpGuiUtil.createOverlay({width, height}, - {AITargetHotspot.FILENAME, self.uvs.goalSymbol}, + {AIPlaceableMarkerHotspot.FILENAME, self.uvs.goalSymbol}, self.OFF_COLOR, self.alignments.bottomRight) diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index 920ca61e6..ced616b40 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -341,24 +341,23 @@ function CpHud:onDraw() if not self:getIsEntered() then return end - --- TODO_25 - -- local spec = self.spec_cpHud - - -- spec.hud:draw(spec.status) - -- if spec.hud:getIsOpen() then - -- if spec.lastShownWorkWidthTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then - -- if spec.hud:isBunkerSiloLayoutActive() or spec.hud:isSiloLoaderLayoutActive() then - -- CpHud.showCpBunkerSiloWorkWidth(self) - -- elseif spec.hud:isCombineUnloaderLayoutActive() then - -- CpHud.showCpCombineUnloaderWorkWidth(self) - -- else - -- CpHud.showCpCourseWorkWidth(self) - -- end - -- end - -- if spec.lastShownBaleCollectorOffsetTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then - -- ImplementUtil.showBaleCollectorOffset(self, self:getCpSettings().baleCollectorOffset:getValue()) - -- end - -- end + local spec = self.spec_cpHud + + spec.hud:draw(spec.status) + if spec.hud:getIsOpen() then + if spec.lastShownWorkWidthTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then + if spec.hud:isBunkerSiloLayoutActive() or spec.hud:isSiloLoaderLayoutActive() then + CpHud.showCpBunkerSiloWorkWidth(self) + elseif spec.hud:isCombineUnloaderLayoutActive() then + CpHud.showCpCombineUnloaderWorkWidth(self) + else + CpHud.showCpCourseWorkWidth(self) + end + end + if spec.lastShownBaleCollectorOffsetTimeStamp + CpHud.workWidthDisplayDelayMs > g_time then + ImplementUtil.showBaleCollectorOffset(self, self:getCpSettings().baleCollectorOffset:getValue()) + end + end end function CpHud:showCpBunkerSiloWorkWidth() From 17a3633b5654ee19dc198678d1d7d84fa7103b7f Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 16 Nov 2024 16:25:34 -0500 Subject: [PATCH 009/158] doc: update readme --- README.md | 108 ++++-------------------------------------------------- 1 file changed, 8 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 53cc3ba82..a2b5f7935 100644 --- a/README.md +++ b/README.md @@ -1,108 +1,16 @@ -# Courseplay Beta for Farming Simulator 2022 +# Courseplay Beta for Farming Simulator 2025 -<!-- [![Modhub release (latest by date)](https://img.shields.io/badge/dynamic/xml?color=blue&style=flat-square&label=Modhub+Release&prefix=v&query=%2F%2Fdiv%5B%40class%3D%27table-cell%27%5D%5B2%5D%5Bcontains%28text%28%29%2C%227.%22%29%5D&url=https%3A%2F%2Fwww.farming-simulator.com%2Fmod.php%3Flang%3Dde%26country%3Dde%26mod_id%3D248390%26title%3Dfs2022)](https://www.farming-simulator.com/mod.php?lang=de&country=de&mod_id=248390&title=fs2022) --> -[![Modhub release](https://img.shields.io/badge/Modhub%20Release-Modification-blue.svg)](https://www.farming-simulator.com/mod.php?mod_id=248390title=fs2022) -[![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Courseplay/Courseplay_FS22?include_prereleases&style=flat-square&label=Github+Release)](https://github.com/Courseplay/Courseplay_FS22/releases/latest) -[![GitHub Pre-Releases (by Asset)](https://img.shields.io/github/downloads-pre/Courseplay/Courseplay_FS22/latest/FS22_Courseplay.zip?style=flat-square)](https://github.com/Courseplay/Courseplay_FS22/releases/latest/download/FS22_Courseplay.zip) -[![GitHub issues](https://img.shields.io/github/issues/Courseplay/Courseplay_FS22?style=flat-square)](https://github.com/Courseplay/Courseplay_FS22/issues) +We are working on migrating Courseplay to Farming Simulator 2025. +We don't yet know how long it'll take, it depends on how much the Giants API changed, +when they make documentation and tools available, and of course, how much time we can spend on it. -**[Download the latest developer version](https://github.com/Courseplay/Courseplay_FS22/releases/latest)** (the file FS22_Courseplay.zip). +We'll keep you updated on our progress here, please be patient. -**[Courseplay Website](https://courseplay.github.io/Courseplay_FS22.github.io/)** - -## What Works - -* **Multiplayer support** -* Fieldwork mode: - * Course generator for complex fields with many option like headlands or beets with combines and so on .. - * Up to 5 workers with the same tools can work together on a field with the same course (multi tools) - * Generate courses for vine work - * Save/load/rename/move courses - * Load courses for baling, straw or grass collection and so on - * Combines can automatically unload into nearby trailers (combine self unload) -* Bale collector mode: - * Wrapping bales on a field without a course - * Collecting bales on the field without a course and unloading them with [AutoDrive](https://github.com/Stephan-S/FS22_AutoDrive) -* Combine unloader mode: - * Unload combines on the field - * Sending the giants helper or [AutoDrive](https://github.com/Stephan-S/FS22_AutoDrive) to unload at an unload station - * Creating heaps of sugar beets or other fruits on the field - * Unloading a loader vehicle, like the ``ROPA Maus`` and letting [AutoDrive](https://github.com/Stephan-S/FS22_AutoDrive) or Giants unload the trailer after that -* Silo load mode: - * Loading from a heap or bunker silo with loader, like the ``ROPA Maus`` - * Using a wheel loader or a front loader to load from a heap or a bunker silo and unload to: - * Unloading to nearby trailers - * Unloading to an unloading station, which needs to be selected on the AI menu -* Bunker silo mode: - * Compacting the silo with or without tools like this one [Silo distributor](https://www.farming-simulator.com/mod.php?lang=de&country=de&mod_id=242708&title=fs2022) - * Using a shield in a silo with a back wall to push the chaff to the back of silo -* Misc: - * Creating custom fields by recording the boarder with a vehicle or drawing on the AI Map. - * Course editor in the buy menu to edit courses or custom fields. -* Mod support with [AutoDrive](https://github.com/Stephan-S/FS22_AutoDrive): - * Sending the fieldwork driver to refill seeds/fertilizers and so on. - * Sending the fieldworker/ bale collector to unload collected straw and so on. - * Sending the fieldwork driver to refuel or repair. -* Bale collector mod support for: - * [Pallet Autoload Specialization](https://www.farming-simulator.com/mod.php?lang=en&country=gb&mod_id=228819) - * [Universal Autoload](https://farming-simulator.com/mod.php?lang=en&country=us&mod_id=237080&title=fs2022) - -## Usage - -Courseplay functions are now documented in the in-game help menu: - -![image](https://user-images.githubusercontent.com/2379521/195123670-20773556-48d4-4292-ba06-28443a2f9c69.png) - -If you prefer videos, YouTube has many great [tutorials](https://www.youtube.com/results?search_query=courseplay+fs22) - -## Turning on Debug Channels - -When there's an issue, you can turn on debug logging on the Courseplay vehicle settings page for each vehicle. This will -enable logging of debug information for only this vehicle. **Devs need those logs for troubleshooting and fixing bugs.** - -What information is logged when you activated the debug logging for the vehicle depends on the active debug channels. This -are similar to those we had in CP 19, but the way to turn them on/off is different: you can bring up the debug channel menu -by pressing Shift+4, then use Shift+1 and Shift+3 to select a channel, and then Shift+2 to toggle the selected debug channel -(green is on). - -Remember, you have to activate debug mode for the vehicle in the vehicle settings page, otherwise nothing is logged, even if -the channel is active. - -## Developer version - -Please be aware you're using a developer version, which may and will contain errors, bugs, mistakes and unfinished code. Chances are you computer will explode when using it. Twice. If you have no idea what "beta", "alpha", or "developer" means and entails, steer clear. The Courseplay team will not take any responsibility for crop destroyed, savegames deleted or baby pandas killed. - -You have been warned. - -If you're still ok with this, please remember to post possible issues that you find in the developer version. That's the only way we can find sources of error and fix them. -Be as specific as possible: - -* tell us the version number -* only use the vehicles necessary, not 10 other ones at a time -* which vehicles are involved, what is the intended action? -* Post! The! Log! to [Gist](https://gist.github.com/) or [PasteBin](http://pastebin.com/) -* For more details on how to post a proper bug report, visit our [Wiki](https://github.com/Courseplay/Courseplay_FS22/wiki) +This (and later, the Giants modhub) is the **only official source for Courseplay**, +if you see it anywhere else, it's a scam. ## Help Us Out We work long, hard, in our own free time at developing and improving Courseplay. If you like the project, show us your undying love: -[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=7PDM2P6HQ5D56&item_name=Promote+the+development+of+Courseplay¤cy_code=EUR&source=url) - -___ - -## Contributors - -See [Contributors](/Contributors.md) - -___ - -## Supporters - -People and teams who support us - -* Ameyer1233 [ModHoster Profile](https://www.modhoster.de/community/user/meyer123) - -* Burning Gamers [YouTube Channel](https://www.youtube.com/c/BurningGamersde/featured) - -* Mario Hirschfeld [YouTube Channel](https://www.youtube.com/c/MarioHirschfeld/featured) +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=7PDM2P6HQ5D56&item_name=Promote+the+development+of+Courseplay¤cy_code=EUR&source=url) \ No newline at end of file From 5c886d09bec9e2dad9cd1977d31b5060796bdeba Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 17 Nov 2024 00:17:16 +0100 Subject: [PATCH 010/158] WIP CP Menu --- Courseplay.lua | 37 +- config/gui/CpInGameMenu.xml | 47 +++ config/gui/GlobalSettingsFrame.xml | 34 -- config/gui/pages/CourseGeneratorFrame.xml | 78 ++++ config/gui/pages/GlobalSettingsFrame.xml | 68 ++++ config/gui/pages/VehicleSettingsFrame.xml | 68 ++++ modDesc.xml | 14 +- scripts/CpSettingsUtil.lua | 56 ++- scripts/ai/jobs/CpAIJob.lua | 3 +- scripts/gui/CpGlobalSettingsFrame.lua | 54 --- scripts/gui/CpGuiUtil.lua | 4 +- scripts/gui/CpInGameMenu.lua | 350 ++++++++++++++++++ scripts/gui/CpVehicleSettingsFrame.lua | 68 ---- .../gui/elements/CpBinaryOptionElement.lua | 76 ++++ .../{ => elements}/CpOptionToggleElement.lua | 15 +- scripts/gui/pages/CpCourseGeneratorFrame.lua | 114 ++++++ scripts/gui/pages/CpGlobalSettingsFrame.lua | 109 ++++++ scripts/gui/pages/CpVehicleSettingsFrame.lua | 111 ++++++ .../CpCourseGeneratorSettings.lua | 6 +- 19 files changed, 1096 insertions(+), 216 deletions(-) create mode 100644 config/gui/CpInGameMenu.xml delete mode 100644 config/gui/GlobalSettingsFrame.xml create mode 100644 config/gui/pages/CourseGeneratorFrame.xml create mode 100644 config/gui/pages/GlobalSettingsFrame.xml create mode 100644 config/gui/pages/VehicleSettingsFrame.xml delete mode 100644 scripts/gui/CpGlobalSettingsFrame.lua create mode 100644 scripts/gui/CpInGameMenu.lua delete mode 100644 scripts/gui/CpVehicleSettingsFrame.lua create mode 100644 scripts/gui/elements/CpBinaryOptionElement.lua rename scripts/gui/{ => elements}/CpOptionToggleElement.lua (91%) create mode 100644 scripts/gui/pages/CpCourseGeneratorFrame.lua create mode 100644 scripts/gui/pages/CpGlobalSettingsFrame.lua create mode 100644 scripts/gui/pages/CpVehicleSettingsFrame.lua diff --git a/Courseplay.lua b/Courseplay.lua index a340ce441..45c07662f 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -9,7 +9,8 @@ Courseplay.baseXmlKey = "Courseplay" Courseplay.xmlKey = Courseplay.baseXmlKey.."." function Courseplay:init() - g_gui:loadProfiles( Utils.getFilename("config/gui/GUIProfiles.xml", Courseplay.BASE_DIRECTORY) ) + ---TODO_25 + -- g_gui:loadProfiles( Utils.getFilename("config/gui/GUIProfiles.xml", Courseplay.BASE_DIRECTORY) ) --- Base cp folder self.baseDir = getUserProfileAppPath() .. "modSettings/" .. Courseplay.MOD_NAME .. "/" @@ -126,30 +127,20 @@ function Courseplay:deleteMap() end function Courseplay:setupGui() - local vehicleSettingsFrame = CpVehicleSettingsFrame.new() - local globalSettingsFrame = CpGlobalSettingsFrame.new() - local courseManagerFrame = CpCourseManagerFrame.new(self.courseStorage) - g_gui:loadGui(Utils.getFilename("config/gui/VehicleSettingsFrame.xml", Courseplay.BASE_DIRECTORY), - "CpVehicleSettingsFrame", vehicleSettingsFrame, true) - g_gui:loadGui(Utils.getFilename("config/gui/GlobalSettingsFrame.xml", Courseplay.BASE_DIRECTORY), - "CpGlobalSettingsFrame", globalSettingsFrame, true) - --g_gui:loadGui(Utils.getFilename("config/gui/CourseManagerFrame.xml", Courseplay.BASE_DIRECTORY), - -- "CpCourseManagerFrame", courseManagerFrame, true) - local function predicateFunc() - -- Only allow the vehicle bound pages, when a vehicle with cp functionality is chosen/entered. - local vehicle = CpInGameMenuAIFrameExtended.getVehicle() - return vehicle ~= nil and vehicle.spec_cpAIWorker ~= nil - end - - --- As precision farming decided to be moved in between the normal map and the ai map, - --- we move it down one position. - local pos = g_modIsLoaded["FS22_precisionFarming"] and 4 or 3 + CpInGameMenu.setupGui() + --[[ TODO 25 CpGuiUtil.fixInGameMenuPage(vehicleSettingsFrame, "pageCpVehicleSettings", {896, 0, 128, 128}, pos + 1, predicateFunc) - CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings", - {768, 0, 128, 128}, pos + 1, function () return true end) +]] + + -- CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings", + -- {768, 0, 128, 128}, pos + 1, function () return true end) + -- CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings", + -- {768, 0, 128, 128}, pos + 1, function () return true end) + +--[[ TODO 25 CpGuiUtil.fixInGameMenuPage(courseManagerFrame, "pageCpCourseManager", {256, 0, 128, 128}, pos + 1, predicateFunc) ]] @@ -158,6 +149,10 @@ function Courseplay:setupGui() -- TODO_25 -- g_currentMission.hud.ingameMap.drawFields = Utils.appendedFunction(g_currentMission.hud.ingameMap.drawFields, Courseplay.drawHudMap) + -- local page = g_gui.currentGui.target.pageSettings + + -- local newPage = page.subCategoryPages[1].copy(page.subCategoryPages[1].parent) + -- self:fixGui() end --- Enables drawing onto the hud map. diff --git a/config/gui/CpInGameMenu.xml b/config/gui/CpInGameMenu.xml new file mode 100644 index 000000000..acab99879 --- /dev/null +++ b/config/gui/CpInGameMenu.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<GUI onOpen="onOpen" onClose="onClose" onCreate="onCreate"> + <Bitmap profile="fs25_fullScreenBackground" id="background" /> + <Paging profile="uiInGameMenuPaging" onPageChange="onPageChange" onPageUpdate="onPageUpdate" + id="pagingElement"> + <FrameReference ref="cpInGameMenuGlobalSettings" name="cpInGameMenuGlobalSettings" id="pageGlobalSettings" /> + <FrameReference ref="cpInGameMenuVehicleSettings" name="cpInGameMenuVehicleSettings" id="pageVehicleSettings" /> + <FrameReference ref="cpInGameMenuCourseGenerator" name="cpInGameMenuCourseGenerator" id="pageCourseGenerator" /> + </Paging> + <Bitmap profile="fs25_tabListContainer" id="header"> + <MultiTextOption profile="uiInGameMenuHeaderSelector" onClick="onClickPageSelection" + id="pageSelector" soundDisabled="true" /> + <SmoothList profile="fs25_tabList" id="pagingTabList" + onSelectionChanged="onTabMenuSelectionChanged" onScroll="onTabMenuScroll"> + <ListItem profile="fs25_tabListItem"> + <Button profile="fs25_tabListItemButton" name="tabButton" soundDisabled="true" /> + <Bitmap profile="fs25_tabListItemSeparator" /> + <Bitmap profile="fs25_tabListItemSeparatorBottom" /> + </ListItem> + </SmoothList> + </Bitmap> + <BoxLayout profile="fs25_buttonBox" id="buttonsPanel"> + <Button profile="buttonBack" onClick="onClickBack" id="menuButton[1]"> + <Bitmap profile="fs25_buttonBoxSeparator" name="separator" /> + </Button> + <Button profile="buttonBack" onClick="onClickBack" id="menuButton[2]"> + <Bitmap profile="fs25_buttonBoxSeparator" name="separator" /> + </Button> + <Button profile="buttonBack" onClick="onClickBack" id="menuButton[3]"> + <Bitmap profile="fs25_buttonBoxSeparator" name="separator" /> + </Button> + <Button profile="buttonBack" onClick="onClickBack" id="menuButton[4]"> + <Bitmap profile="fs25_buttonBoxSeparator" name="separator" /> + </Button> + <Button profile="buttonBack" onClick="onClickBack" id="menuButton[5]"> + <Bitmap profile="fs25_buttonBoxSeparator" name="separator" /> + </Button> + <Button profile="buttonBack" onClick="onClickBack" id="menuButton[6]"> + <Bitmap profile="fs25_buttonBoxSeparator" name="separator" /> + </Button> + </BoxLayout> + <GUIProfiles> + <Profile name="uiInGameMenuHeaderDark" extends="uiInGameMenuHeader"> + <imageColor value="$preset_colorGlass" /> + </Profile> + </GUIProfiles> +</GUI> \ No newline at end of file diff --git a/config/gui/GlobalSettingsFrame.xml b/config/gui/GlobalSettingsFrame.xml deleted file mode 100644 index cb8941e97..000000000 --- a/config/gui/GlobalSettingsFrame.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no" ?> -<GUI name="cpGlobalSettings"> - <GuiElement type="empty" profile="uiInGameMenuFrame"> - <GuiElement type="empty" profile="ingameMenuFrameHeaderPanel"> - <GuiElement type="bitmap" profile="menuHeaderIcon" size="64px 64px"/> - <GuiElement type="text" profile="ingameMenuFrameHeaderText" id="header"/> - </GuiElement> - - <GuiElement type="text" profile="settingsMenuSubtitle" id="subTitlePrefab"/> - - <GuiElement type="cpOptionToggle" profile="multiTextOptionSettings" id="multiTextOptionPrefab" onClick="onClickCpMultiTextOption" namedComponents="true"> - <GuiElement type="button" profile="multiTextOptionSettingsLeft" name="left"/> - <GuiElement type="button" profile="multiTextOptionSettingsRight" name="right"/> - <GuiElement type="button" profile="cpMultiTextOptionSettingsText" name="text"/> - <GuiElement type="text" profile="multiTextOptionSettingsTitle" name="label"/> - <GuiElement type="text" profile="multiTextOptionSettingsTooltip" name="tooltip"/> - <GuiElement type="bitmap" profile="multiTextOptionSettingsBg" name="gradient"/> - </GuiElement> - - <GuiElement type="empty" profile="ingameMenuSettingsBox" id="settingsContainer"> - <GuiElement type="bitmap" profile="topScrollClipper" name="topClipper" /> - <GuiElement type="bitmap" profile="bottomScrollClipper" name="bottomClipper" /> - - <GuiElement type="scrollingLayout" profile="ingameMenuSettingsLayout" id="boxLayout" topClipperElementName="topClipper" bottomClipperElementName="bottomClipper"> - - - </GuiElement> - </GuiElement> - - <GuiElement type="threePartBitmap" profile="verticalListSliderRightDocked"> - <GuiElement type="slider" profile="verticalListSliderBar" dataElementId="boxLayout" handleFocus="false" /> - </GuiElement> - </GuiElement> -</GUI> diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml new file mode 100644 index 000000000..29cd19636 --- /dev/null +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="utf-8" standalone="no" ?> +<GUI name="cpInGameMenuCourseGenerator"> + <GuiElement profile="fs25_menuContainer"> + <GuiElement profile="fs25_menuHeaderPanel"> + <Bitmap profile="fs25_menuHeaderIconBg"> + <Bitmap profile="fs25_menuHeaderIcon" id="categoryHeaderIcon" /> + </Bitmap> + <Text profile="fs25_menuHeaderTitle" id="categoryHeaderText" /> + </GuiElement> + <BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"> + + </BoxLayout> + <ThreePartBitmap profile="fs25_lineSeparatorTopHighlighted" position="0px -57px" /> + <MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" + position="0px -10px" onClick="updateSubCategoryPages" /> + + <!-- Prefabs --> + + <Button profile="fs25_subCategorySelectorTabbedTab" id="selectorPrefab"> + <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> + </Button> + + <GuiElement profile="fs25_subCategorySelectorTabbedContainer" id="containerPrefab"> + <Text profile="fs25_settingsNoPermissionText" name="noPermissionText" + text="$l10n_ui_settingsNoPermission" visible="false"/> + <ScrollingLayout profile="fs25_settingsLayout" name="layout" bottomClipperElementName="bottomClipper"> + + </ScrollingLayout> + <Bitmap profile="fs25_settingsTooltipSeparator" name="separator" /> + <Bitmap profile="fs25_stopClipper" name="bottomClipper" /> + </GuiElement> + + <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> + <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <!-- <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> --> + <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + </CpBinaryyOption> + </Bitmap> + <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> + <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> + <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <!-- <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> --> + <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + </CpOptionToggle> + </Bitmap> + + <!-- <GuiElement profile="fs25_aiSettingsMapContainer" id="mapBox"> + <InGameMapPreview profile="fs25_aiSettingsIngameMap" id="ingameMapElement" + onDrawPostIngameMapHotspots="onDrawPostIngameMapHotspots" /> + <Text profile="fs25_aiSettingsNoFieldFound" id="noFieldFoundText" + text="$l10n_ui_aiSettingsFieldNotFound" /> + <Animation profile="fs25_aiSettingsLoadingAnimation" id="loadingAnimation" + visible="false" /> + <Bitmap profile="fs25_aiSettingsWorkDirection" id="workDirectionArrow" /> + </GuiElement> --> + + </GuiElement> + <ThreePartBitmap profile="fs25_sliderDockedBg" id="settingsSliderBox"> + <ThreePartBitmap profile="fs25_sliderDockedBox"> + <Slider profile="fs25_sliderDocked" id="settingsSlider" /> + </ThreePartBitmap> + </ThreePartBitmap> + <GUIProfiles> + <Profile name="fs25_settingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> + <position value="-500px 0px" /> + </Profile> + <Profile name="fs25_settingsNoPermissionText" extends="fs25_textDefault" + with="anchorMiddleStretchingX"> + <size value="100% 40px" /> + <textSize value="20px" /> + <textAlignment value="center" /> + <textColor value="$preset_fs25_colorMainLight" /> + </Profile> + <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> + <position value="-41px 0px" /> + </Profile> + </GUIProfiles> +</GUI> diff --git a/config/gui/pages/GlobalSettingsFrame.xml b/config/gui/pages/GlobalSettingsFrame.xml new file mode 100644 index 000000000..a63bd3570 --- /dev/null +++ b/config/gui/pages/GlobalSettingsFrame.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8" standalone="no" ?> +<GUI name="cpInGameMenuGlobalSettings"> + <GuiElement profile="fs25_menuContainer"> + <GuiElement profile="fs25_menuHeaderPanel"> + <Bitmap profile="fs25_menuHeaderIconBg"> + <Bitmap profile="fs25_menuHeaderIcon" id="categoryHeaderIcon" /> + </Bitmap> + <Text profile="fs25_menuHeaderTitle" id="categoryHeaderText" /> + </GuiElement> + <BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"> + + </BoxLayout> + <ThreePartBitmap profile="fs25_lineSeparatorTopHighlighted" position="0px -57px" /> + <MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" + position="0px -10px" onClick="updateSubCategoryPages" /> + + <!-- Prefabs --> + + <Button profile="fs25_subCategorySelectorTabbedTab" id="selectorPrefab"> + <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> + </Button> + + <GuiElement profile="fs25_subCategorySelectorTabbedContainer" id="containerPrefab"> + <Text profile="fs25_settingsNoPermissionText" name="noPermissionText" + text="$l10n_ui_settingsNoPermission" visible="false"/> + <ScrollingLayout profile="fs25_settingsLayout" name="layout" bottomClipperElementName="bottomClipper"> + + </ScrollingLayout> + <Bitmap profile="fs25_settingsTooltipSeparator" name="separator" /> + <Bitmap profile="fs25_stopClipper" name="bottomClipper" /> + </GuiElement> + + <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> + <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> + <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + </CpBinaryyOption> + </Bitmap> + <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> + <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> + <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> + <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + </CpOptionToggle> + </Bitmap> + + </GuiElement> + <ThreePartBitmap profile="fs25_sliderDockedBg" id="settingsSliderBox"> + <ThreePartBitmap profile="fs25_sliderDockedBox"> + <Slider profile="fs25_sliderDocked" id="settingsSlider" /> + </ThreePartBitmap> + </ThreePartBitmap> + <GUIProfiles> + <Profile name="fs25_settingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> + <position value="-500px 0px" /> + </Profile> + <Profile name="fs25_settingsNoPermissionText" extends="fs25_textDefault" + with="anchorMiddleStretchingX"> + <size value="100% 40px" /> + <textSize value="20px" /> + <textAlignment value="center" /> + <textColor value="$preset_fs25_colorMainLight" /> + </Profile> + <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> + <position value="-41px 0px" /> + </Profile> + </GUIProfiles> +</GUI> diff --git a/config/gui/pages/VehicleSettingsFrame.xml b/config/gui/pages/VehicleSettingsFrame.xml new file mode 100644 index 000000000..233a7ddd6 --- /dev/null +++ b/config/gui/pages/VehicleSettingsFrame.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8" standalone="no" ?> +<GUI name="cpInGameMenuVehicleSettings"> + <GuiElement profile="fs25_menuContainer"> + <GuiElement profile="fs25_menuHeaderPanel"> + <Bitmap profile="fs25_menuHeaderIconBg"> + <Bitmap profile="fs25_menuHeaderIcon" id="categoryHeaderIcon" /> + </Bitmap> + <Text profile="fs25_menuHeaderTitle" id="categoryHeaderText" /> + </GuiElement> + <BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"> + + </BoxLayout> + <ThreePartBitmap profile="fs25_lineSeparatorTopHighlighted" position="0px -57px" /> + <MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" + position="0px -10px" onClick="updateSubCategoryPages" /> + + <!-- Prefabs --> + + <Button profile="fs25_subCategorySelectorTabbedTab" id="selectorPrefab"> + <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> + </Button> + + <GuiElement profile="fs25_subCategorySelectorTabbedContainer" id="containerPrefab"> + <Text profile="fs25_settingsNoPermissionText" name="noPermissionText" + text="$l10n_ui_settingsNoPermission" visible="false"/> + <ScrollingLayout profile="fs25_settingsLayout" name="layout" bottomClipperElementName="bottomClipper"> + + </ScrollingLayout> + <Bitmap profile="fs25_settingsTooltipSeparator" name="separator" /> + <Bitmap profile="fs25_stopClipper" name="bottomClipper" /> + </GuiElement> + + <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> + <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> + <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + </CpBinaryyOption> + </Bitmap> + <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> + <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> + <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> + <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + </CpOptionToggle> + </Bitmap> + + </GuiElement> + <ThreePartBitmap profile="fs25_sliderDockedBg" id="settingsSliderBox"> + <ThreePartBitmap profile="fs25_sliderDockedBox"> + <Slider profile="fs25_sliderDocked" id="settingsSlider" /> + </ThreePartBitmap> + </ThreePartBitmap> + <GUIProfiles> + <Profile name="fs25_settingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> + <position value="-500px 0px" /> + </Profile> + <Profile name="fs25_settingsNoPermissionText" extends="fs25_textDefault" + with="anchorMiddleStretchingX"> + <size value="100% 40px" /> + <textSize value="20px" /> + <textAlignment value="center" /> + <textColor value="$preset_fs25_colorMainLight" /> + </Profile> + <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> + <position value="-41px 0px" /> + </Profile> + </GUIProfiles> +</GUI> diff --git a/modDesc.xml b/modDesc.xml index ff0591c89..55b3f5d49 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -491,12 +491,16 @@ Changelog 7.1.0.0: <sourceFile filename="scripts/gui/CpStatus.lua"/> <!-- <sourceFile filename="scripts/gui/CpAIHotspots.lua"/> <sourceFile filename="scripts/gui/CpAIFrameExtended.lua"/> --> - <sourceFile filename="scripts/gui/CpVehicleSettingsFrame.lua"/> - <sourceFile filename="scripts/gui/CpGlobalSettingsFrame.lua"/> <sourceFile filename="scripts/gui/CpCourseManagerFrame.lua"/> <sourceFile filename="scripts/gui/CourseDisplay.lua"/> - <sourceFile filename="scripts/gui/CpGamePadHudScreen.lua"/> - <sourceFile filename="scripts/gui/CpOptionToggleElement.lua"/> + <!-- <sourceFile filename="scripts/gui/CpGamePadHudScreen.lua"/> --> + <sourceFile filename="scripts/gui/elements/CpBinaryOptionElement.lua"/> + <sourceFile filename="scripts/gui/elements/CpOptionToggleElement.lua"/> + + <sourceFile filename="scripts/gui/pages/CpCourseGeneratorFrame.lua"/> + <sourceFile filename="scripts/gui/pages/CpVehicleSettingsFrame.lua"/> + <sourceFile filename="scripts/gui/pages/CpGlobalSettingsFrame.lua"/> + <sourceFile filename="scripts/gui/CpInGameMenu.lua"/> <sourceFile filename="scripts/gui/hud/HudElements.lua"/> <sourceFile filename="scripts/gui/hud/CpBaseHud.lua"/> @@ -542,7 +546,7 @@ Changelog 7.1.0.0: <specialization name="cpAICombineUnloader" className="CpAICombineUnloader" filename="scripts/specializations/CpAICombineUnloader.lua"/> <specialization name="cpAIBunkerSiloWorker" className="CpAIBunkerSiloWorker" filename="scripts/specializations/CpAIBunkerSiloWorker.lua"/> <specialization name="cpAISiloLoaderWorker" className="CpAISiloLoaderWorker" filename="scripts/specializations/CpAISiloLoaderWorker.lua"/> - <specialization name="cpGamePadHud" className="CpGamePadHud" filename="scripts/specializations/CpGamePadHud.lua"/> + <!-- <specialization name="cpGamePadHud" className="CpGamePadHud" filename="scripts/specializations/CpGamePadHud.lua"/> --> <specialization name="cpHud" className="CpHud" filename="scripts/specializations/CpHud.lua"/> <specialization name="cpInfoTexts" className="CpInfoTexts" filename="scripts/specializations/CpInfoTexts.lua"/> <specialization name="cpShovelPositions" className="CpShovelPositions" filename="scripts/specializations/CpShovelPositions.lua"/> diff --git a/scripts/CpSettingsUtil.lua b/scripts/CpSettingsUtil.lua index 7d37b4767..ec30d6297 100644 --- a/scripts/CpSettingsUtil.lua +++ b/scripts/CpSettingsUtil.lua @@ -278,36 +278,58 @@ function CpSettingsUtil.copySettingsValues(settingsTable, settingsTableToCopy) end end +function CpSettingsUtil.generateAndBindGuiElements(settingsData, parentGuiElement, + genericMultiSettingElement, genericBooleanSettingElement, settings) + local alternate = false + for _, raw_setting in ipairs(settingsData.elements) do + local clonedSettingElement + if raw_setting:is_a(AIParameterBooleanSetting) then + clonedSettingElement = genericBooleanSettingElement:clone(parentGuiElement) + else + clonedSettingElement = genericMultiSettingElement:clone(parentGuiElement) + end + if alternate then + clonedSettingElement:setImageColor(1, unpack(GuiUtils.getColorArray(g_gui.presets["fs25_colorGreyDark"]))) + else + clonedSettingElement:setImageColor(1, unpack(GuiUtils.getColorArray(g_gui.presets["fs25_colorGreyDark_50"]))) + end + alternate = not alternate + local setting = settings[raw_setting:getName()] + if setting then + --- TODO_25 + clonedSettingElement:getDescendantByName("setting"):setDataSource(setting) + clonedSettingElement.aiParameter = setting + -- clonedSettingElement:applyScreenAlignment() + FocusManager:loadElementFromCustomValues(clonedSettingElement) + end + end + parentGuiElement:invalidateLayout() +end + function CpSettingsUtil.generateAndBindGuiElementsToSettings(settingsBySubTitle, parentGuiElement, - genericSettingElement, genericSubTitleElement, settings) + genericMultiSettingElement, genericBooleanSettingElement, genericSubTitleElement, settings) for _, data in ipairs(settingsBySubTitle) do local clonedSubTitleElement = genericSubTitleElement:clone(parentGuiElement) clonedSubTitleElement:setText(g_i18n:getText(data.title)) clonedSubTitleElement.subTitleConfigData = data - clonedSubTitleElement:applyScreenAlignment() + -- clonedSubTitleElement:applyScreenAlignment() FocusManager:loadElementFromCustomValues(clonedSubTitleElement) - for _, raw_setting in ipairs(data.elements) do - local clonedSettingElement = genericSettingElement:clone(parentGuiElement) - local setting = settings[raw_setting:getName()] - if setting then - clonedSettingElement:setDataSource(setting) - clonedSettingElement.aiParameter = setting - clonedSettingElement:applyScreenAlignment() - FocusManager:loadElementFromCustomValues(clonedSettingElement) - end - end + + CpSettingsUtil.generateAndBindGuiElements(data, + parentGuiElement, genericMultiSettingElement, + genericBooleanSettingElement, settings) end - parentGuiElement:invalidateLayout() end function CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) local subTitleVisible = true for i, element in pairs(layout.elements) do + ---@type AIParameterSetting local setting = element.aiParameter if setting then - element:setDisabled(setting:getIsDisabled()) + element:getDescendantByName("setting"):setDisabled(setting:getIsDisabled()) element:setVisible(subTitleVisible and setting:getIsVisible()) - element:updateTitle() + element:getDescendantByName("setting"):updateTitle() else if element.subTitleConfigData then local isVisible = true @@ -324,14 +346,14 @@ function CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) isVisible = class[isVisibleFunc](vehicle) end if class[isDisabledFunc] then - element:setDisabled(class[isDisabledFunc](vehicle)) + element:getDescendantByName("setting"):setDisabled(class[isDisabledFunc](vehicle)) end else if class[isVisibleFunc] then isVisible = class[isVisibleFunc](class) end if class[isDisabledFunc] then - element:setDisabled(class[isDisabledFunc](vehicle)) + element:getDescendantByName("setting"):setDisabled(class[isDisabledFunc](vehicle)) end end subTitleVisible = isVisible diff --git a/scripts/ai/jobs/CpAIJob.lua b/scripts/ai/jobs/CpAIJob.lua index 018e49d5a..b60af9c96 100644 --- a/scripts/ai/jobs/CpAIJob.lua +++ b/scripts/ai/jobs/CpAIJob.lua @@ -239,7 +239,8 @@ end --- Is the job valid? function CpAIJob:validate(farmId) - self:setParamterValid(true) + --- TODO_25 + -- self:setParamterValid(true) local isValid, errorMessage = self.vehicleParameter:validate() diff --git a/scripts/gui/CpGlobalSettingsFrame.lua b/scripts/gui/CpGlobalSettingsFrame.lua deleted file mode 100644 index 7eb1112b5..000000000 --- a/scripts/gui/CpGlobalSettingsFrame.lua +++ /dev/null @@ -1,54 +0,0 @@ ---[[ - This frame is a page for all global settings in the in game menu. - All the layout, gui elements are cloned from the general settings page of the in game menu. -]]-- - -CpGlobalSettingsFrame = { - CONTROLS = { - HEADER = "header", - SUB_TITLE_PREFAB = "subTitlePrefab", - MULTI_TEXT_OPTION_PREFAB = "multiTextOptionPrefab", - SETTINGS_CONTAINER = "settingsContainer", - BOX_LAYOUT = "boxLayout" - }, -} - -local CpGlobalSettingsFrame_mt = Class(CpGlobalSettingsFrame, TabbedMenuFrameElement) - -function CpGlobalSettingsFrame.new(target, custom_mt) - local self = TabbedMenuFrameElement.new(target, custom_mt or CpGlobalSettingsFrame_mt) - self:exposeControlsAsFields(CpGlobalSettingsFrame.CONTROLS) - return self -end - -function CpGlobalSettingsFrame:onGuiSetupFinished() - CpGlobalSettingsFrame:superClass().onGuiSetupFinished(self) - - self.subTitlePrefab:unlinkElement() - FocusManager:removeElement(self.subTitlePrefab) - self.multiTextOptionPrefab:unlinkElement() - FocusManager:removeElement(self.multiTextOptionPrefab) -end - -function CpGlobalSettingsFrame:onFrameOpen() - CpGlobalSettingsFrame:superClass().onFrameOpen(self) - - local settings = g_Courseplay.globalSettings:getSettings() - local settingsBySubTitle, pageTitle = g_Courseplay.globalSettings:getSettingSetup() - settingsBySubTitle = settingsBySubTitle - self.header:setText(g_i18n:getText(pageTitle)) - for i = #self.boxLayout.elements, 1, -1 do - self.boxLayout.elements[i]:delete() - end - CpSettingsUtil.generateAndBindGuiElementsToSettings(settingsBySubTitle, - self.boxLayout, self.multiTextOptionPrefab, - self.subTitlePrefab, settings) - CpSettingsUtil.updateGuiElementsBoundToSettings(self.boxLayout) - self:setSoundSuppressed(true) - FocusManager:setFocus(self.boxLayout) - self:setSoundSuppressed(false) -end - -function CpGlobalSettingsFrame:onClickCpMultiTextOption(_, guiElement) - CpSettingsUtil.updateGuiElementsBoundToSettings(self.boxLayout) -end diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua index 99ce197c3..76812eefc 100644 --- a/scripts/gui/CpGuiUtil.lua +++ b/scripts/gui/CpGuiUtil.lua @@ -4,7 +4,7 @@ CpGuiUtil = {} --- Adds a new page to the in game menu. function CpGuiUtil.fixInGameMenuPage(frame, pageName, uvs, position, predicateFunc) - local inGameMenu = g_gui.screenControllers[InGameMenu] + local inGameMenu = g_inGameMenu -- g_gui.screenControllers[InGameMenu] -- remove all to avoid warnings for k, v in pairs({pageName}) do @@ -41,7 +41,7 @@ function CpGuiUtil.fixInGameMenuPage(frame, pageName, uvs, position, predicateFu inGameMenu:registerPage(inGameMenu[pageName], position, predicateFunc) local iconFileName = Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY) inGameMenu:addPageTab(inGameMenu[pageName], iconFileName, GuiUtils.getUVs(uvs)) - inGameMenu[pageName]:applyScreenAlignment() + -- inGameMenu[pageName]:applyScreenAlignment() inGameMenu[pageName]:updateAbsolutePosition() for i = 1, #inGameMenu.pageFrames do diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua new file mode 100644 index 000000000..56ac5594e --- /dev/null +++ b/scripts/gui/CpInGameMenu.lua @@ -0,0 +1,350 @@ +CpInGameMenu = {} +local CpInGameMenu_mt = Class(CpInGameMenu, TabbedMenu) +function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager) + local self = CpInGameMenu:superClass().new(target, customMt or CpInGameMenu_mt, messageCenter, l10n, inputManager) + + self.messageCenter = messageCenter + self.l10n = l10n + self.inputManager = inputManager + + -- self:exposeControlsAsFields(CpInGameMenu.CONTROLS) + + self.defaultMenuButtonInfo = {} + self.backButtonInfo = {} + + return self +end + +-- Lines 135-193 +function CpInGameMenu.createFromExistingGui(gui, guiName) + CpGlobalSettingsFrame.createFromExistingGui(g_gui.frames.cpInGameMenuGlobalSettings.target, "CpGlobalSettingsFrame") + CpVehicleSettingsFrame.createFromExistingGui(g_gui.frames.cpInGameMenuVehicleSettings.target, "CpVehicleSettingsFrame") + CpCourseGeneratorFrame.createFromExistingGui(g_gui.frames.cpInGameMenuCourseGenerator.target, "CpCourseGeneratorFrame") + + local messageCenter = gui.messageCenter + local l10n = gui.l10n + local inputManager = gui.inputManager + local newGui = CpInGameMenu.new(nil, nil, messageCenter, l10n, inputManager) + + g_gui.guis.CpInGameMenu:delete() + g_gui.guis.CpInGameMenu.target:delete() + g_gui:loadGui(gui.xmlFilename, guiName, newGui) + + g_cpInGameMenu = newGui + + return newGui +end + +function CpInGameMenu.setupGui() + CpCourseGeneratorFrame.setupGui() + CpGlobalSettingsFrame.setupGui() + CpVehicleSettingsFrame.setupGui() + + local inGameMenu = CpInGameMenu.new(nil, nil, g_messageCenter, g_i18n, g_inputBinding) + g_gui:loadGui(Utils.getFilename("config/gui/CpInGameMenu.xml", Courseplay.BASE_DIRECTORY), + "CpInGameMenu", inGameMenu) +end + +-- Lines 279-324 +function CpInGameMenu:initializePages() + self.clickBackCallback = self:makeSelfCallback(self.onButtonBack) + self.pageGlobalSettings:initialize() + self.pageVehicleSettings:initialize() + self.pageCourseGenerator:initialize() +end + +-- Lines 327-362 +function CpInGameMenu:setupMenuPages() + + local orderedDefaultPages = { + { + self.pageGlobalSettings, + function () + return true + end, + {768, 0, 128, 128} + }, + { + self.pageVehicleSettings, + function () + return CpUtil.getCurrentVehicle() ~= nil + end, + {896, 0, 128, 128} + }, + { + self.pageCourseGenerator, + function () + return CpUtil.getCurrentVehicle() ~= nil + end, + {128, 0, 128, 128} + } + } + + for i, pageDef in ipairs(orderedDefaultPages) do + local page, predicate, iconUVs = unpack(pageDef) + + if page ~= nil then + self:registerPage(page, i, predicate) + + local normalizedUVs = GuiUtils.getUVs(iconUVs) + + self:addPageTab(page, Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY), normalizedUVs) + end + end +end + +-- Lines 365-397 +function CpInGameMenu:setupMenuButtonInfo() + CpInGameMenu:superClass().setupMenuButtonInfo(self) + + local onButtonBackFunction = self.clickBackCallback + local onButtonQuitFunction = self:makeSelfCallback(self.onButtonQuit) + local onButtonSaveGameFunction = self:makeSelfCallback(self.onButtonSaveGame) + self.backButtonInfo = { + inputAction = InputAction.MENU_BACK, + text = self.l10n:getText(CpInGameMenu.L10N_SYMBOL.BUTTON_BACK), + callback = onButtonBackFunction + } + self.saveButtonInfo = { + showWhenPaused = true, + inputAction = InputAction.MENU_ACTIVATE, + text = self.l10n:getText(CpInGameMenu.L10N_SYMBOL.BUTTON_SAVE_GAME), + callback = onButtonSaveGameFunction + } + self.quitButtonInfo = { + showWhenPaused = true, + inputAction = InputAction.MENU_CANCEL, + text = self.l10n:getText(CpInGameMenu.L10N_SYMBOL.BUTTON_CANCEL_GAME), + callback = onButtonQuitFunction + } + + self.defaultMenuButtonInfo = { + self.backButtonInfo, + self.saveButtonInfo, + self.quitButtonInfo + } + + self.defaultMenuButtonInfoByActions[InputAction.MENU_BACK] = self.defaultMenuButtonInfo[1] + self.defaultMenuButtonInfoByActions[InputAction.MENU_ACTIVATE] = self.defaultMenuButtonInfo[2] + self.defaultMenuButtonInfoByActions[InputAction.MENU_CANCEL] = self.defaultMenuButtonInfo[3] + self.defaultButtonActionCallbacks = { + [InputAction.MENU_BACK] = onButtonBackFunction, + [InputAction.MENU_CANCEL] = onButtonQuitFunction, + [InputAction.MENU_ACTIVATE] = onButtonSaveGameFunction + } +end + +-- Lines 399-424 +function CpInGameMenu:onGuiSetupFinished() + CpInGameMenu:superClass().onGuiSetupFinished(self) + + self:initializePages() + self:setupMenuPages() +end + +-- Lines 431-433 +function CpInGameMenu:updateBackground() + -- self.background:setVisible(self.currentPage.needsSolidBackground) +end + +-- Lines 512-527 +function CpInGameMenu:reset() + CpInGameMenu:superClass().reset(self) + +end + +-- Lines 529-556 +function CpInGameMenu:onMenuOpened() + -- if self.playerFarmId == FarmManager.SPECTATOR_FARM_ID then + -- self:setSoundSuppressed(true) + + -- local farmsPageId = self.pagingElement:getPageIdByElement(self.pageMultiplayerFarms) + -- local farmsPageIndex = self.pagingElement:getPageMappingIndex(farmsPageId) + + -- self.pageSelector:setState(farmsPageIndex, true) + -- self:setSoundSuppressed(false) + -- end + + -- if GS_IS_MOBILE_VERSION then + -- g_currentMission:setManualPause(true) + -- end + + -- if self.currentPage.dynamicMapImageLoading ~= nil then + -- if not self.currentPage.dynamicMapImageLoading:getIsVisible() then + -- self.messageCenter:publish(MessageType.GUI_INGAME_OPEN) + -- else + -- self.sendDelayedOpenMessage = true + -- end + -- else + -- self.messageCenter:publish(MessageType.GUI_INGAME_OPEN) + -- end +end + +-- Lines 559-578 +function CpInGameMenu:onClose(element) + CpInGameMenu:superClass().onClose(self) + +end + +-- Lines 650-699 +function CpInGameMenu:update(dt) + + CpInGameMenu:superClass().update(self, dt) +end + +-- Lines 820-823 +function CpInGameMenu:onClickMenu() + self:exitMenu() + + return true +end + +-- Lines 844-866 +function CpInGameMenu:onPageChange(pageIndex, pageMappingIndex, element, skipTabVisualUpdate) + + + CpInGameMenu:superClass().onPageChange(self, pageIndex, pageMappingIndex, element, skipTabVisualUpdate) + + -- self.header:applyProfile(self:getTabBarProfile()) + self:updateBackground() +end + +-- Lines 869-873 +function CpInGameMenu:getPageButtonInfo(page) + local buttonInfo = CpInGameMenu:superClass().getPageButtonInfo(self, page) + + return buttonInfo +end + + +CpInGameMenu.TAB_UV = { + MAP = { + 0, + 0, + 65, + 65 + }, + AI = { + 910, + 65, + 65, + 65 + }, + CALENDAR = { + 65, + 0, + 65, + 65 + }, + WEATHER = { + 130, + 0, + 65, + 65 + }, + PRICES = { + 195, + 0, + 65, + 65 + }, + VEHICLES = { + 260, + 0, + 65, + 65 + }, + FINANCES = { + 325, + 0, + 65, + 65 + }, + ANIMALS = { + 390, + 0, + 65, + 65 + }, + CONTRACTS = { + 455, + 0, + 65, + 65 + }, + PRODUCTION = { + 520, + 0, + 65, + 65 + }, + STATISTICS = { + 585, + 0, + 65, + 65 + }, + GAME_SETTINGS = { + 650, + 0, + 65, + 65 + }, + GENERAL_SETTINGS = { + 715, + 0, + 65, + 65 + }, + CONTROLS_SETTINGS = { + 845, + 0, + 65, + 65 + }, + TOUR = { + 780, + 130, + 65, + 65 + }, + HELP = { + 0, + 65, + 65, + 65 + }, + FARMS = { + 260, + 65, + 65, + 65 + }, + USERS = { + 650, + 65, + 65, + 65 + } +} +CpInGameMenu.L10N_SYMBOL = { + END_WITHOUT_SAVING = "ui_endWithoutSaving", + BUTTON_SAVE_GAME = "button_saveGame", + BUTTON_RESTART = "button_restart", + SAVE_OVERWRITE = "dialog_savegameOverwrite", + TUTORIAL_NOT_SAVED = "ui_tutorialIsNotSaved", + END_GAME = "ui_youWantToQuitGame", + SAVING_CONTENT = "ui_savingContent", + SAVE_NO_SPACE = "ui_savegameSaveNoSpace", + SELECT_DEVICE = "dialog_savegameSelectDevice", + MASTER_SERVER_CONNECTION_LOST = "ui_masterServerConnectionLost", + BUTTON_CANCEL_GAME = "button_cancelGame", + END_TUTORIAL = "ui_endTutorial", + NOT_SAVED = "ui_savegameNotSaved", + SAVE_NO_DEVICE = "ui_savegameSaveNoDevice", + BUTTON_BACK = "button_back" +} +CpInGameMenu.PROFILES = { + TAB_BAR_DARK = "uiCpInGameMenuHeaderDark", + TAB_BAR_LIGHT = "uiCpInGameMenuHeader" +} diff --git a/scripts/gui/CpVehicleSettingsFrame.lua b/scripts/gui/CpVehicleSettingsFrame.lua deleted file mode 100644 index 53028b49d..000000000 --- a/scripts/gui/CpVehicleSettingsFrame.lua +++ /dev/null @@ -1,68 +0,0 @@ ---[[ - This frame is a page for all vehicle settings in the in game menu. - This page is visible once a vehicle is selected in the ai menu. - The selected vehicle settings are then automatically bound to the gui on opening of the page. - All the layout, gui elements are cloned from the general settings page of the in game menu. -]]-- - ---VehicleSettingFrame - -CpVehicleSettingsFrame = { - CONTROLS = { - HEADER = "header", - SUB_TITLE_PREFAB = "subTitlePrefab", - MULTI_TEXT_OPTION_PREFAB = "multiTextOptionPrefab", - SETTINGS_CONTAINER = "settingsContainer", - BOX_LAYOUT = "boxLayout" - }, -} - -local CpVehicleSettingsFrame_mt = Class(CpVehicleSettingsFrame, TabbedMenuFrameElement) - -function CpVehicleSettingsFrame.new(target, custom_mt) - local self = TabbedMenuFrameElement.new(target, custom_mt or CpVehicleSettingsFrame_mt) - self:exposeControlsAsFields(CpVehicleSettingsFrame.CONTROLS) - return self -end - -function CpVehicleSettingsFrame:onGuiSetupFinished() - CpVehicleSettingsFrame:superClass().onGuiSetupFinished(self) - - self.subTitlePrefab:unlinkElement() - FocusManager:removeElement(self.subTitlePrefab) - self.multiTextOptionPrefab:unlinkElement() - FocusManager:removeElement(self.multiTextOptionPrefab) -end - ---- Binds the settings of the selected vehicle to the gui elements. -function CpVehicleSettingsFrame:onFrameOpen() - CpVehicleSettingsFrame:superClass().onFrameOpen(self) - self.currentVehicle = CpInGameMenuAIFrameExtended.getVehicle() - --- Changes the page title. - if self.currentVehicle ~=nil then - if self.currentVehicle.getCpSettings then - CpUtil.debugVehicle( CpUtil.DBG_HUD,self.currentVehicle, "onFrameOpen CpVehicleSettingsFrame" ) - local settings = self.currentVehicle:getCpSettings() - local settingsBySubTitle, pageTitle = CpVehicleSettings.getSettingSetup() - pageTitle = g_i18n:getText(pageTitle) - for i = #self.boxLayout.elements, 1, -1 do - self.boxLayout.elements[i]:delete() - end - CpSettingsUtil.generateAndBindGuiElementsToSettings(settingsBySubTitle, - self.boxLayout, self.multiTextOptionPrefab, - self.subTitlePrefab, settings) - CpSettingsUtil.updateGuiElementsBoundToSettings(self.boxLayout, self.currentVehicle) - local title = string.format(pageTitle, self.currentVehicle:getName()) - self.header:setText(title) - end - end - self:setSoundSuppressed(true) - FocusManager:setFocus(self.boxLayout) - self:setSoundSuppressed(false) -end - -function CpVehicleSettingsFrame:onClickCpMultiTextOption(_, guiElement) - if self.currentVehicle then - CpSettingsUtil.updateGuiElementsBoundToSettings(self.boxLayout, self.currentVehicle) - end -end \ No newline at end of file diff --git a/scripts/gui/elements/CpBinaryOptionElement.lua b/scripts/gui/elements/CpBinaryOptionElement.lua new file mode 100644 index 000000000..9275d9e43 --- /dev/null +++ b/scripts/gui/elements/CpBinaryOptionElement.lua @@ -0,0 +1,76 @@ +CpBinaryOptionElement = {} +local CpBinaryOptionElement_mt = Class(CpBinaryOptionElement, BinaryOptionElement) + +function CpBinaryOptionElement.new(target, custom_mt) + local self = BinaryOptionElement.new(target, custom_mt or CpBinaryOptionElement_mt) + self.dataSource = nil + self.toolTipElement = nil + return self +end + +-- Lines 19-23 +function CpBinaryOptionElement:delete() + self.dataSource = nil + + CpBinaryOptionElement:superClass().delete(self) +end + +-- Lines 25-28 +function CpBinaryOptionElement:setDataSource(dataSource) + self.dataSource = dataSource + + self:updateTitle() +end + + +function CpBinaryOptionElement:addElement(element, ...) + CpBinaryOptionElement:superClass().addElement(self, element, ...) + -- if self.textElement then + -- self.textElement.forceHighlight = true + -- self.textElement:setHandleFocus(false) + -- self.textElement.target = self + -- self.textElement:setCallback("onClickCallback", "onCenterButtonClicked") + -- end + if element.name == "tooltip" then + self.toolTipElement = element + end +end + +-- Lines 30-33 +function CpBinaryOptionElement:updateTitle() + if self.dataSource:getValue() then + self:setState(2) + else + self:setState(1) + end + if self.labelElement then + self.labelElement:setText(self.dataSource:getTitle()) + end + if self.toolTipElement then + self.toolTipElement:setText(self.dataSource:getTooltip()) + end + -- self:setState(1) +end + +-- Lines 35-42 +function CpBinaryOptionElement:onRightButtonClicked(steps, noFocus) + if self.dataSource ~= nil then + self.dataSource:setNextItem() + + -- self.texts[1] = self.dataSource:getString() + end + + CpBinaryOptionElement:superClass().onRightButtonClicked(self, steps, noFocus) +end + +-- Lines 44-51 +function CpBinaryOptionElement:onLeftButtonClicked(steps, noFocus) + if self.dataSource ~= nil then + self.dataSource:setPreviousItem() + + -- self.texts[1] = self.dataSource:getString() + end + + CpBinaryOptionElement:superClass().onLeftButtonClicked(self, steps, noFocus) +end +Gui.registerGuiElement("CpBinaryyOption", CpBinaryOptionElement) diff --git a/scripts/gui/CpOptionToggleElement.lua b/scripts/gui/elements/CpOptionToggleElement.lua similarity index 91% rename from scripts/gui/CpOptionToggleElement.lua rename to scripts/gui/elements/CpOptionToggleElement.lua index 0f1cc1195..32b2bad71 100644 --- a/scripts/gui/CpOptionToggleElement.lua +++ b/scripts/gui/elements/CpOptionToggleElement.lua @@ -5,7 +5,7 @@ local CpOptionToggleElement_mt = Class(CpOptionToggleElement, OptionToggleElemen function CpOptionToggleElement.new(target, custom_mt) local self = OptionToggleElement.new(target, custom_mt or CpOptionToggleElement_mt) - + self.toolTipElement = nil return self end @@ -38,10 +38,8 @@ function CpOptionToggleElement:addElement(element, ...) self.textElement.target = self self.textElement:setCallback("onClickCallback", "onCenterButtonClicked") end - if self.namedComponents then - if element.name == "tooltip" then - self.toolTipElement = element - end + if element.name == "tooltip" then + self.toolTipElement = element end end @@ -53,7 +51,7 @@ function CpOptionToggleElement:updateTitle() if self.labelElement and self.labelElement.setText then self.labelElement:setText(self.dataSource:getTitle()) end - if self.toolTipElement and self.toolTipElement.setText and self.dataSource.getTooltip then + if self.toolTipElement then self.toolTipElement:setText(self.dataSource:getTooltip()) end end @@ -89,12 +87,9 @@ function CpOptionToggleElement:inputEvent(action, value, eventUsed) end return eventUsed end - function CpOptionToggleElement:raiseClickCallback(...) CpOptionToggleElement:superClass().raiseClickCallback(self, ...) --- Magic gui fix, no idea why this is needed ... FocusManager:unsetFocus(self) end - - -Gui.CONFIGURATION_CLASS_MAPPING.cpOptionToggle = CpOptionToggleElement \ No newline at end of file +Gui.registerGuiElement("CpOptionToggle", CpOptionToggleElement) diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua new file mode 100644 index 000000000..00df12539 --- /dev/null +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -0,0 +1,114 @@ +--[[ + This frame is a page for all global settings in the in game menu. + All the layout, gui elements are cloned from the general settings page of the in game menu. +]]-- + +CpCourseGeneratorFrame = { + CATEGRORIES = { + BASIC_SETTINGS = 1, + DEBUG_SETTINGS = 2 + }, + CATEGRORY_TEXTS = { + "CP_global_setting_subTitle_general", + "TODO: Debug" + } +} +CpCourseGeneratorFrame.NUM_CATEGORIES = #CpCourseGeneratorFrame.CATEGRORY_TEXTS + +local CpCourseGeneratorFrame_mt = Class(CpCourseGeneratorFrame, TabbedMenuFrameElement) + +function CpCourseGeneratorFrame.new(target, custom_mt) + local self = TabbedMenuFrameElement.new(target, custom_mt or CpCourseGeneratorFrame_mt) + self.subCategoryPages = {} + self.subCategoryTabs = {} + return self +end + +function CpCourseGeneratorFrame.setupGui() + local courseGeneratorFrame = CpCourseGeneratorFrame.new() + g_gui:loadGui(Utils.getFilename("config/gui/pages/CourseGeneratorFrame.xml", Courseplay.BASE_DIRECTORY), + "CpCourseGeneratorFrame", courseGeneratorFrame, true) +end + +function CpCourseGeneratorFrame.createFromExistingGui(gui, guiName) + local newGui = CpCourseGeneratorFrame.new(nil, nil) + + g_gui.frames[gui.name].target:delete() + g_gui.frames[gui.name]:delete() + g_gui:loadGui(gui.xmlFilename, guiName, newGui, true) + + return newGui +end + +function CpCourseGeneratorFrame:initialize() + self.booleanPrefab:unlinkElement() + FocusManager:removeElement(self.booleanPrefab) + self.multiTextPrefab:unlinkElement() + FocusManager:removeElement(self.multiTextPrefab) + self.sectionHeaderPrefab:unlinkElement() + FocusManager:removeElement(self.sectionHeaderPrefab) + self.selectorPrefab:unlinkElement() + FocusManager:removeElement(self.selectorPrefab) + self.containerPrefab:unlinkElement() + FocusManager:removeElement(self.containerPrefab) + + for key = 1, CpCourseGeneratorFrame.NUM_CATEGORIES do + self.subCategoryPaging:addText(tostring(key)) + self.subCategoryPages[key] = self.containerPrefab:clone(self) + self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" + FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) + self.subCategoryTabs[key] = self.selectorPrefab:clone(self.subCategoryBox) + FocusManager:loadElementFromCustomValues(self.subCategoryTabs[key]) + self.subCategoryBox:invalidateLayout() + self.subCategoryTabs[key]:setText(g_i18n:getText(self.CATEGRORY_TEXTS[key])) + self.subCategoryTabs[key]:getDescendantByName("background"):setSize( + self.subCategoryTabs[key].size[1], self.subCategoryTabs[key].size[2]) + self.subCategoryTabs[key].onClickCallback = function () + self:updateSubCategoryPages(key) + end + end +end + +function CpCourseGeneratorFrame:onFrameOpen() + local vehicle = CpUtil.getCurrentVehicle() + if not vehicle then + return + end + local settings = vehicle:getCourseGeneratorSettings() + local settingsBySubTitle, pageTitle = CpCourseGeneratorSettings.getSettingSetup() + local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) + self.categoryHeaderText:setText(title) + + local layout = self.subCategoryPages[1]:getDescendantByName("layout") + for i = #layout.elements, 1, -1 do + layout.elements[i]:delete() + end + CpSettingsUtil.generateAndBindGuiElementsToSettings(settingsBySubTitle, + layout, self.multiTextPrefab, self.booleanPrefab, + self.sectionHeaderPrefab, settings) + CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) + + self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) + FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) +end + +function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) + local vehicle = CpUtil.getCurrentVehicle() + CpSettingsUtil.updateGuiElementsBoundToSettings( + self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout"), vehicle) +end + +function CpCourseGeneratorFrame:updateSubCategoryPages(state) + for i, _ in ipairs(self.subCategoryPages) do + self.subCategoryPages[i]:setVisible(false) + self.subCategoryTabs[i]:setSelected(false) + end + self.subCategoryPages[state]:setVisible(true) + self.subCategoryTabs[state]:setSelected(true) + self.subCategoryPages[state]:getDescendantByName("layout"):invalidateLayout() + self.settingsSlider:setDataElement(self.subCategoryPages[state]:getDescendantByName("layout")) +end + +function CpCourseGeneratorFrame:onDrawPostIngameMapHotspots() + +end \ No newline at end of file diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua new file mode 100644 index 000000000..8fe599ddd --- /dev/null +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -0,0 +1,109 @@ +--[[ + This frame is a page for all global settings in the in game menu. + All the layout, gui elements are cloned from the general settings page of the in game menu. +]]-- + +CpGlobalSettingsFrame = { + CATEGRORIES = { + BASIC_SETTINGS = 1, + USER_SETTINGS = 2, + DEBUG_SETTINGS = 3 + }, + CATEGRORY_TEXTS = { + "CP_global_setting_subTitle_general", + "CP_global_setting_subTitle_userSettings", + "TODO: Debug" + } +} +CpGlobalSettingsFrame.NUM_CATEGORIES = #CpGlobalSettingsFrame.CATEGRORY_TEXTS + +local CpGlobalSettingsFrame_mt = Class(CpGlobalSettingsFrame, TabbedMenuFrameElement) + +function CpGlobalSettingsFrame.new(target, custom_mt) + local self = TabbedMenuFrameElement.new(target, custom_mt or CpGlobalSettingsFrame_mt) + self.subCategoryPages = {} + self.subCategoryTabs = {} + return self +end + +function CpGlobalSettingsFrame.setupGui() + local globalSettingsFrame = CpGlobalSettingsFrame.new() + g_gui:loadGui(Utils.getFilename("config/gui/pages/GlobalSettingsFrame.xml", Courseplay.BASE_DIRECTORY), + "CpGlobalSettingsFrame", globalSettingsFrame, true) +end + +function CpGlobalSettingsFrame.createFromExistingGui(gui, guiName) + local newGui = CpGlobalSettingsFrame.new(nil, nil) + + g_gui.frames[gui.name].target:delete() + g_gui.frames[gui.name]:delete() + g_gui:loadGui(gui.xmlFilename, guiName, newGui, true) + + return newGui +end + +function CpGlobalSettingsFrame:initialize() + + self.booleanPrefab:unlinkElement() + FocusManager:removeElement(self.booleanPrefab) + self.multiTextPrefab:unlinkElement() + FocusManager:removeElement(self.multiTextPrefab) + self.sectionHeaderPrefab:unlinkElement() + FocusManager:removeElement(self.sectionHeaderPrefab) + self.selectorPrefab:unlinkElement() + FocusManager:removeElement(self.selectorPrefab) + self.containerPrefab:unlinkElement() + FocusManager:removeElement(self.containerPrefab) + for key = 1, CpGlobalSettingsFrame.NUM_CATEGORIES do + self.subCategoryPaging:addText(tostring(key)) + self.subCategoryPages[key] = self.containerPrefab:clone(self) + self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" + FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) + self.subCategoryTabs[key] = self.selectorPrefab:clone(self.subCategoryBox) + FocusManager:loadElementFromCustomValues(self.subCategoryTabs[key]) + self.subCategoryBox:invalidateLayout() + self.subCategoryTabs[key]:setText(g_i18n:getText(self.CATEGRORY_TEXTS[key])) + self.subCategoryTabs[key]:getDescendantByName("background"):setSize( + self.subCategoryTabs[key].size[1], self.subCategoryTabs[key].size[2]) + self.subCategoryTabs[key].onClickCallback = function () + self:updateSubCategoryPages(key) + end + end + + local settings = g_Courseplay.globalSettings:getSettings() + local settingsBySubTitle, pageTitle = g_Courseplay.globalSettings:getSettingSetup() + self.categoryHeaderText:setText(g_i18n:getText(pageTitle)) + + local ix = 1 + for _, data in pairs(settingsBySubTitle) do + CpSettingsUtil.generateAndBindGuiElements(data, + self.subCategoryPages[ix]:getDescendantByName("layout"), + self.multiTextPrefab, self.booleanPrefab, settings) + CpSettingsUtil.updateGuiElementsBoundToSettings( + self.subCategoryPages[ix]:getDescendantByName("layout")) + if ix >= 2 then + break + end + ix = ix + 1 + end +end + +function CpGlobalSettingsFrame:onFrameOpen() + self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) +end + +function CpGlobalSettingsFrame:onClickCpMultiTextOption(_, guiElement) + CpSettingsUtil.updateGuiElementsBoundToSettings( + self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout")) +end + +function CpGlobalSettingsFrame:updateSubCategoryPages(state) + for i, _ in ipairs(self.subCategoryPages) do + self.subCategoryPages[i]:setVisible(false) + self.subCategoryTabs[i]:setSelected(false) + end + self.subCategoryPages[state]:setVisible(true) + self.subCategoryTabs[state]:setSelected(true) + self.settingsSlider:setDataElement(self.subCategoryPages[state]:getDescendantByName("layout")) + FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) +end diff --git a/scripts/gui/pages/CpVehicleSettingsFrame.lua b/scripts/gui/pages/CpVehicleSettingsFrame.lua new file mode 100644 index 000000000..3b681e5f6 --- /dev/null +++ b/scripts/gui/pages/CpVehicleSettingsFrame.lua @@ -0,0 +1,111 @@ +--[[ + This frame is a page for all global settings in the in game menu. + All the layout, gui elements are cloned from the general settings page of the in game menu. +]]-- + +CpVehicleSettingsFrame = { + CATEGRORIES = { + BASIC_SETTINGS = 1, + DEBUG_SETTINGS = 2 + }, + CATEGRORY_TEXTS = { + "CP_global_setting_subTitle_general", + "TODO: Debug" + } +} +CpVehicleSettingsFrame.NUM_CATEGORIES = #CpVehicleSettingsFrame.CATEGRORY_TEXTS + +local CpVehicleSettingsFrame_mt = Class(CpVehicleSettingsFrame, TabbedMenuFrameElement) + +function CpVehicleSettingsFrame.new(target, custom_mt) + local self = TabbedMenuFrameElement.new(target, custom_mt or CpVehicleSettingsFrame_mt) + self.subCategoryPages = {} + self.subCategoryTabs = {} + return self +end + +function CpVehicleSettingsFrame.setupGui() + local vehicleSettingsFrame = CpVehicleSettingsFrame.new() + g_gui:loadGui(Utils.getFilename("config/gui/pages/VehicleSettingsFrame.xml", Courseplay.BASE_DIRECTORY), + "CpVehicleSettingsFrame", vehicleSettingsFrame, true) +end + +function CpVehicleSettingsFrame.createFromExistingGui(gui, guiName) + local newGui = CpVehicleSettingsFrame.new(nil, nil) + + g_gui.frames[gui.name].target:delete() + g_gui.frames[gui.name]:delete() + g_gui:loadGui(gui.xmlFilename, guiName, newGui, true) + + return newGui +end + +function CpVehicleSettingsFrame:initialize() + + self.booleanPrefab:unlinkElement() + FocusManager:removeElement(self.booleanPrefab) + self.multiTextPrefab:unlinkElement() + FocusManager:removeElement(self.multiTextPrefab) + self.sectionHeaderPrefab:unlinkElement() + FocusManager:removeElement(self.sectionHeaderPrefab) + self.selectorPrefab:unlinkElement() + FocusManager:removeElement(self.selectorPrefab) + self.containerPrefab:unlinkElement() + FocusManager:removeElement(self.containerPrefab) + + for key = 1, CpVehicleSettingsFrame.NUM_CATEGORIES do + self.subCategoryPaging:addText(tostring(key)) + self.subCategoryPages[key] = self.containerPrefab:clone(self) + self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" + FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) + self.subCategoryTabs[key] = self.selectorPrefab:clone(self.subCategoryBox) + FocusManager:loadElementFromCustomValues(self.subCategoryTabs[key]) + self.subCategoryBox:invalidateLayout() + + self.subCategoryTabs[key]:setText(g_i18n:getText(self.CATEGRORY_TEXTS[key])) + self.subCategoryTabs[key]:getDescendantByName("background"):setSize( + self.subCategoryTabs[key].size[1], self.subCategoryTabs[key].size[2]) + self.subCategoryTabs[key].onClickCallback = function () + self:updateSubCategoryPages(key) + end + end +end + +function CpVehicleSettingsFrame:onFrameOpen() + local vehicle = CpUtil.getCurrentVehicle() + if not vehicle then + return + end + local settings = vehicle:getCpSettings() + local settingsBySubTitle, pageTitle = CpVehicleSettings.getSettingSetup() + local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) + self.categoryHeaderText:setText(title) + + local layout = self.subCategoryPages[1]:getDescendantByName("layout") + for i = #layout.elements, 1, -1 do + layout.elements[i]:delete() + end + CpSettingsUtil.generateAndBindGuiElementsToSettings(settingsBySubTitle, + layout, self.multiTextPrefab, self.booleanPrefab, + self.sectionHeaderPrefab, settings) + CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) + + self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) + FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) +end + +function CpVehicleSettingsFrame:onClickCpMultiTextOption(_, guiElement) + local vehicle = CpUtil.getCurrentVehicle() + CpSettingsUtil.updateGuiElementsBoundToSettings( + self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout"), vehicle) +end + +function CpVehicleSettingsFrame:updateSubCategoryPages(state) + for i, _ in ipairs(self.subCategoryPages) do + self.subCategoryPages[i]:setVisible(false) + self.subCategoryTabs[i]:setSelected(false) + end + self.subCategoryPages[state]:setVisible(true) + self.subCategoryTabs[state]:setSelected(true) + self.settingsSlider:setDataElement(self.subCategoryPages[state]:getDescendantByName("layout")) +end diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua index c3f5f157b..def1f871b 100644 --- a/scripts/specializations/CpCourseGeneratorSettings.lua +++ b/scripts/specializations/CpCourseGeneratorSettings.lua @@ -194,11 +194,9 @@ function CpCourseGeneratorSettings.loadSettingsSetup() CpSettingsUtil.loadSettingsFromSetup(CpCourseGeneratorSettings.vineSettings,filePath) end -function CpCourseGeneratorSettings.getSettingSetup(vehicle) - local title = g_i18n:getText(CpCourseGeneratorSettings.pageTitle) +function CpCourseGeneratorSettings.getSettingSetup() return CpCourseGeneratorSettings.settingsBySubTitle, - vehicle and string.format(title, vehicle:getName()) - or title + CpCourseGeneratorSettings.pageTitle end function CpCourseGeneratorSettings.getVineSettingSetup(vehicle) From 4ca1d453066e8f7759ed3acd0a2da9c74aebbde4 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 17 Nov 2024 12:02:55 +0100 Subject: [PATCH 011/158] =?UTF-8?q?WIP=20Coursemanager=20Men=C3=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Courseplay.lua | 17 +- config/gui/CourseManagerFrame.xml | 48 -- config/gui/CpInGameMenu.xml | 1 + config/gui/pages/CourseManagerFrame.xml | 517 ++++++++++++++++++ modDesc.xml | 3 +- scripts/gui/CpGuiUtil.lua | 22 +- scripts/gui/CpInGameMenu.lua | 63 ++- .../gui/{ => pages}/CpCourseManagerFrame.lua | 76 +-- 8 files changed, 622 insertions(+), 125 deletions(-) delete mode 100644 config/gui/CourseManagerFrame.xml create mode 100644 config/gui/pages/CourseManagerFrame.xml rename scripts/gui/{ => pages}/CpCourseManagerFrame.lua (92%) diff --git a/Courseplay.lua b/Courseplay.lua index 45c07662f..6d7c8e7db 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -127,23 +127,9 @@ function Courseplay:deleteMap() end function Courseplay:setupGui() - CpInGameMenu.setupGui() + CpInGameMenu.setupGui(self.courseStorage) ---[[ TODO 25 - CpGuiUtil.fixInGameMenuPage(vehicleSettingsFrame, "pageCpVehicleSettings", - {896, 0, 128, 128}, pos + 1, predicateFunc) -]] - - -- CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings", - -- {768, 0, 128, 128}, pos + 1, function () return true end) - -- CpGuiUtil.fixInGameMenuPage(globalSettingsFrame, "pageCpGlobalSettings", - -- {768, 0, 128, 128}, pos + 1, function () return true end) - ---[[ TODO 25 - CpGuiUtil.fixInGameMenuPage(courseManagerFrame, "pageCpCourseManager", - {256, 0, 128, 128}, pos + 1, predicateFunc) -]] self.infoTextsHud = CpHudInfoTexts() -- TODO_25 @@ -275,7 +261,6 @@ function Courseplay:load() CpAIMessages.register() g_vineScanner:setup() end - ------------------------------------------------------------------------------------------------------------------------ -- Player action events ------------------------------------------------------------------------------------------------------------------------ diff --git a/config/gui/CourseManagerFrame.xml b/config/gui/CourseManagerFrame.xml deleted file mode 100644 index faa11c024..000000000 --- a/config/gui/CourseManagerFrame.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no" ?> -<GUI name="cpCourseManager"> - <GuiElement type="empty" profile="uiInGameMenuFrame"> - <GuiElement type="empty" profile="ingameMenuFrameHeaderPanel"> - <GuiElement type="bitmap" profile="ingameMenuPriceHeaderIcon" /> - <GuiElement type="text" profile="ingameMenuFrameHeaderText" id="header"/> - </GuiElement> - - <GuiElement type="empty" profile="ingameMenuSettingsBox" id="mainBox"> - - <GuiElement type="empty" profile="ingameMenuPriceLeftColumn" id="leftColumn"> - <GuiElement type="boxLayout" profile="fs25_calendarHeaderBox" id="tableHeaderBox" size="680px 104px"> - <GuiElement type="text" profile="ingameMenuPriceHeader" size="340px 104px" id="leftColumnHeader" /> - </GuiElement> - - <GuiElement type="smoothList" profile="fs25_pricesPriceList" id="leftList" focusChangeTop="nil" focusChangeBottom="nil" selectedWithoutFocus="true"> - <GuiElement type="listItem" profile="fs25_pricesPriceListItem"> - <GuiElement type="bitmap" name="icon" profile="ingameMenuPriceGoodsIcon" /> - <GuiElement type="text" name="title" profile="ingameMenuPriceItemTitle" /> - </GuiElement> - </GuiElement> - - <GuiElement type="threePartBitmap" profile="verticalListSliderRightDocked" size="12px 652px" position="28px -104px"> - <GuiElement type="slider" profile="verticalListSliderBar" size="6px 644px" dataElementId="leftList" id="leftListSlider" /> - </GuiElement> - </GuiElement> - - <GuiElement type="empty" profile="ingameMenuPriceRightColumn" id="rightColumn"> - <GuiElement type="boxLayout" profile="fs25_calendarHeaderBox" id="tableHeaderBox" size="680px 104px"> - <GuiElement type="text" profile="ingameMenuPriceHeader" size="330px 104px" id="rightColumnHeader" /> - </GuiElement> - - <GuiElement type="smoothList" profile="fs25_pricesPriceList" id="rightList" focusChangeTop="nil" focusChangeBottom="nil"> - <GuiElement type="listItem" profile="fs25_pricesPriceListItem"> - <GuiElement type="bitmap" name="icon" profile="fs25_pricesPriceListItemHotspot" /> - <GuiElement type="text" name="title" profile="ingameMenuPriceSellpointTitle" /> - </GuiElement> - </GuiElement> - - <GuiElement type="threePartBitmap" profile="verticalListSliderRightDocked" size="12px 652px" position="28px -104px"> - <GuiElement type="slider" profile="verticalListSliderBar" size="6px 644px" dataElementId="rightList" id="rightListSlider" /> - </GuiElement> - </GuiElement> - - - </GuiElement> - </GuiElement> -</GUI> diff --git a/config/gui/CpInGameMenu.xml b/config/gui/CpInGameMenu.xml index acab99879..42c5dc03c 100644 --- a/config/gui/CpInGameMenu.xml +++ b/config/gui/CpInGameMenu.xml @@ -6,6 +6,7 @@ <FrameReference ref="cpInGameMenuGlobalSettings" name="cpInGameMenuGlobalSettings" id="pageGlobalSettings" /> <FrameReference ref="cpInGameMenuVehicleSettings" name="cpInGameMenuVehicleSettings" id="pageVehicleSettings" /> <FrameReference ref="cpInGameMenuCourseGenerator" name="cpInGameMenuCourseGenerator" id="pageCourseGenerator" /> + <FrameReference ref="cpInGameMenuCourseManager" name="cpInGameMenuCourseManager" id="pageCourseManager"/> </Paging> <Bitmap profile="fs25_tabListContainer" id="header"> <MultiTextOption profile="uiInGameMenuHeaderSelector" onClick="onClickPageSelection" diff --git a/config/gui/pages/CourseManagerFrame.xml b/config/gui/pages/CourseManagerFrame.xml new file mode 100644 index 000000000..5713ad86e --- /dev/null +++ b/config/gui/pages/CourseManagerFrame.xml @@ -0,0 +1,517 @@ +<?xml version="1.0" encoding="utf-8" standalone="no" ?> +<GUI name="cpInGameMenuCourseManager"> + <GuiElement profile="fs25_menuContainer"> + <GuiElement profile="fs25_menuHeaderPanel"> + <Bitmap profile="fs25_menuHeaderIconBg"> + <Bitmap profile="fs25_menuHeaderIcon" id="categoryHeaderIcon" /> + </Bitmap> + <Text profile="fs25_menuHeaderTitle" id="categoryHeaderText" /> + </GuiElement> + <BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"> + <Button profile="fs25_subCategorySelectorTabbedTab" id="subCategoryTabs[1]" + text="TODO"> + <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> + </Button> + <Button profile="fs25_subCategorySelectorTabbedTab" id="subCategoryTabs[2]" + text="TODO"> + <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> + </Button> + </BoxLayout> + <ThreePartBitmap profile="fs25_lineSeparatorTopHighlighted" position="0px -57px" /> + <MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" + position="0px -10px" /> + <GuiElement profile="fs25_subCategorySelectorTabbedContainer" id="subCategoryPages[1]"> + <GuiElement profile="fs25_statisticsHeaderBox"> + <Text profile="fs25_statisticsHeaderText" id="leftColumnHeader" /> + <Text profile="fs25_statisticsHeaderText" text="..." + position="116px 0px" textAlignment="right" /> + <Text profile="fs25_statisticsHeaderText" text="..." + position="456px 0px" /> + <Text profile="fs25_statisticsHeaderText" text="..." + position="877px 0px" /> + <Bitmap profile="fs25_statisticsHeaderIcon" position="1144px 0px" /> + <Text profile="fs25_statisticsHeaderText" text="..." position="1134px 0px" /> + <Bitmap profile="fs25_statisticsHeaderIcon" position="1390px 0px" + imageSliceId="gui.prices_selling" /> + <Text profile="fs25_statisticsHeaderText" + position="1376px 0px" id="rightColumnHeader"/> + <ThreePartBitmap profile="fs25_lineSeparatorBottom" position="0px -2dp" /> + </GuiElement> + <GuiElement id="leftColumn"> + <!-- <BoxLayout profile="fs25_statisticsHeaderBox" id="tableHeaderBox" size="680px 104px"> + <Text profile="fs25_statisticsHeaderText" size="340px 104px" id="leftColumnHeader" /> + </BoxLayout> --> + + <SmoothList profile="fs25_pricesProductList" id="leftList" focusChangeTop="nil" focusChangeBottom="nil" selectedWithoutFocus="true"> + <ListItem profile="fs25_pricesProductListItem"> + <Bitmap name="icon" profile="fs25_pricesProductListItemIcon" /> + <Text name="title" profile="fs25_pricesProductListItemName" /> + </ListItem> + </SmoothList> + + <ThreePartBitmap profile="fs25_pricesProductListSliderBox"> + <Slider profile="fs25_listSlider" dataElementId="leftList" id="leftListSlider" /> + </ThreePartBitmap> + </GuiElement> + + <GuiElement id="rightColumn"> + <!-- <BoxLayout profile="fs25_statisticsHeaderBox" id="tableHeaderBox" size="680px 104px"> + <Text profile="fs25_statisticsHeaderText" size="330px 104px" id="rightColumnHeader" /> + </BoxLayout> --> + + <SmoothList profile="fs25_pricesPriceList" id="rightList" focusChangeTop="nil" focusChangeBottom="nil"> + <ListItem profile="fs25_pricesPriceListItem"> + <Bitmap name="icon" profile="fs25_pricesPriceListItemHotspot" /> + <Text name="title" profile="fs25_pricesPriceListItemName" /> + </ListItem> + </SmoothList> + + <ThreePartBitmap profile="fs25_pricesPriceSliderBox"> + <Slider profile="fs25_listSlider" dataElementId="rightList" id="rightListSlider" /> + </ThreePartBitmap> + </GuiElement> + </GuiElement> + </GuiElement> + <GUIProfiles> + <Profile name="fs25_statisticsHeaderBox" extends="emptyPanel" with="anchorTopStretchingX"> + <height value="32px" /> + </Profile> + <Profile name="fs25_statisticsHeaderText" extends="fs25_textDefault" + with="anchorStretchingYLeft"> + <textSize value="16px" /> + <textBold value="true" /> + <textUpperCase value="true" /> + <textOffset value="40px 0px" /> + <textColor value="$preset_colorWhite_50" /> + </Profile> + <Profile name="fs25_statisticsHeaderName" extends="fs25_statisticsHeaderText" + with="anchorStretchingYLeft"> + <width value="500px" /> + </Profile> + <Profile name="fs25_statisticsHeaderIcon" extends="baseReference" with="anchorMiddleLeft"> + <size value="20px 20px" /> + <imageColor value="$preset_colorWhite_50" /> + <imageSliceId value="gui.prices_buying" /> + </Profile> + <Profile name="fs25_pricesProductList" extends="emptyPanel" + with="anchorStretchingYLeft pivotTopLeft"> + <width value="396px" /> + <absoluteSizeOffset value="0px 56px" /> + <position value="25px -56px" /> + </Profile> + <Profile name="fs25_pricesProductListItem" extends="baseReference" + with="anchorTopStretchingX alternating"> + <height value="32px" /> + <imageColor value="$preset_fs25_colorGrey" /> + <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <alternateBackgroundColor value="$preset_fs25_colorGreyDark_50" /> + </Profile> + <Profile name="fs25_pricesProductListItemIcon" extends="baseReference" + with="anchorMiddleLeft"> + <size value="24px 24px" /> + <position value="8px 0px" /> + <imageSliceId value="noSlice" /> + </Profile> + <Profile name="fs25_pricesProductListItemName" extends="fs25_textDarkHighlight" + with="anchorStretchingYLeft"> + <textBold value="true" /> + <textOffset value="40px 0px" /> + </Profile> + <Profile name="fs25_pricesProductListItemStorage" extends="fs25_textDarkHighlight" + with="anchorStretchingYRight"> + <textOffset value="-10px 0px" /> + <textAlignment value="right" /> + </Profile> + <Profile name="fs25_pricesProductListSliderBox" extends="fs25_listSliderBox" + with="anchorTopLeft"> + <absoluteSizeOffset value="0px 56px" /> + <position value="434px -56px" /> + </Profile> + <Profile name="fs25_pricesPriceList" extends="emptyPanel" with="anchorTopRight"> + <size value="1025px 240px" /> + <absoluteSizeOffset value="0px 56px" /> + <position value="-63px -56px" /> + <selectedWithoutFocus value="false" /> + </Profile> + <Profile name="fs25_pricesPriceListItem" extends="baseReference" + with="anchorTopStretchingX alternating"> + <height value="32px" /> + <imageColor value="$preset_fs25_colorGrey" /> + <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <alternateBackgroundColor value="$preset_fs25_colorGreyDark_50" /> + </Profile> + <Profile name="fs25_pricesPriceListItemHotspot" extends="baseReference" + with="anchorMiddleLeft"> + <size value="24px 24px" /> + <position value="10px 0px" /> + <imageSliceId value="gui.visit" /> + <imageColor value="$preset_colorWhite_08" /> + <imageSelectedColor value="$preset_colorWhite" /> + </Profile> + <Profile name="fs25_pricesPriceListItemName" extends="fs25_textDarkHighlight" + with="anchorStretchingYLeft"> + <position value="40px 0px" /> + <textBold value="true" /> + <textAutoWidth value="true" /> + <textMaxWidth value="370px" /> + <textLayoutMode value="scrolling" /> + </Profile> + <Profile name="fs25_pricesPriceListItemIconTrain" extends="baseReference" + with="anchorMiddleRight"> + <size value="20px 20px" /> + <position value="30px 0px" /> + <imageSliceId value="gui.sellingStation_train" /> + </Profile> + <Profile name="fs25_pricesPriceListItemIconPallet" + extends="fs25_pricesPriceListItemIconTrain"> + <imageSliceId value="gui.sellingStation_pallet" /> + </Profile> + <Profile name="fs25_pricesPriceListItemInfo" extends="fs25_textDarkHighlight" + with="anchorStretchingYRight"> + <position value="-62px 0px" /> + <format value="currency" /> + <formatDecimalPlaces value="0" /> + </Profile> + <Profile name="fs25_pricesPriceSliderBox" extends="fs25_listSliderBox" with="anchorTopRight"> + <height value="240px" /> + <position value="-41px -56px" /> + </Profile> + <Profile name="fs25_pricesNoSellpointsText" extends="textDefault" with="anchorTopCenter"> + <position value="200px -150px" /> + <textSize value="20px" /> + <textAlignment value="center" /> + <textAutoWidth value="true" /> + <textMaxNumLines value="2" /> + <textMaxWidth value="450px" /> + </Profile> + <Profile name="fs25_pricesPriceListArrow" extends="baseReference" with="anchorMiddleRight"> + <size value="18px 18px" /> + <position value="-83px 0px" /> + <imageSliceId value="gui.priceTrend" /> + <imageColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_pricesPriceListArrowClimbing" extends="fs25_pricesPriceListArrow"> + <imageColor value="$preset_colorGreen" /> + <imageRotation value="0" /> + </Profile> + <Profile name="fs25_pricesPriceListArrowFalling" extends="fs25_pricesPriceListArrow"> + <imageColor value="$preset_colorRed" /> + <imageInvertY value="true" /> + </Profile> + <Profile name="fs25_pricesPriceListArrowGreatDemand" extends="fs25_pricesPriceListArrow"> + <imageColor value="$preset_colorBlue" /> + <imageSelectedColor value="$preset_colorWhite" /> + <imageRotation value="90" /> + </Profile> + <Profile name="fs25_pricesFluctuationsLayoutBg" extends="emptyPanel" + with="anchorBottomRight"> + <size value="1045px 405px" /> + <position value="-43px 0px" /> + <handleFocus value="false" /> + </Profile> + <Profile name="fs25_pricesSeparator" extends="baseReference" with="anchorTopLeft"> + <size value="1dp 375px" /> + <margin value="0px 30px 0px 0px" /> + <imageColor value="$preset_fs25_colorGreyLight_50" /> + </Profile> + <Profile name="fs25_pricesMonthText" extends="fs25_textDefault" with="anchorTopLeft"> + <width value="86px" /> + <margin value="0px 5px 0px 0px" /> + <textBold value="true" /> + <textUpperCase value="true" /> + <textAlignment value="center" /> + <textColor value="$preset_colorWhite_50" /> + </Profile> + <Profile name="fs25_pricesFluctuationsContainer" extends="emptyPanel" + with="anchorBottomRight"> + <size value="1045px 375px" /> + <position value="-43px 0px" /> + </Profile> + <Profile name="fs25_pricesFluctuationsTodayBar" extends="emptyPanel" + with="anchorStretchingYLeft"> + <width value="2dp" /> + </Profile> + <Profile name="fs25_pricesFluctuationsPrice" extends="fs25_textDefault"> + <position value="0px 20px" /> + <textAlignment value="center" /> + <format value="currency" /> + <formatDecimalPlaces value="0" /> + </Profile> + <Profile name="fs25_pricesFluctuationsPriceBg" extends="baseReference" + with="anchorMiddleCenter"> + <size value="70px 25px" /> + <position value="0px -1px" /> + <imageColor value="$preset_fs25_colorGrey" /> + <imageSliceId value="gui.button_middle" /> + <startImageSize value="6px 0" /> + <startImageSliceId value="gui.button_left" /> + <endImageSize value="6px 0" /> + <endImageSliceId value="gui.button_right" /> + </Profile> + <Profile name="fs25_vehiclesHeaderName" extends="fs25_statisticsHeaderText"> + <size value="450px 32px" /> + <imageColor value="0 0 0 0" /> + <textFocusedColor value="$preset_fs25_colorMainHighlight" /> + <textHighlightedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="fs25_vehiclesHeaderTextContainer" extends="emptyPanel"> + <size value="180px 32px" /> + </Profile> + <Profile name="fs25_vehiclesHeaderText" extends="fs25_vehiclesHeaderName" + with="anchorMiddleCenter"> + <size value="160px 32px" /> + <textOffset value="0px 0px" /> + <textAutoWidth value="true" /> + <textAlignment value="center" /> + </Profile> + <Profile name="fs25_vehiclesHeaderIconSortAscending" extends="baseReference" + with="anchorMiddleLeft"> + <size value="7px 7px" /> + <position value="-15.5px 0px" /> + <imageColor value="$preset_colorWhite" /> + <imageSliceId value="gui.tourdialogue_arrow" /> + <imageFocusedColor value="$preset_fs25_colorMainHighlight" /> + <imageHighlightedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="fs25_vehiclesHeaderIconSortDescending" + extends="fs25_vehiclesHeaderIconSortAscending"> + <imageRotation value="180" /> + </Profile> + <Profile name="fs25_vehiclesList" extends="emptyPanel" + with="anchorStretchingYStretchingX pivotTopCenter"> + <position value="0px -34px" /> + <itemizedScrollDelta value="3" /> + <absoluteSizeOffset value="0px 249px" /> + </Profile> + <Profile name="fs25_vehiclesListItem" extends="baseReference" with="anchorTopStretchingX"> + <height value="45px" /> + <alternateChildren value="true" /> + <imageSliceId value="gui.empty" /> + <imageColor value="$preset_fs25_colorGreyListItem" /> + <alternateBackgroundColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_vehiclesListItemBg" extends="baseReference" + with="anchorStretchingYStretchingX"> + <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <startImageSize value="100px 30px" /> + <startImageSliceId value="gui.list_gradient" /> + <startImageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <endImageSize value="100px 30px" /> + <endImageSliceId value="gui.list_gradient" /> + <endImageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <endImageInvertX value="true" /> + </Profile> + <Profile name="fs25_vehiclesListItemName" extends="fs25_textDarkHighlight" + with="anchorStretchingYLeft"> + <width value="450px" /> + <textSize value="18px" /> + <textBold value="true" /> + <textOffset value="40px 0px" /> + <Variant name="mobile"> + <size value="748px 104px" /> + <textSize value="33px" /> + </Variant> + </Profile> + <Profile name="fs25_vehiclesListItemInfo" extends="fs25_vehiclesListItemName" + with="anchorStretchingYLeft"> + <width value="200px" /> + <position value="-100px 0px" /> + <textBold value="false" /> + <textAlignment value="center" /> + <textOffset value="0px 0px" /> + </Profile> + <Profile name="fs25_vehiclesListItemInfoLarge" extends="fs25_vehiclesListItemInfo"> + <width value="500px" /> + <textAlignment value="left" /> + </Profile> + <Profile name="fs25_vehiclesDetailsImageBg" extends="baseReference"> + <size value="250px 250px" /> + <position value="0px -35px" /> + <imageSliceId value="gui.shopMods" /> + </Profile> + <Profile name="fs25_vehiclesDetailsImage" extends="baseReference" with="anchorMiddleCenter"> + <size value="210px 210px" /> + <imageSliceId value="noSlice" /> + </Profile> + <Profile name="fs25_vehiclesDetailsName" extends="fs25_textDefault" with="anchorTopLeft"> + <size value="100% 35px" /> + <absoluteSizeOffset value="270px 0px" /> + <position value="280px -30px" /> + <textSize value="32px" /> + <textBold value="true" /> + <textUpperCase value="true" /> + <textVerticalAlignment value="top" /> + </Profile> + <Profile name="fs25_vehiclesDetailsInfoBox" extends="emptyPanel" with="anchorTopLeft"> + <size value="100% 140px" /> + <absoluteSizeOffset value="300px 0px" /> + <position value="280px -75px" /> + <numFlows value="3" /> + <fitFlowToElements value="true" /> + </Profile> + <Profile name="fs25_financesHeaderToday" extends="fs25_statisticsHeaderName" + with="anchorStretchingYRight"> + <width value="190px" /> + <position value="-100px 0px" /> + <textSize value="16px" /> + <textAlignment value="right" /> + <textOffset value="-20px 0px" /> + <Variant name="mobile"> + <textOffset value="-30px 0px" /> + <textSize value="33px" /> + <size value="300px 100px" /> + </Variant> + </Profile> + <Profile name="fs25_financesHeaderTodayMinusOne" extends="fs25_financesHeaderToday"> + <position value="-290px 0px" /> + <Variant name="mobile"> + <position value="-300px 0px" /> + </Variant> + </Profile> + <Profile name="fs25_financesHeaderTodayMinusTwo" extends="fs25_financesHeaderToday"> + <position value="-480px 0px" /> + <Variant name="mobile"> + <position value="-600px 0px" /> + </Variant> + </Profile> + <Profile name="fs25_financesHeaderTodayMinusThree" extends="fs25_financesHeaderToday"> + <position value="-670px 0px" /> + <Variant name="mobile"> + <position value="-900px 0px" /> + </Variant> + </Profile> + <Profile name="fs25_financesHeaderTodayMinusFour" extends="fs25_financesHeaderToday"> + <position value="-860px 0px" /> + <Variant name="mobile"> + <visible value="false" /> + </Variant> + </Profile> + <Profile name="fs25_financesList" extends="emptyPanel" + with="anchorStretchingYStretchingX pivotTopCenter"> + <position value="0px -34px" /> + <itemizedScrollDelta value="3" /> + <absoluteSizeOffset value="0px 154px" /> + </Profile> + <Profile name="fs25_financesListItem" extends="baseReference" with="anchorTopStretchingX"> + <height value="30px" /> + <alternateChildren value="true" /> + <imageSliceId value="gui.empty" /> + <imageColor value="$preset_fs25_colorGreyListItem" /> + <alternateBackgroundColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_financesListItemBg" extends="baseReference" + with="anchorStretchingYStretchingX"> + <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <startImageSize value="100px 30px" /> + <startImageSliceId value="gui.list_gradient" /> + <startImageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <endImageSize value="100px 30px" /> + <endImageSliceId value="gui.list_gradient" /> + <endImageSelectedColor value="$preset_fs25_colorMainHighlight" /> + <endImageInvertX value="true" /> + </Profile> + <Profile name="fs25_financesListItemName" extends="fs25_textDarkHighlight" + with="anchorStretchingYLeft"> + <width value="500px" /> + <textSize value="18px" /> + <textBold value="true" /> + <textOffset value="40px 0px" /> + <Variant name="mobile"> + <size value="748px 104px" /> + <textSize value="33px" /> + </Variant> + </Profile> + <Profile name="fs25_financesListItemToday" extends="fs25_financesListItemName" + with="anchorStretchingYRight"> + <width value="190px" /> + <position value="-100px 0px" /> + <textBold value="false" /> + <textAlignment value="right" /> + <textOffset value="-20px 0px" /> + </Profile> + <Profile name="fs25_financesFooterBox" extends="baseReference" with="anchorTopStretchingX"> + <size value="100% 122px" /> + <position value="0px -630px" /> + <Variant name="mobile"> + <hasFrame value="false" /> + <size value="1768px 180px" /> + <position value="0px -500px" /> + </Variant> + </Profile> + <Profile name="fs25_financesTotalContainer" extends="fs25_financesListItem" + with="anchorTopLeft"> + <position value="0px -10px" /> + <Variant name="mobile"> + <position value="0px -20px" /> + </Variant> + </Profile> + <Profile name="fs25_financesBalanceContainer" extends="fs25_financesListItem" + with="anchorTopLeft"> + <position value="0px -40px" /> + </Profile> + <Profile name="fs25_financesLoanContainer" extends="fs25_financesListItem" + with="anchorTopLeft"> + <position value="0px -70px" /> + </Profile> + <Profile name="fs25_financesTotalName" extends="fs25_financesListItemName"> + <textSize value="20px" /> + <textBold value="true" /> + <textUpperCase value="true" /> + </Profile> + <Profile name="fs25_statisticsTextWhite"> + <textColor value="$preset_colorWhite" /> + <textSelectedColor value="$preset_fs25_colorMainDark" /> + <textFocusedColor value="$preset_colorWhite" /> + <textHighlightedColor value="$preset_colorWhite" /> + </Profile> + <Profile name="fs25_statisticsTextRed"> + <textColor value="$preset_colorRed" /> + <textSelectedColor value="$preset_colorDarkRed" /> + <textFocusedColor value="$preset_colorRed" /> + <textHighlightedColor value="$preset_colorRed" /> + </Profile> + <Profile name="fs25_statisticsHeaderTime" extends="fs25_statisticsHeaderText" + with="anchorStretchingYLeft"> + <width value="190px" /> + <textOffset value="0px 0px" /> + <textAlignment value="right" /> + </Profile> + <Profile name="fs25_statisticsList" extends="emptyPanel" + with="anchorStretchingYStretchingX pivotTopLeft"> + <width value="50%" /> + <absoluteSizeOffset value="0px 55px" /> + <position value="0px -34px" /> + <soundDisabled value="true" /> + </Profile> + <Profile name="fs25_statisticsListItem" extends="baseReference" with="anchorTopStretchingX"> + <height value="45px" /> + <alternateChildren value="true" /> + <imageSliceId value="gui.empty" /> + <imageColor value="$preset_fs25_colorGreyListItem" /> + <alternateBackgroundColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_statisticsListItemBg" extends="baseReference" + with="anchorStretchingYStretchingX"> + <startImageSize value="100px 30px" /> + <startImageSliceId value="gui.list_gradient" /> + <endImageSize value="100px 30px" /> + <endImageSliceId value="gui.list_gradient" /> + <endImageInvertX value="true" /> + </Profile> + <Profile name="fs25_statisticsListItemName" extends="fs25_textDefault" + with="anchorStretchingYLeft"> + <width value="500px" /> + <textSize value="18px" /> + <textBold value="true" /> + <textOffset value="40px 0px" /> + </Profile> + <Profile name="fs25_statisticsListItemValue" extends="fs25_textDefault" + with="anchorStretchingYLeft"> + <width value="200px" /> + <textSize value="18px" /> + <textBold value="false" /> + <textOffset value="0px 0px" /> + <textAlignment value="right" /> + </Profile> + </GUIProfiles> +</GUI> diff --git a/modDesc.xml b/modDesc.xml index 55b3f5d49..b99b41ce9 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -491,12 +491,11 @@ Changelog 7.1.0.0: <sourceFile filename="scripts/gui/CpStatus.lua"/> <!-- <sourceFile filename="scripts/gui/CpAIHotspots.lua"/> <sourceFile filename="scripts/gui/CpAIFrameExtended.lua"/> --> - <sourceFile filename="scripts/gui/CpCourseManagerFrame.lua"/> <sourceFile filename="scripts/gui/CourseDisplay.lua"/> <!-- <sourceFile filename="scripts/gui/CpGamePadHudScreen.lua"/> --> <sourceFile filename="scripts/gui/elements/CpBinaryOptionElement.lua"/> <sourceFile filename="scripts/gui/elements/CpOptionToggleElement.lua"/> - + <sourceFile filename="scripts/gui/pages/CpCourseManagerFrame.lua"/> <sourceFile filename="scripts/gui/pages/CpCourseGeneratorFrame.lua"/> <sourceFile filename="scripts/gui/pages/CpVehicleSettingsFrame.lua"/> <sourceFile filename="scripts/gui/pages/CpGlobalSettingsFrame.lua"/> diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua index 76812eefc..9ef10b013 100644 --- a/scripts/gui/CpGuiUtil.lua +++ b/scripts/gui/CpGuiUtil.lua @@ -448,22 +448,12 @@ function CpGuiUtil.movesMapCenterTo(map, worldX, worldZ) map:moveCenter(-dx, -dy) end -function CpGuiUtil.preOpeningInGameMenu(vehicle) - local inGameMenu = g_currentMission.inGameMenu - inGameMenu.pageAI.hudVehicle = vehicle - if g_gui.currentGuiName ~= "InGameMenu" then - g_gui:showGui("InGameMenu") - end - return inGameMenu -end - function CpGuiUtil.openCourseManagerGui(vehicle) - --- TODO_25 - -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - -- inGameMenu:goToPage(inGameMenu.pageCpCourseManager) + g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_COURSE_MANAGER) end function CpGuiUtil.openCourseGeneratorGui(vehicle) + g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR) --- TODO_25 -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) -- local pageAI = inGameMenu.pageAI @@ -516,15 +506,11 @@ function CpGuiUtil.openCourseGeneratorGui(vehicle) end function CpGuiUtil.openVehicleSettingsGui(vehicle) - --- TODO_25 - -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - -- inGameMenu:goToPage(inGameMenu.pageCpVehicleSettings) + g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_VEHICLE_SETTINGS) end function CpGuiUtil.openGlobalSettingsGui(vehicle) - --- TODO_25 - -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - -- inGameMenu:goToPage(inGameMenu.pageCpGlobalSettings) + g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_GLOBAL_SETTINGS) end CpGuiUtil.UNIT_EXTENSIONS = { diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 56ac5594e..8e37835f9 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -1,17 +1,46 @@ CpInGameMenu = {} local CpInGameMenu_mt = Class(CpInGameMenu, TabbedMenu) -function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager) +function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager, courseStorage) local self = CpInGameMenu:superClass().new(target, customMt or CpInGameMenu_mt, messageCenter, l10n, inputManager) self.messageCenter = messageCenter self.l10n = l10n self.inputManager = inputManager + self.courseStorage = courseStorage - -- self:exposeControlsAsFields(CpInGameMenu.CONTROLS) - self.defaultMenuButtonInfo = {} self.backButtonInfo = {} + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN, function (menu) + g_gui:showGui("CpInGameMenu") + self:changeScreen(CpInGameMenu) + -- local index = self.pagingElement:getPageMappingIndexByElement(self.page) + -- self.pageSelector:setState(pageAIIndex, true) + end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_GLOBAL_SETTINGS, function (menu) + g_gui:showGui("CpInGameMenu") + self:changeScreen(CpInGameMenu) + local index = self.pagingElement:getPageMappingIndexByElement(self.pageGlobalSettings) + self.pageSelector:setState(index, true) + end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_VEHICLE_SETTINGS, function (menu) + g_gui:showGui("CpInGameMenu") + self:changeScreen(CpInGameMenu) + local index = self.pagingElement:getPageMappingIndexByElement(self.pageVehicleSettings) + self.pageSelector:setState(index, true) + end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR, function (menu) + g_gui:showGui("CpInGameMenu") + self:changeScreen(CpInGameMenu) + local index = self.pagingElement:getPageMappingIndexByElement(self.pageCourseGenerator) + self.pageSelector:setState(index, true) + end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_COURSE_MANAGER, function (menu) + g_gui:showGui("CpInGameMenu") + self:changeScreen(CpInGameMenu) + local index = self.pagingElement:getPageMappingIndexByElement(self.pageCourseManager) + self.pageSelector:setState(index, true) + end, self) return self end @@ -20,11 +49,12 @@ function CpInGameMenu.createFromExistingGui(gui, guiName) CpGlobalSettingsFrame.createFromExistingGui(g_gui.frames.cpInGameMenuGlobalSettings.target, "CpGlobalSettingsFrame") CpVehicleSettingsFrame.createFromExistingGui(g_gui.frames.cpInGameMenuVehicleSettings.target, "CpVehicleSettingsFrame") CpCourseGeneratorFrame.createFromExistingGui(g_gui.frames.cpInGameMenuCourseGenerator.target, "CpCourseGeneratorFrame") + CpCourseManagerFrame.createFromExistingGui(g_gui.frames.cpInGameMenuCourseManager.target, "CpCourseManagerFrame") local messageCenter = gui.messageCenter local l10n = gui.l10n local inputManager = gui.inputManager - local newGui = CpInGameMenu.new(nil, nil, messageCenter, l10n, inputManager) + local newGui = CpInGameMenu.new(nil, nil, messageCenter, l10n, inputManager, g_Courseplay.courseStorage) g_gui.guis.CpInGameMenu:delete() g_gui.guis.CpInGameMenu.target:delete() @@ -35,14 +65,22 @@ function CpInGameMenu.createFromExistingGui(gui, guiName) return newGui end -function CpInGameMenu.setupGui() +function CpInGameMenu.setupGui(courseStorage) + + MessageType.GUI_CP_INGAME_OPEN = nextMessageTypeId() + MessageType.GUI_CP_INGAME_OPEN_GLOBAL_SETTINGS = nextMessageTypeId() + MessageType.GUI_CP_INGAME_OPEN_VEHICLE_SETTINGS = nextMessageTypeId() + MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR = nextMessageTypeId() + MessageType.GUI_CP_INGAME_OPEN_COURSE_MANAGER = nextMessageTypeId() + CpCourseGeneratorFrame.setupGui() CpGlobalSettingsFrame.setupGui() CpVehicleSettingsFrame.setupGui() - - local inGameMenu = CpInGameMenu.new(nil, nil, g_messageCenter, g_i18n, g_inputBinding) + CpCourseManagerFrame.setupGui() + + g_cpInGameMenu = CpInGameMenu.new(nil, nil, g_messageCenter, g_i18n, g_inputBinding, courseStorage) g_gui:loadGui(Utils.getFilename("config/gui/CpInGameMenu.xml", Courseplay.BASE_DIRECTORY), - "CpInGameMenu", inGameMenu) + "CpInGameMenu", g_cpInGameMenu) end -- Lines 279-324 @@ -51,6 +89,8 @@ function CpInGameMenu:initializePages() self.pageGlobalSettings:initialize() self.pageVehicleSettings:initialize() self.pageCourseGenerator:initialize() + self.pageCourseManager:initialize() + self.pageCourseManager:setCourseStorage(self.courseStorage) end -- Lines 327-362 @@ -77,6 +117,13 @@ function CpInGameMenu:setupMenuPages() return CpUtil.getCurrentVehicle() ~= nil end, {128, 0, 128, 128} + }, + { + self.pageCourseManager, + function () + return true + end, + {256, 0, 128, 128} } } diff --git a/scripts/gui/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua similarity index 92% rename from scripts/gui/CpCourseManagerFrame.lua rename to scripts/gui/pages/CpCourseManagerFrame.lua index ab3b7a452..d4fa0a2c0 100644 --- a/scripts/gui/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -4,18 +4,7 @@ This frame is a page for the course manager. ]]-- -CpCourseManagerFrame = { - CONTROLS = { - HEADER = "header", - MAIN_BOC = "mainBox", - LEFT_COLUMN = "leftColumn", - RIGHT_COLUMN = "rightColumn", - LEFT_LIST = "leftList", - RIGHT_LIST = "rightList", - LEFT_COLUMN_HEADER ="leftColumnHeader", - RIGHT_COLUMN_HEADER = "rightColumnHeader", - }, -} +CpCourseManagerFrame = {} CpCourseManagerFrame.translations = { title = "CP_courseManager_title", @@ -72,13 +61,32 @@ CpCourseManagerFrame.colors = { local CpCourseManagerFrame_mt = Class(CpCourseManagerFrame, TabbedMenuFrameElement) -function CpCourseManagerFrame.new(courseStorage, target, custom_mt) +function CpCourseManagerFrame.new(target, custom_mt) local self = TabbedMenuFrameElement.new(target, custom_mt or CpCourseManagerFrame_mt) - self:exposeControlsAsFields(CpCourseManagerFrame.CONTROLS) - self.courseStorage = courseStorage + return self end +function CpCourseManagerFrame.createFromExistingGui(gui, guiName) + local newGui = CpCourseManagerFrame.new(nil, nil) + + g_gui.frames[gui.name].target:delete() + g_gui.frames[gui.name]:delete() + g_gui:loadGui(gui.xmlFilename, guiName, newGui, true) + + return newGui +end + +function CpCourseManagerFrame.setupGui() + local courseManagerFrame = CpCourseManagerFrame.new() + g_gui:loadGui(Utils.getFilename("config/gui/pages/CourseManagerFrame.xml", Courseplay.BASE_DIRECTORY), + "CpCourseManagerFrame", courseManagerFrame, true) +end + +function CpCourseManagerFrame:setCourseStorage(courseStorage) + self.courseStorage = courseStorage +end + function CpCourseManagerFrame:getCurrentEntry() local layout = FocusManager:getFocusedElement() if not layout then @@ -88,11 +96,7 @@ function CpCourseManagerFrame:getCurrentEntry() return element.viewEntry end -function CpCourseManagerFrame:onGuiSetupFinished() - CpCourseManagerFrame:superClass().onGuiSetupFinished(self) - - - +function CpCourseManagerFrame:initialize() --- Changes the input actions. self.modeButton = { profile = "buttonActivate", @@ -126,7 +130,8 @@ function CpCourseManagerFrame:onGuiSetupFinished() local viewEntry = self:getCurrentEntry() if viewEntry then if not viewEntry:isDirectory() then - self.currentVehicle:appendLoadedCpCourse(viewEntry:getEntity()) + local vehicle = CpUtil.getCurrentVehicle() + vehicle:appendLoadedCpCourse(viewEntry:getEntity()) else self.showInfoDialog( self.translations.targetIsNoCourse, viewEntry) @@ -245,11 +250,10 @@ function CpCourseManagerFrame:onGuiSetupFinished() self.rightList:setDataSource(self) end function CpCourseManagerFrame:onFrameOpen() - InGameMenuPricesFrame:superClass().onFrameOpen(self) + self:superClass().onFrameOpen(self) self.curMode = self.minMode self.actionState = self.actionStates.disabled self.selectedEntry = nil - self.currentVehicle = CpInGameMenuAIFrameExtended.getVehicle() self:setSoundSuppressed(true) FocusManager:loadElementFromCustomValues(self.leftList) FocusManager:loadElementFromCustomValues(self.rightList) @@ -262,10 +266,10 @@ function CpCourseManagerFrame:onFrameOpen() end function CpCourseManagerFrame:onFrameClose() + self:superClass().onFrameClose(self) if self.moveElementSelected then self.moveElementSelected.element:setAlternating(false) end - InGameMenuPricesFrame:superClass().onFrameClose(self) self.initialized = false end @@ -459,10 +463,11 @@ end --- Updates the button at the bottom, which depends on the current select mode. function CpCourseManagerFrame:updateMenuButtons() - local courseName = self.currentVehicle:getCurrentCpCourseName() - local title = string.format(g_i18n:getText(self.translations.title), self.currentVehicle:getName(), courseName) + local vehicle = CpUtil.getCurrentVehicle() + local courseName = vehicle:getCurrentCpCourseName() + local title = string.format(g_i18n:getText(self.translations.title), vehicle:getName(), courseName) - self.header:setText(title) + self.categoryHeaderText:setText(title) self.menuButtonInfo = { { inputAction = InputAction.MENU_BACK, @@ -514,9 +519,10 @@ end --- Clears the current courses. function CpCourseManagerFrame:onClickClearCurrentCourse() CpUtil.debugFormat(CpUtil.DBG_HUD, "onClickClearCurrentCourse") - local hasCourse = self.currentVehicle:hasCpCourse() + local vehicle = CpUtil.getCurrentVehicle() + local hasCourse = vehicle:hasCpCourse() if hasCourse then - self.currentVehicle:resetCpCoursesFromGui() + vehicle:resetCpCoursesFromGui() end self:updateLists() end @@ -532,7 +538,8 @@ function CpCourseManagerFrame:onClickSaveEntryDialog(text, clickOk, viewEntry) self.translations.entryExistAlreadyError, viewEntry) return end - if not self.currentVehicle:saveCpCourses(file, text) then + local vehicle = CpUtil.getCurrentVehicle() + if not vehicle:saveCpCourses(file, text) then g_gui:showInfoDialog({ text = string.format(g_i18n:getText(self.translations.invalidNameError), text) @@ -651,15 +658,18 @@ end --------------------------------------------------- function CpCourseManagerFrame:clearCurrentCourseDisabled() - return not self.currentVehicle:hasCpCourse() or self.actionState ~= self.actionStates.disabled + local vehicle = CpUtil.getCurrentVehicle() + return not vehicle:hasCpCourse() or self.actionState ~= self.actionStates.disabled end function CpCourseManagerFrame:loadCourseDisabled() - return self.currentVehicle:hasCpCourse() or self.actionState ~= self.actionStates.disabled or not self.courseStorage.currentDirectoryView:areEntriesVisible() + local vehicle = CpUtil.getCurrentVehicle() + return vehicle:hasCpCourse() or self.actionState ~= self.actionStates.disabled or not self.courseStorage.currentDirectoryView:areEntriesVisible() end function CpCourseManagerFrame:saveCourseDisabled() - return not self.currentVehicle:hasCpCourse() or self.actionState ~= self.actionStates.disabled or not self.courseStorage.currentDirectoryView:areEntriesVisible() + local vehicle = CpUtil.getCurrentVehicle() + return not vehicle:hasCpCourse() or self.actionState ~= self.actionStates.disabled or not self.courseStorage.currentDirectoryView:areEntriesVisible() end function CpCourseManagerFrame:createDirectoryDisabled() From 050c01f8c241c9caf4bf164987cc2ae019c63361 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 17 Nov 2024 12:15:27 +0100 Subject: [PATCH 012/158] Fixes raycastAll parameter swap ...... --- scripts/ai/AIDriveStrategyCombineCourse.lua | 2 +- scripts/ai/VehicleScanner.lua | 2 +- scripts/ai/controllers/ShovelController.lua | 5 ++--- scripts/silo/BunkerSiloWrapper.lua | 12 ++++++------ 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index a997993e6..f80d1ce72 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -2013,7 +2013,7 @@ function AIDriveStrategyCombineCourse:measureBackDistance() -- raycast from a point behind the vehicle forward towards the direction node local nx, ny, nz = localDirectionToWorld(AIUtil.getDirectionNode(self.vehicle), 0, 0, 1) local x, y, z = localToWorld(AIUtil.getDirectionNode(self.vehicle), 0, 1.5, -self.maxBackDistance) - raycastAll(x, y, z, nx, ny, nz, 'raycastBackCallback', self.maxBackDistance, self) + raycastAll(x, y, z, nx, ny, nz, self.maxBackDistance, 'raycastBackCallback', self) end -- I believe this tries to figure out how far the back of a combine is from its direction node. diff --git a/scripts/ai/VehicleScanner.lua b/scripts/ai/VehicleScanner.lua index df62ee38b..6ea4ebb1b 100644 --- a/scripts/ai/VehicleScanner.lua +++ b/scripts/ai/VehicleScanner.lua @@ -21,7 +21,7 @@ function VehicleScanner:measureWidth() for d = 0.5, self.vehicle.size.width / 2 + 0.5, 0.1 do self.hit = false local x, y, z = localToWorld(self.vehicle.rootNode, d, 1, -self.vehicle.size.length / 2 + self.vehicle.size.lengthOffset) - raycastAll(x, y, z, nx, ny, nz, 'raycastBackCallback', self.vehicle.size.length, self) + raycastAll(x, y, z, nx, ny, nz, self.vehicle.size.length, 'raycastBackCallback', self) if not self.hit then self.width = 2 * d self:debug('Found vehicle width %.1f', self.width) diff --git a/scripts/ai/controllers/ShovelController.lua b/scripts/ai/controllers/ShovelController.lua index bddee17e7..2883b7c96 100644 --- a/scripts/ai/controllers/ShovelController.lua +++ b/scripts/ai/controllers/ShovelController.lua @@ -188,9 +188,8 @@ function ShovelController:calculateMinimalUnloadingHeight(triggerNode) local maxHeightObjectHit = 0 for i=self.MIN_TRIGGER_HEIGHT, self.MAX_TRIGGER_HEIGHT, 0.1 do self.objectWasHit = false - raycastAll(sx, terrainHeight + i, sz, dx, 0, dz, - "calculateMinimalUnloadingHeightRaycastCallback", - length, self, + raycastAll(sx, terrainHeight + i, sz, dx, 0, dz, length + "calculateMinimalUnloadingHeightRaycastCallback", self, self.TRIGGER_HEIGHT_RAYCAST_COLLISION_MASK) if self.objectWasHit then maxHeightObjectHit = i diff --git a/scripts/silo/BunkerSiloWrapper.lua b/scripts/silo/BunkerSiloWrapper.lua index 18558d71e..50da2a464 100644 --- a/scripts/silo/BunkerSiloWrapper.lua +++ b/scripts/silo/BunkerSiloWrapper.lua @@ -288,15 +288,15 @@ end --- Checks if the silo has a back wall and sets the plot area afterwards. function CpBunkerSilo:initialize() --- TODO_25 - -- local x, z = self.sx + self.dirXWidth * self.width/2 + self.dirXLength * 2, self.sz + self.dirZWidth * self.width/2 + self.dirZLength * 2 - -- local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 + local x, z = self.sx + self.dirXWidth * self.width/2 + self.dirXLength * 2, self.sz + self.dirZWidth * self.width/2 + self.dirZLength * 2 + local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 - -- raycastAll(x, y, z, self.dirXLength, 0, self.dirZLength, 'rayCastCallbackOneSidedSilo', self.length + 2, self) + raycastAll(x, y, z, self.dirXLength, 0, self.dirZLength, self.length + 2, 'rayCastCallbackOneSidedSilo', self) - -- local x, z = self.hx + self.dirXWidth * self.width/2 - self.dirXLength * 2, self.hz + self.dirZWidth * self.width/2 - self.dirZLength * 2 - -- local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 + local x, z = self.hx + self.dirXWidth * self.width/2 - self.dirXLength * 2, self.hz + self.dirZWidth * self.width/2 - self.dirZLength * 2 + local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 - -- raycastAll(x, y, z, -self.dirXLength, 0, -self.dirZLength, 'rayCastCallbackOneSidedSiloInverted', self.length + 2, self) + raycastAll(x, y, z, -self.dirXLength, 0, -self.dirZLength, self.length + 2, 'rayCastCallbackOneSidedSiloInverted', self) self.plot:setAreas(self:getPlotAreas()) From 1f8cb3a3d55bd299d2bc08380c685e450d599d9f Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 17 Nov 2024 12:18:39 +0100 Subject: [PATCH 013/158] The same fix for raycastClosest --- scripts/ai/ProximitySensor.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index fe6a57db1..c672a787d 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -113,7 +113,7 @@ function ProximitySensor:update() self.hitTerrain = false if self.enabled then local raycastMask = CollisionFlag.STATIC_OBJECTS + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE - raycastClosest(x, y1 + self.height, z, nx, ny, nz, 'raycastCallback', self.range, self, raycastMask) + raycastClosest(x, y1 + self.height, z, nx, ny, nz, self.range, 'raycastCallback', self, raycastMask) if CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then DebugUtil.drawDebugLine(x, y1 + self.height, z, x + 5 * nx, y1 + self.height + 5 * ny, z + 5 * nz, 0, 1, 0) end @@ -459,8 +459,9 @@ function VerticalProximitySensor:update() if self.enabled then local raycastMask = CollisionFlag.STATIC_OBJECTS + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE -- straight up from 10 cm above the ground to height - raycastClosest(x, y + self.minHeightAboveGround, z, 0, 1, 0, - 'raycastCallback', self.height - self.minHeightAboveGround, self, raycastMask) + raycastClosest(x, y + self.minHeightAboveGround, z, 0, 1, 0, + self.height - self.minHeightAboveGround, + 'raycastCallback', self, raycastMask) if CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then DebugUtil.drawDebugLine(x, y + self.minHeightAboveGround, z, x, y + self.height - self.minHeightAboveGround, z, 1, 1, 0) end From 8f963e48a4493eb1eb87502026085649e03706bf Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 17 Nov 2024 12:36:31 +0100 Subject: [PATCH 014/158] Reactivate commented code --- scripts/ai/jobs/CpAIJobCombineUnloader.lua | 12 ++++----- scripts/ai/jobs/CpAIJobSiloLoader.lua | 26 +++++++++---------- scripts/silo/BunkerSiloWrapper.lua | 1 - .../CpCourseGeneratorSettings.lua | 6 ++--- scripts/specializations/CpCourseManager.lua | 5 ++-- 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index d4821ec41..2a0860b5b 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -19,8 +19,8 @@ function CpAIJobCombineUnloader.new(isServer, customMt) self.selectedFieldPlot:setVisible(false) self.selectedFieldPlot:setBrightColor(true) - --TODO 25 self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) - --TODO 25 self.heapPlot:setVisible(false) + self.heapPlot = HeapPlot() + self.heapPlot:setVisible(false) self.heapNode = CpUtil.createNode("siloNode", 0, 0, 0, nil) --- Giants unload @@ -138,7 +138,7 @@ end --- Called when parameters change, scan field function CpAIJobCombineUnloader:validate(farmId) self.selectedFieldPlot:setVisible(false) - --TODO 25 self.heapPlot:setVisible(false) + self.heapPlot:setVisible(false) local isValid, errorMessage = CpAIJob.validate(self, farmId) if not isValid then return isValid, errorMessage @@ -221,8 +221,8 @@ function CpAIJobCombineUnloader:validate(farmId) setRotation(self.heapNode, 0, angle, 0) local found, heapSilo = BunkerSiloManagerUtil.createHeapBunkerSilo(vehicle, self.heapNode, 0, self.maxHeapLength, -10) if found then - --TODO 25 self.heapPlot:setArea(heapSilo:getArea()) - --TODO 25 self.heapPlot:setVisible(true) + self.heapPlot:setArea(heapSilo:getArea()) + self.heapPlot:setVisible(true) end end @@ -233,7 +233,7 @@ function CpAIJobCombineUnloader:draw(map, isOverviewMap) CpAIJob.draw(self, map, isOverviewMap) if not isOverviewMap then self.selectedFieldPlot:draw(map) - --TODO 25 self.heapPlot:draw(map) + self.heapPlot:draw(map) end end diff --git a/scripts/ai/jobs/CpAIJobSiloLoader.lua b/scripts/ai/jobs/CpAIJobSiloLoader.lua index 5584b7874..1a4f78526 100644 --- a/scripts/ai/jobs/CpAIJobSiloLoader.lua +++ b/scripts/ai/jobs/CpAIJobSiloLoader.lua @@ -20,10 +20,10 @@ CpAIJobSiloLoader.MAX_UNLOAD_TARGET_DISTANCE_FROM_SILO = 180 function CpAIJobSiloLoader.new(isServer, customMt) local self = CpAIJob.new(isServer, customMt or AIJobCombineUnloaderCp_mt) - --TODO 25 self.heapPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) - --TODO 25 self.heapPlot:setVisible(false) + self.heapPlot = HeapPlot() + self.heapPlot:setVisible(false) - --TODO 25 self.trailerAreaPlot = HeapPlot(g_currentMission.inGameMenu.ingameMap) + self.trailerAreaPlot = HeapPlot() self.heapNode = CpUtil.createNode("siloNode", 0, 0, 0, nil) @@ -101,8 +101,8 @@ function CpAIJobSiloLoader:setValues() if bunkerSilo then self.bunkerSilo = bunkerSilo elseif heapSilo then - --TODO 25 self.heapPlot:setArea(heapSilo:getArea()) - --TODO 25 self.heapPlot:setVisible(true) + self.heapPlot:setArea(heapSilo:getArea()) + self.heapPlot:setVisible(true) self.heap = heapSilo end self.siloLoaderTask:setSiloAndHeap(self.bunkerSilo, self.heap) @@ -112,8 +112,8 @@ end --- Called when parameters change, scan field function CpAIJobSiloLoader:validate(farmId) - --TODO 25 self.heapPlot:setVisible(false) - --TODO 25 self.trailerAreaPlot:setVisible(false) + self.heapPlot:setVisible(false) + self.trailerAreaPlot:setVisible(false) self.heap = nil self.bunkerSilo = nil self.unloadStation = nil @@ -136,8 +136,8 @@ function CpAIJobSiloLoader:validate(farmId) if bunkerSilo then self.bunkerSilo = bunkerSilo elseif heapSilo then - --TODO 25 self.heapPlot:setArea(heapSilo:getArea()) - --TODO 25 self.heapPlot:setVisible(true) + self.heapPlot:setArea(heapSilo:getArea()) + self.heapPlot:setVisible(true) self.heap = heapSilo end self.siloLoaderTask:setSiloAndHeap(self.bunkerSilo, self.heap) @@ -176,8 +176,8 @@ function CpAIJobSiloLoader:validate(farmId) local found, area, validDistanceToSilo = CpAIJobSiloLoader.getTrailerUnloadArea( self.cpJobParameters.unloadPosition, self.bunkerSilo or self.heap) if found then - --TODO 25 self.trailerAreaPlot:setVisible(true) - --TODO 25 self.trailerAreaPlot:setArea(area) + self.trailerAreaPlot:setVisible(true) + self.trailerAreaPlot:setArea(area) end if not validDistanceToSilo then return false, g_i18n:getText("CP_error_unload_target_to_far_away_from_silo") @@ -324,7 +324,7 @@ end function CpAIJobSiloLoader:draw(map, isOverviewMap) CpAIJob.draw(self, map, isOverviewMap) if not isOverviewMap then - --TODO 25 self.heapPlot:draw(map) + self.heapPlot:draw(map) g_bunkerSiloManager:drawSilos(map, self.bunkerSilo) if self.cpJobParameters.unloadAt:getValue() == CpSiloLoaderJobParameters.UNLOAD_TRIGGER then local fillTypes = self:getConvertedFillTypes() @@ -335,7 +335,7 @@ function CpAIJobSiloLoader:draw(map, isOverviewMap) g_triggerManager:drawDischargeableTriggers(map, self.unloadTrigger, fillTypes) else --- Drawing trailer area - --TODO 25 self.trailerAreaPlot:draw(map) + self.trailerAreaPlot:draw(map) end end end diff --git a/scripts/silo/BunkerSiloWrapper.lua b/scripts/silo/BunkerSiloWrapper.lua index 50da2a464..bf10197a8 100644 --- a/scripts/silo/BunkerSiloWrapper.lua +++ b/scripts/silo/BunkerSiloWrapper.lua @@ -287,7 +287,6 @@ end --- Checks if the silo has a back wall and sets the plot area afterwards. function CpBunkerSilo:initialize() - --- TODO_25 local x, z = self.sx + self.dirXWidth * self.width/2 + self.dirXLength * 2, self.sz + self.dirZWidth * self.width/2 + self.dirZLength * 2 local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 0, z) + 2 diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua index def1f871b..351f3f120 100644 --- a/scripts/specializations/CpCourseGeneratorSettings.lua +++ b/scripts/specializations/CpCourseGeneratorSettings.lua @@ -92,8 +92,7 @@ function CpCourseGeneratorSettings:onLoad(savegame) --- Register the spec: spec_cpCourseGeneratorSettings self.spec_cpCourseGeneratorSettings = self["spec_" .. CpCourseGeneratorSettings.SPEC_NAME] local spec = self.spec_cpCourseGeneratorSettings - -- TODO 25 - -- spec.gui = g_currentMission.inGameMenu.pageAI + --- Clones the generic settings to create different settings containers for each vehicle. CpSettingsUtil.cloneSettingsTable(spec,CpCourseGeneratorSettings.settings,self,CpCourseGeneratorSettings) @@ -245,8 +244,7 @@ end ---@param callbackStr string event to be raised ---@param setting AIParameterSettingList setting that raised the callback. function CpCourseGeneratorSettings:raiseCallback(callbackStr, setting, ...) - -- TODO 25 event is registered by the HUD which is now disabled - --TODO 25 SpecializationUtil.raiseEvent(self, callbackStr, setting, ...) + SpecializationUtil.raiseEvent(self, callbackStr, setting, ...) end function CpCourseGeneratorSettings:raiseDirtyFlag(setting) diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index 80786fc2b..643647dcf 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -125,7 +125,7 @@ function CpCourseManager:onLoad(savegame) --- Register the spec: spec_cpCourseManager self.spec_cpCourseManager = self["spec_" .. CpCourseManager.SPEC_NAME] local spec = self.spec_cpCourseManager - -- TODO 25 spec.coursePlot = CoursePlot(g_currentMission.inGameMenu.ingameMap) + spec.coursePlot = CoursePlot(g_inGameMenu.ingameMap) spec.courses = {} @@ -359,8 +359,7 @@ end function CpCourseManager:onPreDelete() g_assignedCoursesManager:unregisterVehicle(self, self.id) - --- TODO_25 - -- CpCourseManager.resetCourses(self) + CpCourseManager.resetCourses(self) local spec = self.spec_cpCourseManager spec.courseDisplay:delete() end From 56ad4652ca4f36c7d7cf190503756b11d194e56b Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 17 Nov 2024 12:45:04 +0100 Subject: [PATCH 015/158] Fixes g_currentMission.player changes --- scripts/dev/ConsoleCommands.lua | 2 +- scripts/field/CustomField.lua | 2 +- scripts/pathfinder/PathfinderUtil.lua | 2 +- scripts/specializations/CpAIBaleFinder.lua | 4 ++-- scripts/specializations/CpAIBunkerSiloWorker.lua | 4 ++-- scripts/specializations/CpAICombineUnloader.lua | 4 ++-- scripts/specializations/CpAIFieldWorker.lua | 4 ++-- scripts/specializations/CpAISiloLoaderWorker.lua | 4 ++-- scripts/specializations/CpAIWorker.lua | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/scripts/dev/ConsoleCommands.lua b/scripts/dev/ConsoleCommands.lua index ffebb2721..4b2067f08 100644 --- a/scripts/dev/ConsoleCommands.lua +++ b/scripts/dev/ConsoleCommands.lua @@ -87,7 +87,7 @@ end ---@param amount number function CpConsoleCommands:addMoney(amount) - g_currentMission:addMoney(amount ~= nil and tonumber(amount) or 0, g_currentMission.player.farmId, MoneyType.OTHER) + g_currentMission:addMoney(amount ~= nil and tonumber(amount) or 0, g_currentMission.playerSystem:getLocalPlayer().farmId, MoneyType.OTHER) end ---Prints a variable to the console or a xmlFile. diff --git a/scripts/field/CustomField.lua b/scripts/field/CustomField.lua index e4a08fef1..468aafcfa 100644 --- a/scripts/field/CustomField.lua +++ b/scripts/field/CustomField.lua @@ -69,7 +69,7 @@ end function CustomField:addHotspot() self.fieldHotspot = CustomFieldHotspot.new() self.fieldHotspot:setField(self) - self.fieldHotspot:setOwnerFarmId(g_currentMission.player.farmId) + self.fieldHotspot:setOwnerFarmId(g_currentMission.playerSystem:getLocalPlayer().farmId) g_currentMission:addMapHotspot(self.fieldHotspot) end diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index e6f3ed09b..d42567954 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -175,7 +175,7 @@ end --- Is the land at this position owned by me? function PathfinderUtil.isWorldPositionOwned(posX, posZ) local farmland = g_farmlandManager:getFarmlandAtWorldPosition(posX, posZ) - local missionAllowed = g_missionManager:getIsMissionWorkAllowed(g_currentMission.player.farmId, posX, posZ, nil) + local missionAllowed = g_missionManager:getIsMissionWorkAllowed(g_currentMission.playerSystem:getLocalPlayer().farmId, posX, posZ, nil) return (farmland and farmland.isOwned) or missionAllowed end diff --git a/scripts/specializations/CpAIBaleFinder.lua b/scripts/specializations/CpAIBaleFinder.lua index f6bd00d97..afd6573cd 100644 --- a/scripts/specializations/CpAIBaleFinder.lua +++ b/scripts/specializations/CpAIBaleFinder.lua @@ -145,7 +145,7 @@ function CpAIBaleFinder:startCpAtFirstWp(superFunc) --- Applies the bale wrap type set in the hud, so ad can start with the correct type. --- TODO: This should only be applied, if the driver was started for the first time by ad and not every time. spec.cpJobStartAtLastWp:getCpJobParameters().baleWrapType:setValue(spec.cpJob:getCpJobParameters().baleWrapType:getValue()) - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then @@ -163,7 +163,7 @@ function CpAIBaleFinder:startCpAtLastWp(superFunc) if not superFunc(self) then if self:getCanStartCpBaleFinder() then local spec = self.spec_cpAIBaleFinder - spec.cpJobStartAtLastWp:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJobStartAtLastWp:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJobStartAtLastWp:setValues() local success = spec.cpJobStartAtLastWp:validate(false) if success then diff --git a/scripts/specializations/CpAIBunkerSiloWorker.lua b/scripts/specializations/CpAIBunkerSiloWorker.lua index 5856a9495..e585377db 100644 --- a/scripts/specializations/CpAIBunkerSiloWorker.lua +++ b/scripts/specializations/CpAIBunkerSiloWorker.lua @@ -134,7 +134,7 @@ function CpAIBunkerSiloWorker:startCpAtFirstWp(superFunc, ...) if not superFunc(self, ...) then if self:getCanStartCpBunkerSiloWorker() then local spec = self.spec_cpAIBunkerSiloWorker - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then @@ -152,7 +152,7 @@ function CpAIBunkerSiloWorker:startCpAtLastWp(superFunc, ...) if not superFunc(self, ...) then if self:getCanStartCpBunkerSiloWorker() then local spec = self.spec_cpAIBunkerSiloWorker - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then diff --git a/scripts/specializations/CpAICombineUnloader.lua b/scripts/specializations/CpAICombineUnloader.lua index e1a35757e..657c449c4 100644 --- a/scripts/specializations/CpAICombineUnloader.lua +++ b/scripts/specializations/CpAICombineUnloader.lua @@ -255,7 +255,7 @@ function CpAICombineUnloader:startCpAtFirstWp(superFunc) if not superFunc(self) then if self:getCanStartCpCombineUnloader() then local spec = self.spec_cpAICombineUnloader - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then @@ -273,7 +273,7 @@ function CpAICombineUnloader:startCpAtLastWp(superFunc) if not superFunc(self) then if self:getCanStartCpCombineUnloader() then local spec = self.spec_cpAICombineUnloader - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then diff --git a/scripts/specializations/CpAIFieldWorker.lua b/scripts/specializations/CpAIFieldWorker.lua index c6d8389bc..cb0518db8 100644 --- a/scripts/specializations/CpAIFieldWorker.lua +++ b/scripts/specializations/CpAIFieldWorker.lua @@ -196,7 +196,7 @@ function CpAIFieldWorker:startCpAtFirstWp() local spec = self.spec_cpAIFieldWorker self:updateAIFieldWorkerImplementData() if self:hasCpCourse() and self:getCanStartCpFieldWork() then - spec.cpJobStartAtFirstWp:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJobStartAtFirstWp:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) --- Applies the lane offset set in the hud, so ad can start with the correct lane offset. spec.cpJobStartAtFirstWp:getCpJobParameters().laneOffset:setValue(self:getCpLaneOffsetSetting():getValue()) spec.cpJobStartAtFirstWp:setValues() @@ -213,7 +213,7 @@ function CpAIFieldWorker:startCpAtLastWp() local spec = self.spec_cpAIFieldWorker self:updateAIFieldWorkerImplementData() if self:hasCpCourse() and self:getCanStartCpFieldWork() then - spec.cpJobStartAtLastWp:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJobStartAtLastWp:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJobStartAtLastWp:setValues() CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "lane offset: %s", spec.cpJobStartAtLastWp:getCpJobParameters().laneOffset:getString()) local success = spec.cpJobStartAtLastWp:validate(false) diff --git a/scripts/specializations/CpAISiloLoaderWorker.lua b/scripts/specializations/CpAISiloLoaderWorker.lua index 3ae531cfa..1b6017ed5 100644 --- a/scripts/specializations/CpAISiloLoaderWorker.lua +++ b/scripts/specializations/CpAISiloLoaderWorker.lua @@ -131,7 +131,7 @@ function CpAISiloLoaderWorker:startCpAtFirstWp(superFunc, ...) if not superFunc(self, ...) then if self:getCanStartCpSiloLoaderWorker() then local spec = self.spec_cpAISiloLoaderWorker - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then @@ -149,7 +149,7 @@ function CpAISiloLoaderWorker:startCpAtLastWp(superFunc, ...) if not superFunc(self, ...) then if self:getCanStartCpSiloLoaderWorker() then local spec = self.spec_cpAISiloLoaderWorker - spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true) + spec.cpJob:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true) spec.cpJob:setValues() local success = spec.cpJob:validate(false) if success then diff --git a/scripts/specializations/CpAIWorker.lua b/scripts/specializations/CpAIWorker.lua index f66bf1f43..48fa2ccb5 100644 --- a/scripts/specializations/CpAIWorker.lua +++ b/scripts/specializations/CpAIWorker.lua @@ -245,7 +245,7 @@ function CpAIWorker:cpStartStopDriver(isStartedByHud) end if self:getCanStartCp() and job then - job:applyCurrentState(self, g_currentMission, g_currentMission.player.farmId, true, true) + job:applyCurrentState(self, g_currentMission, g_currentMission.playerSystem:getLocalPlayer().farmId, true, true) job:setValues() local success, message = job:validate(false) if success then From e62395b67d2093a2310a7386402f998a550d8f12 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 17 Nov 2024 08:49:47 -0500 Subject: [PATCH 016/158] fix: waypoint clone --- scripts/Waypoint.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Waypoint.lua b/scripts/Waypoint.lua index 542838f68..834e976d1 100644 --- a/scripts/Waypoint.lua +++ b/scripts/Waypoint.lua @@ -39,7 +39,7 @@ function Waypoint:init(wp) self.rev = self.rev or wp.gear and wp.gear == Gear.Backward -- dynamically added/calculated properties self.useTightTurnOffset = wp.useTightTurnOffset - self.turnControls = table.clone(wp.turnControls) + self.turnControls = wp.turnControls and table.clone(wp.turnControls) self.dToNext = wp.dToNext self.yRot = wp.yRot --- Set, when generated for a multi tool course From d45ea5ba730ee36caa07ab0f1e90ac01646c5d4d Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 17 Nov 2024 09:17:40 -0500 Subject: [PATCH 017/158] fix: dummy coroutine The good people at Giants removed the coroutine from their lua interpreter. Until they condescend to publish some documentation to figure out if they have a replacement, implement a dummy one which never yields. This will make the pathfinder always run synchronously, so it'll probably freeze the game until done. --- scripts/debug/CpDebug.lua | 6 +++--- scripts/pathfinder/HybridAStar.lua | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/debug/CpDebug.lua b/scripts/debug/CpDebug.lua index bb17c3d44..8128ff6ba 100644 --- a/scripts/debug/CpDebug.lua +++ b/scripts/debug/CpDebug.lua @@ -169,9 +169,9 @@ end function CpDebug:drawVehicleDebugTable(vehicle, info, height, size) local d = DebugInfoTable.new() - - d:createWithNodeToCamera(vehicle.rootNode, height or 4, info, size or 0.05) - g_debugManager:addFrameElement(d) + -- TODO 25 + --d:createWithNodeToCamera(vehicle.rootNode, height or 4, info, size or 0.05) + --g_debugManager:addFrameElement(d) end diff --git a/scripts/pathfinder/HybridAStar.lua b/scripts/pathfinder/HybridAStar.lua index 539c3a403..b764b91ae 100644 --- a/scripts/pathfinder/HybridAStar.lua +++ b/scripts/pathfinder/HybridAStar.lua @@ -30,6 +30,23 @@ https://github.com/karlkurzer/path_planner ]] +--- TODO 25 +--- Dummy coroutine replacement as Giants removed in 2025. Will see if they have something replacing it, until +--- then, just run the function synchronously and never yield control back. +local coroutine = {} + +function coroutine.create(f) + return f +end + +function coroutine.resume(f, ...) + return f(...) +end + +--- coroutine.yield is not supported, always returns false +function coroutine.running() + return false +end --- Interface definition for all pathfinders ---@class PathfinderInterface From 659b0299c524cbc8bd0bd43e3a46fa90fa0cad49 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 17 Nov 2024 10:32:40 -0500 Subject: [PATCH 018/158] fix: MathUtil.clamp(), division by zero The same good folks at Giants implemented the division to throw an error on zero, instead of returning math.huge or inf... --- scripts/Course.lua | 7 +++---- scripts/ai/AIDriveStrategyAttachHeader.lua | 2 +- scripts/ai/AIDriveStrategyFindBales.lua | 6 +++--- scripts/ai/AIDriveStrategyUnloadCombine.lua | 16 ++++++++-------- scripts/ai/AIReverseDriver.lua | 12 ++++++------ scripts/ai/ImplementUtil.lua | 2 +- scripts/ai/ProximitySensor.lua | 2 +- scripts/ai/controllers/ConveyorController.lua | 4 ++-- scripts/ai/controllers/LevelerController.lua | 6 +++--- scripts/ai/controllers/PipeController.lua | 4 ++-- scripts/courseGenerator/FieldworkCourse.lua | 2 +- scripts/courseGenerator/Util.lua | 4 ---- .../courseGenerator/test/mock-GiantsEngine.lua | 2 +- scripts/geometry/Vertex.lua | 6 +++--- scripts/pathfinder/HybridAStar.lua | 10 ++++++---- scripts/silo/BunkerSiloVehicleController.lua | 6 +++--- scripts/specializations/CpShovelPositions.lua | 4 ++-- scripts/util/CpMathUtil.lua | 9 +++++++++ scripts/util/CpRemainingTime.lua | 2 +- 19 files changed, 56 insertions(+), 50 deletions(-) diff --git a/scripts/Course.lua b/scripts/Course.lua index fd84ecfa8..f53fda973 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -210,9 +210,8 @@ function Course:enrichWaypointData(startIx) self.waypoints[i].turnsToHere = self.totalTurns -- TODO: looks like we may end up with the first two waypoint of a course being the same. This takes care -- of setting dx/dz to 0 (instead of NaN) but should investigate as it does not make sense - local dx, dz = MathUtil.vector2Normalize(nx - cx, nz - cz) - -- check for NaN - if dx == dx and dz == dz and not (dx == 0 and dz == 0)then + if MathUtil.vector2Length(nx - cx, nz - cz) > 0 then + local dx, dz = MathUtil.vector2Normalize(nx - cx, nz - cz) self.waypoints[i].dx, self.waypoints[i].dz = dx, dz self.waypoints[i].yRot = MathUtil.getYRotationFromDirection(dx, dz) else @@ -276,7 +275,7 @@ end function Course:calculateSignedRadius(ix) local deltaAngle = CpMathUtil.getDeltaAngle(self.waypoints[ix].yRot, self.waypoints[ix - 1].yRot) - return self:getDistanceToNextWaypoint(ix) / (2 * math.sin(deltaAngle / 2)) + return CpMathUtil.divide(self:getDistanceToNextWaypoint(ix), (2 * math.sin(deltaAngle / 2))) end function Course:calculateRadius(ix) diff --git a/scripts/ai/AIDriveStrategyAttachHeader.lua b/scripts/ai/AIDriveStrategyAttachHeader.lua index 6e83c905a..587ba82b9 100644 --- a/scripts/ai/AIDriveStrategyAttachHeader.lua +++ b/scripts/ai/AIDriveStrategyAttachHeader.lua @@ -165,7 +165,7 @@ function AIDriveStrategyAttachHeader:getDriveData(dt, vX, vY, vZ) --- Slowdown near the header, to allow smooth attach timing. local _, _, z = localToLocal(self.attacherJointController:getCutterJointPositionNode(), self.cutterNode, 0, 0, 0) - local speed = MathUtil.clamp(-2 * z, 1, self.settings.reverseSpeed:getValue()) + local speed = CpMathUtil.clamp(-2 * z, 1, self.settings.reverseSpeed:getValue()) self:setMaxSpeed(speed) if self.attacherJointController:canAttachCutter() then Timer.createOneshot(120, function() diff --git a/scripts/ai/AIDriveStrategyFindBales.lua b/scripts/ai/AIDriveStrategyFindBales.lua index 60148bef5..b656deb06 100644 --- a/scripts/ai/AIDriveStrategyFindBales.lua +++ b/scripts/ai/AIDriveStrategyFindBales.lua @@ -332,18 +332,18 @@ end --- or the driver stops directly. function AIDriveStrategyFindBales:setFinished() if not self:isReadyToFoldImplements() then - -- Watiting until the folding has finished.. + -- 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 - -- Watiting until the folding has finished.. + -- 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 invered goal node") + self:debug("A valid start position is found, so the driver tries to finish at the inverted goal node") self:startPathfindingToStartMarker() else self:finishJob() diff --git a/scripts/ai/AIDriveStrategyUnloadCombine.lua b/scripts/ai/AIDriveStrategyUnloadCombine.lua index 957b9c489..e4ae8bf5d 100644 --- a/scripts/ai/AIDriveStrategyUnloadCombine.lua +++ b/scripts/ai/AIDriveStrategyUnloadCombine.lua @@ -649,7 +649,7 @@ function AIDriveStrategyUnloadCombine:driveBesideCombine() -- use a factor to make sure we reach the pipe fast, but be more gentle while discharging local factor = strategy:isDischarging() and 0.75 or 2 local combineSpeed = self.combineToUnload.lastSpeedReal * 3600 - local speed = combineSpeed + MathUtil.clamp(dz * factor, -10, 15) + local speed = combineSpeed + CpMathUtil.clamp(dz * factor, -10, 15) if dz > 0 and speed < 2 then -- Giants does not like speeds under 2, it just stops. So if we calculated a small speed -- like when the combine is stopped, but not there yet, make sure we set a speed which @@ -749,12 +749,12 @@ function AIDriveStrategyUnloadCombine:followChopper() -- the back of the harvester dz = dz + self:getCombinesMeasuredBackDistance() end - speed = self.combineToUnload.lastSpeedReal * 3600 + MathUtil.clamp( + speed = self.combineToUnload.lastSpeedReal * 3600 + CpMathUtil.clamp( math.min(-dz, dFollowProxy - self.targetDistanceBehindChopper, dProxy - self.targetDistanceBehindChopper) * 2, -10, 15) else -- not aligned with the chopper, drive forward to get closer, regardless of dz - speed = MathUtil.clamp( + speed = CpMathUtil.clamp( math.min(dFollowProxy - self.targetDistanceBehindChopper, dProxy - self.targetDistanceBehindChopper) * 2, 0, self.settings.turnSpeed:getValue()) end @@ -892,11 +892,11 @@ function AIDriveStrategyUnloadCombine:handleChopperTurn(harvester) if sameDirection then -- reverse speed is controlled around combine's speed dReference = harvester:getCpDriveStrategy():isDischarging() and dz or dz - 3 - speed = combineSpeed + MathUtil.clamp(self.targetDistanceBehindChopper - dReference, -combineSpeed, + speed = combineSpeed + CpMathUtil.clamp(self.targetDistanceBehindChopper - dReference, -combineSpeed, self.settings.reverseSpeed:getValue() * 1.5) else -- reverse speed only depends on distance from the combine, stop when at working width - speed = MathUtil.clamp(harvester:getCpDriveStrategy():getWorkWidth() - d, 0, + speed = CpMathUtil.clamp(harvester:getCpDriveStrategy():getWorkWidth() - d, 0, self.settings.reverseSpeed:getValue() * 1.5) end else @@ -909,7 +909,7 @@ function AIDriveStrategyUnloadCombine:handleChopperTurn(harvester) -- get closer to the chopper when beside it dReference = (math.abs(dx) > 3) and dz or d speed = combineSpeed + - (MathUtil.clamp(dReference - self.targetDistanceBehindChopper, -turnSpeed, turnSpeed)) + (CpMathUtil.clamp(dReference - self.targetDistanceBehindChopper, -turnSpeed, turnSpeed)) end self:renderDebugTableFromLists( @@ -1791,7 +1791,7 @@ function AIDriveStrategyUnloadCombine:updateCombineStatus() -- direction changed self.combineToUnloadReversing = -1 else - self.combineToUnloadReversing = MathUtil.clamp(combineToUnloadReversing, -1, 1) + self.combineToUnloadReversing = CpMathUtil.clamp(combineToUnloadReversing, -1, 1) end end @@ -2315,7 +2315,7 @@ function AIDriveStrategyUnloadCombine:backUpForReversingCombine() local d = self:getDistanceFromCombine(blockedVehicle) local dProximity, vehicleInFront = self.proximityController:checkBlockingVehicleFront() local combineSpeed = (blockedVehicle.lastSpeedReal * 3600) - local speed = combineSpeed + MathUtil.clamp(self.minDistanceWhenMovingOutOfWay - math.min(d, dProximity), + local speed = combineSpeed + CpMathUtil.clamp(self.minDistanceWhenMovingOutOfWay - math.min(d, dProximity), -combineSpeed, self.settings.reverseSpeed:getValue() * 1.2) self:setMaxSpeed(speed) diff --git a/scripts/ai/AIReverseDriver.lua b/scripts/ai/AIReverseDriver.lua index 58d8a9f90..d78a5cd41 100644 --- a/scripts/ai/AIReverseDriver.lua +++ b/scripts/ai/AIReverseDriver.lua @@ -109,21 +109,21 @@ function AIReverseDriver:getDriveData() local rotDelta = (self.reversingImplement.reversingProperties.nodeDistance * (0.5 - (0.023 * self.reversingImplement.reversingProperties.nodeDistance - 0.073))) local trailerToWaypointAngle = self:getLocalYRotationToPoint(trailerNode, tx, ty, tz, -1) * rotDelta - trailerToWaypointAngle = MathUtil.clamp(trailerToWaypointAngle, -math.rad(90), math.rad(90)) + trailerToWaypointAngle = CpMathUtil.clamp(trailerToWaypointAngle, -math.rad(90), math.rad(90)) local dollyToTrailerAngle = self:getLocalYRotationToPoint(trailerFrontNode, xTrailer, yTrailer, zTrailer, -1) local tractorToDollyAngle = self:getLocalYRotationToPoint(tractorNode, xFrontNode, yFrontNode, zFrontNode, -1) local rearAngleDiff = (dollyToTrailerAngle - trailerToWaypointAngle) - rearAngleDiff = MathUtil.clamp(rearAngleDiff, -math.rad(45), math.rad(45)) + rearAngleDiff = CpMathUtil.clamp(rearAngleDiff, -math.rad(45), math.rad(45)) local frontAngleDiff = (tractorToDollyAngle - dollyToTrailerAngle) - frontAngleDiff = MathUtil.clamp(frontAngleDiff, -math.rad(45), math.rad(45)) + frontAngleDiff = CpMathUtil.clamp(frontAngleDiff, -math.rad(45), math.rad(45)) angleDiff = (frontAngleDiff - rearAngleDiff) * (1.5 - (self.reversingImplement.reversingProperties.nodeDistance * 0.4 - 0.9) + rotDelta) - angleDiff = MathUtil.clamp(angleDiff, -math.rad(45), math.rad(45)) + angleDiff = CpMathUtil.clamp(angleDiff, -math.rad(45), math.rad(45)) lx, lz = MathUtil.getDirectionFromYRotation(angleDiff) else @@ -131,7 +131,7 @@ function AIReverseDriver:getDriveData() -- is supported by the tractor local crossTrackError, orientationError, curvatureError, currentHitchAngle = self:calculateErrors(tractorNode, trailerNode) angleDiff = self:calculateHitchCorrectionAngle(crossTrackError, orientationError, curvatureError, currentHitchAngle) - angleDiff = MathUtil.clamp(angleDiff, -maxTractorAngle, maxTractorAngle) + angleDiff = CpMathUtil.clamp(angleDiff, -maxTractorAngle, maxTractorAngle) lx, lz = MathUtil.getDirectionFromYRotation(angleDiff) end @@ -264,7 +264,7 @@ function AIReverseDriver:calculateHitchCorrectionAngle(crossTrackError, orientat kCeBase * curvatureError ) local maxHitchAngle = math.rad(35) - hitchAngle = MathUtil.clamp(hitchAngle, -maxHitchAngle, maxHitchAngle) + hitchAngle = CpMathUtil.clamp(hitchAngle, -maxHitchAngle, maxHitchAngle) local correctionAngle = -(hitchAngle - currentHitchAngle) diff --git a/scripts/ai/ImplementUtil.lua b/scripts/ai/ImplementUtil.lua index 344e4460b..1c171353c 100644 --- a/scripts/ai/ImplementUtil.lua +++ b/scripts/ai/ImplementUtil.lua @@ -380,7 +380,7 @@ function ImplementUtil.moveMovingToolToRotation(implement, tool, dt, rotTarget, local oldRot = tool.curRot[tool.rotationAxis] local diff = rotTarget - oldRot local dir = MathUtil.sign(diff) - local rotSpeed = MathUtil.clamp( math.abs(diff) * math.abs(tool.rotSpeed), math.abs(tool.rotSpeed)/3, 0.5 ) + local rotSpeed = CpMathUtil.clamp( math.abs(diff) * math.abs(tool.rotSpeed), math.abs(tool.rotSpeed)/3, 0.5 ) rotSpeed = dir * rotSpeed if math.abs(diff) < minDiffNeeded or rotSpeed == 0 then ImplementUtil.stopMovingTool(implement, tool) diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index c672a787d..c586a03b2 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -91,7 +91,7 @@ function ProximitySensor:update() ((self.vehicle.rotatedTime / self.xOffset) >= 0 and self.angleToDirectionNode or -self.angleToDirectionNode) - self:setRotation(MathUtil.clamp( + self:setRotation(CpMathUtil.clamp( self.vehicle.rotatedTime * (2 * self.maxRotation + correction) * self.vehicle:getSteeringDirection(), -2 * self.maxRotation, 2 * self.maxRotation)) diff --git a/scripts/ai/controllers/ConveyorController.lua b/scripts/ai/controllers/ConveyorController.lua index 9351e94f2..5b02714ff 100644 --- a/scripts/ai/controllers/ConveyorController.lua +++ b/scripts/ai/controllers/ConveyorController.lua @@ -184,7 +184,7 @@ function ConveyorController:updateMoveablePipe(dt) end self:debug("Arm: yRot: %.2f, dyRot: %.2f, oldRot: %.2f, dx: %.2f", math.deg(yRot), math.deg(dyRot), math.deg(oldRot), dx) ImplementUtil.moveMovingToolToRotation(self.implement, self.armRotationTool, dt, - MathUtil.clamp(oldRot + dyRot, self.armRotationTool.rotMin, self.armRotationTool.rotMax)) + CpMathUtil.clamp(oldRot + dyRot, self.armRotationTool.rotMin, self.armRotationTool.rotMax)) local _, _, dz = localToLocal(self.pipeRotationTool.node, self.implement.rootNode, 0, 0, 0) local px, py, pz if self.pipeSide == self.LEFT_SIDE then @@ -222,7 +222,7 @@ function ConveyorController:updateMoveablePipe(dt) local bx, _, bz = getWorldTranslation(dischargeNode.node) if MathUtil.vector2Length(px - bx, pz - bz) > 1 then ImplementUtil.moveMovingToolToRotation(self.implement, self.pipeRotationTool, dt, - MathUtil.clamp(MathUtil.normalizeRotationForShortestPath(dyRot, oldRot), self.pipeRotationTool.rotMin, self.pipeRotationTool.rotMax)) + CpMathUtil.clamp(MathUtil.normalizeRotationForShortestPath(dyRot, oldRot), self.pipeRotationTool.rotMin, self.pipeRotationTool.rotMax)) self:debug("Pipe: yRot: %.2f, dyRot: %.2f, oldRot: %.2f, dx: %.2f, dz: %.2f, dz2: %.2f", math.deg(yRot), math.deg(dyRot), math.deg(oldRot), dx, dz, dz2) end diff --git a/scripts/ai/controllers/LevelerController.lua b/scripts/ai/controllers/LevelerController.lua index 0dd488b2a..6fb422ad4 100644 --- a/scripts/ai/controllers/LevelerController.lua +++ b/scripts/ai/controllers/LevelerController.lua @@ -128,7 +128,7 @@ function LevelerController:setCylinderedLevelerRotation(dt, offsetDeg) -- angle, targetRot, math.rad(offsetDeg), self.levelerTool.rotMin, self.levelerTool.rotMax) return ImplementUtil.moveMovingToolToRotation(self.levelerToolVehicle, self.levelerTool, dt, - MathUtil.clamp(angle - targetRot, self.levelerTool.rotMin, self.levelerTool.rotMax)) + CpMathUtil.clamp(angle - targetRot, self.levelerTool.rotMin, self.levelerTool.rotMax)) end @@ -176,7 +176,7 @@ function LevelerController:updateHeight(dt) local lowerDistanceToGround = hy + delta --calculate the target alpha - local alpha = MathUtil.clamp((lowerDistanceToGround - jointDesc.upperDistanceToGround) / (jointDesc.lowerDistanceToGround - jointDesc.upperDistanceToGround), 0, 1) + local alpha = CpMathUtil.clamp((lowerDistanceToGround - jointDesc.upperDistanceToGround) / (jointDesc.lowerDistanceToGround - jointDesc.upperDistanceToGround), 0, 1) self:debug("lastCurAlpha: %.2f, nextAlpha: %.2f, heightDiff: %.2f", spec.lastHeightAlpha, alpha, heightDiff) self:debug("terrainHeight: %.2f, shieldHeight: %.2f, shieldHeightOffset: %.2f, targetHeight: %.2f", terrainHeight, y, self.shieldHeightOffset, targetHeight) @@ -232,7 +232,7 @@ end function LevelerController:updateShieldHeightOffset() --- A small reduction to the offset, as the shield should be lifted after a only a bit silage. local smallOffsetReduction = 0.3 - --self.shieldHeightOffset = MathUtil.clamp(-self.levelerSpec.lastForce/self.levelerSpec.maxForce - smallOffsetReduction, 0, 1) + --self.shieldHeightOffset = CpMathUtil.clamp(-self.levelerSpec.lastForce/self.levelerSpec.maxForce - smallOffsetReduction, 0, 1) end --- Is the shield full ? diff --git a/scripts/ai/controllers/PipeController.lua b/scripts/ai/controllers/PipeController.lua index 62b0b861d..4005b6ce9 100644 --- a/scripts/ai/controllers/PipeController.lua +++ b/scripts/ai/controllers/PipeController.lua @@ -595,7 +595,7 @@ function PipeController:moveDependedPipePart(tool, dt) 1, 0, 0, 0, false) end end - ImplementUtil.moveMovingToolToRotation(self.implement, tool, dt, MathUtil.clamp(targetRot, tool.rotMin, tool.rotMax)) + ImplementUtil.moveMovingToolToRotation(self.implement, tool, dt, CpMathUtil.clamp(targetRot, tool.rotMin, tool.rotMax)) end function PipeController:movePipeUp(tool, childToolNode, dt) @@ -659,7 +659,7 @@ function PipeController:movePipeUp(tool, childToolNode, dt) end end end - ImplementUtil.moveMovingToolToRotation(self.implement, tool, dt, MathUtil.clamp(targetRot, tool.rotMin, tool.rotMax)) + ImplementUtil.moveMovingToolToRotation(self.implement, tool, dt, CpMathUtil.clamp(targetRot, tool.rotMin, tool.rotMax)) end function PipeController:delete() diff --git a/scripts/courseGenerator/FieldworkCourse.lua b/scripts/courseGenerator/FieldworkCourse.lua index e30f003e4..2bdfbbf71 100644 --- a/scripts/courseGenerator/FieldworkCourse.lua +++ b/scripts/courseGenerator/FieldworkCourse.lua @@ -368,7 +368,7 @@ function FieldworkCourse:findPathToNextRow(boundaryId, rowEnd, rowStart, minDist local headlands = self:_getCachedHeadlands(boundaryId) local headlandWidth = #headlands * self:_getHeadlandWorkingWidth() local usableHeadlandWidth = headlandWidth - (minDistanceFromRowEnd or 0) - local headlandPassNumber = CourseGenerator.clamp(math.floor(usableHeadlandWidth / self:_getHeadlandWorkingWidth()), 1, #headlands) + local headlandPassNumber = CpMathUtil.clamp(math.floor(usableHeadlandWidth / self:_getHeadlandWorkingWidth()), 1, #headlands) local headland = headlands[headlandPassNumber] if headland == nil then return Polyline() diff --git a/scripts/courseGenerator/Util.lua b/scripts/courseGenerator/Util.lua index 5e7aa9e90..ff0026a5c 100644 --- a/scripts/courseGenerator/Util.lua +++ b/scripts/courseGenerator/Util.lua @@ -5,7 +5,3 @@ function CourseGenerator.reverseArray(a) a[i], a[#a - i + 1] = a[#a - i + 1], a[i] end end - -function CourseGenerator.clamp(val, min, max) - return math.min(math.max(val, min), max) -end \ No newline at end of file diff --git a/scripts/courseGenerator/test/mock-GiantsEngine.lua b/scripts/courseGenerator/test/mock-GiantsEngine.lua index 5c42a7563..1ad86d74b 100644 --- a/scripts/courseGenerator/test/mock-GiantsEngine.lua +++ b/scripts/courseGenerator/test/mock-GiantsEngine.lua @@ -1,7 +1,7 @@ -- Mock the Giants engine functions for unit tests local MathUtil = {} -function MathUtil.clamp(val, min, max) +function CpMathUtil.clamp(val, min, max) return math.min(math.max(val, min), max) end diff --git a/scripts/geometry/Vertex.lua b/scripts/geometry/Vertex.lua index 7bbfd0c39..0692f17e6 100644 --- a/scripts/geometry/Vertex.lua +++ b/scripts/geometry/Vertex.lua @@ -117,9 +117,9 @@ function Vertex:calculateProperties(entry, exit) self.dA = CpMathUtil.getDeltaAngle(self.entryHeading, self.exitHeading) -- This is the radius of a circle written between -- entryEdge and exitEdge, which are tangents of the circle, touching them 1 unit away from the vertex - self.unitRadius = 1 / (math.tan(self.dA / 2)) - self.curvature = 1 / self.unitRadius - self.xte = math.abs(1 / math.cos(self.dA / 2)) - 1 + self.unitRadius = CpMathUtil.divide(1, (math.tan(self.dA / 2))) + self.curvature = CpMathUtil.divide(1, self.unitRadius) + self.xte = math.abs(CpMathUtil.divide(1, math.cos(self.dA / 2))) - 1 end end diff --git a/scripts/pathfinder/HybridAStar.lua b/scripts/pathfinder/HybridAStar.lua index b764b91ae..abbe6216f 100644 --- a/scripts/pathfinder/HybridAStar.lua +++ b/scripts/pathfinder/HybridAStar.lua @@ -40,7 +40,7 @@ function coroutine.create(f) end function coroutine.resume(f, ...) - return f(...) + return true, f(...) end --- coroutine.yield is not supported, always returns false @@ -717,7 +717,7 @@ function HybridAStarWithAStarInTheMiddle:init(vehicle, hybridRange, yieldAfter, -- path generation phases self.vehicle = vehicle self.START_TO_MIDDLE = 1 - self.MIDDLE = 2 + self.ASTAR = 2 self.MIDDLE_TO_END = 3 self.ALL_HYBRID = 4 -- start and goal close enough, we only need a single phase with hybrid self.hybridRange = hybridRange @@ -758,7 +758,7 @@ function HybridAStarWithAStarInTheMiddle:start(start, goal, turnRadius, allowRev self.hybridRange = self.hybridRange and self.hybridRange or turnRadius * 3 -- how far is start/goal apart? self.startNode:updateH(self.goalNode, turnRadius) - self.phase = self.MIDDLE + self.phase = self.ASTAR self:debug('Finding fast A* path between start and goal...') self.coroutine = coroutine.create(self.aStarPathfinder.findPath) self.currentPathfinder = self.aStarPathfinder @@ -803,6 +803,7 @@ function HybridAStarWithAStarInTheMiddle:resume(...) if not ok then print(done) printCallstack() + self:debug('Pathfinding failed') self.coroutine = nil return PathfinderResult(true, nil, goalNodeInvalid, self.currentPathfinder.nodes.highestDistance, self.constraints) @@ -818,9 +819,10 @@ function HybridAStarWithAStarInTheMiddle:resume(...) return PathfinderResult(true, nil, goalNodeInvalid, self.currentPathfinder.nodes.highestDistance, self.constraints) end - elseif self.phase == self.MIDDLE then + elseif self.phase == self.ASTAR then self.constraints:resetStrictMode() if not path then + self:debug('fast A*: no path found') return PathfinderResult(true, nil, goalNodeInvalid, self.currentPathfinder.nodes.highestDistance, self.constraints) end diff --git a/scripts/silo/BunkerSiloVehicleController.lua b/scripts/silo/BunkerSiloVehicleController.lua index 3e83f83a8..acd3d6e25 100644 --- a/scripts/silo/BunkerSiloVehicleController.lua +++ b/scripts/silo/BunkerSiloVehicleController.lua @@ -175,7 +175,7 @@ function CpBunkerSiloVehicleController:getFirstLineApproach(numLines, width) elseif dhx > 0 then line = MathUtil.round(dhx / width) end - return MathUtil.clamp(line, 1, numLines) + return CpMathUtil.clamp(line, 1, numLines) end @@ -224,7 +224,7 @@ function CpBunkerSiloVehicleController:isEndReached(node, margin) local x, _, z = localToWorld(node, 0, 0, margin) local dx, dz = unpack(self.drivingTarget[2]) local dist = MathUtil.vector2Length(x - dx, z - dz) - return not self.silo:isPointInSilo(x, z) and dist < 5, MathUtil.clamp(2 * dist, 5, math.huge) + return not self.silo:isPointInSilo(x, z) and dist < 5, CpMathUtil.clamp(2 * dist, 5, math.huge) end return false, math.huge end @@ -435,7 +435,7 @@ function CpBunkerSiloLoaderController:isEndReached(node, margin) local _, _, zOffset = localToLocal(node, AIUtil.getDirectionNode(self.vehicle), 0, 0, 0) local _, _, z = worldToLocal(AIUtil.getDirectionNode(self.vehicle), dx, 0, dz) self:debug("Silo/heap end offset %.1f", z-zOffset) - return z - zOffset < margin, MathUtil.clamp(z * 2, 5, math.huge) + return z - zOffset < margin, CpMathUtil.clamp(z * 2, 5, math.huge) end return false, math.huge end diff --git a/scripts/specializations/CpShovelPositions.lua b/scripts/specializations/CpShovelPositions.lua index e8a9f4b7c..6925a43e8 100644 --- a/scripts/specializations/CpShovelPositions.lua +++ b/scripts/specializations/CpShovelPositions.lua @@ -277,7 +277,7 @@ function CpShovelPositions:controlShovelPosition(dt, targetAngle) local curRot = {} curRot[1], curRot[2], curRot[3] = getRotation(spec.shovelTool.node) local oldShovelRot = curRot[spec.shovelTool.rotationAxis] - local goalAngle = MathUtil.clamp(oldShovelRot + targetAngle, spec.shovelTool.rotMin, spec.shovelTool.rotMax) + local goalAngle = CpMathUtil.clamp(oldShovelRot + targetAngle, spec.shovelTool.rotMin, spec.shovelTool.rotMax) return ImplementUtil.moveMovingToolToRotation(spec.shovelVehicle, spec.shovelTool, dt, goalAngle, CpShovelPositions.getMovingToolMinDiffNeeded(self)) or isDirty @@ -455,7 +455,7 @@ function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, height alpha = math.atan2(i1y - ay, i1z - az) local beta = -math.atan2(i2y - ay, i2z - az) - local a = MathUtil.clamp(oldArmRot - MathUtil.getAngleDifference( + local a = CpMathUtil.clamp(oldArmRot - MathUtil.getAngleDifference( alpha, oldRotRelativeArmRot), armTool.rotMin, armTool.rotMax) if not skipArm then isDirty = ImplementUtil.moveMovingToolToRotation( diff --git a/scripts/util/CpMathUtil.lua b/scripts/util/CpMathUtil.lua index 299da6c94..c23c49c96 100644 --- a/scripts/util/CpMathUtil.lua +++ b/scripts/util/CpMathUtil.lua @@ -307,4 +307,13 @@ function CpMathUtil.angleFromGame(angle) a = 2 * math.pi + a end return a +end + +function CpMathUtil.clamp(val, min, max) + return math.min(math.max(val, min), max) +end + +--- Divide a by b, but instead of throwing an error when b is 0, return math.huge +function CpMathUtil.divide(a, b) + return b == 0 and math.huge or a / b end \ No newline at end of file diff --git a/scripts/util/CpRemainingTime.lua b/scripts/util/CpRemainingTime.lua index f1a14c312..393e2211f 100644 --- a/scripts/util/CpRemainingTime.lua +++ b/scripts/util/CpRemainingTime.lua @@ -83,7 +83,7 @@ function CpRemainingTime:getOptimalSpeed() -- in m/s if speedLimit == math.huge then -- Giants ..., happens when for example the work tool is raised .. return 0 end - return MathUtil.kmhToMps(MathUtil.clamp(speedLimit, 0, fieldSettingSpeed)) + return MathUtil.kmhToMps(CpMathUtil.clamp(speedLimit, 0, fieldSettingSpeed)) end --- Estimate of the course time left with penalties increased. From 0e845a919af0dd2ab1b3075e1184531b5a0bebea Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 17 Nov 2024 10:45:33 -0500 Subject: [PATCH 019/158] fix: motor controller --- scripts/ai/controllers/MotorController.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ai/controllers/MotorController.lua b/scripts/ai/controllers/MotorController.lua index 0007b4d85..112f405c1 100644 --- a/scripts/ai/controllers/MotorController.lua +++ b/scripts/ai/controllers/MotorController.lua @@ -26,7 +26,7 @@ function MotorController:update() return end if not self.settings.fuelSave:getValue() then - if not self.motorSpec.isMotorStarted then + if not self.vehicle:getIsMotorStarted() then self:startMotor() self.vehicle:raiseAIEvent('onAIFieldWorkerContinue', 'onAIImplementContinue') @@ -36,7 +36,7 @@ function MotorController:update() end if self:isFuelSaveDisabled() or self.driveStrategy:getMaxSpeed() > self.speedThreshold then - if not self.motorSpec.isMotorStarted then + if not self.vehicle:getIsMotorStarted() then self:startMotor() self.vehicle:raiseAIEvent('onAIFieldWorkerContinue', 'onAIImplementContinue') @@ -49,7 +49,7 @@ function MotorController:update() self.timerSet = true end if self.timer:get() then - if self.motorSpec.isMotorStarted then + if self.vehicle:getIsMotorStarted() then self.vehicle:raiseAIEvent('onAIFieldWorkerBlock', 'onAIImplementBlock') self:stopMotor() From 72d9c0cc201798f3cfa50064ddfdbfc10cd78f23 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 18 Nov 2024 06:57:44 -0500 Subject: [PATCH 020/158] fix: onStateChange() workaround --- scripts/CpObject.lua | 3 ++- scripts/specializations/CpVehicleSettings.lua | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/CpObject.lua b/scripts/CpObject.lua index 098c14b09..1da8d977a 100644 --- a/scripts/CpObject.lua +++ b/scripts/CpObject.lua @@ -59,7 +59,8 @@ function CpObject(base, init) end return false end - c.__tostring = function (self) + -- TODO 25 deactivated (no leading __) as the for loop, probably pairs() sometimes leads to a stack overflow + c.tostring = function (self) -- Default tostring function for printing all attributes and assigned functions. local str = '[ ' for attribute, value in pairs(self) do diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index dda6b848f..ac53f2da3 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -120,9 +120,15 @@ function CpVehicleSettings:onUpdate() end end +-- TODO 25 These are defined somewhere else, not in Vehicle +Vehicle.STATE_CHANGE_ATTACH = 1 +Vehicle.STATE_CHANGE_DETACH = 2 + --- Changes the sprayer work width on fill type change, as it might depend on the loaded fill type. --- For example Lime and Fertilizer might have a different work width. function CpVehicleSettings:onStateChange(state, data) + -- TODO 25 + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, '%s: onStateChange %s', tostring(state)) local spec = self.spec_cpVehicleSettings if state == Vehicle.STATE_CHANGE_FILLTYPE_CHANGE and self:getIsSynchronized() then local _, hasSprayer = AIUtil.getAllChildVehiclesWithSpecialization(self, Sprayer, nil) From a8090a8fdc49ed63f7e4f2c501a340b63080291a Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 18 Nov 2024 11:52:42 -0500 Subject: [PATCH 021/158] feat: cpGenerateDefaultCourse console command --- scripts/CpGlobalSettings.lua | 2 +- scripts/ai/Markers.lua | 4 +- scripts/ai/ProximitySensor.lua | 55 ++++++++++--------- .../controllers/SowingMachineController.lua | 3 +- scripts/config/VehicleConfigurations.lua | 14 ++--- .../CourseGeneratorInterface.lua | 22 ++++++++ scripts/dev/ConsoleCommands.lua | 13 +++++ scripts/dev/DevHelper.lua | 19 +++---- scripts/gui/CourseDisplay.lua | 4 +- .../specializations/CpAICombineUnloader.lua | 12 ++-- scripts/specializations/CpAIWorker.lua | 4 +- .../CpCourseGeneratorSettings.lua | 2 +- scripts/specializations/CpShovelPositions.lua | 10 ++-- scripts/specializations/CpVehicleSettings.lua | 4 +- 14 files changed, 103 insertions(+), 65 deletions(-) diff --git a/scripts/CpGlobalSettings.lua b/scripts/CpGlobalSettings.lua index 6836a64de..47efaea34 100644 --- a/scripts/CpGlobalSettings.lua +++ b/scripts/CpGlobalSettings.lua @@ -110,7 +110,7 @@ end --------------------------------------------- function CpGlobalSettings:registerConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpSettingsPrintGlobal", + g_consoleCommands:registerConsoleCommand("cpSettingsPrintGlobal", "Prints the global settings or a given setting", "consoleCommandPrintSetting", self) end diff --git a/scripts/ai/Markers.lua b/scripts/ai/Markers.lua index 9f5918833..6e534889d 100644 --- a/scripts/ai/Markers.lua +++ b/scripts/ai/Markers.lua @@ -16,9 +16,9 @@ Markers = {} function Markers.registerConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpFrontAndBackerMarkerCalculate", + g_consoleCommands:registerConsoleCommand("cpFrontAndBackerMarkerCalculate", "Calculates the front and back markers", "consoleCommandReload", Markers) - g_devHelper.consoleCommands:registerConsoleCommand("cpFrontAndBackerMarkerPrintDebug", + g_consoleCommands:registerConsoleCommand("cpFrontAndBackerMarkerPrintDebug", "Print Marker data", "consoleCommandPrintDebug", Markers) end Markers.registerConsoleCommands() diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index c586a03b2..da17ae61c 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -26,7 +26,7 @@ function ProximitySensor:init(node, yRotationDeg, range, height, xOffset, vehicl self.node = node self.xOffset = xOffset self.rotationEnabled = rotationEnabled - local _, _, dz = localToLocal(node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + local _, _, dz = localToLocal(node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) self.angleToDirectionNode = math.abs(math.atan2(xOffset, dz)) CpUtil.debugVehicle(CpDebug.DBG_TRAFFIC, vehicle, 'Proximity sensor dx %.1f, angle %.1f, angle to root %.1f', xOffset, yRotationDeg, math.deg(self.angleToDirectionNode)) @@ -74,7 +74,9 @@ end function ProximitySensor:update() -- already updated in this loop, no need to raycast again - if g_updateLoopIndex == self.lastUpdateLoopIndex then return end + if g_updateLoopIndex == self.lastUpdateLoopIndex then + return + end self.lastUpdateLoopIndex = g_updateLoopIndex -- rotate with the steering angle @@ -88,9 +90,9 @@ function ProximitySensor:update() -- direction (cab is rotated, driver facing to the back of the tractor), so we need getSteeringDirection() -- to compensate for that local correction = self.vehicle:getSteeringDirection() * - ((self.vehicle.rotatedTime / self.xOffset) >= 0 - and self.angleToDirectionNode or - -self.angleToDirectionNode) + ((CpMathUtil.divide(self.vehicle.rotatedTime, self.xOffset)) >= 0 + and self.angleToDirectionNode or + -self.angleToDirectionNode) self:setRotation(CpMathUtil.clamp( self.vehicle.rotatedTime * (2 * self.maxRotation + correction) * self.vehicle:getSteeringDirection(), -2 * self.maxRotation, @@ -112,7 +114,7 @@ function ProximitySensor:update() self.objectId = nil self.hitTerrain = false if self.enabled then - local raycastMask = CollisionFlag.STATIC_OBJECTS + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE raycastClosest(x, y1 + self.height, z, nx, ny, nz, self.range, 'raycastCallback', self, raycastMask) if CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then DebugUtil.drawDebugLine(x, y1 + self.height, z, x + 5 * nx, y1 + self.height + 5 * ny, z + 5 * nz, 0, 1, 0) @@ -160,7 +162,9 @@ function ProximitySensor:getClosestRootVehicle() end function ProximitySensor:showDebugInfo() - if not CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then return end + if not CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then + return + end local text = string.format('%.1f ', self.distanceOfClosestObject) if self.objectId then local object = g_currentMission:getNodeObject(self.objectId) @@ -227,7 +231,7 @@ function ProximitySensorPack:getXOffsets(width, nSensors) local xOffsets = {} -- spread them out evenly across the width local dx = width / nSensors - for xOffset = width / 2 - dx / 2, - width / 2 + dx / 2 - 0.1, - dx do + for xOffset = width / 2 - dx / 2, -width / 2 + dx / 2 - 0.1, -dx do table.insert(xOffsets, xOffset) end return xOffsets @@ -237,7 +241,7 @@ function ProximitySensorPack:adjustForwardPosition() -- are we looking forward local forward = 1 -- if a sensor about in the middle is pointing back, we are looking back - if math.abs(self.directionsDeg[math.max(math.floor(#self.directionsDeg / 2),1)]) > 90 then + if math.abs(self.directionsDeg[math.max(math.floor(#self.directionsDeg / 2), 1)]) > 90 then forward = -1 end local x, y, z = getTranslation(self.node) @@ -287,7 +291,7 @@ end ---@return table|nil closest object ---@return boolean terrain was hit ---@return number average direction of the obstacle in degrees, > 0 right, < 0 left ----@return number +---@return number function ProximitySensorPack:getClosestObjectDistanceAndRootVehicle() -- make sure we have the latest info, the sensors will make sure they only raycast once per loop self:update() @@ -315,7 +319,8 @@ function ProximitySensorPack:getClosestObjectDistanceAndRootVehicle() if closestRootVehicle == self.vehicle then self:adjustForwardPosition() end - return closestDistance, closestRootVehicle, closestObject, hitTerrain, totalDegs / totalWeight, totalDistance / totalWeight + return closestDistance, closestRootVehicle, closestObject, hitTerrain, + CpMathUtil.divide(totalDegs, totalWeight), CpMathUtil.divide(totalDistance, totalWeight) end function ProximitySensorPack:disableRightSide() @@ -334,7 +339,6 @@ function ProximitySensorPack:enableRightSide() end end - function ProximitySensorPack:disableLeftSide() for _, sensor in ipairs(self.sensors) do if sensor:getBaseRotationDeg() > 0 then @@ -357,8 +361,8 @@ ForwardLookingProximitySensorPack = CpObject(ProximitySensorPack) --- Pack looking forward, all sensors are in the middle of the vehicle function ForwardLookingProximitySensorPack:init(vehicle, node, range, height) ProximitySensorPack.init(self, 'forward', vehicle, node, range, height, - {0, 15, 30, 60, 80, -15, -30, -60, -80}, - {0, 0, 0, 0, 0, 0, 0, 0, 0}) + { 0, 15, 30, 60, 80, -15, -30, -60, -80 }, + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }) end ---@class WideForwardLookingProximitySensorPack : ProximitySensorPack @@ -367,7 +371,7 @@ WideForwardLookingProximitySensorPack = CpObject(ProximitySensorPack) --- Pack looking forward, but sensors distributed evenly through the width of the vehicle function WideForwardLookingProximitySensorPack:init(vehicle, node, range, height, width, directionsDeg) CpUtil.debugVehicle(CpDebug.DBG_TRAFFIC, vehicle, 'Creating wide forward proximity sensor %.1fm', width) - directionsDeg = directionsDeg or {10, 8, 5, 3, 0, -3, -5, -8, -10} + directionsDeg = directionsDeg or { 10, 8, 5, 3, 0, -3, -5, -8, -10 } local xOffsets = self:getXOffsets(width, #directionsDeg) ProximitySensorPack.init(self, 'wideForward', vehicle, node, range, height, directionsDeg, xOffsets, true) @@ -379,22 +383,20 @@ WideBackwardLookingProximitySensorPack = CpObject(ProximitySensorPack) --- Pack looking backward, but sensors distributed evenly through the width of the vehicle function WideBackwardLookingProximitySensorPack:init(vehicle, node, range, height, width, rotationEnabled) CpUtil.debugVehicle(CpDebug.DBG_TRAFFIC, vehicle, 'Creating wide backward proximity sensor %.1fm', width) - local directionsDeg = {-190, -188, -185, -183, 180, 183, 185, 188, 190} + local directionsDeg = { -190, -188, -185, -183, 180, 183, 185, 188, 190 } local xOffsets = self:getXOffsets(width, #directionsDeg) ProximitySensorPack.init(self, 'wideBackward', vehicle, node, range, height, directionsDeg, xOffsets, rotationEnabled) end - - ---@class BackwardLookingProximitySensorPack : ProximitySensorPack BackwardLookingProximitySensorPack = CpObject(ProximitySensorPack) function BackwardLookingProximitySensorPack:init(vehicle, node, range, height) CpUtil.debugVehicle(CpDebug.DBG_TRAFFIC, vehicle, 'Creating backward proximity sensor') ProximitySensorPack.init(self, 'backward', vehicle, node, range, height, - {120, 150, 180, -150, -120}, - {0, 0, 0, 0, 0}) + { 120, 150, 180, -150, -120 }, + { 0, 0, 0, 0, 0 }) end ---@class SingleForwardLookingProximitySensorPack : ProximitySensorPack @@ -403,17 +405,16 @@ SingleForwardLookingProximitySensorPack = CpObject(ProximitySensorPack) function SingleForwardLookingProximitySensorPack:init(vehicle, node, range, height) CpUtil.debugVehicle(CpDebug.DBG_TRAFFIC, vehicle, 'Creating single forward proximity sensor') ProximitySensorPack.init(self, 'singleForward', vehicle, node, range, height, - {0}, {0}, false) + { 0 }, { 0 }, false) end - ---@class SingleBackwardLookingProximitySensorPack : ProximitySensorPack SingleBackwardLookingProximitySensorPack = CpObject(ProximitySensorPack) function SingleBackwardLookingProximitySensorPack:init(vehicle, node, range, height) CpUtil.debugVehicle(CpDebug.DBG_TRAFFIC, vehicle, 'Creating single backward proximity sensor') ProximitySensorPack.init(self, 'singleBackward', vehicle, node, range, height, - {180}, {0}, false) + { 180 }, { 0 }, false) end ------------------------------------------------------------------------------------------------------------------------ @@ -448,7 +449,9 @@ end function VerticalProximitySensor:update() -- already updated in this loop, no need to raycast again - if g_updateLoopIndex == self.lastUpdateLoopIndex then return end + if g_updateLoopIndex == self.lastUpdateLoopIndex then + return + end self.lastUpdateLoopIndex = g_updateLoopIndex local x, _, z = localToWorld(self.node, self.xOffset, 0, self.zOffset) @@ -457,9 +460,9 @@ function VerticalProximitySensor:update() self.objectId = nil self.hitTerrain = false if self.enabled then - local raycastMask = CollisionFlag.STATIC_OBJECTS + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE -- straight up from 10 cm above the ground to height - raycastClosest(x, y + self.minHeightAboveGround, z, 0, 1, 0, + raycastClosest(x, y + self.minHeightAboveGround, z, 0, 1, 0, self.height - self.minHeightAboveGround, 'raycastCallback', self, raycastMask) if CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then diff --git a/scripts/ai/controllers/SowingMachineController.lua b/scripts/ai/controllers/SowingMachineController.lua index 34322d61a..1286ac3d1 100644 --- a/scripts/ai/controllers/SowingMachineController.lua +++ b/scripts/ai/controllers/SowingMachineController.lua @@ -28,7 +28,8 @@ function SowingMachineController:update() end if not self.implement:getCanPlantOutsideSeason() then local fruitType = self.sowingMachineSpec.workAreaParameters.seedsFruitType - if fruitType ~= nil and not g_currentMission.growthSystem:canFruitBePlanted(fruitType) then + -- TODO 25 no canFruitBePlanted() in growthSystem + if false and fruitType ~= nil and not g_currentMission.growthSystem:canFruitBePlanted(fruitType) then self:debug("Fruit can't be planted in this season!") self.vehicle:stopCurrentAIJob(AIMessageErrorWrongSeason.new()) end diff --git a/scripts/config/VehicleConfigurations.lua b/scripts/config/VehicleConfigurations.lua index 9072bd0d4..f3cd52b94 100644 --- a/scripts/config/VehicleConfigurations.lua +++ b/scripts/config/VehicleConfigurations.lua @@ -213,25 +213,25 @@ end ----------------------------------------------- function VehicleConfigurations:registerConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsReload", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsReload", "Read custom vehicle configurations", "consoleCommandReload", self) - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintConfigFileNames", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintConfigFileNames", "Prints the config filename of the entered vehicle and implements", "consoleCommandPrintConfigFileNames", self) - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsQuerySingleAttribute", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsQuerySingleAttribute", "Prints the given attribute value for current vehicle and implements", "consoleCommandPrintSingleAttributeValuesForVehicleAndImplements", self) - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsQueryForAllAttributes", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsQueryForAllAttributes", "Prints all attribute values found for vehicle and it's implements", "consoleCommandPrintAllAttributeValuesForVehicleAndImplements", self) - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsListAttributes", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsListAttributes", "Prints all valid attribute names", "consoleCommandPrintAttributeNames", self) - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintAttributes", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintAttributes", "Prints all normal attributes", "consoleCommandPrintAllNormalVehicleConfigurations", self) - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintModAttributes", + g_consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintModAttributes", "Prints all mod name attributes", "consoleCommandPrintAllModNameVehicleConfigurations", self) end diff --git a/scripts/courseGenerator/CourseGeneratorInterface.lua b/scripts/courseGenerator/CourseGeneratorInterface.lua index 57edb774c..56735d828 100644 --- a/scripts/courseGenerator/CourseGeneratorInterface.lua +++ b/scripts/courseGenerator/CourseGeneratorInterface.lua @@ -171,3 +171,25 @@ function CourseGeneratorInterface.generateVineCourse( course:setFieldPolygon(fieldPolygon) return true, course end + +--------------------------------------------- +--- Console Commands +--------------------------------------------- + +function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) + local vehicle = CpUtil.getCurrentVehicle() + local x, _, z = getWorldTranslation(vehicle.rootNode) + local valid, points = g_fieldScanner:findContour(x, z) + local settings = CpUtil.getCurrentVehicle():getCourseGeneratorSettings() + local width, offset, _, _ = WorkWidthUtil.getAutomaticWorkWidthAndOffset(vehicle) + settings.workWidth:refresh() + settings.workWidth:setFloatValue(width) + vehicle:getCpSettings().toolOffsetX:setFloatValue(offset) + settings.numberOfHeadlands:setFloatValue(nHeadlands or 3) + settings.sharpenCorners:setValue(true) + CpUtil.infoVehicle(vehicle, "Generating default course with %d headlands", settings.numberOfHeadlands:getValue()) + local ok, course = CourseGeneratorInterface.generate(points, {x = x, z = z}, vehicle, settings) + if ok then + vehicle:setFieldWorkCourse(course) + end +end \ No newline at end of file diff --git a/scripts/dev/ConsoleCommands.lua b/scripts/dev/ConsoleCommands.lua index 4b2067f08..2beceed22 100644 --- a/scripts/dev/ConsoleCommands.lua +++ b/scripts/dev/ConsoleCommands.lua @@ -21,6 +21,7 @@ CpConsoleCommands.commands = { { 'cpFreeze', 'Freeze the CP driver', 'cpFreeze' }, { 'cpUnfreeze', 'Unfreeze the CP driver', 'cpUnfreeze' }, { 'cpStopAll', 'Stops all cp drivers', 'cpStopAll' }, + { 'cpGenerateDefaultCourse', '[number of headlands ] Generate a default course', 'cpGenerateDefaultCourse' }, } function CpConsoleCommands:init(devHelper) @@ -247,6 +248,11 @@ function CpConsoleCommands:cpUnfreeze() CpUtil.getCurrentVehicle():unfreezeCp() end +function CpConsoleCommands:cpGenerateDefaultCourse(nHeadlands) + CpUtil.info('Generating default course with %s headlands', nHeadlands) + CourseGeneratorInterface.generateDefaultCourse(nHeadlands) +end + function CpConsoleCommands:cpStopAll() for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do if vehicle.getIsAIActive and vehicle:getIsAIActive() then @@ -254,3 +260,10 @@ function CpConsoleCommands:cpStopAll() end end end + +-- when reloading, clean up before re-instantiation +if g_consoleCommands then + g_consoleCommands:delete() +end + +g_consoleCommands = CpConsoleCommands() \ No newline at end of file diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 14d71dfd0..38a879a67 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -28,11 +28,6 @@ DevHelper = CpObject() function DevHelper:init() self.data = {} self.isEnabled = false - self.consoleCommands = CpConsoleCommands(self) -end - -function DevHelper:delete() - self.consoleCommands:delete() end function DevHelper:debug(...) @@ -166,9 +161,17 @@ function DevHelper:keyEvent(unicode, sym, modifier, isDown) elseif bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_g then local valid, points = g_fieldScanner:findContour(self.data.x, self.data.z) self:debug('Generate course') + + local vehicle = CpUtil.getCurrentVehicle() + local settings = CpUtil.getCurrentVehicle():getCourseGeneratorSettings() + local width, offset, _, _ = WorkWidthUtil.getAutomaticWorkWidthAndOffset(vehicle) + settings.workWidth:refresh() + settings.workWidth:setFloatValue(width) + vehicle:getCpSettings().toolOffsetX:setFloatValue(offset) + local status, ok, course = CourseGeneratorInterface.generate(points, {x = self.data.x, z = self.data.z}, - 0, 6, 6, 1, true) + vehicle, settings) if ok then self.course = course end @@ -312,9 +315,5 @@ function DevHelper:showDriveData() end -- make sure to recreate the global dev helper whenever this script is (re)loaded -if g_devHelper then - g_devHelper:delete() -end - g_devHelper = DevHelper() diff --git a/scripts/gui/CourseDisplay.lua b/scripts/gui/CourseDisplay.lua index 2c96147cf..7273a4273 100644 --- a/scripts/gui/CourseDisplay.lua +++ b/scripts/gui/CourseDisplay.lua @@ -113,8 +113,8 @@ function SimpleSign:setWaypointData(wp, np) local ny = self:getHeight(np.x, np.z) local yRot, xRot, dist = 0, 0, 0 dist = MathUtil.vector3Length(np.x - wp.x, ny - y, np.z - wp.z) - local dx, dy, dz = MathUtil.vector3Normalize(np.x - wp.x, ny - y, np.z - wp.z) - if dx == dx and dz == dz then + if dist > 0 then + local dx, dy, dz = MathUtil.vector3Normalize(np.x - wp.x, ny - y, np.z - wp.z) xRot = -math.sin((ny-y)/dist) yRot = MathUtil.getYRotationFromDirection(dx, dz) end diff --git a/scripts/specializations/CpAICombineUnloader.lua b/scripts/specializations/CpAICombineUnloader.lua index 657c449c4..d91f8414c 100644 --- a/scripts/specializations/CpAICombineUnloader.lua +++ b/scripts/specializations/CpAICombineUnloader.lua @@ -17,17 +17,17 @@ function CpAICombineUnloader.initSpecialization() CpJobParameters.registerXmlSchema(schema, key..".cpJob") --- Registers pipe controller measurement test and debug functions - g_devHelper.consoleCommands:registerConsoleCommand("cpPipeControllerMeasurePipe", + g_consoleCommands:registerConsoleCommand("cpPipeControllerMeasurePipe", "Measures the pipe properties while unfolded.", "consoleCommandMeasurePipeProperties", CpAICombineUnloader) - g_devHelper.consoleCommands:registerConsoleCommand("cpPipeControllerInstantUnfoldPipe", + g_consoleCommands:registerConsoleCommand("cpPipeControllerInstantUnfoldPipe", "Instant unfold of the pipe", "consoleCommandInstantUnfoldPipe", CpAICombineUnloader) - g_devHelper.consoleCommands:registerConsoleCommand("cpPipeControllerInstantFoldPipeAndImplement", + g_consoleCommands:registerConsoleCommand("cpPipeControllerInstantFoldPipeAndImplement", "Instant fold of the pipe + implement", "consoleCommandInstantFoldPipeAndImplement", CpAICombineUnloader) - g_devHelper.consoleCommands:registerConsoleCommand("cpPipeControllerDebugFoldablePipe", + g_consoleCommands:registerConsoleCommand("cpPipeControllerDebugFoldablePipe", "Debug for foldable pipes", "consoleCommandDebugFoldablePipe", CpAICombineUnloader) - g_devHelper.consoleCommands:registerConsoleCommand("cpPipeControllerSetFoldTime", + g_consoleCommands:registerConsoleCommand("cpPipeControllerSetFoldTime", "Debug for setting foldable pipe time", "consoleCommandSetFoldTime", CpAICombineUnloader) - g_devHelper.consoleCommands:registerConsoleCommand("cpPipeControllerToggleMoveablePipe", + g_consoleCommands:registerConsoleCommand("cpPipeControllerToggleMoveablePipe", "Enables the moveable pipe feature", "consoleCommandToggleMoveablePipe", CpAICombineUnloader) end diff --git a/scripts/specializations/CpAIWorker.lua b/scripts/specializations/CpAIWorker.lua index 48fa2ccb5..5b126d93e 100644 --- a/scripts/specializations/CpAIWorker.lua +++ b/scripts/specializations/CpAIWorker.lua @@ -549,10 +549,10 @@ end --------------------------------------------- function CpAIWorker.registerConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleOnWorkStartTest", + g_consoleCommands:registerConsoleCommand("cpVehicleOnWorkStartTest", "Raise the field work start event.", "consoleCommandRaiseWorkStart", CpAIWorker) - g_devHelper.consoleCommands:registerConsoleCommand("cpSettingsPrintJob", + g_consoleCommands:registerConsoleCommand("cpSettingsPrintJob", "Prints the current job parameters", "consoleCommandPrintCurrentSelectedJobParameters", CpAIWorker) --- TODO: Adding functions to execute the lowering, raising and fieldwork end events. diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua index 351f3f120..3dbeab48f 100644 --- a/scripts/specializations/CpCourseGeneratorSettings.lua +++ b/scripts/specializations/CpCourseGeneratorSettings.lua @@ -313,7 +313,7 @@ end --------------------------------------------- function CpCourseGeneratorSettings.registerConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpSettingsPrintGenerator", + g_consoleCommands:registerConsoleCommand("cpSettingsPrintGenerator", "Prints the course generator settings or a given setting", "consoleCommandPrintSetting", CpCourseGeneratorSettings) end diff --git a/scripts/specializations/CpShovelPositions.lua b/scripts/specializations/CpShovelPositions.lua index 6925a43e8..aa3bc0979 100644 --- a/scripts/specializations/CpShovelPositions.lua +++ b/scripts/specializations/CpShovelPositions.lua @@ -643,19 +643,19 @@ end -------------------------------------------- function CpShovelPositions.initConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpShovelPositionsPrintShovelDebug", + g_consoleCommands:registerConsoleCommand("cpShovelPositionsPrintShovelDebug", "Prints debug information for the shovel", "consoleCommandPrintShovelDebug", CpShovelPositions) - g_devHelper.consoleCommands:registerConsoleCommand("cpShovelPositionsSetState", + g_consoleCommands:registerConsoleCommand("cpShovelPositionsSetState", "Set's the current shovel state", "consoleCommandSetShovelState", CpShovelPositions) - g_devHelper.consoleCommands:registerConsoleCommand("cpShovelPositionsSetArmLimit", + g_consoleCommands:registerConsoleCommand("cpShovelPositionsSetArmLimit", "Set's the arm max limit", "consoleCommandSetPreUnloadArmLimit", CpShovelPositions) - g_devHelper.consoleCommands:registerConsoleCommand('cpShovelPositionsSetMinimalUnloadHeight', + g_consoleCommands:registerConsoleCommand('cpShovelPositionsSetMinimalUnloadHeight', 'Sets the minimal unload height to a fixed value', 'consoleCommandSetMinimalUnloadHeight', CpShovelPositions) - g_devHelper.consoleCommands:registerConsoleCommand('cpShovelPositionsMeasureAndSetMinUnloadHeight', + g_consoleCommands:registerConsoleCommand('cpShovelPositionsMeasureAndSetMinUnloadHeight', 'Measures and sets the minimal unload height', 'consoleCommandMeasureAndSetMinimalUnloadHeight', CpShovelPositions) end diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index ac53f2da3..c0b9edf81 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -128,7 +128,7 @@ Vehicle.STATE_CHANGE_DETACH = 2 --- For example Lime and Fertilizer might have a different work width. function CpVehicleSettings:onStateChange(state, data) -- TODO 25 - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, '%s: onStateChange %s', tostring(state)) + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, 'onStateChange %s', tostring(state)) local spec = self.spec_cpVehicleSettings if state == Vehicle.STATE_CHANGE_FILLTYPE_CHANGE and self:getIsSynchronized() then local _, hasSprayer = AIUtil.getAllChildVehiclesWithSpecialization(self, Sprayer, nil) @@ -484,7 +484,7 @@ end --------------------------------------------- function CpVehicleSettings.registerConsoleCommands() - g_devHelper.consoleCommands:registerConsoleCommand("cpSettingsPrintVehicle", + g_consoleCommands:registerConsoleCommand("cpSettingsPrintVehicle", "Prints the vehicle settings or a given setting", "consoleCommandPrintSetting", CpVehicleSettings) end From fb70f9a8b9f9b2b0195bfcb225f1696c3b8b7f12 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 18 Nov 2024 12:25:31 -0500 Subject: [PATCH 022/158] fix: game exit crash --- Courseplay.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Courseplay.lua b/Courseplay.lua index 6d7c8e7db..d5ccc2c6e 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -123,7 +123,7 @@ function Courseplay:deleteMap() g_courseEditor:delete() BufferedCourseDisplay.deleteBuffer() g_signPrototypes:delete() - g_devHelper:delete() + g_consoleCommands:delete() end function Courseplay:setupGui() From 75ac238a0630c1dfe461921cc3ef7840c28c7e4b Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Tue, 19 Nov 2024 07:59:15 -0500 Subject: [PATCH 023/158] fix: devhelper console command --- scripts/dev/ConsoleCommands.lua | 5 ++--- scripts/field/FieldScanner.lua | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/dev/ConsoleCommands.lua b/scripts/dev/ConsoleCommands.lua index 2beceed22..cf1b640d3 100644 --- a/scripts/dev/ConsoleCommands.lua +++ b/scripts/dev/ConsoleCommands.lua @@ -24,8 +24,7 @@ CpConsoleCommands.commands = { { 'cpGenerateDefaultCourse', '[number of headlands ] Generate a default course', 'cpGenerateDefaultCourse' }, } -function CpConsoleCommands:init(devHelper) - self.devHelper = devHelper +function CpConsoleCommands:init() self:registerConsoleCommands() self.additionalCommands = {} end @@ -212,7 +211,7 @@ function CpConsoleCommands.restoreVehiclePosition(vehicle) end function CpConsoleCommands:cpToggleDevHelper() - self.devHelper:toggle() + g_devHelper:toggle() end function CpConsoleCommands:cpSaveAllFields() diff --git a/scripts/field/FieldScanner.lua b/scripts/field/FieldScanner.lua index 9db016dcd..ee6dd210d 100644 --- a/scripts/field/FieldScanner.lua +++ b/scripts/field/FieldScanner.lua @@ -158,7 +158,7 @@ function FieldScanner:findContour(x, z) -- don't start exactly at yRot 0 as folks tend to put the starting point (and so the probe) -- near the edge of the field. On rectangular fields, this may lead to finding the edge very -- close to the corner which then again, screws up our corner detection. - -- This pi / 7 reduces the likelyhood of ending up in a corner. + -- This pi / 7 reduces the likelihood of ending up in a corner. local probe = CpUtil.createNode('FieldScannerProbe', x, z, math.pi / 7) if not CpFieldUtil.isNodeOnField(probe) then self:debug('%.1f/%.1f is not on a field, can\'t start scanning here', x, z) From df55b853ef38efa7898445900bdc568d482ac623 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Tue, 19 Nov 2024 18:44:14 -0500 Subject: [PATCH 024/158] fix: island detection within the field boundary --- scripts/courseGenerator/Center.lua | 7 ++++--- scripts/courseGenerator/Island.lua | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/courseGenerator/Center.lua b/scripts/courseGenerator/Center.lua index 349afebbb..92606a4fe 100644 --- a/scripts/courseGenerator/Center.lua +++ b/scripts/courseGenerator/Center.lua @@ -12,16 +12,17 @@ local Center = CpObject() ---@param headland CourseGenerator.Headland|nil the innermost headland if exists ---@param startLocation Vector location of the vehicle before it starts working on the center. ---@param bigIslands CourseGenerator.Island[] islands too big to circle -function Center:init(context, boundary, headland, startLocation, bigIslands) +function Center:init(context, boundary, headland, startLocation, bigIslands, noVirtualHeadland) self.logger = Logger('Center', Logger.level.debug) self.context = context if headland == nil then -- if there are no headlands, we generate a virtual one, from the field boundary -- so using this later is equivalent of having an actual headland -- using the nominal (without overlap) working the width for the headland, since the rows adjacent to the - -- headland must not extend beyond the field boundary + -- headland must not extend beyond the field boundary, unless we don't want to have a virtual headland + -- and want to use the actual field boundary instead, such as when detecting islands local virtualHeadland = CourseGenerator.FieldworkCourseHelper.createVirtualHeadland(boundary, self.context.headlandClockwise, - self.context.workingWidth) + noVirtualHeadland and 0 or self.context.workingWidth) if self.context.sharpenCorners then virtualHeadland:sharpenCorners(self.context.turningRadius) end diff --git a/scripts/courseGenerator/Island.lua b/scripts/courseGenerator/Island.lua index d34b1f7ac..66fd285e3 100644 --- a/scripts/courseGenerator/Island.lua +++ b/scripts/courseGenerator/Island.lua @@ -183,7 +183,7 @@ function Island.findIslands(field) context:setAutoRowAngle(false):setRowAngle(0):setRowWaypointDistance(Island.gridSpacing) context:_setGenerateBlocksOnly() local boundary = CourseGenerator.FieldworkCourseHelper.createUsableBoundary(context.field:getBoundary(), context.headlandClockwise) - local center = CourseGenerator.Center(context, boundary, nil, context.startLocation, {}) + local center = CourseGenerator.Center(context, boundary, nil, context.startLocation, {}, true) center:generate() local islandVertices = {} for _, b in ipairs(center:getBlocks()) do From c9a7ace38e1b3e24bfd120e68fc9cf35e4e758f7 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Wed, 20 Nov 2024 06:51:56 -0500 Subject: [PATCH 025/158] feat: save all fields with game field polygons --- scripts/courseGenerator/Center.lua | 3 +- scripts/courseGenerator/CourseGenerator.lua | 13 ++-- scripts/courseGenerator/Field.lua | 2 + scripts/courseGenerator/FieldworkContext.lua | 5 ++ scripts/courseGenerator/Headland.lua | 13 ++-- scripts/courseGenerator/Island.lua | 3 +- scripts/dev/DevHelper.lua | 4 +- scripts/field/CpFieldUtil.lua | 76 ++++++++++---------- 8 files changed, 69 insertions(+), 50 deletions(-) diff --git a/scripts/courseGenerator/Center.lua b/scripts/courseGenerator/Center.lua index 92606a4fe..838c3c214 100644 --- a/scripts/courseGenerator/Center.lua +++ b/scripts/courseGenerator/Center.lua @@ -12,6 +12,7 @@ local Center = CpObject() ---@param headland CourseGenerator.Headland|nil the innermost headland if exists ---@param startLocation Vector location of the vehicle before it starts working on the center. ---@param bigIslands CourseGenerator.Island[] islands too big to circle +---@param noVirtualHeadland boolean if true, do not generate a virtual headland, just use the boundary as is function Center:init(context, boundary, headland, startLocation, bigIslands, noVirtualHeadland) self.logger = Logger('Center', Logger.level.debug) self.context = context @@ -27,7 +28,6 @@ function Center:init(context, boundary, headland, startLocation, bigIslands, noV virtualHeadland:sharpenCorners(self.context.turningRadius) end self.headlandPolygon = virtualHeadland:getPolygon() - CourseGenerator.addDebugPolyline(self.headlandPolygon, { 1, 1, 0, 0.5 }) self.headland = virtualHeadland self.mayOverlapHeadland = false else @@ -35,6 +35,7 @@ function Center:init(context, boundary, headland, startLocation, bigIslands, noV self.headland = headland self.mayOverlapHeadland = true end + CourseGenerator.addDebugPolyline(self.headlandPolygon, {0, 0, 1, 0.2}) self.useBaselineEdge = self.context.useBaselineEdge self.boundary = boundary self.startLocation = startLocation diff --git a/scripts/courseGenerator/CourseGenerator.lua b/scripts/courseGenerator/CourseGenerator.lua index 6f8411d4a..410a985a6 100644 --- a/scripts/courseGenerator/CourseGenerator.lua +++ b/scripts/courseGenerator/CourseGenerator.lua @@ -133,8 +133,9 @@ function CourseGenerator.addDebugPolyline(p, color) end lastDebugPolylineAdded = g_time end - p.debugColor = color - table.insert(CourseGenerator.debugPolylines, p) + local debugPolyline = p:clone() + debugPolyline.debugColor = color + table.insert(CourseGenerator.debugPolylines, debugPolyline) end --- Draw debug polylines in the game @@ -147,9 +148,11 @@ function CourseGenerator.drawDebugPolylines() for i = 1, #p - 1 do local v1 = p[i] local v2 = p[i + 1] - local y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, v1.x, 0, -v1.y) - local y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, v2.x, 0, -v2.y) - DebugUtil.drawDebugLine(v1.x, y1 + 3.5, -v1.y, v2.x, y2 + 3.5, -v2.y, color[1], color[2], color[3]) + if v1.x and v1.y and v2.x and v2.y then + local y1 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, v1.x, 0, -v1.y) + local y2 = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, v2.x, 0, -v2.y) + DebugUtil.drawDebugLine(v1.x, y1 + 3.5, -v1.y, v2.x, y2 + 3.5, -v2.y, color[1], color[2], color[3]) + end end end end diff --git a/scripts/courseGenerator/Field.lua b/scripts/courseGenerator/Field.lua index 82a492f23..98bb6feb1 100644 --- a/scripts/courseGenerator/Field.lua +++ b/scripts/courseGenerator/Field.lua @@ -17,6 +17,8 @@ function Field:init(id, num, boundary) self.islands = {} if boundary then self.boundary:calculateProperties() + -- Giants field polygons are usually just the corner vertices, our generator likes many vertices... + self.boundary:splitEdges(CourseGenerator.cMaxEdgeLength) end end diff --git a/scripts/courseGenerator/FieldworkContext.lua b/scripts/courseGenerator/FieldworkContext.lua index 60941964a..b089d2dae 100644 --- a/scripts/courseGenerator/FieldworkContext.lua +++ b/scripts/courseGenerator/FieldworkContext.lua @@ -229,6 +229,7 @@ end ---@param w number function FieldworkContext:setHeadlandWorkingWidth(w) self.headlandWorkingWidth = w + return self end ---@return number width of a headland pass in meters @@ -247,6 +248,7 @@ end --- no need for a CPU intensive, very long running block sequencing. function FieldworkContext:_setGenerateBlocksOnly() self.generateBlocksOnly = true + return self end function FieldworkContext:_generateBlocksOnly() @@ -279,6 +281,7 @@ end ---@param w number function FieldworkContext:setCenterRowSpacing(w) self.centerRowSpacing = w + return self end ---@return number distance between two adjacent up/down rows. @@ -289,6 +292,7 @@ end ---@see Row.adjustLength() function FieldworkContext:setCenterRowWidthForAdjustment(width) self.centerRowWidthForAdjustment = width + return self end --- Width of the row to use when adjusting a row length for full coverage where it meets the headland at an angle @@ -300,6 +304,7 @@ end ---@see Row.adjustLength() function FieldworkContext:setHeadlandWidthForAdjustment(width) self.headlandWidthForAdjustment = width + return self end --- Width of the headland to use when adjusting a row length for full coverage where it meets the headland at an angle diff --git a/scripts/courseGenerator/Headland.lua b/scripts/courseGenerator/Headland.lua index dd85eaa78..6142b2a5d 100644 --- a/scripts/courseGenerator/Headland.lua +++ b/scripts/courseGenerator/Headland.lua @@ -21,11 +21,16 @@ function Headland:init(basePolygon, clockwise, passNumber, width, outward, mustN self.logger = Logger('Headland ' .. passNumber or '') self.clockwise = clockwise self.passNumber = passNumber - self.logger:debug('start generating, base clockwise %s, desired clockwise %s, width %.1f, outward: %s', - basePolygon:isClockwise(), self.clockwise, width, outward) self.offsetVector = CourseGenerator.FieldworkCourseHelper.getOffsetVectorForHeadland(clockwise, outward) - ---@type Polygon - self.polygon = CourseGenerator.Offset.generate(basePolygon, self.offsetVector, width) + if width == 0 then + self.logger:debug('cloning base polygon as width 0, base clockwise %s, desired clockwise %s', + basePolygon:isClockwise(), self.clockwise) + self.polygon = basePolygon:clone() + else + self.logger:debug('start generating, base clockwise %s, desired clockwise %s, width %.1f, outward: %s', + basePolygon:isClockwise(), self.clockwise, width, outward) + self.polygon = CourseGenerator.Offset.generate(basePolygon:clone(), self.offsetVector, width) + end if self.polygon then self.polygon:calculateProperties() self.polygon:ensureMaximumEdgeLength(CourseGenerator.cMaxEdgeLength) diff --git a/scripts/courseGenerator/Island.lua b/scripts/courseGenerator/Island.lua index 66fd285e3..be237a978 100644 --- a/scripts/courseGenerator/Island.lua +++ b/scripts/courseGenerator/Island.lua @@ -181,9 +181,10 @@ function Island.findIslands(field) Island.logger:debug('Generating grid for field with grid spacing %.1f', Island.gridSpacing) local context = CourseGenerator.FieldworkContext(field, Island.gridSpacing, 5, 0) context:setAutoRowAngle(false):setRowAngle(0):setRowWaypointDistance(Island.gridSpacing) - context:_setGenerateBlocksOnly() + context:_setGenerateBlocksOnly():setSharpenCorners(false) local boundary = CourseGenerator.FieldworkCourseHelper.createUsableBoundary(context.field:getBoundary(), context.headlandClockwise) local center = CourseGenerator.Center(context, boundary, nil, context.startLocation, {}, true) + center:generate() local islandVertices = {} for _, b in ipairs(center:getBlocks()) do diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 38a879a67..d3b6c33ec 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -74,11 +74,9 @@ function DevHelper:update() self.data.hasFruit, self.data.fruitValue, self.data.fruit = PathfinderUtil.hasFruit(self.data.x, self.data.z, 1, 1) - self.data.landId = CpFieldUtil.getFieldIdAtWorldPosition(self.data.x, self.data.z) + self.data.fieldId = CpFieldUtil.getFieldIdAtWorldPosition(self.data.x, self.data.z) --self.data.owned = PathfinderUtil.isWorldPositionOwned(self.data.x, self.data.z) self.data.farmlandId = g_farmlandManager:getFarmlandIdAtWorldPosition(self.data.x, self.data.z) - self.data.farmland = g_farmlandManager:getFarmlandAtWorldPosition(self.data.x, self.data.z) --- self.data.fieldAreaPercent = 100 * self.fieldArea / self.totalFieldArea local y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, self.data.x, self.data.y, self.data.z) self.data.isOnField, self.data.densityBits = FSDensityMapUtil.getFieldDataAtWorldPosition(self.data.x, y, self.data.z) diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index fbf65f91d..55d405b78 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -31,8 +31,7 @@ function CpFieldUtil.isOnField(x, z, fieldId) end function CpFieldUtil.initFieldMod() - local groundTypeMapId, groundTypeFirstChannel, groundTypeNumChannels = - g_currentMission.fieldGroundSystem:getDensityMapData(FieldDensityMap.GROUND_TYPE) + local groundTypeMapId, groundTypeFirstChannel, groundTypeNumChannels = g_currentMission.fieldGroundSystem:getDensityMapData(FieldDensityMap.GROUND_TYPE) CpFieldUtil.groundTypeModifier = DensityMapModifier.new(groundTypeMapId, groundTypeFirstChannel, groundTypeNumChannels, g_currentMission.terrainRootNode) CpFieldUtil.groundTypeFilter = DensityMapFilter.new(CpFieldUtil.groundTypeModifier) @@ -62,54 +61,49 @@ function CpFieldUtil.getFieldNumUnderVehicle(vehicle) return CpFieldUtil.getFieldNumUnderNode(vehicle.rootNode) end ---- Returns a field ID for a position. This really is the ID of the first field which is ---- on the same farmland where the given position is. The farmland is what you can buy in the game, ---- including the area around an actual field, and may extend well beyond the actual field. ---- So do not use this the determine the field ID of a given position, as this does not guarantee ---- that the position is actually on a field. -function CpFieldUtil.getFieldIdAtWorldPosition(posX, posZ) +--- Returns a field for a position. Looks like in FS25, there is a one-to-one mapping between field and farmland. +function CpFieldUtil.getFieldAtWorldPosition(posX, posZ) local farmland = g_farmlandManager:getFarmlandAtWorldPosition(posX, posZ) - if farmland ~= nil then - local fieldMapping = g_fieldManager.farmlandIdFieldMapping[farmland.id] - if fieldMapping ~= nil and fieldMapping[1] ~= nil then - return fieldMapping[1].fieldId - end + if farmland and farmland:getField() then + return farmland:getField() + else + return nil end - return 0 end +--- Returns a field ID for a position, 0 if no field ID found +function CpFieldUtil.getFieldIdAtWorldPosition(posX, posZ) + local field = CpFieldUtil.getFieldAtWorldPosition(posX, posZ) + return field and field:getId() or 0 +end function CpFieldUtil.saveAllFields() local fileName = string.format('%s/cpFields.xml', g_Courseplay.debugPrintDir) local xmlFile = createXMLFile("cpFields", fileName, "CPFields"); if xmlFile and xmlFile ~= 0 then for _, field in pairs(g_fieldManager:getFields()) do - local valid, points = g_fieldScanner:findContour(field.posX, field.posZ) - if valid then - local key = ("CPFields.field(%d)"):format(field.fieldId); - setXMLInt(xmlFile, key .. '#fieldNum', field.fieldId); - setXMLInt(xmlFile, key .. '#numPoints', #points); - for i, point in ipairs(points) do - setXMLString(xmlFile, key .. (".point%d#pos"):format(i), ("%.2f %.2f %.2f"):format(point.x, point.y, point.z)) - end - local islandNodes = CourseGenerator.Island.findIslands( - CourseGenerator.Field(field.fieldId, field.fieldId, Polygon(CpMathUtil.pointsFromGameInPlace(points)))) - CpMathUtil.pointsToGameInPlace(islandNodes) - for i, islandNode in ipairs(islandNodes) do - setXMLString(xmlFile, key .. ( ".islandNode%d#pos"):format( i ), ("%.2f %2.f"):format( islandNode.x, islandNode.z )) - end - CpUtil.info('Field %d saved', field.fieldId) - else - CpUtil.info('Field %d could not be saved', field.fieldId) + local points = CpFieldUtil.getFieldPolygon(field) + local key = ("CPFields.field(%s)"):format(field:getId()); + setXMLInt(xmlFile, key .. '#fieldNum', field:getId()); + setXMLInt(xmlFile, key .. '#numPoints', #points); + for i, point in ipairs(points) do + setXMLString(xmlFile, key .. (".point%d#pos"):format(i), ("%.2f %.2f %.2f"):format(point.x, point.y, point.z)) end + local islandNodes = CourseGenerator.Island.findIslands( + CourseGenerator.Field(field:getId(), field:getId(), Polygon(CpMathUtil.pointsFromGameInPlace(points)))) + CpMathUtil.pointsToGameInPlace(islandNodes) + for i, islandNode in ipairs(islandNodes) do + setXMLString(xmlFile, key .. (".islandNode%d#pos"):format(i), ("%.2f %2.f"):format(islandNode.x, islandNode.z)) + end + CpUtil.info('Field %s saved', field:getId()) end saveXMLFile(xmlFile); delete(xmlFile); - + CpUtil.info('Saved all fields to %s', fileName) else - CpUtil.info("Error: field could not be saved to " , g_Courseplay.debugPrintDir); - end; + CpUtil.info("Error: field could not be saved to ", g_Courseplay.debugPrintDir); + end ; end function CpFieldUtil.initializeFieldMod() @@ -124,8 +118,8 @@ function CpFieldUtil.isField(x, z, widthX, widthZ) end widthX = widthX or 0.5 widthZ = widthZ or 0.5 - local startWorldX, startWorldZ = x, z - local widthWorldX, widthWorldZ = x - widthX, z - widthZ + local startWorldX, startWorldZ = x, z + local widthWorldX, widthWorldZ = x - widthX, z - widthZ local heightWorldX, heightWorldZ = x + widthX, z + widthZ CpFieldUtil.fieldMod.modifier:setParallelogramWorldCoords(startWorldX, startWorldZ, widthWorldX, widthWorldZ, heightWorldX, heightWorldZ, "ppp") @@ -136,6 +130,16 @@ function CpFieldUtil.isField(x, z, widthX, widthZ) return isField, area, totalArea end +function CpFieldUtil.getFieldPolygon(field) + local unpackedVertices = field:getDensityMapPolygon():getVerticesList() + local vertices = {} + for i = 1, #unpackedVertices, 2 do + local x, z = unpackedVertices[i], unpackedVertices[i + 1] + table.insert(vertices, { x = x, y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z), z = z }) + end + return vertices +end + --- Get the field polygon (field edge vertices) at the world position. --- If there is also a custom field at the position it may return that, depending on the user's preference set. ---@return {x, y, z}[], boolean the field polygon, nil if not on field. True if a custom field was selected From 045d90de738113f82f3b56376d76f6fcbbbf3332 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Wed, 20 Nov 2024 07:09:27 -0500 Subject: [PATCH 026/158] feat: cpGenerateDefaultCourse uses the Giants field polygon --- scripts/ai/AIDriveStrategyCombineCourse.lua | 4 ++-- scripts/courseGenerator/CourseGeneratorInterface.lua | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index f80d1ce72..338006c13 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -794,9 +794,9 @@ function AIDriveStrategyCombineCourse:estimateDistanceUntilFull(ix) self.fillLevelAtLastWaypoint = fillLevel end local litersUntilFull = capacity - fillLevel - local dUntilFull = litersUntilFull / self.litersPerMeter + local dUntilFull = CpMathUtil.divide(litersUntilFull, self.litersPerMeter) local litersUntilCallUnloader = capacity * self.settings.callUnloaderPercent:getValue() / 100 - fillLevel - local dUntilCallUnloader = litersUntilCallUnloader / self.litersPerMeter + local dUntilCallUnloader = CpMathUtil.divide(litersUntilCallUnloader, self.litersPerMeter) self.waypointIxWhenFull = self.course:getNextWaypointIxWithinDistance(ix, dUntilFull) or self.course:getNumberOfWaypoints() local wpDistance self.waypointIxWhenCallUnloader, wpDistance = self.course:getNextWaypointIxWithinDistance(ix, dUntilCallUnloader) diff --git a/scripts/courseGenerator/CourseGeneratorInterface.lua b/scripts/courseGenerator/CourseGeneratorInterface.lua index 56735d828..48a647ff3 100644 --- a/scripts/courseGenerator/CourseGeneratorInterface.lua +++ b/scripts/courseGenerator/CourseGeneratorInterface.lua @@ -179,7 +179,12 @@ end function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) local vehicle = CpUtil.getCurrentVehicle() local x, _, z = getWorldTranslation(vehicle.rootNode) - local valid, points = g_fieldScanner:findContour(x, z) + --local valid, points = g_fieldScanner:findContour(x, z) + local field = CpFieldUtil.getFieldAtWorldPosition(x, z) + if field == nil then + CpUtil.infoVehicle(vehicle, "Not on a field, can't generate") + return + end local settings = CpUtil.getCurrentVehicle():getCourseGeneratorSettings() local width, offset, _, _ = WorkWidthUtil.getAutomaticWorkWidthAndOffset(vehicle) settings.workWidth:refresh() @@ -188,7 +193,7 @@ function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) settings.numberOfHeadlands:setFloatValue(nHeadlands or 3) settings.sharpenCorners:setValue(true) CpUtil.infoVehicle(vehicle, "Generating default course with %d headlands", settings.numberOfHeadlands:getValue()) - local ok, course = CourseGeneratorInterface.generate(points, {x = x, z = z}, vehicle, settings) + local ok, course = CourseGeneratorInterface.generate(CpFieldUtil.getFieldPolygon(field), {x = x, z = z}, vehicle, settings) if ok then vehicle:setFieldWorkCourse(course) end From 37965e0c10feb7e31d31d4ca128f9a4752b6a5c0 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 21 Nov 2024 08:05:42 -0500 Subject: [PATCH 027/158] fix: load assigned courses Removed counting the vehicles in the assigned courses xml, as it does not seem to be necessary and looks like calling iterate on the same file a second time throws an error in the xml lib. --- Courseplay.lua | 2 - scripts/Course.lua | 17 ++----- .../courseManager/AssignedCoursesManager.lua | 51 +++++++++---------- scripts/specializations/CpCourseManager.lua | 3 -- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index d5ccc2c6e..805849419 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -108,8 +108,6 @@ function Courseplay:loadMap(filename) if self.xmlFile == nil then return end self.globalSettings:loadFromXMLFile(self.xmlFile, g_Courseplay.xmlKey) self.xmlFile:delete() - - g_assignedCoursesManager:loadAssignedCourses(saveGamePath) end --- Ugly hack to get access to the global AutoDrive table, as this global is dependent on the auto drive folder name. diff --git a/scripts/Course.lua b/scripts/Course.lua index f53fda973..fd53da58b 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -1597,29 +1597,18 @@ function Course.createFromXml(vehicle, courseXml, courseKey) course.islandHeadlandClockwise = courseXml:getValue(courseKey .. '#islandHeadlandClockwise') course.editedByCourseEditor = courseXml:getValue(courseKey .. '#compacted', false) course.compacted = courseXml:getValue(courseKey .. '#compacted', false) - if not course.nVehicles or course.nVehicles == 1 then - -- TODO: not nVehicles for backwards compatibility, remove later + if course.nVehicles == 1 then -- for multi-vehicle courses, we load the multi-vehicle data and restore the current course -- from there, so we don't need to write the same course twice in the savegame course.waypoints = createWaypointsFromXml(courseXml, courseKey) - if #course.waypoints == 0 then - CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, 'No waypoints loaded, trying old format') - courseXml:iterate(courseKey .. '.waypoints' .. Waypoint.xmlKey, function(ix, key) - local d - d = CpUtil.getXmlVectorValues(courseXml:getString(key)) - table.insert(course.waypoints, Waypoint.initFromXmlFileLegacyFormat(d, ix)) - end) - end - end - if course.nVehicles and course.nVehicles > 1 then + else course.multiVehicleData = Course.MultiVehicleData.createFromXmlFile(courseXml, courseKey) course:setPosition(course.multiVehicleData:getPosition()) if vehicle then vehicle:getCpLaneOffsetSetting():setValue(course.multiVehicleData:getPosition()) end - else - course:enrichWaypointData() end + course:enrichWaypointData() CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, 'Course with %d waypoints loaded.', #course.waypoints) return course end diff --git a/scripts/courseManager/AssignedCoursesManager.lua b/scripts/courseManager/AssignedCoursesManager.lua index f1066bf09..ecc554e01 100644 --- a/scripts/courseManager/AssignedCoursesManager.lua +++ b/scripts/courseManager/AssignedCoursesManager.lua @@ -3,28 +3,27 @@ ---@class AssignedCoursesManager AssignedCoursesManager = CpObject() AssignedCoursesManager.rootXmlKey = "AssignedCourses" -AssignedCoursesManager.baseXmlKey = AssignedCoursesManager.rootXmlKey..".Vehicle" -AssignedCoursesManager.xmlKey = AssignedCoursesManager.baseXmlKey.."(?).Course(?)" +AssignedCoursesManager.vehicleXmlKey = AssignedCoursesManager.rootXmlKey..".Vehicle" +AssignedCoursesManager.courseXmlKey = AssignedCoursesManager.vehicleXmlKey .."(?).Course(?)" AssignedCoursesManager.fileName = "CpAssignedCourses.xml" function AssignedCoursesManager:init() self.vehicles = {} - self.numVehiclesWithCourses = 0 end function AssignedCoursesManager:registerXmlSchema() g_messageCenter:subscribe(MessageType.LOADED_ALL_SAVEGAME_VEHICLES, self.finishedLoading, self) self.xmlSchema = XMLSchema.new("AssignedCourses") - CpCourseManager.registerXmlSchemaValues(self.xmlSchema, self.xmlKey) - self.xmlSchema:register(XMLValueType.STRING, self.baseXmlKey.."(?)" .. "#name", "Vehicle name") + self.xmlSchema:register(XMLValueType.STRING, self.vehicleXmlKey.."(?)" .. "#name", "Vehicle name") + CpCourseManager.registerXmlSchemaValues(self.xmlSchema, self.courseXmlKey) end --- Every valid vehicle will be added here, the id is needed to delete a sold vehicle form the table. -function AssignedCoursesManager:registerVehicle(vehicle,id) +function AssignedCoursesManager:registerVehicle(vehicle, id) self.vehicles[id] = vehicle end --- Removes a sold vehicle from the table. -function AssignedCoursesManager:unregisterVehicle(vehicle,id) +function AssignedCoursesManager:unregisterVehicle(vehicle, id) self.vehicles[id] = nil end @@ -35,17 +34,17 @@ end --- Saves all assigned vehicle courses in a single xml file under the savegame folder. function AssignedCoursesManager:saveAssignedCourses(savegameDir) - local xmlFile = XMLFile.create("assignedCoursesXmlFile", savegameDir..self.fileName, + local xmlFile = XMLFile.create("assignedCoursesXmlFile", savegameDir..self.fileName, self.rootXmlKey, self.xmlSchema) local ix = 0 - for _,vehicle in pairs(self.vehicles) do + for _,vehicle in pairs(self.vehicles) do --- Checks if the vehicle has courses. if vehicle:hasCpCourse() then local courses = vehicle:getCpCourses() - --- Safety check, as it might happen, + --- Safety check, as it might happen, --- that a vehicle has a course without waypoints for some reason. if courses[1] and courses[1].waypoints and #courses[1].waypoints > 0 then - local key = string.format("%s(%d)", self.baseXmlKey, ix) + local key = string.format("%s(%d)", self.vehicleXmlKey, ix) xmlFile:setValue(key.."#name", vehicle:getName()) vehicle:saveAssignedCpCourses(xmlFile, key..".Course") --- Sets a unique ID to the vehicle, so the assigned courses can be loaded correctly into the vehicle. @@ -59,17 +58,6 @@ function AssignedCoursesManager:saveAssignedCourses(savegameDir) xmlFile:delete() end ---- Gets the number of vehicles with assigned courses. -function AssignedCoursesManager:loadAssignedCourses(savegameDir) - self.filePath = savegameDir..self.fileName - self.xmlFile = XMLFile.loadIfExists("assignedCoursesXmlFile", self.filePath , self.xmlSchema) - if self.xmlFile then - self.xmlFile:iterate(self.baseXmlKey, function (ix,key) - self.numVehiclesWithCourses = self.numVehiclesWithCourses + 1 - end) - end -end - --- Makes sure the xml file handle gets delete after the courses are loaded into the vehicles. function AssignedCoursesManager:finishedLoading() if self.xmlFile then @@ -78,12 +66,19 @@ function AssignedCoursesManager:finishedLoading() end --- Loads courses by the id in which they were saved into the vehicle. -function AssignedCoursesManager:loadAssignedCoursesByVehicle(vehicle,id) - CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, "Trying to load assigned courses: %s, numVehicles: %d", - tostring(id), self.numVehiclesWithCourses) - if self.xmlFile~=nil and id and id <= self.numVehiclesWithCourses then - local key = string.format("%s(%d).Course", self.baseXmlKey, id) - vehicle:loadAssignedCpCourses(self.xmlFile, key, true) +function AssignedCoursesManager:loadAssignedCoursesByVehicle(vehicle, id) + if id then + CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, "Loading assigned courses, id: %s", tostring(id)) + if self.xmlFile == nil then + self.filePath = g_currentMission.missionInfo.savegameDirectory .."/" .. self.fileName + self.xmlFile = XMLFile.loadIfExists("assignedCoursesXmlFile", self.filePath, self.xmlSchema) + end + if self.xmlFile ~= nil then + local key = string.format("%s(%d).Course", self.vehicleXmlKey, id) + vehicle:loadAssignedCpCourses(self.xmlFile, key, true) + end + else + CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, "Has no assigned courses.") end end diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index 643647dcf..f941826fe 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -24,14 +24,11 @@ function CpCourseManager.registerXmlSchemaValues(schema,baseKey) schema:register(XMLValueType.STRING, baseKey .. "#name", "Course name") schema:register(XMLValueType.FLOAT, baseKey .. "#workWidth", "Course work width") schema:register(XMLValueType.INT, baseKey .. "#numHeadlands", "Course number of headlands") - -- TODO: remove once backwards compatibility is not needed - schema:register(XMLValueType.INT, baseKey .. "#multiTools", "Course multi tools") schema:register(XMLValueType.INT, baseKey .. "#nVehicles", "Number of vehicles for a multi-vehicle course") schema:register(XMLValueType.BOOL, baseKey .. "#headlandClockwise", "Headlands are clockwise.") schema:register(XMLValueType.BOOL, baseKey .. "#islandHeadlandClockwise", "Headlands around islands are clockwise.") schema:register(XMLValueType.BOOL, baseKey .. "#wasEdited", "Was the course edited by the course editor.") schema:register(XMLValueType.BOOL, baseKey .. "#compacted", "Rows are compacted, only start and end is saved.") - schema:register(XMLValueType.STRING, baseKey .. ".waypoints", "Course serialized waypoints") -- old save format Waypoint.registerXmlSchema(schema, baseKey) Course.MultiVehicleData.registerXmlSchema(schema, baseKey) end From ab767715668c128238d357ad1c6fa5b61bbaa307 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 21 Nov 2024 15:54:57 -0500 Subject: [PATCH 028/158] fix: mission field detection --- scripts/courseManager/AssignedCoursesManager.lua | 3 ++- scripts/field/CpFieldUtil.lua | 8 -------- scripts/pathfinder/PathfinderConstraints.lua | 3 +-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/scripts/courseManager/AssignedCoursesManager.lua b/scripts/courseManager/AssignedCoursesManager.lua index ecc554e01..d7d76d704 100644 --- a/scripts/courseManager/AssignedCoursesManager.lua +++ b/scripts/courseManager/AssignedCoursesManager.lua @@ -68,8 +68,9 @@ end --- Loads courses by the id in which they were saved into the vehicle. function AssignedCoursesManager:loadAssignedCoursesByVehicle(vehicle, id) if id then - CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, "Loading assigned courses, id: %s", tostring(id)) + CpUtil.debugVehicle(CpDebug.DBG_COURSES, vehicle, "Loading assigned courses, id: %d", id) if self.xmlFile == nil then + -- if not yet open, do it now. We want to avoid closing/opening the file for each vehicle. self.filePath = g_currentMission.missionInfo.savegameDirectory .."/" .. self.fileName self.xmlFile = XMLFile.loadIfExists("assignedCoursesXmlFile", self.filePath, self.xmlSchema) end diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index 55d405b78..ecaa1e004 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -162,11 +162,3 @@ function CpFieldUtil.getFieldPolygonAtWorldPosition(x, z) end return fieldPolygon, isCustomField end - ---- There is an active mission on fieldId ----@param fieldId number ----@return boolean true if this field is a mission field -function CpFieldUtil.isActiveMissionField(fieldId) - local mission = g_missionManager.fieldToMission[fieldId] - return mission and mission.status == AbstractMission.STATUS_RUNNING -end \ No newline at end of file diff --git a/scripts/pathfinder/PathfinderConstraints.lua b/scripts/pathfinder/PathfinderConstraints.lua index 31534a128..c73c6f39f 100644 --- a/scripts/pathfinder/PathfinderConstraints.lua +++ b/scripts/pathfinder/PathfinderConstraints.lua @@ -104,8 +104,7 @@ function PathfinderConstraints:getNodePenalty(node) -- we are on a field if not PathfinderUtil.isWorldPositionOwned(node.x, -node.y) then -- but we do not own this field - local fieldIdUnderNode = CpFieldUtil.getFieldIdAtWorldPosition(node.x, -node.y) - if not CpFieldUtil.isActiveMissionField(fieldIdUnderNode) then + if not g_missionManager:getMissionAtWorldPosition(node.x, -node.y) then -- the field we are on is not ours and not a mission field, more penalty! offField = true offFieldPenalty = self.offFieldPenalty * 1.2 From d1e87b3495db77902d9f5eac3d19e04ec5b6f4a8 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 21 Nov 2024 18:14:45 -0500 Subject: [PATCH 029/158] fix: missing coroutine workaround Pathfinder runner stores the state now so can be interrupted and resumed just as a coroutine. Pathfinder should not block game anymore. --- scripts/pathfinder/HybridAStar.lua | 132 +++++++++++++++++------------ 1 file changed, 78 insertions(+), 54 deletions(-) diff --git a/scripts/pathfinder/HybridAStar.lua b/scripts/pathfinder/HybridAStar.lua index abbe6216f..e652a205e 100644 --- a/scripts/pathfinder/HybridAStar.lua +++ b/scripts/pathfinder/HybridAStar.lua @@ -31,7 +31,7 @@ https://github.com/karlkurzer/path_planner ]] --- TODO 25 ---- Dummy coroutine replacement as Giants removed in 2025. Will see if they have something replacing it, until +--- Dummy coroutine replacement as Giants removed it in 2025. Will see if they have something replacing it, until --- then, just run the function synchronously and never yield control back. local coroutine = {} @@ -59,14 +59,20 @@ end --- Start a pathfinding. This is the interface to use if you want to run the pathfinding algorithm through -- multiple update loops so it does not block the game. This starts a coroutine and will periodically return control -- (yield). --- If you don't want to use coroutines and wait until the path is found, call findPath directly. +-- If you don't want to use coroutines and wait until the path is found, call run directly. -- -- After start(), call resume() until it returns done == true. ----@see PathfinderInterface#findPath also on how to use. +---@see PathfinderInterface#run also on how to use. function PathfinderInterface:start(...) if not self.coroutine then - self.coroutine = coroutine.create(self.findPath) + self.coroutine = coroutine.create(self.run) end + return self:initRun(...) +end + +--- This starts the pathfinder run in the "background", that is, as a coroutine that will periodically yield. After the +--- yield, resume() must be called until it returns true. +function PathfinderInterface:initRun(...) return self:resume(...) end @@ -445,6 +451,7 @@ function HybridAStar:getAnalyticPath(start, goal, turnRadius, allowReverse, hitc return analyticPath, analyticSolutionLength, pathType end +--- Starts a pathfinder run. This initializes the pathfinder and then calls resume() which does the real work. ---@param start State3D start node ---@param goal State3D goal node ---@param allowReverse boolean allow reverse driving @@ -456,15 +463,20 @@ end --- when we search for a valid analytic solution we use this instead of isValidNode() ---@param hitchLength number hitch length of a trailer (length between hitch on the towing vehicle and the --- rear axle of the trailer), can be nil -function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints, hitchLength) +---@return boolean, {}|nil, boolean done, path, goal node invalid +function HybridAStar:initRun(start, goal, turnRadius, allowReverse, constraints, hitchLength) self:debug('Start pathfinding between %s and %s', tostring(start), tostring(goal)) self:debug(' turnRadius = %.1f, allowReverse: %s', turnRadius, tostring(allowReverse)) + self.goal = goal + self.turnRadius = turnRadius + self.allowReverse = allowReverse + self.hitchLength = hitchLength self.constraints = constraints -- a motion primitive is straight or a few degree turn to the right or left - local hybridMotionPrimitives = self:getMotionPrimitives(turnRadius, allowReverse) + self.hybridMotionPrimitives = self:getMotionPrimitives(turnRadius, allowReverse) -- create the open list for the nodes as a binary heap where -- the node with the lowest total cost is at the top - local openList = BinaryHeap.minUnique(function(a, b) + self.openList = BinaryHeap.minUnique(function(a, b) return a:lt(b) end) @@ -502,33 +514,44 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints start:updateH(goal, analyticSolutionLength) self.distanceToGoal = start.h - start:insert(openList) + start:insert(self.openList) self.iterations = 0 self.expansions = 0 self.yields = 0 - local timer = openIntervalTimer() - while openList:size() > 0 and self.iterations < self.maxIterations do + return false +end + +--- Reentry-safe pathfinder runner +function HybridAStar:run(start, goal, turnRadius, allowReverse, constraints, hitchLength) + if self.iterations == 0 then + local done, path, goalNodeInvalid = self:initRun(start, goal, turnRadius, allowReverse, constraints, hitchLength) + if done then + return done, path, goalNodeInvalid + end + end + self.timer = openIntervalTimer() + while self.openList:size() > 0 and self.iterations < self.maxIterations do -- pop lowest cost node from queue ---@type State3D - local pred = State3D.pop(openList) + local pred = State3D.pop(self.openList) --self:debug('pop %s', tostring(pred)) - if pred:equals(goal, self.deltaPosGoal, self.deltaThetaGoal) then + if pred:equals(self.goal, self.deltaPosGoal, self.deltaThetaGoal) then -- done! self:debug('Popped the goal (%d).', self.iterations) - self:rollUpPath(pred, goal) - constraints:showStatistics() - closeIntervalTimer(timer) + self:rollUpPath(pred, self.goal) + self.constraints:showStatistics() + closeIntervalTimer(self.timer) return true, self.path end self.count = self.count + 1 - -- yield only when we were started in a coroutine. - if coroutine.running() and (self.count % self.yieldAfter == 0 or readIntervalTimerMs(timer) > 20) then + -- yield after the configured iterations or after 20 ms + if (self.count % self.yieldAfter == 0 or readIntervalTimerMs(self.timer) > 20) then self.yields = self.yields + 1 - closeIntervalTimer(timer) - coroutine.yield(false) - timer = openIntervalTimer() + closeIntervalTimer(self.timer) + -- if we had the coroutine package, we would coroutine.yield(false) here + return false end if not pred:isClosed() then -- analytical expansion: try a Dubins/Reeds-Shepp path from here randomly, more often as we getting closer to the goal @@ -537,28 +560,28 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints if self.analyticSolverEnabled and not self.goalNodeIsInvalid and math.random() > 2 * pred.h / self.distanceToGoal then self:debug('Check analytic solution at iteration %d, %.1f, %.1f', self.iterations, pred.h, pred.h / self.distanceToGoal) - analyticPath, pathType = self:getAnalyticPath(pred, goal, turnRadius, allowReverse, hitchLength) + local analyticPath, _, pathType = self:getAnalyticPath(pred, self.goal, self.turnRadius, self.allowReverse, self.hitchLength) if self:isPathValid(analyticPath) then - self:debug('Found collision free analytic path (%s) at iteration %d', self.iterations, pathType) + self:debug('Found collision free analytic path (%s) at iteration %d', pathType, self.iterations) -- remove first node of returned analytic path as it is the same as pred table.remove(analyticPath, 1) - self:rollUpPath(pred, goal, analyticPath) - constraints:showStatistics() - closeIntervalTimer(timer) + self:rollUpPath(pred, self.goal, analyticPath) + self.constraints:showStatistics() + closeIntervalTimer(self.timer) return true, self.path end end end -- create the successor nodes - for _, primitive in ipairs(hybridMotionPrimitives:getPrimitives(pred)) do + for _, primitive in ipairs(self.hybridMotionPrimitives:getPrimitives(pred)) do ---@type State3D - local succ = hybridMotionPrimitives:createSuccessor(pred, primitive, hitchLength) - if succ:equals(goal, self.deltaPosGoal, self.deltaThetaGoal) then + local succ = self.hybridMotionPrimitives:createSuccessor(pred, primitive, self.hitchLength) + if succ:equals(self.goal, self.deltaPosGoal, self.deltaThetaGoal) then succ.pred = succ.pred self:debug('Successor at the goal (%d).', self.iterations) - self:rollUpPath(succ, goal) - constraints:showStatistics() - closeIntervalTimer(timer) + self:rollUpPath(succ, self.goal) + self.constraints:showStatistics() + closeIntervalTimer(self.timer) return true, self.path end @@ -567,15 +590,15 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints -- ignore invalidity of a node in the first few iterations: this is due to the fact that sometimes -- we end up being in overlap with another vehicle when we start the pathfinding and all we need is -- an iteration or two to bring us out of that position - if (self.ignoreValidityAtStart and self.iterations < 3) or constraints:isValidNode(succ) then - succ:updateG(primitive, constraints:getNodePenalty(succ)) + if (self.ignoreValidityAtStart and self.iterations < 3) or self.constraints:isValidNode(succ) then + succ:updateG(primitive, self.constraints:getNodePenalty(succ)) local analyticSolutionCost = 0 if self.analyticSolverEnabled then - local analyticSolution = self.analyticSolver:solve(succ, goal, turnRadius) - analyticSolutionCost = analyticSolution:getLength(turnRadius) - succ:updateH(goal, analyticSolutionCost) + local analyticSolution = self.analyticSolver:solve(succ, self.goal, self.turnRadius) + analyticSolutionCost = analyticSolution:getLength(self.turnRadius) + succ:updateH(self.goal, analyticSolutionCost) else - succ:updateH(goal, 0, succ:distance(goal) * 1.5) + succ:updateH(self.goal, 0, succ:distance(self.goal) * 1.5) end --self:debug(' %s', tostring(succ)) @@ -585,14 +608,14 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints -- add a small number before comparing to adjust for floating point calculation differences if existingSuccNode:getCost() + 0.001 >= succ:getCost() then --self:debug('%.6f replacing %s with %s', succ:getCost() - existingSuccNode:getCost(), tostring(existingSuccNode), tostring(succ)) - if openList:valueByPayload(existingSuccNode) then + if self.openList:valueByPayload(existingSuccNode) then -- existing node is on open list already, remove it here, will replace with - existingSuccNode:remove(openList) + existingSuccNode:remove(self.openList) end -- add (update) to the state space self.nodes:add(succ) -- add to open list - succ:insert(openList) + succ:insert(self.openList) else --self:debug('insert existing node back %s (iteration %d), diff %s', tostring(succ), self.iterations, tostring(succ:getCost() - existingSuccNode:getCost())) end @@ -600,7 +623,7 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints -- successor cell does not yet exist self.nodes:add(succ) -- put it on the open list as well - succ:insert(openList) + succ:insert(self.openList) end else --self:debug('Invalid node %s (iteration %d)', tostring(succ), self.iterations) @@ -616,7 +639,7 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints self.iterations = self.iterations + 1 if self.iterations % 1000 == 0 then self:debug('iteration %d...', self.iterations) - constraints:showStatistics() + self.constraints:showStatistics() end local r = self.iterations / self.maxIterations -- as we reach the maximum iterations, relax our criteria to reach the goal: allow for arriving at @@ -628,12 +651,12 @@ function HybridAStar:findPath(start, goal, turnRadius, allowReverse, constraints math.rad(g_Courseplay.globalSettings:getSettings().deltaAngleRelaxFactorDeg:getValue()) * r) end end - --self:printOpenList(openList) + --self:printOpenList(self.openList) self.path = {} self:debug('No path found: iterations %d, yields %d, cost %.1f - %.1f, deltaTheta %.1f', self.iterations, self.yields, self.nodes.lowestCost, self.nodes.highestCost, math.deg(self.deltaThetaGoal)) - constraints:showStatistics() - closeIntervalTimer(timer) + self.constraints:showStatistics() + closeIntervalTimer(self.timer) return true, nil end @@ -682,6 +705,7 @@ end --- A simple A star implementation based on the hybrid A star. The difference is that the state space isn't really --- 3 dimensional as we do not take the heading into account and we use a different set of motion primitives +---@class AStar : HybridAStar AStar = CpObject(HybridAStar) function AStar:init(vehicle, yieldAfter, maxIterations) @@ -702,7 +726,7 @@ function AStar:getMotionPrimitives(turnRadius, allowReverse) end --- A pathfinder combining the (slow) hybrid A * and the (fast) regular A * star. ---- Near the start and the goal the hybrid A * is used to ensure the generated path is drivable (direction changes +--- Near the start and the goal the hybrid A * is used to ensure the generated path is drivable (direction changes --- always obey the turn radius), but use the A * between the two. --- We'll run 3 pathfindings: one A * between start and goal (phase 1), then trim the ends of the result in hybridRange --- Now run a hybrid A * from the start to the beginning of the trimmed A * path (phase 2), then another hybrid A * from the @@ -760,7 +784,7 @@ function HybridAStarWithAStarInTheMiddle:start(start, goal, turnRadius, allowRev self.startNode:updateH(self.goalNode, turnRadius) self.phase = self.ASTAR self:debug('Finding fast A* path between start and goal...') - self.coroutine = coroutine.create(self.aStarPathfinder.findPath) + self.coroutine = coroutine.create(self.aStarPathfinder.run) self.currentPathfinder = self.aStarPathfinder -- strict mode for the middle part, stay close to the field, for future improvements, disabled for now -- self.constraints:setStrictMode() @@ -771,7 +795,7 @@ end function HybridAStarWithAStarInTheMiddle:findHybridStartToEnd() self.phase = self.ALL_HYBRID self:debug('Goal is closer than %d, use one phase pathfinding only', self.hybridRange * 3) - self.coroutine = coroutine.create(self.hybridAStarPathfinder.findPath) + self.coroutine = coroutine.create(self.hybridAStarPathfinder.run) self.currentPathfinder = self.hybridAStarPathfinder return self:resume(self.startNode, self.goalNode, self.turnRadius, self.allowReverse, self.constraints, self.hitchLength) end @@ -781,7 +805,7 @@ function HybridAStarWithAStarInTheMiddle:findPathFromStartToMiddle() self:debug('Finding path between start and middle section...') self.phase = self.START_TO_MIDDLE -- generate a hybrid part from the start to the middle section's start - self.coroutine = coroutine.create(self.hybridAStarPathfinder.findPath) + self.coroutine = coroutine.create(self.hybridAStarPathfinder.run) self.currentPathfinder = self.hybridAStarPathfinder local goal = State3D(self.middlePath[1].x, self.middlePath[1].y, (self.middlePath[2] - self.middlePath[1]):heading()) return self:resume(self.startNode, goal, self.turnRadius, self.allowReverse, self.constraints, self.hitchLength) @@ -792,7 +816,7 @@ function HybridAStarWithAStarInTheMiddle:findPathFromMiddleToEnd() -- generate middle to end self.phase = self.MIDDLE_TO_END self:debug('Finding path between middle section and goal (allow reverse %s)...', tostring(self.allowReverse)) - self.coroutine = coroutine.create(self.hybridAStarPathfinder.findPath) + self.coroutine = coroutine.create(self.hybridAStarPathfinder.run) self.currentPathfinder = self.hybridAStarPathfinder return self:resume(self.middleToEndStart, self.goalNode, self.turnRadius, self.allowReverse, self.constraints, self.hitchLength) end @@ -888,7 +912,7 @@ function HybridAStarWithAStarInTheMiddle:resume(...) end --- Dummy A* pathfinder implementation, does not calculate a path, just returns a pre-calculated path passed in ---- to its constructor. +--- to its constructor. ---@see HybridAStarWithPathInTheMiddle ---@class DummyAStar : HybridAStar DummyAStar = CpObject(HybridAStar) @@ -899,15 +923,15 @@ function DummyAStar:init(vehicle, path) self.vehicle = vehicle end -function DummyAStar:findPath() +function DummyAStar:run() return true, self.path end --- Similar to HybridAStarWithAStarInTheMiddle, but the middle section is not calculated using the A*, instead ---- it is passed in to to constructor, already created by the caller. +--- it is passed in to to constructor, already created by the caller. --- This is used to find a path on the headland to the next row. The headland section is calculated by the caller --- based on the vehicle's course, HybridAStarWithPathInTheMiddle only finds the path from the vehicle's position ---- to the headland and from the headland to the start of the next row. +--- to the headland and from the headland to the start of the next row. HybridAStarWithPathInTheMiddle = CpObject(HybridAStarWithAStarInTheMiddle) ---@param hybridRange number range in meters around start/goal to use hybrid A * From f049013ef9ccb971ca634fd181e95ee469747a01 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 22 Nov 2024 07:16:45 -0500 Subject: [PATCH 030/158] fix: multi-stage pathfinder cleanup --- scripts/pathfinder/HybridAStar.lua | 46 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/scripts/pathfinder/HybridAStar.lua b/scripts/pathfinder/HybridAStar.lua index e652a205e..144a8b724 100644 --- a/scripts/pathfinder/HybridAStar.lua +++ b/scripts/pathfinder/HybridAStar.lua @@ -519,12 +519,21 @@ function HybridAStar:initRun(start, goal, turnRadius, allowReverse, constraints, self.iterations = 0 self.expansions = 0 self.yields = 0 + self.initialized = true return false end +--- Wrap up this run, clean up timer, reset initialized flag so next run will start cleanly +function HybridAStar:finishRun(result, path) + self.initialized = false + self.constraints:showStatistics() + closeIntervalTimer(self.timer) + return result, path +end + --- Reentry-safe pathfinder runner function HybridAStar:run(start, goal, turnRadius, allowReverse, constraints, hitchLength) - if self.iterations == 0 then + if not self.initialized then local done, path, goalNodeInvalid = self:initRun(start, goal, turnRadius, allowReverse, constraints, hitchLength) if done then return done, path, goalNodeInvalid @@ -540,10 +549,7 @@ function HybridAStar:run(start, goal, turnRadius, allowReverse, constraints, hit if pred:equals(self.goal, self.deltaPosGoal, self.deltaThetaGoal) then -- done! self:debug('Popped the goal (%d).', self.iterations) - self:rollUpPath(pred, self.goal) - self.constraints:showStatistics() - closeIntervalTimer(self.timer) - return true, self.path + return self:finishRun(true, self:rollUpPath(pred, self.goal)) end self.count = self.count + 1 -- yield after the configured iterations or after 20 ms @@ -565,10 +571,8 @@ function HybridAStar:run(start, goal, turnRadius, allowReverse, constraints, hit self:debug('Found collision free analytic path (%s) at iteration %d', pathType, self.iterations) -- remove first node of returned analytic path as it is the same as pred table.remove(analyticPath, 1) - self:rollUpPath(pred, self.goal, analyticPath) - self.constraints:showStatistics() - closeIntervalTimer(self.timer) - return true, self.path + -- TODO why are we calling rollUpPath here? + return self:finishRun(true, self:rollUpPath(pred, self.goal, analyticPath)) end end end @@ -579,10 +583,7 @@ function HybridAStar:run(start, goal, turnRadius, allowReverse, constraints, hit if succ:equals(self.goal, self.deltaPosGoal, self.deltaThetaGoal) then succ.pred = succ.pred self:debug('Successor at the goal (%d).', self.iterations) - self:rollUpPath(succ, self.goal) - self.constraints:showStatistics() - closeIntervalTimer(self.timer) - return true, self.path + return self:finishRun(true, self:rollUpPath(succ, self.goal)) end local existingSuccNode = self.nodes:get(succ) @@ -652,12 +653,9 @@ function HybridAStar:run(start, goal, turnRadius, allowReverse, constraints, hit end end --self:printOpenList(self.openList) - self.path = {} self:debug('No path found: iterations %d, yields %d, cost %.1f - %.1f, deltaTheta %.1f', self.iterations, self.yields, self.nodes.lowestCost, self.nodes.highestCost, math.deg(self.deltaThetaGoal)) - self.constraints:showStatistics() - closeIntervalTimer(self.timer) - return true, nil + return self:finishRun(true, nil) end function HybridAStar:isPathValid(path) @@ -675,21 +673,22 @@ end ---@param node State3D function HybridAStar:rollUpPath(node, goal, path) - self.path = path or {} + path = path or {} local currentNode = node self:debug('Goal node at %.2f/%.2f, cost %.1f (%.1f - %.1f)', goal.x, goal.y, node.cost, self.nodes.lowestCost, self.nodes.highestCost) - table.insert(self.path, 1, currentNode) + table.insert(path, 1, currentNode) while currentNode.pred and currentNode ~= currentNode.pred do --self:debug(' %s', currentNode.pred) - table.insert(self.path, 1, currentNode.pred) + table.insert(path, 1, currentNode.pred) currentNode = currentNode.pred end -- TODO: see if this really is needed after it was fixed in the Reeds-Shepp getWaypoints() -- start node always points forward, make sure it is reverse if the second node is reverse... - self.path[1].gear = self.path[2] and self.path[2].gear or self.path[1].gear - self:debug('Nodes %d, iterations %d, yields %d, deltaTheta %.1f', #self.path, self.iterations, self.yields, + path[1].gear = path[2] and path[2].gear or path[1].gear + self:debug('Nodes %d, iterations %d, yields %d, deltaTheta %.1f', #path, self.iterations, self.yields, math.deg(self.deltaThetaGoal)) + return path end function HybridAStar:printOpenList(openList) @@ -850,6 +849,7 @@ function HybridAStarWithAStarInTheMiddle:resume(...) return PathfinderResult(true, nil, goalNodeInvalid, self.currentPathfinder.nodes.highestDistance, self.constraints) end + CourseGenerator.addDebugPolyline(Polyline(path), {1, 0, 0}) local lMiddlePath = HybridAStar.length(path) self:debug('Direct path is %d m', lMiddlePath) -- do we even need to use the normal A star or the nodes are close enough that the hybrid A star will be fast enough? @@ -870,6 +870,7 @@ function HybridAStarWithAStarInTheMiddle:resume(...) return self:findPathFromStartToMiddle() elseif self.phase == self.START_TO_MIDDLE then if path then + CourseGenerator.addDebugPolyline(Polyline(path), {0, 1, 0}) -- start and middle sections ready, continue with the piece from the middle to the end self.path = path -- create start point at the last waypoint of middlePath before shortening @@ -890,6 +891,7 @@ function HybridAStarWithAStarInTheMiddle:resume(...) end elseif self.phase == self.MIDDLE_TO_END then if path then + CourseGenerator.addDebugPolyline(Polyline(path), {0, 0, 1}) -- last piece is ready, this was generated from the goal point to the end of the middle section so -- first remove the last point of the middle section to make the transition smoother -- and then add the last section in reverse order From 730f10e46b63a10e9808b9fe5674ca6ca3d8423d Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 22 Nov 2024 08:05:19 -0500 Subject: [PATCH 031/158] fix: use math.sign instead of MathUtil.sign Luau has built-in math.sign so MathUtil.sign is deprecated. --- scripts/ai/AIDriveStrategyUnloadCombine.lua | 4 ++-- scripts/ai/ImplementUtil.lua | 2 +- scripts/ai/controllers/LevelerController.lua | 2 +- scripts/gui/CpAIFrameExtended.lua | 8 ++++---- scripts/util/CpMathUtil.lua | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/ai/AIDriveStrategyUnloadCombine.lua b/scripts/ai/AIDriveStrategyUnloadCombine.lua index e4ae8bf5d..9ef447033 100644 --- a/scripts/ai/AIDriveStrategyUnloadCombine.lua +++ b/scripts/ai/AIDriveStrategyUnloadCombine.lua @@ -2717,7 +2717,7 @@ function AIDriveStrategyUnloadCombine:startUnloadingOnField(controller, allowRev else --- Makes sure the x offset for unloading to the side is big enough --- to make sure the unloader doesn't touch the heap. - self.fieldUnloadData.xOffset = MathUtil.sign(self.fieldUnloadData.xOffset) * + self.fieldUnloadData.xOffset = math.sign(self.fieldUnloadData.xOffset) * math.max(math.abs(self.fieldUnloadData.xOffset), siloWidth / 2 + 2 * vehicleWidth / 3) end @@ -2929,7 +2929,7 @@ function AIDriveStrategyUnloadCombine:onFieldUnloadingFinished() --- First time park position for reverse unload offset always on the left of the heap. self.fieldUnloadData.xOffset = siloWidth / 2 + 2 * vehicleWidth / 3 else - self.fieldUnloadData.xOffset = MathUtil.sign(self.fieldUnloadData.xOffset) * + self.fieldUnloadData.xOffset = math.sign(self.fieldUnloadData.xOffset) * math.max(math.abs(self.fieldUnloadData.xOffset), siloWidth / 2 + 2 * vehicleWidth / 3) end self:debug("Found a heap for field unloading park position xOffset: %.2f", self.fieldUnloadData.xOffset) diff --git a/scripts/ai/ImplementUtil.lua b/scripts/ai/ImplementUtil.lua index 1c171353c..a4899b8d6 100644 --- a/scripts/ai/ImplementUtil.lua +++ b/scripts/ai/ImplementUtil.lua @@ -379,7 +379,7 @@ function ImplementUtil.moveMovingToolToRotation(implement, tool, dt, rotTarget, tool.curRot[1], tool.curRot[2], tool.curRot[3] = getRotation(tool.node) local oldRot = tool.curRot[tool.rotationAxis] local diff = rotTarget - oldRot - local dir = MathUtil.sign(diff) + local dir = math.sign(diff) local rotSpeed = CpMathUtil.clamp( math.abs(diff) * math.abs(tool.rotSpeed), math.abs(tool.rotSpeed)/3, 0.5 ) rotSpeed = dir * rotSpeed if math.abs(diff) < minDiffNeeded or rotSpeed == 0 then diff --git a/scripts/ai/controllers/LevelerController.lua b/scripts/ai/controllers/LevelerController.lua index 6fb422ad4..3edd16323 100644 --- a/scripts/ai/controllers/LevelerController.lua +++ b/scripts/ai/controllers/LevelerController.lua @@ -134,7 +134,7 @@ end --- Moves the arm position to achieve a given height from the ground. function LevelerController:setCylinderedArmHeight(currentHeight, terrainHeight, min, max) - local dir = -MathUtil.sign(currentHeight-terrainHeight - min) + local dir = -math.sign(currentHeight-terrainHeight - min) local diff = math.abs(currentHeight-terrainHeight - min) local isDirty = false if max and currentHeight-terrainHeight > max then diff --git a/scripts/gui/CpAIFrameExtended.lua b/scripts/gui/CpAIFrameExtended.lua index d8178cd65..3c9bab91c 100644 --- a/scripts/gui/CpAIFrameExtended.lua +++ b/scripts/gui/CpAIFrameExtended.lua @@ -484,10 +484,10 @@ function CpInGameMenuAIFrameExtended:draw() if Input.isKeyPressed(Input.KEY_lshift) then if math.abs(dx) > math.abs(dz) then dz = 0 - dx = MathUtil.sign(dx) + dx = math.sign(dx) else dx = 0 - dz = MathUtil.sign(dz) + dz = math.sign(dz) end worldX = pos.x + dx * length worldZ = pos.z + dz * length @@ -786,10 +786,10 @@ function CpInGameMenuAIFrameExtended:mouseEvent(superFunc,posX, posY, isDown, is if Input.isKeyPressed(Input.KEY_lshift) then if math.abs(dx) > math.abs(dz) then dz = 0 - dx = MathUtil.sign(dx) + dx = math.sign(dx) else dx = 0 - dz = MathUtil.sign(dz) + dz = math.sign(dz) end worldX = pos.x + dx * length worldZ = pos.z + dz * length diff --git a/scripts/util/CpMathUtil.lua b/scripts/util/CpMathUtil.lua index c23c49c96..69f032ef4 100644 --- a/scripts/util/CpMathUtil.lua +++ b/scripts/util/CpMathUtil.lua @@ -309,6 +309,7 @@ function CpMathUtil.angleFromGame(angle) return a end +-- TODO: consider math.clamp instead, that is part of Luau function CpMathUtil.clamp(val, min, max) return math.min(math.max(val, min), max) end From 9ae95fc6deb3af6f4d0d3fb89fdf2dd078e2b4ad Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 23 Nov 2024 09:34:44 -0500 Subject: [PATCH 032/158] fix: Replaced Vehicle.STATE_CHANGE_* --- scripts/ai/AIDriveStrategyCourse.lua | 4 ++-- scripts/specializations/CpAIFieldWorker.lua | 4 ++-- scripts/specializations/CpHud.lua | 4 ++-- scripts/specializations/CpVehicleSettings.lua | 10 +++------- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/scripts/ai/AIDriveStrategyCourse.lua b/scripts/ai/AIDriveStrategyCourse.lua index a566182dd..8f2395b98 100644 --- a/scripts/ai/AIDriveStrategyCourse.lua +++ b/scripts/ai/AIDriveStrategyCourse.lua @@ -307,7 +307,7 @@ function AIDriveStrategyCourse:raiseImplements() for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do implement.object:aiImplementEndLine() end - self.vehicle:raiseStateChange(Vehicle.STATE_CHANGE_AI_END_LINE) + self.vehicle:raiseStateChange(VehicleStateChange.AI_END_LINE) --- Raises implements, that are not covered by giants. self:raiseControllerEvent(self.onRaisingEvent) end @@ -317,7 +317,7 @@ function AIDriveStrategyCourse:lowerImplements() for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do implement.object:aiImplementStartLine() end - self.vehicle:raiseStateChange(Vehicle.STATE_CHANGE_AI_START_LINE) + self.vehicle:raiseStateChange(VehicleStateChange.AI_START_LINE) --- Lowers implements, that are not covered by giants. self:raiseControllerEvent(self.onLoweringEvent) end diff --git a/scripts/specializations/CpAIFieldWorker.lua b/scripts/specializations/CpAIFieldWorker.lua index cb0518db8..131499828 100644 --- a/scripts/specializations/CpAIFieldWorker.lua +++ b/scripts/specializations/CpAIFieldWorker.lua @@ -105,9 +105,9 @@ end function CpAIFieldWorker:onStateChange(state, data) local spec = self.spec_cpAIFieldWorker - if state == Vehicle.STATE_CHANGE_ATTACH then + if state == VehicleStateChange.ATTACH then spec.cpJob:getCpJobParameters():validateSettings() - elseif state == Vehicle.STATE_CHANGE_DETACH then + elseif state == VehicleStateChange.DETACH then spec.cpJob:getCpJobParameters():validateSettings() end end diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index ced616b40..49689b634 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -310,14 +310,14 @@ end function CpHud:onStateChange(state, data) local spec = self.spec_cpHud - if state == Vehicle.STATE_CHANGE_ATTACH or state == Vehicle.STATE_CHANGE_DETACH then + if state == VehicleStateChange.ATTACH or state == VehicleStateChange.DETACH then if self.isServer then for _, setting in ipairs(spec.hudSettings.settings) do setting:refresh() end self:raiseDirtyFlags(spec.availableClientJobModesDirtyFlag) end - elseif state == Vehicle.STATE_CHANGE_ENTER_VEHICLE then + elseif state == VehicleStateChange.ENTER_VEHICLE then self:raiseDirtyFlags(spec.availableClientJobModesDirtyFlag) end end diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index c0b9edf81..a40e2ef48 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -120,17 +120,13 @@ function CpVehicleSettings:onUpdate() end end --- TODO 25 These are defined somewhere else, not in Vehicle -Vehicle.STATE_CHANGE_ATTACH = 1 -Vehicle.STATE_CHANGE_DETACH = 2 - --- Changes the sprayer work width on fill type change, as it might depend on the loaded fill type. --- For example Lime and Fertilizer might have a different work width. function CpVehicleSettings:onStateChange(state, data) -- TODO 25 CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, 'onStateChange %s', tostring(state)) local spec = self.spec_cpVehicleSettings - if state == Vehicle.STATE_CHANGE_FILLTYPE_CHANGE and self:getIsSynchronized() then + if state == VehicleStateChange.FILLTYPE_CHANGE and self:getIsSynchronized() then local _, hasSprayer = AIUtil.getAllChildVehiclesWithSpecialization(self, Sprayer, nil) if hasSprayer then local width, offset = WorkWidthUtil.getAutomaticWorkWidthAndOffset(self, nil, nil) @@ -140,7 +136,7 @@ function CpVehicleSettings:onStateChange(state, data) self:getCourseGeneratorSettings().workWidth:setFloatValue(width) end end - elseif state == Vehicle.STATE_CHANGE_ATTACH then + elseif state == VehicleStateChange.ATTACH then CpVehicleSettings.setAutomaticWorkWidthAndOffset(self) CpVehicleSettings.setAutomaticBunkerSiloWorkWidth(self) CpVehicleSettings.setAutomaticBaleCollectorOffset(self) @@ -153,7 +149,7 @@ function CpVehicleSettings:onStateChange(state, data) CpVehicleSettings.setFromVehicleConfiguration(self, data.attachedVehicle, spec.loadingShovelHeightOffset, 'loadingShovelOffset') spec.needsRefresh = true - elseif state == Vehicle.STATE_CHANGE_DETACH then + elseif state == VehicleStateChange.DETACH then CpVehicleSettings.setAutomaticWorkWidthAndOffset(self, data.attachedVehicle) CpVehicleSettings.setAutomaticBunkerSiloWorkWidth(self, data.attachedVehicle) From e40324e53c3ff1d3d2e96267661b390ee14d10a0 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 23 Nov 2024 11:43:50 -0500 Subject: [PATCH 033/158] fix: wait for lowering implements --- scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index d822be2bc..a31ba009d 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -275,7 +275,8 @@ end -- true otherwise. Due to some timing issues it may return true just after we started lowering it, so we -- set a different state for those implements function AIDriveStrategyFieldWorkCourse:startWaitingForLower() - if AIUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine) or self.ppc:isReversing() then + -- TODO 25 looks like we always need to wait that extra cycle with FS25 + if true or AIUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine) or self.ppc:isReversing() then -- sowing machines want to stop while the implement is being lowered -- also, when reversing, we assume that we'll switch to forward, so stop while lowering, then start forward self.state = self.states.WAITING_FOR_LOWER_DELAYED From 5e0b5b968bf9ebd2778dda9549190b60003439ca Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 24 Nov 2024 08:04:01 -0500 Subject: [PATCH 034/158] fix: wait for unfolding Looks like checking for unfolded state isn't included anymore in the getCanAIFieldWorkerContinueWork(), so added that check to our FoldableController. --- scripts/ai/controllers/FoldableController.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/ai/controllers/FoldableController.lua b/scripts/ai/controllers/FoldableController.lua index 8b76df008..1e93560ae 100644 --- a/scripts/ai/controllers/FoldableController.lua +++ b/scripts/ai/controllers/FoldableController.lua @@ -39,4 +39,12 @@ function FoldableController:delete() --- Restores the controlledAction Foldable.onRootVehicleChanged(self.implement, self.vehicle) end +end + +function FoldableController:canContinueWork() + if self.foldActionWasRemoved then + return true + else + return self.foldableSpec:getIsUnfolded() + end end \ No newline at end of file From 37842b0d99d84e209d161dd215219535ca168a3b Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 24 Nov 2024 08:47:02 -0500 Subject: [PATCH 035/158] doc: TODO in README --- README.md | 5 +++++ scripts/ai/controllers/MotorController.lua | 2 ++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index a2b5f7935..b59d2b41a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ We'll keep you updated on our progress here, please be patient. This (and later, the Giants modhub) is the **only official source for Courseplay**, if you see it anywhere else, it's a scam. +# TODO + +- [ ] Can't start worker when the field is not owned. This may or may not be problem, need to check what happens on mission fields. TurnOnVehicle:getCanBeTurnedOn() returns false, and ever loop when we try to turn the vehicle on, it turns it off in the next loop. +- [ ] Pea harvester doesn't lower implements on start. + ## Help Us Out We work long, hard, in our own free time at developing and improving Courseplay. If you like the project, show us your undying love: diff --git a/scripts/ai/controllers/MotorController.lua b/scripts/ai/controllers/MotorController.lua index 112f405c1..c4f65f813 100644 --- a/scripts/ai/controllers/MotorController.lua +++ b/scripts/ai/controllers/MotorController.lua @@ -116,6 +116,8 @@ end function MotorController:startMotor() self.vehicle.spec_cpAIWorker.motorDisabled = false + -- TODO 25 for whatever reason, vehicle:getIsMotorStarted() returns true only much later after the motor was started + -- so we call this and log for quite a few seconds when the motor was not running when the helper was started self.implement:startMotor() self:debug('Started motor after fuel save.') end From 6f4c68c0298d8db61a6f1c4d4b9aab68fffa033a Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 24 Nov 2024 19:06:22 +0100 Subject: [PATCH 036/158] Kleine Hud Anpassung --- scripts/gui/hud/CpBaseHud.lua | 7 +++++-- scripts/gui/hud/CpHudInfoTexts.lua | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 2d13bae2d..f1d6d26fa 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -15,7 +15,7 @@ CpBaseHud.BACKGROUND_COLOR = {0, 0, 0, 0.7} CpBaseHud.DARK_BACKGROUND_COLOR = {0, 0, 0, 0.8} CpBaseHud.HEADER_COLOR = { - 0, 0.4, 0.6, 1 + 0.22323, 0.40724, 0.00368, 1 } CpBaseHud.BASE_COLOR = {1, 1, 1, 1} @@ -106,6 +106,9 @@ CpBaseHud.uvs = { baleFinderSymbol = { {7*128, 3*128, 128, 128} }, + playSymbol = { + {224, 296, 32, 32}, {256, 512} + } } --- Vertical + horizontal overlay alignment @@ -276,7 +279,7 @@ function CpBaseHud:init(vehicle) --- Create start/stop button local onOffBtnWidth, height = getNormalizedScreenValues(20, 20) local onOffIndicatorOverlay = CpGuiUtil.createOverlay({onOffBtnWidth, height}, - {g_baseUIFilename, GuiUtils.getUVs(unpack(self.uvs.streetDriveToSymbol))}, --TODO 25 + {imageFilename, GuiUtils.getUVs(unpack(self.uvs.playSymbol))}, self.OFF_COLOR, self.alignments.bottomRight) self.onOffButton = CpHudButtonElement.new(onOffIndicatorOverlay, self.baseHud) diff --git a/scripts/gui/hud/CpHudInfoTexts.lua b/scripts/gui/hud/CpHudInfoTexts.lua index e3adf6b38..33fddf617 100644 --- a/scripts/gui/hud/CpHudInfoTexts.lua +++ b/scripts/gui/hud/CpHudInfoTexts.lua @@ -20,7 +20,7 @@ CpHudInfoTexts.colorDefault = { } CpHudInfoTexts.colorHeader = { - 0, 0.4, 0.6, 1 + 0.22323, 0.40724, 0.00368, 1 } CpHudInfoTexts.uvs = { From c226143914e943c04e1e57fb8f4d24e1c9a1fc64 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 24 Nov 2024 14:05:49 -0500 Subject: [PATCH 037/158] fix: work start Now using the new onAIFieldWorkerPrepareForWork and onAIImplementPrepareForWork events. Not sure where else we need to add them, needs testing. --- scripts/ai/controllers/MotorController.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ai/controllers/MotorController.lua b/scripts/ai/controllers/MotorController.lua index c4f65f813..b29d114ea 100644 --- a/scripts/ai/controllers/MotorController.lua +++ b/scripts/ai/controllers/MotorController.lua @@ -38,8 +38,8 @@ function MotorController:update() self.speedThreshold then if not self.vehicle:getIsMotorStarted() then self:startMotor() - self.vehicle:raiseAIEvent('onAIFieldWorkerContinue', - 'onAIImplementContinue') + self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', + 'onAIImplementPrepareForWork') end self.timerSet = false elseif self.vehicle:getLastSpeed() <= self.speedThreshold then @@ -118,7 +118,7 @@ function MotorController:startMotor() self.vehicle.spec_cpAIWorker.motorDisabled = false -- TODO 25 for whatever reason, vehicle:getIsMotorStarted() returns true only much later after the motor was started -- so we call this and log for quite a few seconds when the motor was not running when the helper was started - self.implement:startMotor() + self.vehicle:startMotor() self:debug('Started motor after fuel save.') end From 8a9561b23877148a541ed646ab2e8212f6ad59b1 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Wed, 27 Nov 2024 15:49:55 -0500 Subject: [PATCH 038/158] fix: patch callstack --- scripts/specializations/CpAIWorker.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/specializations/CpAIWorker.lua b/scripts/specializations/CpAIWorker.lua index 5b126d93e..82377f54f 100644 --- a/scripts/specializations/CpAIWorker.lua +++ b/scripts/specializations/CpAIWorker.lua @@ -318,6 +318,7 @@ function CpAIWorker:stopCurrentAIJob(superFunc, message, ...) return superFunc(self, message, ...) end + local job = self:getJob() local wasCpActive = self:getIsCpActive() if wasCpActive then local driveStrategy = self:getCpDriveStrategy() @@ -339,12 +340,11 @@ function CpAIWorker:stopCurrentAIJob(superFunc, message, ...) end end end - local job = self:getJob() - if not job:isFinishingAllowed(message) then + if not job:isFinishingAllowed(message) then return end end - CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "stop message: %s", message:getMessage()) + CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "stop message: %s", message:getMessage(job)) superFunc(self, message,...) end From 9ff8288fd70ce5ac980ff98a4e8117c0d975d0f2 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 28 Nov 2024 07:44:20 -0500 Subject: [PATCH 039/158] fix: harvester start The pipe measurement, likely the unfold/fold part messes up the vehicle stated, so disabled for now. --- Courseplay.lua | 10 +++++++++ README.md | 5 +++-- scripts/ai/AIDriveStrategyCourse.lua | 13 ++++++++++++ scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 21 ++++++++++++++++++- scripts/ai/controllers/MotorController.lua | 15 +++++++------ scripts/ai/controllers/PipeController.lua | 3 ++- scripts/dev/ConsoleCommands.lua | 16 ++++++++++++++ scripts/dev/DevHelper.lua | 9 ++++++++ scripts/specializations/CpVehicleSettings.lua | 1 + 9 files changed, 81 insertions(+), 12 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index 805849419..33a734233 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -177,6 +177,16 @@ function Courseplay.saveToXMLFile(missionInfo) end FSCareerMissionInfo.saveToXMLFile = Utils.prependedFunction(FSCareerMissionInfo.saveToXMLFile, Courseplay.saveToXMLFile) +-- TODO 25 +function Courseplay.raiseAIEvent(vehicle, event1, event2, something, ...) + if event1 ~= 'onAIFieldWorkerActive' and event1 ~= 'onAIFieldWorkerTurnProgress' then + CpUtil.infoVehicle(vehicle, "raiseAIEvent: %s %s %s", tostring(event1), tostring(event2), tostring(something)) + g_devHelper:showFs25Debug(vehicle) + end +end + +AIVehicle.raiseAIEvent = Utils.prependedFunction(AIVehicle.raiseAIEvent, Courseplay.raiseAIEvent) + function Courseplay:update(dt) g_devHelper:update() g_bunkerSiloManager:update(dt) diff --git a/README.md b/README.md index b59d2b41a..091dbf703 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,9 @@ if you see it anywhere else, it's a scam. # TODO -- [ ] Can't start worker when the field is not owned. This may or may not be problem, need to check what happens on mission fields. TurnOnVehicle:getCanBeTurnedOn() returns false, and ever loop when we try to turn the vehicle on, it turns it off in the next loop. -- [ ] Pea harvester doesn't lower implements on start. +- [x] Can't start worker when the field is not owned. This may or may not be problem, need to check what happens on mission fields. TurnOnVehicle:getCanBeTurnedOn() returns false, and ever loop when we try to turn the vehicle on, it turns it off in the next loop. +- [x] Pea harvester doesn't lower implements on start. +- [ ] Measure unfolded pipe somehow as PipeController:measurePipeProperties() messes up the vehicle state ## Help Us Out diff --git a/scripts/ai/AIDriveStrategyCourse.lua b/scripts/ai/AIDriveStrategyCourse.lua index 8f2395b98..aaa2fd98b 100644 --- a/scripts/ai/AIDriveStrategyCourse.lua +++ b/scripts/ai/AIDriveStrategyCourse.lua @@ -114,6 +114,7 @@ end function AIDriveStrategyCourse:setAIVehicle(vehicle, jobParameters) self.vehicle = vehicle + --self:fixTurnOnEvent() self.jobParameters = jobParameters self:initializeImplementControllers(vehicle) self.ppc = PurePursuitController(vehicle) @@ -151,6 +152,16 @@ function AIDriveStrategyCourse:setAIVehicle(vehicle, jobParameters) self:raiseControllerEvent(self.onStartEvent) end +function AIDriveStrategyCourse:fixTurnOnEvent() + for _, action in ipairs(self.vehicle.actionController.actions) do + for _, listener in ipairs(action.aiEventListener) do + if listener.eventName == 'onAIImplementStart' then + listener.direction = 1 + end + end + end +end + --- Does the strategy need the current assigned course? function AIDriveStrategyCourse:isGeneratedCourseNeeded() return true @@ -313,8 +324,10 @@ function AIDriveStrategyCourse:raiseImplements() end function AIDriveStrategyCourse:lowerImplements() + self:debug('Lowering all implements') --- Lowers all implements, that are available for the giants field worker. for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do + CpUtil.debugImplement(CpDebug.DBG_IMPLEMENTS, implement.object,'Lowering implement') implement.object:aiImplementStartLine() end self.vehicle:raiseStateChange(VehicleStateChange.AI_START_LINE) diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index a31ba009d..8dc5626a7 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -89,6 +89,7 @@ function AIDriveStrategyFieldWorkCourse:start(course, startIx, jobParameters) self:debug('Close enough to start waypoint %d, no alignment course needed', startIx) self:startCourse(course, startIx) self.state = self.states.INITIAL + self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', 'onAIImplementPrepareForWork') end --- Store a reference to the original generated course self.originalGeneratedFieldWorkCourse = self.vehicle:getFieldWorkCourse() @@ -836,4 +837,22 @@ local function emptyFunction(object, superFunc, ...) end --- Makes sure the automatic work width isn't being reset. VariableWorkWidth.onAIFieldWorkerStart = Utils.overwrittenFunction(VariableWorkWidth.onAIFieldWorkerStart, emptyFunction) -VariableWorkWidth.onAIImplementStart = Utils.overwrittenFunction(VariableWorkWidth.onAIImplementStart, emptyFunction) \ No newline at end of file +VariableWorkWidth.onAIImplementStart = Utils.overwrittenFunction(VariableWorkWidth.onAIImplementStart, emptyFunction) + +-- TODO 25 +-- This seems to be called when the Giants AI is done generating a field course, but we don't want the stock +-- AI to do anything when the course is loaded, so we just return if the vehicle is a CP vehicle. +local function noOpWhenCpActive(self, superFunc, ...) + -- TODO this also comes when we just stopped a CP vehicle right after it was started + if self.vehicle:getIsCpActive() then + return + end + return superFunc(self, ...) +end +AIDriveStrategyFieldCourse.onFieldCourseLoadedCallback = Utils.overwrittenFunction(AIDriveStrategyFieldCourse.onFieldCourseLoadedCallback, noOpWhenCpActive) + +-- TODO 25 +-- The other thing messing us up is the delete() when we remove the Giants strategies in CpAITaskFieldWork, +-- because it triggers an AI end line event, raising all implements + +AIDriveStrategyFieldCourse.delete = Utils.overwrittenFunction(AIDriveStrategyFieldCourse.delete, noOpWhenCpActive) diff --git a/scripts/ai/controllers/MotorController.lua b/scripts/ai/controllers/MotorController.lua index b29d114ea..d74ccda95 100644 --- a/scripts/ai/controllers/MotorController.lua +++ b/scripts/ai/controllers/MotorController.lua @@ -28,8 +28,7 @@ function MotorController:update() if not self.settings.fuelSave:getValue() then if not self.vehicle:getIsMotorStarted() then self:startMotor() - self.vehicle:raiseAIEvent('onAIFieldWorkerContinue', - 'onAIImplementContinue') + self.vehicle:raiseAIEvent('onAIFieldWorkerContinue', 'onAIImplementContinue') end self.timerSet = false return @@ -38,8 +37,7 @@ function MotorController:update() self.speedThreshold then if not self.vehicle:getIsMotorStarted() then self:startMotor() - self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', - 'onAIImplementPrepareForWork') + self.vehicle:raiseAIEvent("onAIFieldWorkerContinue", "onAIImplementContinue") end self.timerSet = false elseif self.vehicle:getLastSpeed() <= self.speedThreshold then @@ -50,8 +48,7 @@ function MotorController:update() end if self.timer:get() then if self.vehicle:getIsMotorStarted() then - self.vehicle:raiseAIEvent('onAIFieldWorkerBlock', - 'onAIImplementBlock') + self.vehicle:raiseAIEvent('onAIFieldWorkerBlock', 'onAIImplementBlock') self:stopMotor() end end @@ -118,8 +115,10 @@ function MotorController:startMotor() self.vehicle.spec_cpAIWorker.motorDisabled = false -- TODO 25 for whatever reason, vehicle:getIsMotorStarted() returns true only much later after the motor was started -- so we call this and log for quite a few seconds when the motor was not running when the helper was started - self.vehicle:startMotor() - self:debug('Started motor after fuel save.') + if self.vehicle:getCanBeTurnedOn() then + self.implement:startMotor() + self:debug('Started motor after fuel save.') + end end function MotorController:stopMotor() diff --git a/scripts/ai/controllers/PipeController.lua b/scripts/ai/controllers/PipeController.lua index 4005b6ce9..531915d73 100644 --- a/scripts/ai/controllers/PipeController.lua +++ b/scripts/ai/controllers/PipeController.lua @@ -24,7 +24,8 @@ function PipeController:init(vehicle, implement, isConsoleCommand) self.pipeOffsetX, self.pipeOffsetZ = 0, 0 self.pipeOnLeftSide = true if not isConsoleCommand then - self:measurePipeProperties() + -- TODO 25 this messes up the vehicle, turns off the harvester and messes up the folded state + --self:measurePipeProperties() end self.isDischargingTimer = CpTemporaryObject(false) diff --git a/scripts/dev/ConsoleCommands.lua b/scripts/dev/ConsoleCommands.lua index cf1b640d3..80cf25c01 100644 --- a/scripts/dev/ConsoleCommands.lua +++ b/scripts/dev/ConsoleCommands.lua @@ -22,6 +22,8 @@ CpConsoleCommands.commands = { { 'cpUnfreeze', 'Unfreeze the CP driver', 'cpUnfreeze' }, { 'cpStopAll', 'Stops all cp drivers', 'cpStopAll' }, { 'cpGenerateDefaultCourse', '[number of headlands ] Generate a default course', 'cpGenerateDefaultCourse' }, + { 'cpRaiseAIEvent', 'vehicle/fieldworkerEvent AIImplementEvent', 'cpRaiseAIEvent'}, + { 'cpRaiseStateChange', 'VehicleStateChange.*', 'cpRaiseStateChange'} } function CpConsoleCommands:init() @@ -260,6 +262,20 @@ function CpConsoleCommands:cpStopAll() end end +function CpConsoleCommands:cpRaiseAIEvent(vehicleEvent, implementEvent) + local vehicle = CpUtil.getCurrentVehicle() + if vehicle then + vehicle:raiseAIEvent(vehicleEvent, implementEvent) + end +end + +function CpConsoleCommands:cpRaiseStateChange(state) + local vehicle = CpUtil.getCurrentVehicle() + if vehicle then + vehicle:raiseStateChange(VehicleStateChange[state]) + end +end + -- when reloading, clean up before re-instantiation if g_consoleCommands then g_consoleCommands:delete() diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index d3b6c33ec..9255f50fe 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -312,6 +312,15 @@ function DevHelper:showDriveData() strategy.reverser:getDriveData() end +function DevHelper:showFs25Debug(vehicle) + local a, b = vehicle:getCanAIFieldWorkerContinueWork() + local c, d = vehicle:getAttachedAIImplements()[1] and vehicle:getAttachedAIImplements()[1].object:getCanAIImplementContinueWork() + local e, f = vehicle:getIsMotorStarted() + local g, h = vehicle.getCanBeTurnedOn and vehicle:getCanBeTurnedOn() + CpUtil.infoVehicle(vehicle, 'fw can work: %s %s | imp1 can work: %s %s | motorstarted: %s %s | canbeturnedon: %s %s', tostring(a), tostring(b), tostring(c), tostring(d), tostring(e), tostring(f), tostring(g), tostring(h)) + --printCallstack() +end + -- make sure to recreate the global dev helper whenever this script is (re)loaded g_devHelper = DevHelper() diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index a40e2ef48..186a28b69 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -125,6 +125,7 @@ end function CpVehicleSettings:onStateChange(state, data) -- TODO 25 CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, 'onStateChange %s', tostring(state)) + g_devHelper:showFs25Debug(self) local spec = self.spec_cpVehicleSettings if state == VehicleStateChange.FILLTYPE_CHANGE and self:getIsSynchronized() then local _, hasSprayer = AIUtil.getAllChildVehiclesWithSpecialization(self, Sprayer, nil) From 859c8a4a9441c368dfb67e4fcaa99241bba839e8 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 28 Nov 2024 09:49:36 -0500 Subject: [PATCH 040/158] fix: pipe measurement Measure the pipe length after load and attach, instead of when starting the driver to prevent messing up vehicle states with the pipe measurement hack. Add two hidden vehicle settings for the pipe offset. Seems to work for most, certainly not for the Oxbo BP2140e, the x offset is way too low, may be a vehicle bug. --- config/VehicleSettingsSetup.xml | 4 ++++ scripts/ai/controllers/PipeController.lua | 20 +++++++++++----- scripts/ai/jobs/CpAIJobFieldWork.lua | 2 +- .../specializations/CpAICombineUnloader.lua | 2 +- scripts/specializations/CpVehicleSettings.lua | 23 +++++++++++++++++++ 5 files changed, 43 insertions(+), 8 deletions(-) diff --git a/config/VehicleSettingsSetup.xml b/config/VehicleSettingsSetup.xml index 29628eb04..386445ff1 100644 --- a/config/VehicleSettingsSetup.xml +++ b/config/VehicleSettingsSetup.xml @@ -122,6 +122,10 @@ <Text>onlyCenter</Text> </Texts> </Setting> + <Setting classType="AIParameterSettingList" name="pipeOffsetX" min="-100" max="100" incremental="0.1" default="0" unit="2" precision="3" + isVisible="isPipeOffsetSettingsVisible"/> + <Setting classType="AIParameterSettingList" name="pipeOffsetZ" min="-100" max="100" incremental="0.1" default="0" unit="2" precision="3" + isVisible="isPipeOffsetSettingsVisible"/> </SettingSubTitle> <SettingSubTitle title="seeder" isVisible="areSowingMachineSettingsVisible"> diff --git a/scripts/ai/controllers/PipeController.lua b/scripts/ai/controllers/PipeController.lua index 531915d73..b12ccb806 100644 --- a/scripts/ai/controllers/PipeController.lua +++ b/scripts/ai/controllers/PipeController.lua @@ -11,7 +11,11 @@ PipeController.PIPE_STATE_OPEN = 2 PipeController.moveablePipeHeightOffset = 1 --- Offset to the calculated pipe height. PipeController.unloadingToGroundSpeed = 3 -function PipeController:init(vehicle, implement, isConsoleCommand) + +---@param doMeasure boolean true if the controller should measure the pipe by unfolding/opening. In FS25, +--- this messes up the vehicle, turns off the harvester and messes up the folded state, etc., so the strategy +--- should not do this when instantiating the pipe controller. +function PipeController:init(vehicle, implement, doMeasure) self.isConsoleCommand = isConsoleCommand ImplementController.init(self, vehicle, implement) self.pipeSpec = self.implement.spec_pipe @@ -23,9 +27,13 @@ function PipeController:init(vehicle, implement, isConsoleCommand) self.pipeOffsetX, self.pipeOffsetZ = 0, 0 self.pipeOnLeftSide = true - if not isConsoleCommand then - -- TODO 25 this messes up the vehicle, turns off the harvester and messes up the folded state - --self:measurePipeProperties() + if doMeasure then + self:measurePipeProperties() + else + -- the vehicle settings should have measured this automatically on load and changes + self.pipeOffsetX = vehicle:getCpSettings().pipeOffsetX:getValue() + self.pipeOffsetZ = vehicle:getCpSettings().pipeOffsetZ:getValue() + self.pipeOnLeftSide = self.pipeOffsetX >= 0 end self.isDischargingTimer = CpTemporaryObject(false) @@ -342,7 +350,7 @@ function PipeController:instantUnfold(isFolded, currentPipeState, targetPipeStat --- First we need to unfold the implement and then open the pipe if self.foldableSpec.turnOnFoldDirection == -1 then Foldable.setAnimTime(self.implement, 0, false) - else + else Foldable.setAnimTime(self.implement, 1, false) end AnimatedVehicle.updateAnimations(self.implement, 99999999, true) @@ -371,7 +379,7 @@ function PipeController:instantFold() self.implement:setFoldDirection(-self.foldableSpec.turnOnFoldDirection, true) if self.foldableSpec.turnOnFoldDirection == -1 then Foldable.setAnimTime(self.implement, 1, false) - else + else Foldable.setAnimTime(self.implement, 0, false) end self.implement:setFoldDirection(0, true) diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index 99ba122a7..2fb2f9005 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -262,7 +262,7 @@ function CpAIJobFieldWork:isPipeOnLeftSide(vehicle) local pipeObject = AIUtil.getImplementOrVehicleWithSpecialization(vehicle, Pipe) if pipeObject and SpecializationUtil.hasSpecialization(Combine, pipeObject.specializations) then --- The controller measures the pipe attributes on creation. - local controller = PipeController(vehicle, pipeObject) + local controller = PipeController(vehicle, pipeObject, true) local isPipeOnLeftSide = controller:isPipeOnTheLeftSide() controller:delete() return isPipeOnLeftSide diff --git a/scripts/specializations/CpAICombineUnloader.lua b/scripts/specializations/CpAICombineUnloader.lua index d91f8414c..4d1cac74e 100644 --- a/scripts/specializations/CpAICombineUnloader.lua +++ b/scripts/specializations/CpAICombineUnloader.lua @@ -46,7 +46,7 @@ local function executePipeControllerCommand(lambdaFunc, ...) if vehicle.spec_cpAICombineUnloader and vehicle.spec_cpAICombineUnloader.pipeController then controller = vehicle.spec_cpAICombineUnloader.pipeController else - controller = PipeController(vehicle, pipeObject, true) + controller = PipeController(vehicle, pipeObject, false) end if not lambdaFunc(controller, vehicle, ...) then vehicle.spec_cpAICombineUnloader.pipeController = nil diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index 186a28b69..a84227ce6 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -112,6 +112,8 @@ function CpVehicleSettings:onUpdate() spec.lowerImplementEarly:resetToLoadedValue() spec.bunkerSiloWorkWidth:resetToLoadedValue() spec.loadingShovelHeightOffset:resetToLoadedValue() + -- for vehicles with a non-attached pipe + CpVehicleSettings.setPipeOffset(self) end spec.finishedFirstUpdate = true if spec.needsRefresh then @@ -149,6 +151,7 @@ function CpVehicleSettings:onStateChange(state, data) spec.bunkerSiloWorkWidth, 'workingWidth') CpVehicleSettings.setFromVehicleConfiguration(self, data.attachedVehicle, spec.loadingShovelHeightOffset, 'loadingShovelOffset') + CpVehicleSettings.setPipeOffset(self) spec.needsRefresh = true elseif state == VehicleStateChange.DETACH then CpVehicleSettings.setAutomaticWorkWidthAndOffset(self, data.attachedVehicle) @@ -422,6 +425,26 @@ function CpVehicleSettings:setAutomaticBaleCollectorOffset() spec.baleCollectorOffset:setFloatValue(offset) end +--- If the vehicle has a pipe, instantiate a pipe controller to measure the pipe offsets. This is better +--- done here and not when the vehicle starts as measuring is a hack and may mess up the vehicle states. +function CpVehicleSettings:setPipeOffset() + local pipeObject = AIUtil.getImplementOrVehicleWithSpecialization(self, Pipe) + if pipeObject then + local spec = self.spec_cpVehicleSettings + -- ask the pipe controller to get the offsets + local pipeController = PipeController(self, pipeObject, true) + spec.pipeOffsetX:setFloatValue(pipeController:getPipeOffsetX()) + spec.pipeOffsetZ:setFloatValue(pipeController:getPipeOffsetZ()) + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, 'Pipe offsetX: %.1f, offsetZ: %.1f', + pipeController:getPipeOffsetX(), pipeController:getPipeOffsetZ()) + end +end + +--- For now, we don't want these to show up on the settings page. +function CpVehicleSettings:isPipeOffsetSettingsVisible() + return false +end + function CpVehicleSettings:isLoadingShovelOffsetSettingVisible() return not AIUtil.hasChildVehicleWithSpecialization(self, ConveyorBelt) end From e677118d4b6cbdeafabe274573971b7f9737b7f4 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 28 Nov 2024 16:39:27 -0500 Subject: [PATCH 041/158] fix: combine headland turn --- scripts/ai/turns/AITurn.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 4a0035b06..f9524b0da 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -461,7 +461,7 @@ function CombineHeadlandTurn:turn(dt) maxSpeed = self:getReverseSpeed() moveForwards = false gx, gz = self:getGoalPointForTurn(moveForwards, nil) - if math.abs(dx) < 0.2 then + if dx < 0.2 then self.state = self.states.REVERSE_ARC self:debug('Combine headland turn start reversing arc') end From 561a7ba023c2ed47f63490b6d2152c998b76703f Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 29 Nov 2024 12:40:58 -0500 Subject: [PATCH 042/158] fix: another division by zero error --- scripts/Course.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/Course.lua b/scripts/Course.lua index fd53da58b..c8fb4c419 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -614,9 +614,8 @@ function Course:getWaypointYRotation(ix) end local cx, _, cz = self:getWaypointPosition(i) local nx, _, nz = self:getWaypointPosition(i + 1) - local dx, dz = MathUtil.vector2Normalize(nx - cx, nz - cz) - -- check for NaN, or if current and next are at the same position - if dx ~= dx or dz ~= dz or (dx == 0 and dz == 0) then + -- if current and next are at the same position, to avoid division by zero + if MathUtil.vector2Length(nx - cx, nz - cz) < 0.00001 then -- use the direction of the previous waypoint if exists, otherwise the next. This is to make sure that -- the WaypointNode used by the PPC has a valid direction if i > 1 then @@ -626,6 +625,7 @@ function Course:getWaypointYRotation(ix) end return 0 end + local dx, dz = MathUtil.vector2Normalize(nx - cx, nz - cz) return MathUtil.getYRotationFromDirection(dx, dz) end From b5dddc33890ffa395374affbe238bf381188847e Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 29 Nov 2024 12:43:28 -0500 Subject: [PATCH 043/158] fix: yet another division by zero error --- scripts/ai/FillLevelManager.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ai/FillLevelManager.lua b/scripts/ai/FillLevelManager.lua index 05f024faf..2470411a9 100644 --- a/scripts/ai/FillLevelManager.lua +++ b/scripts/ai/FillLevelManager.lua @@ -106,7 +106,7 @@ end ---@param object table function FillLevelManager.getTotalFillLevelPercentage(object) local fillLevel,capacity = FillLevelManager.getTotalFillLevelAndCapacity(object) - return 100 * fillLevel / capacity + return 100 * CpMathUtil.divide(fillLevel, capacity) end function FillLevelManager.getTotalFillLevelAndCapacityForObject(object) @@ -199,7 +199,7 @@ end ---@return boolean function FillLevelManager.areAllTrailersFull(vehicle, fullThresholdPercentage) local totalFillLevel, totalCapacity, totalFreeCapacity = FillLevelManager.getAllTrailerFillLevels(vehicle) - local fillLevelPercentage = 100 * totalFillLevel / totalCapacity + local fillLevelPercentage = 100 * CpMathUtil.divide(totalFillLevel, totalCapacity) return totalFreeCapacity <= 0 or fillLevelPercentage >= (fullThresholdPercentage or 100) end @@ -209,7 +209,7 @@ end ---@return number total fill level percentage in % relative to max mass adjusted capacity. function FillLevelManager.getTotalTrailerFillLevelPercentage(vehicle) local totalFillLevel, totalCapacity, totalCapacityMassAdjusted = FillLevelManager.getAllTrailerFillLevels(vehicle) - return 100 * totalFillLevel / totalCapacity, 100 * totalFillLevel / totalCapacityMassAdjusted + return 100 * CpMathUtil.divide(totalFillLevel, totalCapacity), 100 * CpMathUtil.divide(totalFillLevel, totalCapacityMassAdjusted) end --- Gets the total fill level, capacity and mass adjusted capacity of all trailers. From 5b3f40f5f2f69554bc6fb516d2427a717967a7e2 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 29 Nov 2024 13:25:15 -0500 Subject: [PATCH 044/158] fix: collision log callstack --- scripts/pathfinder/PathfinderUtil.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index d42567954..9f189a248 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -288,7 +288,7 @@ function PathfinderUtil.CollisionDetector:overlapBoxCallback(transformId) if collidingObject:isa(Bale) then self:debug('collision with bale %d', collidingObject.id) else - self:debug('collision: %s', collidingObject:getName()) + self:debug('collision: %s', collidingObject.getName and collidingObject:getName() or 'unknown') end end if getHasClassId(transformId, ClassIds.TERRAIN_TRANSFORM_GROUP) then From ab76970b4b234c23a848fdde33e231974bacff9d Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 29 Nov 2024 15:37:01 -0500 Subject: [PATCH 045/158] fix: prepare for work on first waypoint Also HUD callstack --- scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 8 +++++++- scripts/gui/hud/CpHudInfoTexts.lua | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index 8dc5626a7..aa2e1bbfa 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -89,7 +89,7 @@ function AIDriveStrategyFieldWorkCourse:start(course, startIx, jobParameters) self:debug('Close enough to start waypoint %d, no alignment course needed', startIx) self:startCourse(course, startIx) self.state = self.states.INITIAL - self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', 'onAIImplementPrepareForWork') + self:prepareForFieldWork() end --- Store a reference to the original generated course self.originalGeneratedFieldWorkCourse = self.vehicle:getFieldWorkCourse() @@ -100,6 +100,11 @@ function AIDriveStrategyFieldWorkCourse:start(course, startIx, jobParameters) end end +--- Make sure all implements are in the working state +function AIDriveStrategyFieldWorkCourse:prepareForFieldWork() + self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', 'onAIImplementPrepareForWork') +end + --- Event raised when the driver has finished. function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished) AIDriveStrategyCourse.onFinished(self, hasFinished) @@ -563,6 +568,7 @@ function AIDriveStrategyFieldWorkCourse:startAlignmentTurn(fieldWorkCourse, star alignmentCourse = self:createAlignmentCourse(fieldWorkCourse, startIx) end self.ppc:setShortLookaheadDistance() + self:prepareForFieldWork() if alignmentCourse then local fm, bm = self:getFrontAndBackMarkers() self.turnContext = RowStartOrFinishContext(self.vehicle, fieldWorkCourse, startIx, startIx, self.turnNodes, diff --git a/scripts/gui/hud/CpHudInfoTexts.lua b/scripts/gui/hud/CpHudInfoTexts.lua index 33fddf617..952689cfe 100644 --- a/scripts/gui/hud/CpHudInfoTexts.lua +++ b/scripts/gui/hud/CpHudInfoTexts.lua @@ -191,7 +191,7 @@ function CpHudInfoTexts:update() end function CpHudInfoTexts:enterVehicle(vehicle) - g_currentMission:requestToEnterVehicle(vehicle) + g_localPlayer:requestToEnterVehicle(vehicle) end function CpHudInfoTexts:isVisible() From 3e40442353090925ae75e29fb125ab0fc6e0d29b Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 30 Nov 2024 06:29:09 -0500 Subject: [PATCH 046/158] fix: use our field scanner again --- .../CourseGeneratorInterface.lua | 7 +++--- scripts/field/CpFieldUtil.lua | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/scripts/courseGenerator/CourseGeneratorInterface.lua b/scripts/courseGenerator/CourseGeneratorInterface.lua index 48a647ff3..1cc042641 100644 --- a/scripts/courseGenerator/CourseGeneratorInterface.lua +++ b/scripts/courseGenerator/CourseGeneratorInterface.lua @@ -179,9 +179,8 @@ end function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) local vehicle = CpUtil.getCurrentVehicle() local x, _, z = getWorldTranslation(vehicle.rootNode) - --local valid, points = g_fieldScanner:findContour(x, z) - local field = CpFieldUtil.getFieldAtWorldPosition(x, z) - if field == nil then + local points = CpFieldUtil.detectFieldBoundary(x, z, true) + if points == nil then CpUtil.infoVehicle(vehicle, "Not on a field, can't generate") return end @@ -193,7 +192,7 @@ function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) settings.numberOfHeadlands:setFloatValue(nHeadlands or 3) settings.sharpenCorners:setValue(true) CpUtil.infoVehicle(vehicle, "Generating default course with %d headlands", settings.numberOfHeadlands:getValue()) - local ok, course = CourseGeneratorInterface.generate(CpFieldUtil.getFieldPolygon(field), {x = x, z = z}, vehicle, settings) + local ok, course = CourseGeneratorInterface.generate(points, {x = x, z = z}, vehicle, settings) if ok then vehicle:setFieldWorkCourse(course) end diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index ecaa1e004..4d3793ad1 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -162,3 +162,25 @@ function CpFieldUtil.getFieldPolygonAtWorldPosition(x, z) end return fieldPolygon, isCustomField end + +--- Find the boundary of field at position, using either the statically configured map field boundaries or our +--- field scanner. +---@param x number +---@param z number +---@param detect boolean use one of the field boundary detectors. If false, only the static map field boundaries are used. +---@param useGiantsDetector boolean use the Giants field boundary detector. If false, the CP field scanner is used. +---@return [{x, z]|nil the field boundary, nil if not on field +function CpFieldUtil.detectFieldBoundary(x, z, detect, useGiantsDetector) + if detect then + if useGiantsDetector then + -- not implemented + return nil + else + local valid, points = g_fieldScanner:findContour(x, z) + return valid and points or nil + end + else + local field = CpFieldUtil.getFieldAtWorldPosition(x, z) + return field and CpFieldUtil.getFieldPolygon(field) or nil + end +end \ No newline at end of file From 243d117b615e906177d4f1c358ac4b2b2a10d40f Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 30 Nov 2024 07:18:24 -0500 Subject: [PATCH 047/158] feat: keybind to generate course Ctrl-g for now. --- config/MasterTranslations.xml | 4 ++++ modDesc.xml | 5 +++++ scripts/courseGenerator/CourseGeneratorInterface.lua | 6 +++++- scripts/specializations/CpAIWorker.lua | 8 ++++++++ scripts/util/CpMathUtil.lua | 2 ++ 5 files changed, 24 insertions(+), 1 deletion(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index f2b0650b1..995585605 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -3089,6 +3089,10 @@ TPS extension <Text language="de"><![CDATA[CP: CP-Helfer starten/stoppen]]></Text> <Text language="en"><![CDATA[CP: Start/stop driver]]></Text> </Translation> + <Translation name="input_CP_GENERATE_COURSE"> + <Text language="de"><![CDATA[CP: Kurs generieren]]></Text> + <Text language="en"><![CDATA[CP: Generate course]></Text> + </Translation> <Translation name="input_CP_CHANGE_SELECTED_JOB"> <Text language="de"><![CDATA[CP: Aktuell ausgewählten Job ändern]]></Text> <Text language="en"><![CDATA[CP: Change current selected job]]></Text> diff --git a/modDesc.xml b/modDesc.xml index b99b41ce9..f69645814 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -573,6 +573,10 @@ Changelog 7.1.0.0: <binding device="KB_MOUSE_DEFAULT" input="KEY_lctrl KEY_h" /> </actionBinding> + <actionBinding action="CP_GENERATE_COURSE"> + <binding device="KB_MOUSE_DEFAULT" input="KEY_lctrl KEY_g" /> + </actionBinding> + <actionBinding action="CP_CHANGE_SELECTED_JOB"> <binding device="" input="" /> </actionBinding> @@ -612,6 +616,7 @@ Changelog 7.1.0.0: <action name="CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" /> <action name="CP_START_STOP" /> + <action name="CP_GENERATE_COURSE" /> <action name="CP_CHANGE_SELECTED_JOB"/> <action name="CP_CHANGE_STARTING_POINT"/> diff --git a/scripts/courseGenerator/CourseGeneratorInterface.lua b/scripts/courseGenerator/CourseGeneratorInterface.lua index 1cc042641..cac348e2f 100644 --- a/scripts/courseGenerator/CourseGeneratorInterface.lua +++ b/scripts/courseGenerator/CourseGeneratorInterface.lua @@ -176,6 +176,8 @@ end --- Console Commands --------------------------------------------- +--- Generate a course with the current course generator settings +---@param nHeadlands number|nil override number of headlands function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) local vehicle = CpUtil.getCurrentVehicle() local x, _, z = getWorldTranslation(vehicle.rootNode) @@ -189,7 +191,9 @@ function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) settings.workWidth:refresh() settings.workWidth:setFloatValue(width) vehicle:getCpSettings().toolOffsetX:setFloatValue(offset) - settings.numberOfHeadlands:setFloatValue(nHeadlands or 3) + if nHeadlands then + settings.numberOfHeadlands:setFloatValue(nHeadlands) + end settings.sharpenCorners:setValue(true) CpUtil.infoVehicle(vehicle, "Generating default course with %d headlands", settings.numberOfHeadlands:getValue()) local ok, course = CourseGeneratorInterface.generate(points, {x = x, z = z}, vehicle, settings) diff --git a/scripts/specializations/CpAIWorker.lua b/scripts/specializations/CpAIWorker.lua index 82377f54f..967220970 100644 --- a/scripts/specializations/CpAIWorker.lua +++ b/scripts/specializations/CpAIWorker.lua @@ -128,6 +128,7 @@ function CpAIWorker:onRegisterActionEvents(isActiveForInput, isActiveForInputIgn end addActionEvent(self, InputAction.CP_START_STOP, CpAIWorker.startStopCpActionEvent) + addActionEvent(self, InputAction.CP_GENERATE_COURSE, CpAIWorker.generateCourse) addActionEvent(self, InputAction.CP_CHANGE_SELECTED_JOB, CpAIWorker.changeCurrentSelectedJob) addActionEvent(self, InputAction.CP_CHANGE_STARTING_POINT, CpAIWorker.changeStartingPoint) addActionEvent(self, InputAction.CP_CLEAR_COURSE, CpAIWorker.clearCourse, @@ -544,6 +545,13 @@ function CpAIWorker:onStartAutoDrive() end end +----------------------------------------------- +--- Generate course +--------------------------------------------- +function CpAIWorker:generateCourse() + CourseGeneratorInterface.generateDefaultCourse() +end + --------------------------------------------- --- Console commands --------------------------------------------- diff --git a/scripts/util/CpMathUtil.lua b/scripts/util/CpMathUtil.lua index 69f032ef4..7183bc410 100644 --- a/scripts/util/CpMathUtil.lua +++ b/scripts/util/CpMathUtil.lua @@ -125,6 +125,8 @@ function CpMathUtil.isPointInPolygon(polygon, x, z) return pointInPolygon ~= -1 end +-- TODO: this isn't the distance to the edge, this is the distance to a vertex, so won't be accurate for polygons +-- with long edges function CpMathUtil.getClosestDistanceToPolygonEdge(polygon, x, z) local closestDistance = math.huge for _, p in ipairs(polygon) do From b9e92eb6473258aae61d150a89eadb42858c5950 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 30 Nov 2024 07:30:28 -0500 Subject: [PATCH 048/158] fix: division by zero --- scripts/courseGenerator/Center.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/courseGenerator/Center.lua b/scripts/courseGenerator/Center.lua index 838c3c214..152120c18 100644 --- a/scripts/courseGenerator/Center.lua +++ b/scripts/courseGenerator/Center.lua @@ -174,7 +174,7 @@ function Center:generate() local sequencedBlocks, entries = chromosome:getBlockSequenceAndEntries() local distance, _ = calculateDistanceAndConnectingPaths(sequencedBlocks, entries) chromosome:setDistance(distance) - chromosome:setFitness(10000 / distance) + chromosome:setFitness(CpMathUtil.divide(10000 / distance)) end local blocksInSequence, entries, _ = CourseGenerator.BlockSequencer(blocks):findBlockSequence(calculateFitness) From 733b0fb5b43b14431fff3efca72334b3e361519a Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 30 Nov 2024 07:30:46 -0500 Subject: [PATCH 049/158] fix: division by zero --- scripts/courseGenerator/Center.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/courseGenerator/Center.lua b/scripts/courseGenerator/Center.lua index 152120c18..6bd5ddf19 100644 --- a/scripts/courseGenerator/Center.lua +++ b/scripts/courseGenerator/Center.lua @@ -174,7 +174,7 @@ function Center:generate() local sequencedBlocks, entries = chromosome:getBlockSequenceAndEntries() local distance, _ = calculateDistanceAndConnectingPaths(sequencedBlocks, entries) chromosome:setDistance(distance) - chromosome:setFitness(CpMathUtil.divide(10000 / distance)) + chromosome:setFitness(CpMathUtil.divide(10000, distance)) end local blocksInSequence, entries, _ = CourseGenerator.BlockSequencer(blocks):findBlockSequence(calculateFitness) From 4685360bb3dd82a3909cd9d6f63367ac7002478e Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 30 Nov 2024 10:12:22 -0500 Subject: [PATCH 050/158] fix: division by zero --- scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index aa2e1bbfa..c4ec2224f 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -223,11 +223,13 @@ end function AIDriveStrategyFieldWorkCourse:setAITarget() --local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) local wp = self.ppc:getCurrentWaypoint() - --- TODO: For some reason wp.dx and wp.dz are nil sometimes. + --- TODO: For some reason wp.dx and wp.dz are nil sometimes local dx, dz = wp.dx or 0, wp.dz or 0 - local length = MathUtil.vector2Length(dx, dz) - dx = dx / length - dz = dz / length + if wp.dx ~= 0 or wp.dz ~= 0 then + local length = MathUtil.vector2Length(dx, dz) + dx = dx / length + dz = dz / length + end self.vehicle.aiDriveDirection = { dx, dz } local x, _, z = getWorldTranslation(self.vehicle:getAIDirectionNode()) self.vehicle.aiDriveTarget = { x, z } From 32b8d404ac9619058601b0c774663e39a2bc3b9c Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 30 Nov 2024 19:06:35 +0100 Subject: [PATCH 051/158] Disables CP jobs in the ai menu for now --- scripts/ai/jobs/CpAIJobBaleFinder.lua | 2 +- scripts/ai/jobs/CpAIJobBunkerSilo.lua | 2 +- scripts/ai/jobs/CpAIJobCombineUnloader.lua | 2 +- scripts/ai/jobs/CpAIJobFieldWork.lua | 2 +- scripts/ai/jobs/CpAIJobSiloLoader.lua | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ai/jobs/CpAIJobBaleFinder.lua b/scripts/ai/jobs/CpAIJobBaleFinder.lua index 34dd06be2..9825b63e3 100644 --- a/scripts/ai/jobs/CpAIJobBaleFinder.lua +++ b/scripts/ai/jobs/CpAIJobBaleFinder.lua @@ -30,7 +30,7 @@ function CpAIJobBaleFinder:setupJobParameters() end function CpAIJobBaleFinder:getIsAvailableForVehicle(vehicle) - return vehicle.getCanStartCpBaleFinder and vehicle:getCanStartCpBaleFinder() + return false--vehicle.getCanStartCpBaleFinder and vehicle:getCanStartCpBaleFinder() -- TODO_25 end function CpAIJobBaleFinder:getCanStartJob() diff --git a/scripts/ai/jobs/CpAIJobBunkerSilo.lua b/scripts/ai/jobs/CpAIJobBunkerSilo.lua index ac73421ec..6f81c431c 100644 --- a/scripts/ai/jobs/CpAIJobBunkerSilo.lua +++ b/scripts/ai/jobs/CpAIJobBunkerSilo.lua @@ -29,7 +29,7 @@ function CpAIJobBunkerSilo:setupJobParameters() end function CpAIJobBunkerSilo:getIsAvailableForVehicle(vehicle) - return vehicle.getCanStartCpBunkerSiloWorker and vehicle:getCanStartCpBunkerSiloWorker() + return false--vehicle.getCanStartCpBunkerSiloWorker and vehicle:getCanStartCpBunkerSiloWorker() -- TODO_25 end function CpAIJobBunkerSilo:getCanStartJob() diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index 2a0860b5b..05865df4e 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -59,7 +59,7 @@ function CpAIJobCombineUnloader:setupJobParameters() end function CpAIJobCombineUnloader:getIsAvailableForVehicle(vehicle) - return vehicle.getCanStartCpCombineUnloader and vehicle:getCanStartCpCombineUnloader() + return false--vehicle.getCanStartCpCombineUnloader and vehicle:getCanStartCpCombineUnloader() -- TODO_25 end function CpAIJobCombineUnloader:getCanStartJob() diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index 2fb2f9005..aca970f0f 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -272,7 +272,7 @@ function CpAIJobFieldWork:isPipeOnLeftSide(vehicle) end function CpAIJobFieldWork:getIsAvailableForVehicle(vehicle) - return vehicle.getCanStartCpFieldWork and vehicle:getCanStartCpFieldWork() + return false --vehicle.getCanStartCpFieldWork and vehicle:getCanStartCpFieldWork() -- TODO_25 end function CpAIJobFieldWork:resetStartPositionAngle(vehicle) diff --git a/scripts/ai/jobs/CpAIJobSiloLoader.lua b/scripts/ai/jobs/CpAIJobSiloLoader.lua index 1a4f78526..54b4d4174 100644 --- a/scripts/ai/jobs/CpAIJobSiloLoader.lua +++ b/scripts/ai/jobs/CpAIJobSiloLoader.lua @@ -52,7 +52,7 @@ function CpAIJobSiloLoader:setupJobParameters() end function CpAIJobSiloLoader:getIsAvailableForVehicle(vehicle) - return vehicle.getCanStartCpSiloLoaderWorker and vehicle:getCanStartCpSiloLoaderWorker() + return false --vehicle.getCanStartCpSiloLoaderWorker and vehicle:getCanStartCpSiloLoaderWorker() -- TODO_25 end function CpAIJobSiloLoader:getCanStartJob() From b230a81241182d66674f866c9d9650342d3683b0 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 30 Nov 2024 19:51:46 +0100 Subject: [PATCH 052/158] Fixes the dialog abi changes ... --- Courseplay.lua | 4 +- scripts/ai/jobs/CpAIJobFieldWork.lua | 6 +-- .../ai/parameters/AIParameterSettingList.lua | 12 ++---- scripts/editor/CourseEditor.lua | 20 ++++------ scripts/editor/brushes/BaseBrush.lua | 24 ++++-------- scripts/field/CustomFieldManager.lua | 39 +++++++------------ scripts/gui/pages/CpCourseManagerFrame.lua | 39 +++++++------------ 7 files changed, 50 insertions(+), 94 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index 33a734233..f365a9d85 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -75,9 +75,7 @@ function Courseplay:showUserInformation(xmlFile, key) self.MOD_NAME, self.currentVersion, lastLoadedVersion) if showInfoDialog then - g_gui:showDialog({ - text = string.format(g_i18n:getText("CP_infoText"), self.currentVersion) - }) + InfoDialog.show(string.format(g_i18n:getText("CP_infoText"), self.currentVersion)) if xmlFile then xmlFile:setValue(key.."#lastVersion", self.currentVersion) end diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index aca970f0f..8addbad25 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -242,10 +242,8 @@ function CpAIJobFieldWork:onClickGenerateFieldWorkCourse() ) end if not ok then - g_gui:showInfoDialog({ - dialogType = DialogElement.TYPE_ERROR, - text = g_i18n:getText('CP_error_could_not_generate_course') - }) + InfoDialog.show(g_i18n:getText('CP_error_could_not_generate_course'), + nil, nil, DialogElement.TYPE_ERROR) return false end diff --git a/scripts/ai/parameters/AIParameterSettingList.lua b/scripts/ai/parameters/AIParameterSettingList.lua index 8edb0dcdb..e050686ae 100644 --- a/scripts/ai/parameters/AIParameterSettingList.lua +++ b/scripts/ai/parameters/AIParameterSettingList.lua @@ -538,9 +538,8 @@ end --- Used for text input settings. function AIParameterSettingList:showInputTextDialog(guiElement) - g_gui:showTextInputDialog({ - disableFilter = true, - callback = function (self, value, clickOk) + TextInputDialog.show( + function (self, value, clickOk) if clickOk and value ~= nil then local v = value:match("-%d[%d., ]*") v = v or value:match("%d[%d., ]*") @@ -569,11 +568,8 @@ function AIParameterSettingList:showInputTextDialog(guiElement) FocusManager:setFocus(guiElement) end end, - maxCharacters = 7, - target = self, - dialogPrompt = self:getTitle(), - confirmText = g_i18n:getText("button_ok"), - }) + self, nil, self:getTitle(), nil, 7, + g_i18n:getText("button_ok"), nil, nil, false) end function AIParameterSettingList:resetGuiElement() diff --git a/scripts/editor/CourseEditor.lua b/scripts/editor/CourseEditor.lua index cb89cd69a..1359f422c 100644 --- a/scripts/editor/CourseEditor.lua +++ b/scripts/editor/CourseEditor.lua @@ -68,19 +68,17 @@ function CourseEditor:onClickLaneOffsetSetting(closure, ignoreDialog) local allowedValues = Course.MultiVehicleData.getAllowedPositions(course:getMultiTools()) local texts = CpFieldWorkJobParameters.laneOffset:getTextsForValues(allowedValues) if not ignoreDialog and not g_gui:getIsDialogVisible() then - g_gui:showOptionDialog({ - title = "", - text = CpFieldWorkJobParameters.laneOffset:getTitle(), - options = texts, - callback = function (item) + OptionDialog.show( + function (item) if item > 0 then local value = allowedValues[item] self.courseWrapper:getCourse():setPosition(value) self.courseDisplay:setCourse(self.courseWrapper) closure(texts[item]) end - end - }) + end, + CpFieldWorkJobParameters.laneOffset:getTitle(), + "", texts) else local position = course.multiVehicleData.position for ix, v in ipairs(allowedValues) do @@ -142,14 +140,12 @@ end function CourseEditor:showYesNoDialog(title, callbackFunc) - g_gui:showYesNoDialog({ - text = string.format(g_i18n:getText(title)), - callback = function (self, clickOk, viewEntry) + YesNoDialog.show( + function (self, clickOk, viewEntry) callbackFunc(self, clickOk, viewEntry) self:updateLists() end, - target = self, - }) + self, string.format(g_i18n:getText(title))) end function CourseEditor:delete() diff --git a/scripts/editor/brushes/BaseBrush.lua b/scripts/editor/brushes/BaseBrush.lua index 5ce8f2497..96ea85380 100644 --- a/scripts/editor/brushes/BaseBrush.lua +++ b/scripts/editor/brushes/BaseBrush.lua @@ -61,26 +61,16 @@ function CpBrush:update(dt) end function CpBrush:openTextInput(callback, title, args) - g_gui:showTextInputDialog({ - disableFilter = true, - callback = callback, - target = self, - defaultText = "", - dialogPrompt = title, - imePrompt = title, - maxCharacters = 50, - confirmText = g_i18n:getText("button_ok"), - args = args - }) + TextInputDialog.show( + callback, self, "", + title, title, 50, + g_i18n:getText("button_ok"), args) end function CpBrush:showYesNoDialog(callback, title, args) - g_gui:showYesNoDialog({ - text = title, - callback = callback, - target = self, - args = args - }) + YesNoDialog.show( + callback, self, title, + nil, nil, nil, nil, nil, nil, args) end --- Gets the translation with the translation prefix. diff --git a/scripts/field/CustomFieldManager.lua b/scripts/field/CustomFieldManager.lua index 48b15fa8a..32f097dd6 100644 --- a/scripts/field/CustomFieldManager.lua +++ b/scripts/field/CustomFieldManager.lua @@ -76,35 +76,26 @@ function CustomFieldManager:addField(waypoints) ---@type CustomField local newField = CustomField() newField:setup(self.namePrefix..self:getNewFieldNumber(), waypoints) - g_gui:showYesNoDialog({ - text = string.format(g_i18n:getText("CP_customFieldManager_confirm_save"), newField:getName()), - callback = CustomFieldManager.onClickSaveDialog, - target = self, - args = newField - }) + YesNoDialog.show( + CustomFieldManager.onClickSaveDialog, self, + string.format(g_i18n:getText("CP_customFieldManager_confirm_save"), newField:getName()), + nil, nil, nil, nil, nil, nil, newField) end function CustomFieldManager:deleteField(fieldToDelete) - g_gui:showYesNoDialog({ - text = string.format(g_i18n:getText("CP_customFieldManager_confirm_delete"), fieldToDelete:getName()), - callback = CustomFieldManager.onClickDeleteDialog, - target = self, - args = fieldToDelete - }) + YesNoDialog.show( + CustomFieldManager.onClickDeleteDialog, self, + string.format(g_i18n:getText("CP_customFieldManager_confirm_delete"), fieldToDelete:getName()), + nil, nil, nil, nil, nil, nil, fieldToDelete) end function CustomFieldManager:renameField(field, hotspot) - g_gui:showTextInputDialog({ - disableFilter = true, - callback = CustomFieldManager.onClickRenameDialog, - target = self, - defaultText = field:getName() or "", - dialogPrompt = g_i18n:getText("CP_customFieldManager_rename"), - maxCharacters = 30, - confirmText = g_i18n:getText("button_ok"), - args = field - }) + TextInputDialog.show( + CustomFieldManager.onClickRenameDialog, self, + field:getName() or "", + g_i18n:getText("CP_customFieldManager_rename"), + nil, 30, g_i18n:getText("button_ok"), field) end function CustomFieldManager:editField(fieldToEdit, hotspot) @@ -185,9 +176,7 @@ function CustomFieldManager:onClickRenameDialog(newName, clickOk, fieldToRename) else CpUtil.debugFormat(CpDebug.DBG_COURSES, 'Could not rename custom field from %s to %s.', fieldToRename:getName(), newName) --- New field name already in use. - g_gui:showInfoDialog({ - text = string.format(g_i18n:getText("CP_customFieldManager_rename_error"), newName) - }) + InfoDialog.show(string.format(g_i18n:getText("CP_customFieldManager_rename_error"), newName)) end return end diff --git a/scripts/gui/pages/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua index d4fa0a2c0..b03f3e190 100644 --- a/scripts/gui/pages/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -96,7 +96,7 @@ function CpCourseManagerFrame:getCurrentEntry() return element.viewEntry end -function CpCourseManagerFrame:initialize() +function CpCourseManagerFrame:initialize(menu) --- Changes the input actions. self.modeButton = { profile = "buttonActivate", @@ -540,10 +540,8 @@ function CpCourseManagerFrame:onClickSaveEntryDialog(text, clickOk, viewEntry) end local vehicle = CpUtil.getCurrentVehicle() if not vehicle:saveCpCourses(file, text) then - g_gui:showInfoDialog({ - text = string.format(g_i18n:getText(self.translations.invalidNameError), - text) - }) + InfoDialog.show( + string.format(g_i18n:getText(self.translations.invalidNameError), text)) end end end @@ -618,39 +616,30 @@ end function CpCourseManagerFrame:showInputTextDialog(title, callbackFunc, viewEntry, defaultText) - g_gui:showTextInputDialog({ - disableFilter = true, - callback = function (self, text, clickOk, viewEntry) + TextInputDialog.show( + function (self, text, clickOk, viewEntry) text = CpUtil.cleanFilePath(text) callbackFunc(self, text, clickOk, viewEntry) self:updateLists() end, - target = self, - defaultText = defaultText or "", - dialogPrompt = string.format(g_i18n:getText(title), viewEntry and viewEntry:getName()), - imePrompt = g_i18n:getText(title), - maxCharacters = 50, - confirmText = g_i18n:getText("button_ok"), - args = viewEntry - }) + self, defaultText or "", + string.format(g_i18n:getText(title), viewEntry and viewEntry:getName()), + g_i18n:getText(title), 50, g_i18n:getText("button_ok"), viewEntry) end function CpCourseManagerFrame:showYesNoDialog(title, callbackFunc, viewEntry) - g_gui:showYesNoDialog({ - text = string.format(g_i18n:getText(title), viewEntry:getName()), - callback = function (self, clickOk, viewEntry) + YesNoDialog.show( + function (self, clickOk, viewEntry) callbackFunc(self, clickOk, viewEntry) self:updateLists() end, - target = self, - args = viewEntry - }) + self, string.format(g_i18n:getText(title), viewEntry:getName()), + nil, nil, nil, nil, + nil, nil, viewEntry) end function CpCourseManagerFrame.showInfoDialog(title, viewEntry) - g_gui:showInfoDialog({ - text = string.format(g_i18n:getText(title), viewEntry:getName()) - }) + InfoDialog.show(string.format(g_i18n:getText(title), viewEntry:getName())) end --------------------------------------------------- From da04c924cc5f3fdfff325c81561c2333d9ca93c0 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 30 Nov 2024 20:08:53 +0100 Subject: [PATCH 053/158] Small course manager gui adjustments and disabled course editor --- config/gui/pages/CourseManagerFrame.xml | 30 +++++++++++----------- scripts/editor/CourseEditor.lua | 6 ++--- scripts/gui/pages/CpCourseManagerFrame.lua | 2 +- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/config/gui/pages/CourseManagerFrame.xml b/config/gui/pages/CourseManagerFrame.xml index 5713ad86e..9c4615aaf 100644 --- a/config/gui/pages/CourseManagerFrame.xml +++ b/config/gui/pages/CourseManagerFrame.xml @@ -7,34 +7,34 @@ </Bitmap> <Text profile="fs25_menuHeaderTitle" id="categoryHeaderText" /> </GuiElement> - <BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"> - <Button profile="fs25_subCategorySelectorTabbedTab" id="subCategoryTabs[1]" + <!-- <BoxLayout profile="fs25_subCategorySelectorTabbedBox" id="subCategoryBox"> --> + <!-- <Button profile="fs25_subCategorySelectorTabbedTab" id="subCategoryTabs[1]" text="TODO"> <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> </Button> <Button profile="fs25_subCategorySelectorTabbedTab" id="subCategoryTabs[2]" text="TODO"> <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> - </Button> - </BoxLayout> + </Button> --> + <!-- </BoxLayout> --> <ThreePartBitmap profile="fs25_lineSeparatorTopHighlighted" position="0px -57px" /> <MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" position="0px -10px" /> <GuiElement profile="fs25_subCategorySelectorTabbedContainer" id="subCategoryPages[1]"> <GuiElement profile="fs25_statisticsHeaderBox"> - <Text profile="fs25_statisticsHeaderText" id="leftColumnHeader" /> - <Text profile="fs25_statisticsHeaderText" text="..." - position="116px 0px" textAlignment="right" /> - <Text profile="fs25_statisticsHeaderText" text="..." - position="456px 0px" /> - <Text profile="fs25_statisticsHeaderText" text="..." - position="877px 0px" /> - <Bitmap profile="fs25_statisticsHeaderIcon" position="1144px 0px" /> - <Text profile="fs25_statisticsHeaderText" text="..." position="1134px 0px" /> + <Text profile="fs25_statisticsHeaderText" id="leftColumnHeader"/> + <Text profile="fs25_statisticsHeaderText" + position="116px 0px" textAlignment="right" visible="false"/> + <Text profile="fs25_statisticsHeaderText" + position="456px 0px" visible="false"/> + <Text profile="fs25_statisticsHeaderText" + position="877px 0px" visible="false"/> + <Bitmap profile="fs25_statisticsHeaderIcon" position="1144px 0px" visible="false"/> + <Text profile="fs25_statisticsHeaderText" position="1134px 0px" visible="false"/> <Bitmap profile="fs25_statisticsHeaderIcon" position="1390px 0px" - imageSliceId="gui.prices_selling" /> + imageSliceId="gui.prices_selling" visible="false"/> <Text profile="fs25_statisticsHeaderText" - position="1376px 0px" id="rightColumnHeader"/> + position="1376px 0px" id="rightColumnHeader" visible="false"/> <ThreePartBitmap profile="fs25_lineSeparatorBottom" position="0px -2dp" /> </GuiElement> <GuiElement id="leftColumn"> diff --git a/scripts/editor/CourseEditor.lua b/scripts/editor/CourseEditor.lua index 1359f422c..91de51b45 100644 --- a/scripts/editor/CourseEditor.lua +++ b/scripts/editor/CourseEditor.lua @@ -100,8 +100,7 @@ function CourseEditor:activate(file) self.file = file self:loadCourse() g_gui:showGui("ShopMenu") - local shopMenu = g_currentMission.shopMenu - shopMenu:onButtonConstruction() + g_shopMenu:onButtonConstruction() end end @@ -116,8 +115,7 @@ function CourseEditor:activateCustomField(file, field) self.courseWrapper = EditorCourseWrapper(Course(nil, field:getVertices())) self.courseDisplay:setCourse(self.courseWrapper) g_gui:showGui("ShopMenu") - local shopMenu = g_currentMission.shopMenu - shopMenu:onButtonConstruction() + g_shopMenu:onButtonConstruction() end end diff --git a/scripts/gui/pages/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua index b03f3e190..f12c09a38 100644 --- a/scripts/gui/pages/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -690,5 +690,5 @@ function CpCourseManagerFrame:modeDisabled() end function CpCourseManagerFrame:openEditorDisabled() - return not self:clearCurrentCourseDisabled() or self.actionState ~= self.actionStates.disabled + return true --not self:clearCurrentCourseDisabled() or self.actionState ~= self.actionStates.disabled -- TODO_25 end From 39841c645d56375a1e83028038e143e695366397 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 30 Nov 2024 20:27:49 +0100 Subject: [PATCH 054/158] Added nil checks --- scripts/gui/pages/CpCourseManagerFrame.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/scripts/gui/pages/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua index f12c09a38..3a93dc858 100644 --- a/scripts/gui/pages/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -92,8 +92,10 @@ function CpCourseManagerFrame:getCurrentEntry() if not layout then return end - local element = layout:getSelectedElement() - return element.viewEntry + if layout.getSelectedElement then + local element = layout:getSelectedElement() + return element.viewEntry + end end function CpCourseManagerFrame:initialize(menu) @@ -190,9 +192,10 @@ function CpCourseManagerFrame:initialize(menu) inputAction = InputAction.MENU_EXTRA_2, text = g_i18n:getText(self.translations.createDirectory), callback = function () + local viewEntry = self:getCurrentEntry() CpCourseManagerFrame.showInputTextDialog( - self, self.translations.folderDialogTitle, - self.onClickCreateDirectoryDialog) + self, self.translations.folderDialogTitle, + self.onClickCreateDirectoryDialog, viewEntry) -- self.actionState = CpCourseManagerFrame.actionStates.createDirectory self:updateMenuButtons() end, From 2048d93220c209c4bdadfa07b298e3b55d754ba7 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 1 Dec 2024 11:36:52 +0100 Subject: [PATCH 055/158] Disables the lane offet setting, while cp is active --- config/jobParameters/FieldWorkJobParameterSetup.xml | 3 ++- scripts/ai/jobs/CpJobParameters.lua | 9 +++++++++ scripts/gui/hud/CpBaseHud.lua | 10 +++++----- scripts/gui/hud/CpFieldworkHudPage.lua | 5 +++-- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/config/jobParameters/FieldWorkJobParameterSetup.xml b/config/jobParameters/FieldWorkJobParameterSetup.xml index 553eb7388..c37675d5e 100644 --- a/config/jobParameters/FieldWorkJobParameterSetup.xml +++ b/config/jobParameters/FieldWorkJobParameterSetup.xml @@ -27,7 +27,8 @@ </Setting> </SettingSubTitle> <SettingSubTitle title="laneOffset"> - <Setting classType="AIParameterSettingList" name="laneOffset" default="0" isDisabled ="noMultiToolsCourseSelected" onChangeCallback="onLaneOffsetChanged"> + <Setting classType="AIParameterSettingList" name="laneOffset" default="0" isDisabled ="isLaneOffsetDisabled" + isVisible="isLaneOffsetVisible" onChangeCallback="onLaneOffsetChanged"> <Values> <Value name="LANE_LEFT_2" isDisabled ="lessThanThreeMultiTools">-2</Value> <Value name="LANE_LEFT" isDisabled = "noMultiToolsCourseSelected">-1</Value> diff --git a/scripts/ai/jobs/CpJobParameters.lua b/scripts/ai/jobs/CpJobParameters.lua index ef7720e6a..36ef46e44 100644 --- a/scripts/ai/jobs/CpJobParameters.lua +++ b/scripts/ai/jobs/CpJobParameters.lua @@ -189,6 +189,15 @@ function CpFieldWorkJobParameters:onLaneOffsetChanged(setting) end end +function CpFieldWorkJobParameters:isLaneOffsetVisible() + return not self:noMultiToolsCourseSelected() +end + +function CpFieldWorkJobParameters:isLaneOffsetDisabled() + local vehicle = self.job:getVehicle() + return self:noMultiToolsCourseSelected() or vehicle and vehicle:getIsCpActive() +end + --- Are the setting values roughly equal. ---@param otherParameters CpJobParameters ---@return boolean diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index f1d6d26fa..00573c0a4 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -77,10 +77,10 @@ CpBaseHud.uvs = { {40, 256, 32, 32}, {256, 512} }, eye = { - {148, 148, 32, 32}, {256, 512} + {148, 150, 32, 32}, {256, 512} }, refresh = { - {220, 220, 32, 32}, {256, 512} + {220, 222, 32, 32}, {256, 512} }, cpIcon = { {80, 26, 144, 144}, {256, 256} @@ -107,7 +107,7 @@ CpBaseHud.uvs = { {7*128, 3*128, 128, 128} }, playSymbol = { - {224, 296, 32, 32}, {256, 512} + {224, 294, 32, 32}, {256, 512} } } @@ -284,7 +284,7 @@ function CpBaseHud:init(vehicle) self.alignments.bottomRight) self.onOffButton = CpHudButtonElement.new(onOffIndicatorOverlay, self.baseHud) local x, y = unpack(self.lines[8].right) - self.onOffButton:setPosition(x, y) + self.onOffButton:setPosition(x, y - self.hMargin/8) self.onOffButton:setCallback("onClickPrimary", self.vehicle, function(vehicle) vehicle:cpStartStopDriver(true) end) @@ -299,7 +299,7 @@ function CpBaseHud:init(vehicle) self.startStopRecordingBtn = CpHudButtonElement.new(circleOverlay, self.baseHud) local x, y = unpack(self.lines[8].right) x = x - onOffBtnWidth - self.wMargin/2 - self.startStopRecordingBtn:setPosition(x, y) + self.startStopRecordingBtn:setPosition(x, y - self.hMargin/16) self.startStopRecordingBtn:setCallback("onClickPrimary", self.vehicle, function (vehicle) if vehicle:getIsCpCourseRecorderActive() then vehicle:cpStopCourseRecorder() diff --git a/scripts/gui/hud/CpFieldworkHudPage.lua b/scripts/gui/hud/CpFieldworkHudPage.lua index b587e53a4..2145f9427 100644 --- a/scripts/gui/hud/CpFieldworkHudPage.lua +++ b/scripts/gui/hud/CpFieldworkHudPage.lua @@ -27,7 +27,7 @@ function CpFieldWorkHudPageElement:setupElements(baseHud, vehicle, lines, wMargi CpBaseHud.alignments.bottomRight) self.courseVisibilityBtn = CpHudButtonElement.new(courseVisibilityOverlay, self) local x, y = unpack(lines[8].right) - y = y - hMargin/16 + y = y - hMargin/8 local courseVisibilityBtnX = x - width - wMargin/4 self.courseVisibilityBtn:setPosition(courseVisibilityBtnX, y) self.courseVisibilityBtn:setCallback("onClickPrimary", vehicle, function (vehicle) @@ -99,7 +99,8 @@ function CpFieldWorkHudPageElement:updateContent(vehicle, status) self.startingPointBtn:setTextDetails(startingPoint:getString()) local laneOffset = vehicle:getCpLaneOffsetSetting() - self.laneOffsetBtn:setVisible(laneOffset:getCanBeChanged()) + self.laneOffsetBtn:setVisible(laneOffset:getIsVisible()) + self.laneOffsetBtn:setDisabled(laneOffset:getIsDisabled()) self.laneOffsetBtn:setTextDetails(laneOffset:getString()) local workWidth = vehicle:getCourseGeneratorSettings().workWidth From 2d9ec4bd013bd4418a017166011b056c75433e0c Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 1 Dec 2024 08:09:13 -0500 Subject: [PATCH 056/158] fix: multitool course loading --- scripts/ai/jobs/CpAIJobFieldWork.lua | 27 +++++++------------ .../CourseGeneratorInterface.lua | 17 ++++++++++-- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index 8addbad25..cc7ea1411 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -33,8 +33,8 @@ function CpAIJobFieldWork:onPreStart() self:removeTask(self.driveToFieldWorkStartTask) self:removeTask(self.fieldWorkTask) local vehicle = self:getVehicle() - if vehicle and (AIUtil.hasCutterOnTrailerAttached(vehicle) - or AIUtil.hasCutterAsTrailerAttached(vehicle)) then + if vehicle and (AIUtil.hasCutterOnTrailerAttached(vehicle) + or AIUtil.hasCutterAsTrailerAttached(vehicle)) then --- Only add the attach header task, if needed. self:addTask(self.attachHeaderTask) end @@ -49,19 +49,19 @@ end function CpAIJobFieldWork:isFinishingAllowed(message) local nextTaskIndex = self:getNextTaskIndex() - if message:isa(AIMessageErrorOutOfFill) then + 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 + 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 + 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 @@ -88,10 +88,10 @@ function CpAIJobFieldWork:applyCurrentState(vehicle, mission, farmId, isDirectSt self.cpJobParameters.fieldPosition:setPosition(x, z) if isStartPositionInvalid then - local x, _, z = getWorldTranslation(vehicle.rootNode) + local x, _, z = getWorldTranslation(vehicle.rootNode) local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1) local angle = MathUtil.getYRotationFromDirection(dirX, dirZ) - + self.cpJobParameters.startPosition:setPosition(x, z) self.cpJobParameters.startPosition:setAngle(angle) @@ -110,7 +110,7 @@ function CpAIJobFieldWork:validateFieldSetup(isValid, errorMessage) -- everything else is valid, now find the field local tx, tz = self.cpJobParameters.fieldPosition:getPosition() - if tx == nil or tz == nil then + if tx == nil or tz == nil then return false, g_i18n:getText("CP_error_not_on_field") end self.hasValidPosition = false @@ -246,14 +246,7 @@ function CpAIJobFieldWork:onClickGenerateFieldWorkCourse() nil, nil, DialogElement.TYPE_ERROR) return false end - - vehicle:setFieldWorkCourse(course) - if course and course:getMultiTools() > 1 then - --- Um das Setting zu übernehmen warten wir bis der Kurs ins Fahrzeug geladen ist. - --- Zusätzlich muss dieses jetzt noch geupdated werden. - course:setPosition(vehicle:getCpLaneOffsetSetting():getValue()) - SpecializationUtil.raiseEvent(vehicle, "onCpCourseChange", vehicle:getFieldWorkCourse(), true) - end + return true end function CpAIJobFieldWork:isPipeOnLeftSide(vehicle) diff --git a/scripts/courseGenerator/CourseGeneratorInterface.lua b/scripts/courseGenerator/CourseGeneratorInterface.lua index cac348e2f..86a4389ee 100644 --- a/scripts/courseGenerator/CourseGeneratorInterface.lua +++ b/scripts/courseGenerator/CourseGeneratorInterface.lua @@ -111,11 +111,14 @@ function CourseGeneratorInterface.generate(fieldPolygon, settings.workWidth:getValue(), numberOfHeadlands, settings.multiTools:getValue(), settings.headlandClockwise:getValue(), settings.islandHeadlandClockwise:getValue(), not settings.useBaseLineEdge:getValue()) course:setFieldPolygon(fieldPolygon) + CourseGeneratorInterface.setCourse(vehicle, course) return true, course end --- Generates a vine course, where the fieldPolygon are the start/end of the vine node. ---@param fieldPolygon table +---@param startPosition table {x, z} +---@param vehicle table ---@param workWidth number ---@param turningRadius number ---@param manualRowAngleDeg number @@ -124,6 +127,7 @@ end function CourseGeneratorInterface.generateVineCourse( fieldPolygon, startPosition, + vehicle, workWidth, turningRadius, manualRowAngleDeg, @@ -166,12 +170,21 @@ function CourseGeneratorInterface.generateVineCourse( CourseGeneratorInterface.logger:debug('Generated vine course: %d center waypoints', #CourseGeneratorInterface.generatedCourse:getCenterPath()) - local course = Course.createFromGeneratedCourse(nil, CourseGeneratorInterface.generatedCourse, + local course = Course.createFromGeneratedCourse(vehicle, CourseGeneratorInterface.generatedCourse, workWidth, 0, multiTools, true, true, true) course:setFieldPolygon(fieldPolygon) + CourseGeneratorInterface.setCourse(vehicle, course) return true, course end +--- Load the course into the vehicle +function CourseGeneratorInterface.setCourse(vehicle, course) + if course and course:getMultiTools() > 1 then + course:setPosition(vehicle:getCpLaneOffsetSetting():getValue()) + end + vehicle:setFieldWorkCourse(course) +end + --------------------------------------------- --- Console Commands --------------------------------------------- @@ -198,6 +211,6 @@ function CourseGeneratorInterface.generateDefaultCourse(nHeadlands) CpUtil.infoVehicle(vehicle, "Generating default course with %d headlands", settings.numberOfHeadlands:getValue()) local ok, course = CourseGeneratorInterface.generate(points, {x = x, z = z}, vehicle, settings) if ok then - vehicle:setFieldWorkCourse(course) + CourseGeneratorInterface.setCourse(vehicle, course) end end \ No newline at end of file From 70705aabd36805fd2920c96b6b2c826fb8f54966 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 3 Dec 2024 16:19:06 +0100 Subject: [PATCH 057/158] WIP --- config/gui/pages/CourseGeneratorFrame.xml | 540 ++++++++++++ scripts/ai/jobs/CpAIJob.lua | 4 +- scripts/ai/jobs/CpAIJobBaleFinder.lua | 4 +- scripts/ai/jobs/CpAIJobBunkerSilo.lua | 4 +- scripts/ai/jobs/CpAIJobCombineUnloader.lua | 2 +- scripts/ai/jobs/CpAIJobFieldWork.lua | 2 +- scripts/ai/jobs/CpAIJobSiloLoader.lua | 2 +- scripts/gui/CpInGameMenu.lua | 14 +- scripts/gui/pages/CpCourseGeneratorFrame.lua | 864 ++++++++++++++++++- scripts/gui/pages/CpGlobalSettingsFrame.lua | 2 +- scripts/gui/pages/CpVehicleSettingsFrame.lua | 2 +- 11 files changed, 1415 insertions(+), 25 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 29cd19636..a2a59ad50 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -1,5 +1,13 @@ <?xml version="1.0" encoding="utf-8" standalone="no" ?> <GUI name="cpInGameMenuCourseGenerator"> + <InGameMap id="ingameMap" profile="cpIngameMapOverview" cursorId="mapCursor" + onClickMap="onClickMap" onDrawPostIngameMap="onDrawPostIngameMap" + onDrawPostIngameMapHotspots="onDrawPostIngameMapHotspots" onClickHotspot="onClickHotspot" /> + <Bitmap profile="ingameMapCursor" id="mapCursor" visible="false"/> + <ThreePartBitmap profile="cpLeftSideBackground" id="leftBackground"/> + <ThreePartBitmap profile="cpRightSideBackground" id="rightBackground"/> + <ThreePartBitmap profile="cpTopSideBackground" id="topBackground"/> + <ThreePartBitmap profile="cpBottomSideBackground" id="bottomBackground"/> <GuiElement profile="fs25_menuContainer"> <GuiElement profile="fs25_menuHeaderPanel"> <Bitmap profile="fs25_menuHeaderIconBg"> @@ -14,6 +22,162 @@ <MultiTextOption profile="fs25_subCategorySelectorTabbed" id="subCategoryPaging" position="0px -10px" onClick="updateSubCategoryPages" /> + <GuiElement profile="fs25_subCategorySelectorTabbedContainer" id="containerMap" visible="false"> + <GuiElement absoluteSizeOffset="0 -21px"> + <ThreePartBitmap profile="fs25_subCategoryContainerBg" id="leftBox"> + <Bitmap profile="fs25_subCategoryContainerArrow" /> + </ThreePartBitmap> + <Bitmap profile="fs25_subCategoryContainer" id="filterBox"> + <MultiTextOption profile="fs25_subCategorySelector" id="mapOverviewSelector" + onClick="onClickMapOverviewSelector" /> + <BoxLayout profile="fs25_subCategorySelectorBox" id="subCategoryDotBox"> + <RoundCorner profile="fs25_subCategorySelectorDot" /> + <RoundCorner profile="fs25_subCategorySelectorDot" /> + <RoundCorner profile="fs25_subCategorySelectorDot" /> + </BoxLayout> + <GuiElement profile="fs25_subCategoryListContainer" id="filterListContainer"> + <SmoothList profile="fs25_mapList" id="filterList" focusInit="onOpen" + listSectionHeader="section" startClipperElementName="startClipper" + endClipperElementName="endClipper" onClick="onClickList"> + <ListItem profile="fs25_mapListItem"> + <Bitmap profile="fs25_mapListItemIconBg" name="iconBg" /> + <Bitmap profile="fs25_mapListColorTemplate" name="colorTemplate" /> + <Bitmap profile="fs25_mapListItemIcon" name="icon" /> + <Text profile="fs25_mapListItemName" name="name" /> + </ListItem> + <ListItem profile="fs25_subCategoryListSectionHeader" name="section"> + <Text profile="fs25_subCategoryListSectionHeaderTitle" name="title" /> + </ListItem> + </SmoothList> + <Bitmap profile="fs25_subCategoryStartClipper" name="startClipper" /> + <Bitmap profile="fs25_subCategoryStopClipper" position="0px 60px" name="endClipper" /> + <ThreePartBitmap profile="fs25_subCategoryListSliderBox" absoluteSizeOffset="0px 60px"> + <Slider profile="fs25_listSlider" dataElementId="filterList" /> + </ThreePartBitmap> + <GuiElement profile="fs25_mapButtonContainer" id="buttonDeselectAllContainer" + width="340px" position="-10px 0px"> + <ThreePartBitmap profile="fs25_mapButtonBgLight" /> + <Text profile="fs25_mapButtonText" id="buttonDeselectAllText" + text="$l10n_button_deselectAll" textOffset="15px 0px" /> + <Button profile="fs25_mapButtonAction1" id="buttonDeselectAll" + onClick="onClickDeselectAll" /> + </GuiElement> + </GuiElement> + <GuiElement profile="fs25_subCategoryListContainer" id="createJobContainer" visible="false"> + <MultiTextOption profile="fs25_aiJobTypeMultiTextOption" id="jobTypeElement" + onClick="onJobTypeChanged" /> + <BoxLayout profile="fs25_aiCreateJobLayout" id="jobMenuLayout"> + <OptionToggle profile="fs25_aiCreateJobMultiTextOption" + id="createMultiOptionTemplate" onClick="onClickMultiTextOptionParameter"> + <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> + </OptionToggle> + <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" id="createTextTemplate"> + <Bitmap profile="fs25_aiCreateJobParameterVehicleIcon" /> + <Text profile="fs25_multiTextOptionText" name="title" /> + <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> + </ThreePartBitmap> + <Text profile="fs25_aiCreateJobParameterTitle" id="createTitleTemplate" + text="Group title" /> + <Button profile="fs25_aiCreateJobParameterButton" id="createPositionTemplate" + text="<400, 300>" onClick="onClickPositionParameter"> + <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" /> + <Bitmap profile="fs25_aiCreateJobParameterIcon" /> + <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> + </Button> + <Button profile="fs25_aiCreateJobParameterButton" + id="createPositionRotationTemplate" text="172a" + onClick="onClickPositionRotationParameter"> + <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" /> + <Bitmap profile="fs25_aiCreateJobParameterIcon" /> + <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> + </Button> + </BoxLayout> + <GuiElement profile="fs25_mapButtonContainer" id="buttonStartJobContainer" width="340px" + position="-10px 0px"> + <ThreePartBitmap profile="fs25_mapButtonBgLight" /> + <Text profile="fs25_mapButtonText" id="buttonStartJobText" + text="$l10n_button_startJob" textOffset="15px 0px" /> + <Button profile="fs25_mapButtonAction1" id="buttonStartJob" + onClick="onStartCancelJob" /> + </GuiElement> + <Text profile="fs25_aiContainerEmptyText" id="createJobEmptyText" + text="$l10n_ui_createJobEmpty"/> + </GuiElement> + <GuiElement profile="fs25_subCategoryListContainer" id="workerListContainer"> + <SmoothList profile="fs25_subCategoryList" id="activeWorkerList" + startClipperElementName="startClipper" endClipperElementName="endClipper" + onClick="onClickList" emptyIndicatorId="activeWorkerListEmpty" + selectedWithoutFocus="true"> + <ListItem profile="fs25_subCategoryListItem"> + <Bitmap profile="fs25_workerListItemIcon" /> + <Text profile="fs25_workerListItemHelper" name="helper" /> + <Text profile="fs25_subCategoryListItemName" name="title" /> + <Text profile="fs25_workerListItemDescription" name="text" /> + </ListItem> + </SmoothList> + <Text profile="fs25_aiContainerEmptyText" id="activeWorkerListEmpty" position="0px 37px" + text="$l10n_ui_inGameMenuNoActiveWorkers" /> + <Text profile="ingameMenuAILimitReached" id="limitReachedWarning" + text="$l10n_ai_warningLimitReached" visible="false"/> + <Bitmap profile="fs25_subCategoryStartClipper" name="startClipper" /> + <Bitmap profile="fs25_subCategoryStopClipper" position="0px 60px" name="endClipper" /> + <ThreePartBitmap profile="fs25_subCategoryListSliderBox"> + <Slider profile="fs25_listSlider" dataElementId="activeWorkerList" /> + </ThreePartBitmap> + </GuiElement> + </Bitmap> + </GuiElement> + <GuiElement profile="fs25_mapContextBoxContainer" newLayer="true"> + <GuiElement profile="fs25_mapContextBox" id="contextBox"> + <ThreePartBitmap profile="fs25_mapContextBoxBg" /> + <Bitmap profile="fs25_mapContextImage" name="image" /> + <Bitmap profile="fs25_mapContextImageVehicle" name="imageVehicle" /> + <Bitmap profile="fs25_mapContextSeparator" /> + <Text profile="fs25_mapContextText" name="text" /> + <Text profile="fs25_mapContextFarm" name="farm" /> + <SmoothList profile="fs25_mapContextButtonList" id="contextButtonList" name="buttonBox" + onClick="onClickList"> + <ListItem profile="fs25_mapContextButtonListItem"> + <ThreePartBitmap profile="fs25_mapContextButtonListItemBg" /> + <Text profile="fs25_mapButtonText" name="text" /> + <Button profile="fs25_mapContextButtonListItemButton" /> + </ListItem> + </SmoothList> + </GuiElement> + <GuiElement profile="fs25_mapContextBox" id="contextBoxFarmland" height="150px"> + <Bitmap profile="fs25_mapContextBoxBgFarmland" /> + <Bitmap profile="fs25_mapContextImage" name="image" /> + <Bitmap profile="fs25_mapContextImageVehicle" name="imageVehicle" /> + <Bitmap profile="fs25_mapContextSeparator" /> + <Text profile="fs25_mapContextTextTop" name="text" position="0px -5px" /> + <Text profile="fs25_mapContextFarmTop" name="farm" position="0px -50px" /> + <Text profile="fs25_mapContextFarmlandTitle" text="$l10n_contract_details_fieldSize:" + position="0px -80px" /> + <Text profile="fs25_mapContextFarmlandTitle" text="$l10n_ui_sellValue:" + position="0px -110px" /> + <Text profile="fs25_mapContextFarmlandValue" name="farmlandSize" position="0px -80px" /> + <Text profile="fs25_mapContextFarmlandValue" name="farmlandValue" position="0px -110px" /> + <SmoothList profile="fs25_mapContextButtonList" id="contextButtonListFarmland" + name="buttonBox" onClick="onClickList"> + <ListItem profile="fs25_mapContextButtonListItem"> + <ThreePartBitmap profile="fs25_mapContextButtonListItemBg" /> + <Text profile="fs25_mapButtonText" name="text" /> + <Button profile="fs25_mapContextButtonListItemButton" /> + </ListItem> + </SmoothList> + </GuiElement> + <GuiElement profile="fs25_mapContextBox" id="contextBoxPlayer" height="80px"> + <Bitmap profile="fs25_mapContextBoxBgFarmland" /> + <Text profile="fs25_mapContextTextTop" name="player" position="0px -5px" /> + <Text profile="fs25_mapContextFarmTop" name="farm" position="0px -50px" /> + </GuiElement> + </GuiElement> + <Text profile="ingameMenuAIActionText" id="actionMessage" visible = "false" /> + <GuiElement profile="fs25_inGameMapLoadingBg" visible = "false"> + <Text profile="ingameMenuAIErrorText" id="errorMessage" /> + </GuiElement> + </GuiElement> + <!-- Prefabs --> <Button profile="fs25_subCategorySelectorTabbedTab" id="selectorPrefab"> @@ -74,5 +238,381 @@ <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> <position value="-41px 0px" /> </Profile> + <Profile name="fs25_inGameMapLoadingBg" extends="emptyPanel" with="pivotTopRight"> + <absoluteSizeOffset value="540px 40px" /> + <position value="-125px 0px" /> + <visible value="false" /> + </Profile> + <Profile name="fs25_mapList" extends="fs25_subCategoryList" with="anchorTopCenter"> + <absoluteSizeOffset value="0px 60px" /> + </Profile> + <Profile name="fs25_mapListItem" extends="fs25_subCategoryListItem"> + <height value="70px" /> + <hotspot value="0px 0px 0px 15px" /> + <imageSliceId value="gui.map_box" /> + <imageSelectedSliceId value="gui.map_box_selected" /> + <imageHighlightedSliceId value="gui.map_box_hover" /> + </Profile> + <Profile name="fs25_mapListItemIconBg" extends="baseReference" with="anchorTopLeft"> + <size value="46px 46px" /> + <position value="3px -3px" /> + <imageSliceId value="gui.map_box_colorOverlay" /> + <imageColor value="$preset_colorBlack" /> + <imageSelectedColor value="$preset_colorWhite" /> + </Profile> + <Profile name="fs25_mapListColorTemplate" extends="baseReference"> + <size value="1px 46px" /> + <imageColor value="$preset_colorTransparent" /> + <imageSelectedColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_mapListItemIcon" extends="fs25_subCategoryListItemIcon" + with="anchorTopLeft"> + <size value="40px 40px" /> + <position value="6px -6px" /> + <imageColor value="$preset_colorWhite_25" /> + <imageSelectedColor value="$preset_colorWhite" /> + </Profile> + <Profile name="fs25_mapListItemName" extends="fs25_subCategoryListItemName" + with="anchorTopLeft"> + <width value="230px" /> + <position value="65px -16px" /> + </Profile> + <Profile name="fs25_workerListItemIcon" extends="fs25_subCategoryListItemIcon"> + <imageSliceId value="gui.ingameMap_helper" /> + </Profile> + <Profile name="fs25_workerListItemHelper" extends="fs25_textDefault" with="anchorMiddleLeft"> + <size value="10px 10px" /> + <position value="38px 9px" /> + <textSize value="10px" /> + <textAlignment value="center" /> + <textSelectedColor value="$preset_fs25_colorMainLight" /> + </Profile> + <Profile name="fs25_workerListItemDescription" extends="fs25_subCategoryListItemName"> + <width value="220px" /> + <position value="100px -50px" /> + </Profile> + <Profile name="fs25_mapButtonContainer" extends="emptyPanel" with="anchorBottomStretchingX"> + <height value="25px" /> + </Profile> + <Profile name="fs25_mapButton" extends="emptyPanel" with="anchorStretchingYStretchingX"> + <handleFocus value="false" /> + <textAlignment value="right" /> + <iconSize value="25px 25px" /> + <iconColor value="$preset_fs25_colorMainHighlight" /> + <iconFocusedColor value="$preset_fs25_colorMainDark" /> + <iconHighlightedColor value="$preset_fs25_colorMainDark" /> + <iconSelectedColor value="$preset_fs25_colorMainDark" /> + <iconDisabledColor value="$preset_colorDisabled" /> + <iconBgColor value="$preset_fs25_colorGreenDark" /> + <iconBgFocusedColor value="$preset_fs25_colorMainHighlight" /> + <iconBgHighlightedColor value="$preset_fs25_colorMainHighlight" /> + <iconBgSelectedColor value="$preset_fs25_colorMainHighlight" /> + <iconBgDisabledColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_mapButtonText" extends="fs25_textDefault" + with="anchorStretchingYStretchingX"> + <width value="100%" /> + <textSize value="16px" /> + <textBold value="true" /> + <textUpperCase value="true" /> + <textOffset value="10px 0px" /> + <textSelectedColor value="$preset_fs25_colorMainHighlight" /> + <textFocusedColor value="$preset_fs25_colorMainHighlight" /> + <textHighlightedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="fs25_mapButtonBg" extends="baseReference" with="anchorStretchingYStretchingX"> + <imageColor value="$preset_fs25_colorMainDark_90" /> + <imageSliceId value="gui.button_middle" /> + <startImageSize value="6px 0" /> + <startImageSliceId value="gui.button_left" /> + <endImageSize value="6px 0" /> + <endImageSliceId value="gui.button_right" /> + </Profile> + <Profile name="fs25_mapButtonBgLight" extends="fs25_mapButtonBg"> + <imageColor value="$preset_fs25_colorMainDark_40" /> + </Profile> + <Profile name="fs25_mapButtonAction1" extends="fs25_mapButton"> + <inputAction value="MENU_MAP_ACTION_1" /> + </Profile> + <Profile name="fs25_mapMoneyBoxBg" extends="fs25_shopMoneyBoxBg"> + <color value="$preset_fs25_colorMainDark_90" /> + </Profile> + <Profile name="fs25_mapContextBoxContainer" extends="emptyPanel" with="anchorTopRight"> + <absoluteSizeOffset value="540px 40px" /> + <clipping value="true" /> + </Profile> + <Profile name="fs25_mapContextBox" extends="emptyPanel"> + <size value="276px 330px" /> + <visible value="false" /> + <handleFocus value="false" /> + </Profile> + <Profile name="fs25_mapContextBoxBg" extends="baseReference" with="anchorTopLeft"> + <absoluteSizeOffset value="-34px -20px" /> + <position value="-18px 10px" /> + <isHorizontal value="false" /> + <imageSliceId value="gui.map_selectedVehicle_middle" /> + <startImageSize value="0 210px" /> + <startImageSliceId value="gui.map_selectedVehicle_top" /> + <endImageSize value="0 40px" /> + <endImageSliceId value="gui.map_selectedVehicle_bottom" /> + </Profile> + <Profile name="fs25_mapContextBoxBgFarmland" extends="baseReference" with="anchorTopLeft"> + <absoluteSizeOffset value="-29px -20px" /> + <position value="-10px 10px" /> + <imageSliceId value="gui.map_fieldInfo" /> + </Profile> + <Profile name="fs25_mapContextImageVehicle" extends="baseReference" with="anchorTopCenter"> + <size value="220px 220px" /> + <position value="0px -10px" /> + <visible value="false" /> + </Profile> + <Profile name="fs25_mapContextImage" extends="baseReference" with="anchorTopCenter"> + <size value="200px 200px" /> + <position value="0px -22px" /> + </Profile> + <Profile name="fs25_mapContextText" extends="fs25_textDefault" + with="anchorBottomStretchingX"> + <size value="100% 40px" /> + <absoluteSizeOffset value="40px" /> + <position value="0px 45px" /> + <textSize value="18px" /> + <textBold value="true" /> + <textMaxNumLines value="2" /> + <textAlignment value="center" /> + <textVerticalAlignment value="bottom" /> + </Profile> + <Profile name="fs25_mapContextTextTop" extends="fs25_mapContextText" + with="anchorTopStretchingX" /> + <Profile name="fs25_mapContextFarm" extends="fs25_textDefault" + with="anchorBottomStretchingX"> + <width value="100%" /> + <absoluteSizeOffset value="20px" /> + <position value="0px 19px" /> + <textAlignment value="center" /> + <textLayoutMode value="resize" /> + </Profile> + <Profile name="fs25_mapContextFarmTop" extends="fs25_mapContextFarm" + with="anchorTopStretchingX" /> + <Profile name="fs25_mapContextFarmlandTitle" extends="fs25_textDefault" + with="anchorTopStretchingX"> + <width value="100%" /> + <absoluteSizeOffset value="20px" /> + <textBold value="true" /> + </Profile> + <Profile name="fs25_mapContextFarmlandValue" extends="fs25_mapContextFarmlandTitle" + with="anchorTopStretchingX"> + <width value="100%" /> + <absoluteSizeOffset value="20px" /> + <textBold value="false" /> + <textAlignment value="right" /> + </Profile> + <Profile name="fs25_mapContextSeparator" extends="baseReference" + with="anchorBottomStretchingX"> + <height value="1dp" /> + <position value="0px 90px" /> + <imageColor value="$preset_fs25_colorGreyLight_20" /> + </Profile> + <Profile name="fs25_mapContextButtonList" extends="emptyPanel" + with="anchorBottomStretchingX"> + <height value="200px" /> + <position value="0px -200px" /> + <handleFocus value="true" /> + <listItemSpacing value="3px" /> + <showHighlights value="true" /> + <canReceiveFocusWhileEmpty value="true" /> + </Profile> + <Profile name="fs25_mapContextButtonListItem" extends="emptyPanel" + with="anchorTopStretchingX"> + <height value="25px" /> + </Profile> + <Profile name="fs25_mapContextButtonListItemBg" extends="baseReference" + with="anchorStretchingYStretchingX"> + <imageSliceId value="gui.button_middle" /> + <imageColor value="$preset_fs25_colorMainDark_90" /> + <imageSelectedColor value="$preset_fs25_colorMainDark" /> + <startImageSize value="6px 0" /> + <startImageSliceId value="gui.button_left" /> + <startImageSelectedColor value="$preset_fs25_colorMainDark" /> + <endImageSize value="6px 0" /> + <endImageSliceId value="gui.button_right" /> + <endImageSelectedColor value="$preset_fs25_colorMainDark" /> + </Profile> + <Profile name="fs25_mapContextButtonListItemButton" extends="emptyPanel" + with="anchorStretchingYStretchingX"> + <handleFocus value="false" /> + <textAlignment value="right" /> + <inputAction value="MENU_ACCEPT" /> + <iconSize value="25px 25px" /> + <iconColor value="$preset_colorTransparent" /> + <iconFocusedColor value="$preset_fs25_colorMainDark" /> + <iconHighlightedColor value="$preset_colorTransparent" /> + <iconSelectedColor value="$preset_fs25_colorMainDark" /> + <iconBgColor value="$preset_colorTransparent" /> + <iconBgFocusedColor value="$preset_fs25_colorMainHighlight" /> + <iconBgHighlightedColor value="$preset_colorTransparent" /> + <iconBgSelectedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="fs25_aiJobTypeMultiTextOption" extends="fs25_multiTextOption" + with="anchorTopStretchingX"> + <width value="100%" /> + </Profile> + <Profile name="fs25_aiCreateJobLayout" extends="emptyPanel" + with="anchorStretchingYLeft pivotTopLeft"> + <absoluteSizeOffset value="0px 40px" /> + <position value="0px -40px" /> + <flowDirection value="vertical" /> + </Profile> + <Profile name="fs25_aiCreateJobMultiTextOption" extends="fs25_multiTextOption" + with="anchorTopStretchingX"> + <size value="100% 40px" /> + </Profile> + <Profile name="fs25_aiCreateJobParameterInvalid" extends="baseReference" + with="anchorTopRight"> + <size value="30px 24px" /> + <position value="-5px 26px" /> + <imageSliceId value="gui.contextAction_icon_alert" /> + </Profile> + <Profile name="fs25_aiCreateJobParameterBg" extends="fs25_multiTextOptionBg" + with="anchorTopStretchingX"> + <size value="100% 40px" /> + </Profile> + <Profile name="fs25_aiCreateJobParameterIcon" extends="baseReference" + with="anchorMiddleLeft"> + <size value="22px 22px" /> + <position value="17px 0px" /> + <imageSliceId value="gui.aiParameterPosition" /> + <imageColor value="$preset_colorWhite_25" /> + <imageFocusedColor value="$preset_colorWhite" /> + <imageHighlightedColor value="$preset_colorWhite" /> + <imageDisabledColor value="$preset_colorWhite" /> + </Profile> + <Profile name="fs25_aiCreateJobParameterVehicleIcon" extends="fs25_aiCreateJobParameterIcon"> + <size value="36px 36px" /> + <position value="10px 0px" /> + <imageSliceId value="gui.icon_vehicleDealer_machines" /> + </Profile> + <Profile name="fs25_aiCreateJobParameterTitle" extends="fs25_textDefault" + with="anchorTopStretchingX"> + <size value="100% 40px" /> + <textBold value="true" /> + <textOffset value="0px 5px" /> + <textUpperCase value="true" /> + <textVerticalAlignment value="bottom" /> + </Profile> + <Profile name="fs25_aiCreateJobParameterButton" extends="fs25_multiTextOptionText" + with="anchorTopStretchingX"> + <size value="100% 40px" /> + <textBold value="true" /> + <textMaxWidth value="215px" /> + <textAutoWidth value="false" /> + <imageSliceId value="noSlice" /> + <imageColor value="$preset_colorTransparent" /> + <imageFocusedColor value="$preset_colorTransparent" /> + <imageHighlightedColor value="$preset_colorTransparent" /> + </Profile> + <Profile name="fs25_aiContainerEmptyText" extends="fs25_textDefault" + with="anchorMiddleStretchingX"> + <width value="300px" /> + <textSize value="20px" /> + <textMaxNumLines value="3" /> + <textAlignment value="center" /> + </Profile> + <Profile name="ingameMenuMapDynamicLoadingText" extends="fs25_textDefault" + with="anchorBottomCenter"> + <size value="600px 30px" /> + <position value="0px 50px" /> + <textSize value="24px" /> + <textAlignment value="center" /> + <textBold value="true" /> + <text2Size value="24px" /> + <text2Bold value="true" /> + <text2Offset value="1px -1px" /> + <text2Alignment value="center" /> + <text2Color value="$preset_colorBlack" /> + </Profile> + <Profile name="ingameMenuAILimitReached" extends="fs25_textDefault" with="anchorBottomRight"> + <size value="316px 28px" /> + <position value="-42px 150px" /> + <textMaxNumLines value="4" /> + <textWrapWidth value="300px" /> + </Profile> + <Profile name="ingameMenuAIActionText" extends="fs25_textDefault" with="anchorBottomLeft"> + <textAutoWidth value="true" /> + <textSize value="24px" /> + <textBold value="true" /> + <textOffset value="25px 5px" /> + <text2Size value="24px" /> + <text2Bold value="true" /> + <text2Offset value="24px 4px" /> + <text2Color value="$preset_colorBlack" /> + </Profile> + <Profile name="ingameMenuAIErrorText" extends="fs25_textDefault" with="anchorBottomCenter"> + <position value="0px 50px" /> + <textSize value="24px" /> + <textBold value="true" /> + <textAutoWidth value="true" /> + <text2Size value="24px" /> + <text2Bold value="true" /> + <text2Offset value="-1px -1px" /> + <text2Color value="$preset_colorBlack" /> + </Profile> + <Profile name="fs25_ingameMenuAIStatusText" extends="fs25_textDefault" + with="anchorBottomRight"> + <position value="-20px -35px" /> + <textSize value="20px" /> + <textBold value="true" /> + <textAutoWidth value="true" /> + <textAlignment value="right" /> + <text2Size value="20px" /> + <text2Bold value="true" /> + <text2Offset value="-1px -1px" /> + <text2Color value="$preset_colorBlack" /> + </Profile> + + <Profile name="fs25_ingameMenuAIStatusText" extends="fs25_textDefault" + with="anchorBottomRight"> + <position value="-20px -35px" /> + <textSize value="20px" /> + <textBold value="true" /> + <textAutoWidth value="true" /> + <textAlignment value="right" /> + <text2Size value="20px" /> + <text2Bold value="true" /> + <text2Offset value="-1px -1px" /> + <text2Color value="$preset_colorBlack" /> + </Profile> + + + <Profile name="cpIngameMapOverview" extends="ingameMapOverview" with="anchorStretchingYStretchingX pivotBottomCenter"> + <absoluteSizeOffset value="0px 80px" /> + <size></size> + <!-- <absoluteSizeOffset value="536px 40px" /> + <mapAlpha value="0.9" /> + <limitMapWidth value="true" /> + <Variant name="mobile"> + <limitMapWidth value="true" /> + <mapAlpha value="1" /> + </Variant> --> + </Profile> + <Profile name="cpLeftSideBackground" extends="fs25_fullScreenBackground" with="pivotMiddleLeft"> + <height value="100%"/> + <width value="250px" /> + </Profile> + <Profile name="cpRightSideBackground" extends="fs25_fullScreenBackground" with="pivotMiddleRight"> + <height value="100%"/> + <width value="200px" /> + </Profile> + <Profile name="cpTopSideBackground" extends="fs25_fullScreenBackground" with="pivotTopCenter"> + <width value="100%"/> + <height value="230px" /> + </Profile> + <Profile name="cpBottomSideBackground" extends="fs25_fullScreenBackground" with="pivotBottomCenter"> + <width value="100%"/> + <height value="80px" /> + </Profile> + <Profile name="cpSubCategorySelectorTabbedContainer" extends="fs25_subCategorySelectorTabbedContainer"> + <!-- <absoluteSizeOffset value="56px 0px" /> --> + </Profile> + </GUIProfiles> </GUI> diff --git a/scripts/ai/jobs/CpAIJob.lua b/scripts/ai/jobs/CpAIJob.lua index b60af9c96..0992de5c7 100644 --- a/scripts/ai/jobs/CpAIJob.lua +++ b/scripts/ai/jobs/CpAIJob.lua @@ -206,8 +206,8 @@ function CpAIJob:applyCurrentState(vehicle, mission, farmId, isDirectStart) end --- Can the vehicle be used for this job? -function CpAIJob:getIsAvailableForVehicle(vehicle) - return true +function CpAIJob:getIsAvailableForVehicle(vehicle, cpJobsAllowed) + return cpJobsAllowed end function CpAIJob:getTitle() diff --git a/scripts/ai/jobs/CpAIJobBaleFinder.lua b/scripts/ai/jobs/CpAIJobBaleFinder.lua index 9825b63e3..70f7647ef 100644 --- a/scripts/ai/jobs/CpAIJobBaleFinder.lua +++ b/scripts/ai/jobs/CpAIJobBaleFinder.lua @@ -29,8 +29,8 @@ function CpAIJobBaleFinder:setupJobParameters() self:setupCpJobParameters(CpBaleFinderJobParameters(self)) end -function CpAIJobBaleFinder:getIsAvailableForVehicle(vehicle) - return false--vehicle.getCanStartCpBaleFinder and vehicle:getCanStartCpBaleFinder() -- TODO_25 +function CpAIJobBaleFinder:getIsAvailableForVehicle(vehicle, cpJobsAllowed) + return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpBaleFinder and vehicle:getCanStartCpBaleFinder() -- TODO_25 end function CpAIJobBaleFinder:getCanStartJob() diff --git a/scripts/ai/jobs/CpAIJobBunkerSilo.lua b/scripts/ai/jobs/CpAIJobBunkerSilo.lua index 6f81c431c..1fe1006e3 100644 --- a/scripts/ai/jobs/CpAIJobBunkerSilo.lua +++ b/scripts/ai/jobs/CpAIJobBunkerSilo.lua @@ -28,8 +28,8 @@ function CpAIJobBunkerSilo:setupJobParameters() self:setupCpJobParameters(CpBunkerSiloJobParameters(self)) end -function CpAIJobBunkerSilo:getIsAvailableForVehicle(vehicle) - return false--vehicle.getCanStartCpBunkerSiloWorker and vehicle:getCanStartCpBunkerSiloWorker() -- TODO_25 +function CpAIJobBunkerSilo:getIsAvailableForVehicle(vehicle, cpJobsAllowed) + return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpBunkerSiloWorker and vehicle:getCanStartCpBunkerSiloWorker() -- TODO_25 end function CpAIJobBunkerSilo:getCanStartJob() diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index 05865df4e..10fe78dea 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -59,7 +59,7 @@ function CpAIJobCombineUnloader:setupJobParameters() end function CpAIJobCombineUnloader:getIsAvailableForVehicle(vehicle) - return false--vehicle.getCanStartCpCombineUnloader and vehicle:getCanStartCpCombineUnloader() -- TODO_25 + return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpCombineUnloader and vehicle:getCanStartCpCombineUnloader() -- TODO_25 end function CpAIJobCombineUnloader:getCanStartJob() diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index cc7ea1411..51abab4c6 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -263,7 +263,7 @@ function CpAIJobFieldWork:isPipeOnLeftSide(vehicle) end function CpAIJobFieldWork:getIsAvailableForVehicle(vehicle) - return false --vehicle.getCanStartCpFieldWork and vehicle:getCanStartCpFieldWork() -- TODO_25 + return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpFieldWork and vehicle:getCanStartCpFieldWork() -- TODO_25 end function CpAIJobFieldWork:resetStartPositionAngle(vehicle) diff --git a/scripts/ai/jobs/CpAIJobSiloLoader.lua b/scripts/ai/jobs/CpAIJobSiloLoader.lua index 54b4d4174..44a9012c0 100644 --- a/scripts/ai/jobs/CpAIJobSiloLoader.lua +++ b/scripts/ai/jobs/CpAIJobSiloLoader.lua @@ -52,7 +52,7 @@ function CpAIJobSiloLoader:setupJobParameters() end function CpAIJobSiloLoader:getIsAvailableForVehicle(vehicle) - return false --vehicle.getCanStartCpSiloLoaderWorker and vehicle:getCanStartCpSiloLoaderWorker() -- TODO_25 + return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpSiloLoaderWorker and vehicle:getCanStartCpSiloLoaderWorker() -- TODO_25 end function CpAIJobSiloLoader:getCanStartJob() diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 8e37835f9..fdcf813bd 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -86,10 +86,16 @@ end -- Lines 279-324 function CpInGameMenu:initializePages() self.clickBackCallback = self:makeSelfCallback(self.onButtonBack) - self.pageGlobalSettings:initialize() - self.pageVehicleSettings:initialize() - self.pageCourseGenerator:initialize() - self.pageCourseManager:initialize() + + self.pageCourseGenerator:setInGameMap( + g_inGameMenu.baseIngameMap, + g_currentMission.terrainSize, + g_currentMission.hud) + + self.pageGlobalSettings:initialize(self) + self.pageVehicleSettings:initialize(self) + self.pageCourseGenerator:initialize(self) + self.pageCourseManager:initialize(self) self.pageCourseManager:setCourseStorage(self.courseStorage) end diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 00df12539..4d6ec6ffc 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -6,12 +6,51 @@ CpCourseGeneratorFrame = { CATEGRORIES = { BASIC_SETTINGS = 1, - DEBUG_SETTINGS = 2 + IN_GAME_MAP = 2, + DEBUG_SETTINGS = 3 }, + INPUT_CONTEXT_NAME = "CP_COURSE_GENERATOR_MENU", CATEGRORY_TEXTS = { "CP_global_setting_subTitle_general", + "TODO: MAP", "TODO: Debug" - } + }, + CLEAR_INPUT_ACTIONS = { + InputAction.MENU_ACTIVATE, + InputAction.MENU_CANCEL, + InputAction.MENU_EXTRA_1, + InputAction.MENU_EXTRA_2, + InputAction.SWITCH_VEHICLE, + InputAction.SWITCH_VEHICLE_BACK, + InputAction.CAMERA_ZOOM_IN, + InputAction.CAMERA_ZOOM_OUT + }, + CLEAR_CLOSE_INPUT_ACTIONS = { + InputAction.SWITCH_VEHICLE, + InputAction.SWITCH_VEHICLE_BACK, + InputAction.CAMERA_ZOOM_IN, + InputAction.CAMERA_ZOOM_OUT + }, + CONTEXT_ACTIONS = { + ENTER_VEHICLE = 1, + CREATE_JOB = 2, + START_JOB = 3, + STOP_JOB = 4, + GENERATE_COURSE = 5 + }, + AI_MODE_OVERVIEW = 1, + AI_MODE_CREATE = 2, + AI_MODE_WORKER_LIST = 3, + MAP_SELECTOR_HOTSPOT = 1, + MAP_SELECTOR_CREATE_JOB = 2, + MAP_SELECTOR_ACTIVE_JOBS = 3, + + -- POSITION_UVS = GuiUtils.getUVs({ + -- 760, + -- 4, + -- 100, + -- 100 + -- }, AIPlaceableMarkerHotspot.FILE_RESOLUTION) } CpCourseGeneratorFrame.NUM_CATEGORIES = #CpCourseGeneratorFrame.CATEGRORY_TEXTS @@ -21,6 +60,41 @@ function CpCourseGeneratorFrame.new(target, custom_mt) local self = TabbedMenuFrameElement.new(target, custom_mt or CpCourseGeneratorFrame_mt) self.subCategoryPages = {} self.subCategoryTabs = {} + + + self.contextActions = {} + self.contextActionMapping = {} + self.hasFullScreenMap = true + self.dataTables = {} + self.hotspotModeActive = false + self.currentHotspot = nil + self.staticUIDeadzone = {0, 0, 0, 0} + self.selectedVehicle = nil + self.jobTypeInstances = {} + self.currentJobTypes = {} + self.currentJob = nil + self.currentJobElements = {} + self.statusMessages = {} + self.mode = self.AI_MODE_OVERVIEW + self.lastMousePosX = 0 + self.lastMousePosY = 0 + + self.lastInputHelpMode = 0 + self.hotspotFilterState = {} + self.isInputContextActive = false + self.driveToAiTargetMapHotspot = AITargetHotspot.new() + self.fieldSiloAiTargetMapHotspot = AITargetHotspot.new() + -- self.fieldSiloAiTargetMapHotspot.icon:setUVs(POSITION_UVS) --- TODO_25 + self.unloadAiTargetMapHotspot = AITargetHotspot.new() + self.loadAiTargetMapHotspot = AITargetHotspot.new() + + self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot + + self.mapSelectorTexts = { + g_i18n:getText("ui_mapOverviewHotspots"), + g_i18n:getText("button_createJob"), + g_i18n:getText("ui_activeAIJobs")} + return self end @@ -40,7 +114,19 @@ function CpCourseGeneratorFrame.createFromExistingGui(gui, guiName) return newGui end -function CpCourseGeneratorFrame:initialize() +function CpCourseGeneratorFrame:setInGameMap(ingameMap, terrainSize, hud) + self.ingameMapBase = ingameMap + self.ingameMap:setIngameMap(ingameMap) + self.ingameMap:setTerrainSize(terrainSize) + self.hud = hud +end + +function CpCourseGeneratorFrame:initialize(menu) + + -- self:updateInputGlyphs() + + self:initializeContextActions() + self.booleanPrefab:unlinkElement() FocusManager:removeElement(self.booleanPrefab) self.multiTextPrefab:unlinkElement() @@ -52,11 +138,19 @@ function CpCourseGeneratorFrame:initialize() self.containerPrefab:unlinkElement() FocusManager:removeElement(self.containerPrefab) + self.createMultiOptionTemplate:unlinkElement() + FocusManager:removeElement(self.createMultiOptionTemplate) + self.createTextTemplate:unlinkElement() + FocusManager:removeElement(self.createTextTemplate) + self.createTitleTemplate:unlinkElement() + FocusManager:removeElement(self.createTitleTemplate) + self.createPositionTemplate:unlinkElement() + FocusManager:removeElement(self.createPositionTemplate) + self.createPositionRotationTemplate:unlinkElement() + FocusManager:removeElement(self.createPositionRotationTemplate) + for key = 1, CpCourseGeneratorFrame.NUM_CATEGORIES do self.subCategoryPaging:addText(tostring(key)) - self.subCategoryPages[key] = self.containerPrefab:clone(self) - self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" - FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) self.subCategoryTabs[key] = self.selectorPrefab:clone(self.subCategoryBox) FocusManager:loadElementFromCustomValues(self.subCategoryTabs[key]) self.subCategoryBox:invalidateLayout() @@ -66,7 +160,30 @@ function CpCourseGeneratorFrame:initialize() self.subCategoryTabs[key].onClickCallback = function () self:updateSubCategoryPages(key) end + if key == 2 then + self.subCategoryPages[key] = self.containerMap + else + self.subCategoryPages[key] = self.containerPrefab:clone(self) + self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" + FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) + end end + self:resetUIDeadzones() + self.mapOverviewSelector:setTexts(self.mapSelectorTexts) + self.mapOverviewSelector:setState(1, true) + + self.hotspotFilterCategories = InGameMenuMapFrame.HOTSPOT_FILTER_CATEGORIES + self.hotspotStateFilter = {} + for i, data in ipairs(self.hotspotFilterCategories) do + self.hotspotStateFilter[i] = {} + for j, _ in ipairs(data) do + self.hotspotStateFilter[i][j] = false + end + + end + + self.currentContextBox = self.contextBox + self.currentHotspot = nil end function CpCourseGeneratorFrame:onFrameOpen() @@ -90,12 +207,101 @@ function CpCourseGeneratorFrame:onFrameOpen() self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) + + + -- g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self.onAIVehicleStateChanged, self) + self.activeWorkerList:reloadData() + g_messageCenter:subscribe(MessageType.AI_JOB_STARTED, function(self) + self.activeWorkerList:reloadData() + end, self) + -- g_messageCenter:subscribe(MessageType.AI_JOB_STOPPED, function() + + -- end, self) + g_messageCenter:subscribe(MessageType.AI_JOB_REMOVED, function() + self.activeWorkerList:reloadData() + end, self) + -- g_messageCenter:subscribe(MessageType.AI_TASK_SKIPPED, self.onAITaskSkipped, self) + + local hotspotValue = g_gameSettings:getValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER) + for i, hotspotCategory in pairs(self.hotspotFilterCategories[1]) do + + local isBitSet = Utils.isBitSet(hotspotValue, i) + + --Lx: [1711, 1711], [179, 182], 1 + self.hotspotStateFilter[1][i] = isBitSet + + --Lx: [1712, 1712], [183, 190], 1 + self.ingameMapBase:setDefaultFilterValue(hotspotCategory.id, isBitSet) + end + for i, hotspotCategory in pairs(self.hotspotFilterCategories[2]) do + + local isBitSet = Utils.isBitSet(hotspotValue, i + #self.hotspotFilterCategories[1]) + + --Lx: [1711, 1711], [179, 182], 1 + self.hotspotStateFilter[2][i] = isBitSet + + --Lx: [1712, 1712], [183, 190], 1 + self.ingameMapBase:setDefaultFilterValue(hotspotCategory.id, isBitSet) + end + local allDeactivated = true + for _, filter in pairs(self.hotspotStateFilter) do + for i, state in pairs(filter) do + allDeactivated = allDeactivated and not state + end + end + if allDeactivated then + self.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.SELECT_ALL)) + else + self.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.DESELECT_ALL)) + end + self.filterList:reloadData() + self.ingameMapBase:restoreDefaultFilter() + + self:generateJobTypes() +end + +function CpCourseGeneratorFrame:saveHotspotFilter() + local hotspotValue = 0 + for i, state in pairs(self.hotspotStateFilter[1]) do + if state then + hotspotValue = Utils.setBit(hotspotValue, i) + end + end + for i, state in pairs(self.hotspotStateFilter[2]) do + if state then + hotspotValue = Utils.setBit(hotspotValue, i + #self.hotspotStateFilter[1]) + end + end + g_gameSettings:setValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER, hotspotValue, true) + local allDeactivated = true + for _, filter in pairs(self.hotspotStateFilter) do + for i, state in pairs(filter) do + allDeactivated = allDeactivated and not state + end + end + if allDeactivated then + self.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.SELECT_ALL)) + else + self.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.DESELECT_ALL)) + end +end + +function CpCourseGeneratorFrame:onFrameClose() + self:closeMap() + g_messageCenter:unsubscribeAll(self) + self.jobTypeInstances = {} end function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) local vehicle = CpUtil.getCurrentVehicle() - CpSettingsUtil.updateGuiElementsBoundToSettings( - self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout"), vehicle) + local layout = self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout") + if layout then + CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) + end +end + +function CpCourseGeneratorFrame:isMapVisible() + return self.ingameMap:getIsVisible() end function CpCourseGeneratorFrame:updateSubCategoryPages(state) @@ -105,10 +311,648 @@ function CpCourseGeneratorFrame:updateSubCategoryPages(state) end self.subCategoryPages[state]:setVisible(true) self.subCategoryTabs[state]:setSelected(true) - self.subCategoryPages[state]:getDescendantByName("layout"):invalidateLayout() - self.settingsSlider:setDataElement(self.subCategoryPages[state]:getDescendantByName("layout")) + local layout = self.subCategoryPages[state]:getDescendantByName("layout") + if layout then + self.settingsSliderBox:setVisible(true) + self.ingameMap:setVisible(false) + layout:invalidateLayout() + self.settingsSlider:setDataElement(layout) + self:closeMap() + else + self.settingsSliderBox:setVisible(false) + self.ingameMap:setVisible(true) + self:openMap() + end +end + +function CpCourseGeneratorFrame:delete() + g_messageCenter:unsubscribeAll(self) + self.booleanPrefab:delete() + self.multiTextPrefab:delete() + self.sectionHeaderPrefab:delete() + self.selectorPrefab:delete() + self.containerPrefab:delete() + self.driveToAiTargetMapHotspot:delete() + self.fieldSiloAiTargetMapHotspot:delete() + self.unloadAiTargetMapHotspot:delete() + self.loadAiTargetMapHotspot:delete() + + CpCourseGeneratorFrame:superClass().delete(self) end +------------------------------- +-- InGameMap +------------------------------- function CpCourseGeneratorFrame:onDrawPostIngameMapHotspots() + if self.currentContextBox ~= nil then + InGameMenuMapUtil.updateContextBoxPosition(self.currentContextBox, self.currentHotspot) + end +end + +function CpCourseGeneratorFrame:onDrawPostIngameMap() + +end + +function CpCourseGeneratorFrame:onClickMap() + if self.isPickingLocation then + + return + end + if self.isPickingRotation then + + return + end + self:setMapSelectionItem(nil) +end + +function CpCourseGeneratorFrame:onClickHotspot(element, hotspot) + if self.isPickingLocation then + --- g_inGameMenu.pageMapOverview.executePickingCallback(self, true, hotspot.worldX, hotspot.worldZ) + return + end + if self.isPickingRotation then + --- g_inGameMenu.pageMapOverview.executePickingCallback(self, true, worldX, worldZ) + --- g_inGameMenu.pageMapOverview.executePickingCallback(self, true, math.atan2(hotspot.worldX - self.pickingRotationOrigin[0], hotspot.worldZ - self.pickingRotationOrigin[1])) + return + end + self:setMapSelectionItem(hotspot) + -- self:refreshContextInput() +end + +function CpCourseGeneratorFrame:showMapHotspot(self, hotspot) + self:onClickHotspot(nil, hotspot) + self.ingameMap:panToHotspot(hotspot) +end + +function CpCourseGeneratorFrame:onClickMapOverviewSelector(state) + self.filterListContainer:setVisible(false) + self.createJobContainer:setVisible(false) + self.workerListContainer:setVisible(false) + for i = 1, #self.mapSelectorTexts do + self.subCategoryDotBox.elements[i]:setSelected(false) + end + self.subCategoryDotBox.elements[state]:setSelected(true) + if state == self.MAP_SELECTOR_HOTSPOT then + self.filterListContainer:setVisible(true) + elseif state == self.MAP_SELECTOR_CREATE_JOB then + self.createJobContainer:setVisible(true) + -- self.subCategoryDotBox:invalidateLayout() + elseif state == self.MAP_SELECTOR_ACTIVE_JOBS then + self.workerListContainer:setVisible(true) + end +end + +function CpCourseGeneratorFrame:onClickDeselectAll() + local allDeactivated = true + for _, filter in pairs(self.hotspotStateFilter) do + for i, state in pairs(filter) do + allDeactivated = allDeactivated and not state + end + end + for i, filter in pairs(self.hotspotStateFilter) do + for j, state in pairs(filter) do + self.ingameMapBase:setDefaultFilterValue(self.hotspotFilterCategories[i][j].id, allDeactivated) + self.hotspotStateFilter[i][j] = allDeactivated + end + end + self.ingameMapBase:restoreDefaultFilter() + self:saveHotspotFilter() + self.filterList:reloadData() +end + +function CpCourseGeneratorFrame:onJobTypeChanged() + +end + +function CpCourseGeneratorFrame:onClickMultiTextOptionParameter() + +end + +function CpCourseGeneratorFrame:onClickPositionParameter(element) + local parameter = element.aiParameter + + g_inGameMenu.pageMapOverview.startPickPosition(self, parameter, function (success, x, z) + if success then + element:setText(parameter:getString()) + end + end) +end + +-- Lines 1189-1197 +function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) + local parameter = element.aiParameter + + g_inGameMenu.pageMapOverview.startPickPositionAndRotation(self, parameter, function (success, x, z, angle) + if success then + element:setText(parameter:getString()) + end + end) +end + +function CpCourseGeneratorFrame:getIsPicking() + return self.isPickingRotation or self.isPickingLocation +end + +function CpCourseGeneratorFrame:validateParameters() + +end + +function CpCourseGeneratorFrame:updateParameterValueTexts() + +end + +function CpCourseGeneratorFrame:showActionMessage() + +end + +function CpCourseGeneratorFrame:onStartCancelJob() + +end + + +function CpCourseGeneratorFrame:getNumberOfSections(list) + if list == self.filterList then + return 2 + end + return 1 +end + +function CpCourseGeneratorFrame:getTitleForSectionHeader(list, section) + if list == self.filterList then + if section == 1 then + return g_i18n:getText("ui_mapHotspotFilter_vehicles") + end + return g_i18n:getText("construction_category_buildings") + end +end + +function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) + if list == self.filterList then + return #self.hotspotFilterCategories[section] + end + if list == self.contextButtonList then + if self.mode == self.AI_MODE_CREATE then + return 0 + end + self.contextActionMapping = {} + for index, action in pairs(self.contextActions) do + if action.isActive then + table.insert(self.contextActionMapping, index) + end + end + return #self.contextActionMapping + end + if list == self.activeWorkerList then + local farmId = 1 + if g_localPlayer then + farmId = g_localPlayer.farmId + end + local count = 0 + for _, job in ipairs(g_currentMission.aiSystem:getActiveJobs()) do + if job.startedFarmId == farmId then + count = count + 1 + end + end + self.activeWorkerListEmpty:setVisible(count == 0) + return count + end + return 0 +end + +function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, index, cell) + if list == self.filterList then + local status = self.hotspotFilterCategories[section][index] + cell:getAttribute("name"):setText(g_i18n:getText(status.name)) + cell:getAttribute("icon"):setImageSlice(nil, status.sliceId) + cell:getAttribute("icon"):setSelected(true) + cell:getAttribute("iconBg").getIsSelected = function () + return self.hotspotStateFilter[section][index] + end + g_inGameMenu.pageMapOverview.assignItemColors(self, cell:getAttribute("iconBg"), status.color, cell:getAttribute("colorTemplate")) + elseif list == self.contextButtonList then + local buttonInfo = self.contextActions[self.contextActionMapping[index]] + cell:getAttribute("text"):setText(buttonInfo.text) + cell.onClickCallback = buttonInfo.callback + elseif list == self.activeWorkerList then + local count = 0 + local currentJob = nil + local farmId = 1 + if g_localPlayer then + farmId = g_localPlayer.farmId + end + for _, job in ipairs(g_currentMission.aiSystem:getActiveJobs()) do + if job.startedFarmId == farmId then + count = count + 1 + currentJob = job + break + end + end + if currentJob then + cell:getAttribute("text"):setText(currentJob:getDescription()) + cell:getAttribute("title"):setText(currentJob:getTitle()) + cell:getAttribute("helper"):setText(currentJob:getHelperName()) + end + end +end + +function CpCourseGeneratorFrame:onClickList(list, section, index, listElement) + if list == self.filterList then + self.ingameMapBase:toggleDefaultFilter(self.hotspotFilterCategories[section][index].id) + self.hotspotStateFilter[section][index] = not self.hotspotStateFilter[section][index] + self.ingameMapBase:restoreDefaultFilter() + self:saveHotspotFilter() + elseif list == self.activeWorkerList then + local job = g_currentMission.aiSystem:getJobByIndex(index) + if job ~= nil and not job.vehicleParameter then + local vehicle = job.vehicleParameter:getVehicle() + if vehicle ~= nil then + local hotspot = vehicle:getMapHotspot() + self:showMapHotspot(hotspot) + end + end + elseif list == self.contextButtonList then + listElement.onClickCallback(self) + end +end + +function CpCourseGeneratorFrame:onListSelectionChanged(list, section, index) +end + +function CpCourseGeneratorFrame:updateInputGlyphs() + local moveActions, moveText = nil + + if self.lastInputHelpMode == GS_INPUT_HELP_MODE_GAMEPAD then + moveText = self.moveCursorText + moveActions = { + InputAction.AXIS_MAP_SCROLL_LEFT_RIGHT, + InputAction.AXIS_MAP_SCROLL_UP_DOWN + } + else + moveText = self.panMapText + moveActions = { + InputAction.AXIS_LOOK_LEFTRIGHT_DRAG, + InputAction.AXIS_LOOK_UPDOWN_DRAG + } + end + + self.mapMoveGlyph:setActions(moveActions, nil, nil, moveActions) + self.mapZoomGlyph:setActions({ + InputAction.AXIS_MAP_ZOOM_IN, + InputAction.AXIS_MAP_ZOOM_OUT + }, nil, nil, self) + self.mapMoveGlyphText:setText(moveText) + self.mapZoomGlyphText:setText(self.zoomText) + +end + +-- Lines 992-1005 +function CpCourseGeneratorFrame:registerInput() + self:unregisterInput() + -- g_inputBinding:registerActionEvent(InputAction.MENU_ACTIVATE, self, self.onStartCancelJob, false, true, false, true) + -- g_inputBinding:registerActionEvent(InputAction.MENU_CANCEL, self, self.onStartGoToJob, false, true, false, true) + -- g_inputBinding:registerActionEvent(InputAction.MENU_ACCEPT, self, self.onCreateJob, false, true, false, true) + -- g_inputBinding:registerActionEvent(InputAction.MENU_EXTRA_1, self, self.onSkipJobTask, false, true, false, true) + + -- local _, switchVehicleId = g_inputBinding:registerActionEvent(InputAction.SWITCH_VEHICLE, self, self.onSwitchVehicle, false, true, false, true, 1) + -- self.eventIdSwitchVehicle = switchVehicleId + -- local _, switchVehicleBackId = g_inputBinding:registerActionEvent(InputAction.SWITCH_VEHICLE_BACK, self, self.onSwitchVehicle, false, true, false, true, -1) + -- self.eventIdSwitchVehicleBack = switchVehicleBackId +end + +-- Lines 1008-1017 +function CpCourseGeneratorFrame:unregisterInput(customOnly) + local list = customOnly and self.CLEAR_CLOSE_INPUT_ACTIONS or self.CLEAR_INPUT_ACTIONS + + for _, actionName in pairs(list) do + g_inputBinding:removeActionEventsByActionName(actionName) + end +end + +function CpCourseGeneratorFrame:generateJobTypes() + for name, jobType in pairs(AIJobType) do + if string.match(name, "CP$") then + self.jobTypeInstances[jobType] = g_currentMission.aiJobTypeManager:createJob(jobType) + end + end +end + + +function CpCourseGeneratorFrame:initializeContextActions() + self.contextActions = { + [self.CONTEXT_ACTIONS.ENTER_VEHICLE] = { + text = "button_enterVehicle", + callback = function() + if self.currentHotspot then + local vehicle = self.currentHotspot:getVehicle() + if vehicle then + if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then + self:onClickBackCallback() + g_localPlayer:requestToEnterVehicle(vehicle) + end + end + end + end, + isActive = false + }, + [self.CONTEXT_ACTIONS.CREATE_JOB] = { + text = "button_createJob", + callback = function() + if self.currentHotspot then + local vehicle = self.currentHotspot:getVehicle() + if vehicle then + local currentJobTypesTexts = {} + for index, job in pairs(self.jobTypeInstances) do + if job:getIsAvailableForVehicle(vehicle, true) then + table.insert(self.currentJobTypes, index) + table.insert(currentJobTypesTexts, g_currentMission.aiJobTypeManager:getJobTypeByIndex(index).title) + end + end + self.jobTypeElement:setTexts(currentJobTypesTexts) + self.jobTypeElement:setState(1) + self.mode = self.AI_MODE_CREATE + self.currentJobVehicle = vehicle + self.currentJob = nil + self:setJobMenuVisible(true) + FocusManager:setFocus(self.jobTypeElement) + self:setActiveJobTypeSelection(self.currentJobTypes[1]) + end + end + end, + isActive = false + }, + [self.CONTEXT_ACTIONS.START_JOB] = { + text = "button_startJob", + callback = function() + if self.currentHotspot then + local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) + if vehicle then + if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then + self:onClickBackCallback() + g_localPlayer:requestToEnterVehicle(vehicle) + end + end + end + end, + isActive = false + }, + [self.CONTEXT_ACTIONS.STOP_JOB] = { + text = "button_cancelJob", + callback = function() + if self.currentHotspot then + local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) + if vehicle then + if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then + self:onClickBackCallback() + g_localPlayer:requestToEnterVehicle(vehicle) + end + end + end + end, + isActive = false + }, + [self.CONTEXT_ACTIONS.GENERATE_COURSE] = { + text = "CP_ai_page_generate_course", + callback = function() + if self.currentHotspot then + local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) + if vehicle then + if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then + self:onClickBackCallback() + g_localPlayer:requestToEnterVehicle(vehicle) + end + end + end + end, + isActive = false + } + } +end + +function CpCourseGeneratorFrame:updateContextActions() + local vehicle = self.currentHotspot and self.currentHotspot:getVehicle() + self.contextActions[self.CONTEXT_ACTIONS.ENTER_VEHICLE].isActive = vehicle and vehicle:getIsEnterableFromMenu() + local canCreateJob = false + if not canCreateJob and not self.currentJobVehicle then + for _, job in pairs(self.jobTypeInstances) do + if job:getIsAvailableForVehicle(vehicle, true) then + canCreateJob = true + end + end + end + self.contextActions[self.CONTEXT_ACTIONS.CREATE_JOB].isActive = canCreateJob + self.contextActions[self.CONTEXT_ACTIONS.START_JOB].isActive = false + self.contextActions[self.CONTEXT_ACTIONS.STOP_JOB].isActive = false + self.contextActions[self.CONTEXT_ACTIONS.GENERATE_COURSE].isActive = false + + + self.contextButtonList:reloadData() +end + +function CpCourseGeneratorFrame:toggleMapInput(isActive) + if self.isInputContextActive ~= isActive then + self.isInputContextActive = isActive + + self:toggleCustomInputContext(isActive, self.INPUT_CONTEXT_NAME) + + if not isActive then + self:registerInput() + else + self:unregisterInput(true) + end + end +end + +function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) + if hotspot ~= nil then + + local x, _ = hotspot:getWorldPosition() + if x == nil then + hotspot = nil + end + end + self.ingameMapBase:setSelectedHotspot(nil) + self.selectedFarmland = nil + + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + + local playerName = nil + local farmId = 1 + local name = nil + local imageFilename = nil + local uvs = Overlay.DEFAULT_UVS + + local showContextBox = false + if hotspot ~= nil then + self.currentHotspot = nil + local vehicle = InGameMenuMapUtil.getHotspotVehicle(hotspot) + if vehicle ~= nil and not vehicle.spec_rideable then + playerName = nil + farmId = vehicle:getOwnerFarmId() + name = vehicle:getName() + imageFilename = vehicle:getImageFilename() + uvs = Overlay.DEFAULT_UVS + self.currentHotspot = hotspot + if hotspot:isa(PlayerHotspot) then + playerName = player:getNickname() + farmId = player:getFarmId() + end + if vehicle.getJob ~= nil then + local job = vehicle:getJob() + if job ~= nil then + -- TODO_25 + end + end + showContextBox = true + end + end + if showContextBox then + InGameMenuMapUtil.showContextBox(self.contextBox, hotspot, name, imageFilename, uvs, farmId, playerName, false, true, false) + self:updateContextActions() + else + InGameMenuMapUtil.hideContextBox(self.contextBox) + end +end + +function CpCourseGeneratorFrame:setAIVehicle(vehicle) + local hotspot = vehicle:getMapHotspot() + self:setMapSelectionItem(hotspot) + self.ingameMap:panToHotspot(hotspot) + -- self:onCreateJob() + self.createJobEmptyText:setVisible(false) +end + +function CpCourseGeneratorFrame:resetUIDeadzones() + self.ingameMap:clearCursorDeadzones() + self.ingameMap:addCursorDeadzone(self.rightBackground.absPosition[1], self.rightBackground.absPosition[2], self.rightBackground.size[1], self.rightBackground.size[2]) + self.ingameMap:addCursorDeadzone(self.topBackground.absPosition[1], self.topBackground.absPosition[2], self.topBackground.size[1], self.topBackground.size[2]) + self.ingameMap:addCursorDeadzone(self.bottomBackground.absPosition[1], self.bottomBackground.absPosition[2], self.bottomBackground.size[1], self.bottomBackground.size[2]) + self.ingameMap:addCursorDeadzone(0, 0, self.leftBox.absPosition[1] + self.leftBox.absSize[1], 1) +end + +function CpCourseGeneratorFrame:setJobMenuVisible(isVisible) + -- g_inputBinding:setActionEventActive(self.eventIdSwitchVehicle, not isVisible) + -- g_inputBinding:setActionEventActive(self.eventIdSwitchVehicleBack, not isVisible) + -- g_inputBinding:setContextEventsActive(InGameMenuAIFrame.INPUT_CONTEXT_NAME, InputAction.MENU_AXIS_LEFT_RIGHT, isVisible) + self.errorMessage:setText("") + self.actionMessage:setText("") + self.createJobEmptyText:setVisible(not isVisible) + self.jobTypeElement:setVisible(isVisible) + self.jobMenuLayout:setVisible(isVisible) + if not isVisible then + self:setMapSelectionItem(self.currentHotspot) + end +end + +function CpCourseGeneratorFrame:setActiveJobTypeSelection(jobTypeIndex) + if self.currentJob == nil or jobTypeIndex ~= self.currentJob.jobTypeIndex then + for i = #self.jobMenuLayout.elements, 1, -1 do + self.jobMenuLayout.elements[i]:delete() + end + self.currentJob = g_currentMission.aiJobTypeManager:createJob(jobTypeIndex) + local farmId = 1 + if g_localPlayer then + farmId = g_localPlayer.farmId + end + self.currentJob:applyCurrentState(self.currentJobVehicle, g_currentMission, farmId, false) + self.currentJobElements = {} + for _, group in ipairs(self.currentJob:getGroupedParameters()) do + local titleElement = self.createTitleTemplate:clone(self.jobMenuLayout) + + titleElement:setText(group:getTitle()) + + for _, item in ipairs(group:getParameters()) do + local element = nil + local parameterType = item:getType() + + if parameterType == AIParameterType.TEXT then + element = self.createTextTemplate:clone(self.jobMenuLayout) + elseif parameterType == AIParameterType.POSITION then + element = self.createPositionTemplate:clone(self.jobMenuLayout) + elseif parameterType == AIParameterType.POSITION_ANGLE then + element = self.createPositionRotationTemplate:clone(self.jobMenuLayout) + elseif parameterType == AIParameterType.SELECTOR or parameterType == AIParameterType.UNLOADING_STATION or parameterType == AIParameterType.LOADING_STATION or parameterType == AIParameterType.FILLTYPE then + element = self.createMultiOptionTemplate:clone(self.jobMenuLayout) + + element:setDataSource(item) + end + if element then + FocusManager:loadElementFromCustomValues(element) + + element.aiParameter = item + if element.updateTitle then + element:updateTitle() + end + element:setDisabled(not item:getCanBeChanged()) + table.insert(self.currentJobElements, element) + end + end + end + self:updateParameterValueTexts() + self:validateParameters() + self.jobMenuLayout:invalidateLayout() + FocusManager:setFocus(self.jobTypeElement) + end + self:updateContextActions() +end + +function CpCourseGeneratorFrame:openMap() + ---------------------- + --- Ingame map + ---------------------- + + + self:setMapSelectionItem(nil) + self:toggleMapInput(true) + self.ingameMap:onOpen() + self.ingameMap:registerActionEvents() + -- self:updateInputGlyphs() + self:setJobMenuVisible(false) + self.ingameMapBase:restoreDefaultFilter() + for k, v in pairs(self.ingameMapBase.filter) do + self.hotspotFilterState[k] = v + + self.ingameMapBase:setHotspotFilter(k, false) + end + + + if g_localPlayer ~= nil then + local x, _, z = g_localPlayer:getPosition() + self.ingameMap:setCenterToWorldPosition(x, z) + end + + -- self:setJobMenuVisible(false) + + -- self.activeWorkerList:reloadData() +end + +function CpCourseGeneratorFrame:closeMap() + -- self:setMapSelectionItem(nil) + -- self:setMapSelectionPosition(nil, ) + + -- self.startJobPending = false + + -- if self:getIsPicking() then + -- self:executePickingCallback(false) + -- self:refreshContextInput() + -- end + + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + self.ingameMap:onClose() + self:toggleMapInput(false) + + self.ingameMapBase:restoreDefaultFilter() + -- self.isOpen = false + + self:setJobMenuVisible(false) + -- g_inputBinding:setContextEventsActive(self.INPUT_CONTEXT_NAME, InputAction.MENU_AXIS_LEFT_RIGHT, true) + + -- self.statusMessages = {} + -- self:updateStatusMessages() end \ No newline at end of file diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua index 8fe599ddd..6716ff446 100644 --- a/scripts/gui/pages/CpGlobalSettingsFrame.lua +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -42,7 +42,7 @@ function CpGlobalSettingsFrame.createFromExistingGui(gui, guiName) return newGui end -function CpGlobalSettingsFrame:initialize() +function CpGlobalSettingsFrame:initialize(menu) self.booleanPrefab:unlinkElement() FocusManager:removeElement(self.booleanPrefab) diff --git a/scripts/gui/pages/CpVehicleSettingsFrame.lua b/scripts/gui/pages/CpVehicleSettingsFrame.lua index 3b681e5f6..4c9fd2186 100644 --- a/scripts/gui/pages/CpVehicleSettingsFrame.lua +++ b/scripts/gui/pages/CpVehicleSettingsFrame.lua @@ -40,7 +40,7 @@ function CpVehicleSettingsFrame.createFromExistingGui(gui, guiName) return newGui end -function CpVehicleSettingsFrame:initialize() +function CpVehicleSettingsFrame:initialize(menu) self.booleanPrefab:unlinkElement() FocusManager:removeElement(self.booleanPrefab) From 34ca1e28132b09dc1923e77caad0201d5a1026fb Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 3 Dec 2024 19:00:26 +0100 Subject: [PATCH 058/158] Diff bug fixes .. --- config/MasterTranslations.xml | 10 ++ scripts/ai/jobs/CpAIJobCombineUnloader.lua | 2 +- scripts/ai/jobs/CpAIJobFieldWork.lua | 2 +- scripts/ai/jobs/CpAIJobSiloLoader.lua | 2 +- scripts/gui/CpInGameMenu.lua | 55 ++++---- scripts/gui/pages/CpCourseGeneratorFrame.lua | 141 ++++++++++--------- scripts/gui/pages/CpGlobalSettingsFrame.lua | 6 +- scripts/gui/pages/CpVehicleSettingsFrame.lua | 6 +- 8 files changed, 117 insertions(+), 107 deletions(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index 995585605..da3b6ab31 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -463,6 +463,10 @@ <Text language="de"><![CDATA[Fahrzeugeinstellungen für (%s)]]></Text> <Text language="en"><![CDATA[Vehicle settings for (%s)]]></Text> </Translation> + <Translation name="CP_vehicle_setting_subTitle_vehicle"> + <Text language="de"><![CDATA[Fahrzeugeinstellungen]]></Text> + <Text language="en"><![CDATA[Vehicle settings]]></Text> + </Translation> <Translation name="CP_vehicle_setting_subTitle_basic"> <Text language="de"><![CDATA[Grundeinstellungen]]></Text> <Text language="en"><![CDATA[Basic settings]]></Text> @@ -1823,6 +1827,12 @@ The course is saved automatically on closing of the editor and overrides the sel <Text language="en"><![CDATA[Unpause custom field recording]]></Text> </Translation> </Category> + <Category name="CP ingame menu"> + <Translation name="CP_ingameMenu_map_title"> + <Text language="de"><![CDATA[Karte]]></Text> + <Text language="en"><![CDATA[Map]]></Text> + </Translation> + </Category> <Category name="Help menu"> <Translation name="CP_help_title"> <Text language="de"><![CDATA[Courseplay]]></Text> diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index 10fe78dea..3e218578d 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -58,7 +58,7 @@ function CpAIJobCombineUnloader:setupJobParameters() self.waitForFillingTask = self.combineUnloaderTask end -function CpAIJobCombineUnloader:getIsAvailableForVehicle(vehicle) +function CpAIJobCombineUnloader:getIsAvailableForVehicle(vehicle, cpJobsAllowed) return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpCombineUnloader and vehicle:getCanStartCpCombineUnloader() -- TODO_25 end diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index 51abab4c6..61bdf0b50 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -262,7 +262,7 @@ function CpAIJobFieldWork:isPipeOnLeftSide(vehicle) end end -function CpAIJobFieldWork:getIsAvailableForVehicle(vehicle) +function CpAIJobFieldWork:getIsAvailableForVehicle(vehicle, cpJobsAllowed) return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpFieldWork and vehicle:getCanStartCpFieldWork() -- TODO_25 end diff --git a/scripts/ai/jobs/CpAIJobSiloLoader.lua b/scripts/ai/jobs/CpAIJobSiloLoader.lua index 44a9012c0..11d5ebca3 100644 --- a/scripts/ai/jobs/CpAIJobSiloLoader.lua +++ b/scripts/ai/jobs/CpAIJobSiloLoader.lua @@ -51,7 +51,7 @@ function CpAIJobSiloLoader:setupJobParameters() self.cpJobParameters.unloadPosition:setSnappingAngle(math.pi/8) -- AI menu snapping angle of 22.5 degree. end -function CpAIJobSiloLoader:getIsAvailableForVehicle(vehicle) +function CpAIJobSiloLoader:getIsAvailableForVehicle(vehicle, cpJobsAllowed) return CpAIJob.getIsAvailableForVehicle(self, vehicle, cpJobsAllowed) and vehicle.getCanStartCpSiloLoaderWorker and vehicle:getCanStartCpSiloLoaderWorker() -- TODO_25 end diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index fdcf813bd..604e1d3a6 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -149,41 +149,38 @@ end -- Lines 365-397 function CpInGameMenu:setupMenuButtonInfo() CpInGameMenu:superClass().setupMenuButtonInfo(self) - local onButtonBackFunction = self.clickBackCallback - local onButtonQuitFunction = self:makeSelfCallback(self.onButtonQuit) - local onButtonSaveGameFunction = self:makeSelfCallback(self.onButtonSaveGame) - self.backButtonInfo = { - inputAction = InputAction.MENU_BACK, - text = self.l10n:getText(CpInGameMenu.L10N_SYMBOL.BUTTON_BACK), - callback = onButtonBackFunction - } - self.saveButtonInfo = { - showWhenPaused = true, - inputAction = InputAction.MENU_ACTIVATE, - text = self.l10n:getText(CpInGameMenu.L10N_SYMBOL.BUTTON_SAVE_GAME), - callback = onButtonSaveGameFunction - } - self.quitButtonInfo = { - showWhenPaused = true, - inputAction = InputAction.MENU_CANCEL, - text = self.l10n:getText(CpInGameMenu.L10N_SYMBOL.BUTTON_CANCEL_GAME), - callback = onButtonQuitFunction - } + local onButtonPagePreviousFunction = self:makeSelfCallback(self.onPagePrevious) + local onButtonPageNextFunction = self:makeSelfCallback(self.onPageNext) + + + self.backButtonInfo = { + inputAction = InputAction.MENU_BACK, + text = g_i18n:getText(InGameMenu.L10N_SYMBOL.BUTTON_BACK), + callback = onButtonBackFunction } + self.nextPageButtonInfo = { + inputAction = InputAction.MENU_PAGE_NEXT, + text = g_i18n:getText("ui_ingameMenuNext"), + callback = self.onPageNext } + self.prevPageButtonInfo = { + inputAction = InputAction.MENU_PAGE_PREV, + text = g_i18n:getText("ui_ingameMenuPrev"), + callback = self.onPagePrevious } + self.defaultMenuButtonInfo = { self.backButtonInfo, - self.saveButtonInfo, - self.quitButtonInfo + self.nextPageButtonInfo, + self.prevPageButtonInfo } self.defaultMenuButtonInfoByActions[InputAction.MENU_BACK] = self.defaultMenuButtonInfo[1] - self.defaultMenuButtonInfoByActions[InputAction.MENU_ACTIVATE] = self.defaultMenuButtonInfo[2] - self.defaultMenuButtonInfoByActions[InputAction.MENU_CANCEL] = self.defaultMenuButtonInfo[3] + self.defaultMenuButtonInfoByActions[InputAction.MENU_PAGE_NEXT] = self.defaultMenuButtonInfo[2] + self.defaultMenuButtonInfoByActions[InputAction.MENU_PAGE_PREV] = self.defaultMenuButtonInfo[3] self.defaultButtonActionCallbacks = { [InputAction.MENU_BACK] = onButtonBackFunction, - [InputAction.MENU_CANCEL] = onButtonQuitFunction, - [InputAction.MENU_ACTIVATE] = onButtonSaveGameFunction + [InputAction.MENU_PAGE_NEXT] = onButtonPageNextFunction, + [InputAction.MENU_PAGE_PREV] = onButtonPagePreviousFunction } end @@ -233,6 +230,12 @@ function CpInGameMenu:onMenuOpened() -- end end +function CpInGameMenu:onButtonBack() + if self.currentPage:requestClose(self.clickBackCallback) then + CpInGameMenu:superClass().onButtonBack(self) + end +end + -- Lines 559-578 function CpInGameMenu:onClose(element) CpInGameMenu:superClass().onClose(self) diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 4d6ec6ffc..78808e701 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -7,13 +7,11 @@ CpCourseGeneratorFrame = { CATEGRORIES = { BASIC_SETTINGS = 1, IN_GAME_MAP = 2, - DEBUG_SETTINGS = 3 }, INPUT_CONTEXT_NAME = "CP_COURSE_GENERATOR_MENU", CATEGRORY_TEXTS = { - "CP_global_setting_subTitle_general", - "TODO: MAP", - "TODO: Debug" + "CP_vehicle_courseGeneratorSetting_subTitle_basic", + "CP_ingameMenu_map_title", }, CLEAR_INPUT_ACTIONS = { InputAction.MENU_ACTIVATE, @@ -60,8 +58,6 @@ function CpCourseGeneratorFrame.new(target, custom_mt) local self = TabbedMenuFrameElement.new(target, custom_mt or CpCourseGeneratorFrame_mt) self.subCategoryPages = {} self.subCategoryTabs = {} - - self.contextActions = {} self.contextActionMapping = {} self.hasFullScreenMap = true @@ -122,9 +118,7 @@ function CpCourseGeneratorFrame:setInGameMap(ingameMap, terrainSize, hud) end function CpCourseGeneratorFrame:initialize(menu) - - -- self:updateInputGlyphs() - + self.onClickBackCallback = menu.clickBackCallback self:initializeContextActions() self.booleanPrefab:unlinkElement() @@ -207,8 +201,8 @@ function CpCourseGeneratorFrame:onFrameOpen() self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) - - + + -- g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self.onAIVehicleStateChanged, self) self.activeWorkerList:reloadData() g_messageCenter:subscribe(MessageType.AI_JOB_STARTED, function(self) @@ -221,26 +215,15 @@ function CpCourseGeneratorFrame:onFrameOpen() self.activeWorkerList:reloadData() end, self) -- g_messageCenter:subscribe(MessageType.AI_TASK_SKIPPED, self.onAITaskSkipped, self) - local hotspotValue = g_gameSettings:getValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER) for i, hotspotCategory in pairs(self.hotspotFilterCategories[1]) do - - local isBitSet = Utils.isBitSet(hotspotValue, i) - - --Lx: [1711, 1711], [179, 182], 1 + local isBitSet = Utils.isBitSet(hotspotValue, hotspotCategory.id) self.hotspotStateFilter[1][i] = isBitSet - - --Lx: [1712, 1712], [183, 190], 1 self.ingameMapBase:setDefaultFilterValue(hotspotCategory.id, isBitSet) end for i, hotspotCategory in pairs(self.hotspotFilterCategories[2]) do - - local isBitSet = Utils.isBitSet(hotspotValue, i + #self.hotspotFilterCategories[1]) - - --Lx: [1711, 1711], [179, 182], 1 + local isBitSet = Utils.isBitSet(hotspotValue, hotspotCategory.id) self.hotspotStateFilter[2][i] = isBitSet - - --Lx: [1712, 1712], [183, 190], 1 self.ingameMapBase:setDefaultFilterValue(hotspotCategory.id, isBitSet) end local allDeactivated = true @@ -258,6 +241,14 @@ function CpCourseGeneratorFrame:onFrameOpen() self.ingameMapBase:restoreDefaultFilter() self:generateJobTypes() + + + self.mode = self.AI_MODE_OVERVIEW + self.currentJob = nil + self.currentJobVehicle = nil + self.currentHotspot = nil + self:setMapSelectionItem(nil) + self:setJobMenuVisible(false) end function CpCourseGeneratorFrame:saveHotspotFilter() @@ -290,6 +281,35 @@ function CpCourseGeneratorFrame:onFrameClose() self:closeMap() g_messageCenter:unsubscribeAll(self) self.jobTypeInstances = {} + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + + self.ingameMapBase:restoreDefaultFilter() + -- self.isOpen = false + self.mode = self.AI_MODE_OVERVIEW + self:setJobMenuVisible(false) + self:setMapSelectionItem(nil) +end + +function CpCourseGeneratorFrame:requestClose() + if self.mode == self.AI_MODE_CREATE then + if self:getIsPicking() then + self:executePickingCallback(false) + self:updateContextActions() + return false + end + self.mode = self.AI_MODE_OVERVIEW + self:setJobMenuVisible(false) + FocusManager:setFocus(self.activeWorkerList) + self:setMapSelectionItem(nil) + return false + elseif self.currentHotspot then + self:setMapSelectionItem(nil) + return false + end + return true end function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) @@ -336,7 +356,6 @@ function CpCourseGeneratorFrame:delete() self.fieldSiloAiTargetMapHotspot:delete() self.unloadAiTargetMapHotspot:delete() self.loadAiTargetMapHotspot:delete() - CpCourseGeneratorFrame:superClass().delete(self) end ------------------------------- @@ -438,7 +457,6 @@ function CpCourseGeneratorFrame:onClickPositionParameter(element) end) end --- Lines 1189-1197 function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) local parameter = element.aiParameter @@ -469,7 +487,6 @@ function CpCourseGeneratorFrame:onStartCancelJob() end - function CpCourseGeneratorFrame:getNumberOfSections(list) if list == self.filterList then return 2 @@ -524,7 +541,9 @@ function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, inde local status = self.hotspotFilterCategories[section][index] cell:getAttribute("name"):setText(g_i18n:getText(status.name)) cell:getAttribute("icon"):setImageSlice(nil, status.sliceId) - cell:getAttribute("icon"):setSelected(true) + cell:getAttribute("icon").getIsSelected = function () + return true + end cell:getAttribute("iconBg").getIsSelected = function () return self.hotspotStateFilter[section][index] end @@ -646,7 +665,7 @@ function CpCourseGeneratorFrame:initializeContextActions() if self.currentHotspot then local vehicle = self.currentHotspot:getVehicle() if vehicle then - if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then + if vehicle.getIsEnterableFromMenu ~= nil and vehicle:getIsEnterableFromMenu() then self:onClickBackCallback() g_localPlayer:requestToEnterVehicle(vehicle) end @@ -701,11 +720,8 @@ function CpCourseGeneratorFrame:initializeContextActions() callback = function() if self.currentHotspot then local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) - if vehicle then - if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then - self:onClickBackCallback() - g_localPlayer:requestToEnterVehicle(vehicle) - end + if vehicle and vehicle:getIsAIActive() then + vehicle:stopCurrentAIJob() end end end, @@ -786,8 +802,8 @@ function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) local uvs = Overlay.DEFAULT_UVS local showContextBox = false + self.currentHotspot = nil if hotspot ~= nil then - self.currentHotspot = nil local vehicle = InGameMenuMapUtil.getHotspotVehicle(hotspot) if vehicle ~= nil and not vehicle.spec_rideable then playerName = nil @@ -797,8 +813,11 @@ function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) uvs = Overlay.DEFAULT_UVS self.currentHotspot = hotspot if hotspot:isa(PlayerHotspot) then - playerName = player:getNickname() - farmId = player:getFarmId() + local player = hotspot:getPlayer() + if player then + playerName = player:getNickname() + farmId = player:getFarmId() + end end if vehicle.getJob ~= nil then local job = vehicle:getJob() @@ -843,7 +862,12 @@ function CpCourseGeneratorFrame:setJobMenuVisible(isVisible) self.jobTypeElement:setVisible(isVisible) self.jobMenuLayout:setVisible(isVisible) if not isVisible then - self:setMapSelectionItem(self.currentHotspot) + self:setMapSelectionItem(self.currentHotspot) + FocusManager:setFocus(self.mapOverviewSelector) + self.mapOverviewSelector:setState(self.MAP_SELECTOR_ACTIVE_JOBS, true) + else + self.mapOverviewSelector:setState(self.MAP_SELECTOR_CREATE_JOB, true) + self.mapOverviewSelector:setDisabled(true) end end @@ -903,35 +927,23 @@ function CpCourseGeneratorFrame:openMap() ---------------------- --- Ingame map ---------------------- + --- + -- self:setJobMenuVisible(false) - - self:setMapSelectionItem(nil) + -- self.activeWorkerList:reloadData() self:toggleMapInput(true) self.ingameMap:onOpen() self.ingameMap:registerActionEvents() - -- self:updateInputGlyphs() - self:setJobMenuVisible(false) self.ingameMapBase:restoreDefaultFilter() - for k, v in pairs(self.ingameMapBase.filter) do - self.hotspotFilterState[k] = v - - self.ingameMapBase:setHotspotFilter(k, false) - end - - - if g_localPlayer ~= nil then - local x, _, z = g_localPlayer:getPosition() - self.ingameMap:setCenterToWorldPosition(x, z) - end - - -- self:setJobMenuVisible(false) - - -- self.activeWorkerList:reloadData() + -- if g_localPlayer ~= nil then + -- local x, _, z = g_localPlayer:getPosition() + -- self.ingameMap:setCenterToWorldPosition(x, z) + -- end end function CpCourseGeneratorFrame:closeMap() - -- self:setMapSelectionItem(nil) - -- self:setMapSelectionPosition(nil, ) + self.ingameMap:onClose() + self:toggleMapInput(false) -- self.startJobPending = false @@ -940,17 +952,6 @@ function CpCourseGeneratorFrame:closeMap() -- self:refreshContextInput() -- end - g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) - g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) - g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) - g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) - self.ingameMap:onClose() - self:toggleMapInput(false) - - self.ingameMapBase:restoreDefaultFilter() - -- self.isOpen = false - - self:setJobMenuVisible(false) -- g_inputBinding:setContextEventsActive(self.INPUT_CONTEXT_NAME, InputAction.MENU_AXIS_LEFT_RIGHT, true) -- self.statusMessages = {} diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua index 6716ff446..75f3da8c4 100644 --- a/scripts/gui/pages/CpGlobalSettingsFrame.lua +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -6,13 +6,11 @@ CpGlobalSettingsFrame = { CATEGRORIES = { BASIC_SETTINGS = 1, - USER_SETTINGS = 2, - DEBUG_SETTINGS = 3 + USER_SETTINGS = 2 }, CATEGRORY_TEXTS = { "CP_global_setting_subTitle_general", - "CP_global_setting_subTitle_userSettings", - "TODO: Debug" + "CP_global_setting_subTitle_userSettings" } } CpGlobalSettingsFrame.NUM_CATEGORIES = #CpGlobalSettingsFrame.CATEGRORY_TEXTS diff --git a/scripts/gui/pages/CpVehicleSettingsFrame.lua b/scripts/gui/pages/CpVehicleSettingsFrame.lua index 4c9fd2186..0fbae0287 100644 --- a/scripts/gui/pages/CpVehicleSettingsFrame.lua +++ b/scripts/gui/pages/CpVehicleSettingsFrame.lua @@ -5,12 +5,10 @@ CpVehicleSettingsFrame = { CATEGRORIES = { - BASIC_SETTINGS = 1, - DEBUG_SETTINGS = 2 + BASIC_SETTINGS = 1 }, CATEGRORY_TEXTS = { - "CP_global_setting_subTitle_general", - "TODO: Debug" + "CP_vehicle_setting_subTitle_vehicle", } } CpVehicleSettingsFrame.NUM_CATEGORIES = #CpVehicleSettingsFrame.CATEGRORY_TEXTS From e1d34bcf2ce092d412edcc164d1d9e2016ca4c93 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 3 Dec 2024 21:57:23 +0100 Subject: [PATCH 059/158] Some more fixes --- config/gui/pages/CourseGeneratorFrame.xml | 19 +- scripts/gui/CpInGameMenu.lua | 4 +- scripts/gui/pages/CpCourseGeneratorFrame.lua | 349 ++++++++++++++----- 3 files changed, 273 insertions(+), 99 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index a2a59ad50..fd379b471 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -67,15 +67,20 @@ <MultiTextOption profile="fs25_aiJobTypeMultiTextOption" id="jobTypeElement" onClick="onJobTypeChanged" /> <BoxLayout profile="fs25_aiCreateJobLayout" id="jobMenuLayout"> - <OptionToggle profile="fs25_aiCreateJobMultiTextOption" + <CpOptionToggle profile="fs25_aiCreateJobMultiTextOption" id="createMultiOptionTemplate" onClick="onClickMultiTextOptionParameter"> <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> - </OptionToggle> + </CpOptionToggle> <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" id="createTextTemplate"> <Bitmap profile="fs25_aiCreateJobParameterVehicleIcon" /> <Text profile="fs25_multiTextOptionText" name="title" /> <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </ThreePartBitmap> + <Button profile="fs25_aiCreateJobParameterBg" id="createButtonTemplate"> + <Bitmap profile="fs25_aiCreateJobParameterVehicleIcon" /> + <Text profile="fs25_multiTextOptionText" name="title" /> + <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> + </Button> <Text profile="fs25_aiCreateJobParameterTitle" id="createTitleTemplate" text="Group title" /> <Button profile="fs25_aiCreateJobParameterButton" id="createPositionTemplate" @@ -127,6 +132,16 @@ </GuiElement> </Bitmap> </GuiElement> + <GuiElement profile="fs25_menuContainer"> + <GuiElement profile="fs25_menuHeaderPanel"> + <!-- <RoundCorner profile="fs25_mapMoneyBoxBg" id="shopMoneyBoxBg" /> + <BoxLayout profile="fs25_shopMoneyBox" id="shopMoneyBox"> + <Text profile="fs25_shopBalance" text="$l10n_ui_balance:" /> + <Text profile="fs25_shopMoney" id="currentBalanceText" /> + </BoxLayout> --> + <Text profile="fs25_ingameMenuAIStatusText" id="statusMessage" /> + </GuiElement> + </GuiElement> <GuiElement profile="fs25_mapContextBoxContainer" newLayer="true"> <GuiElement profile="fs25_mapContextBox" id="contextBox"> <ThreePartBitmap profile="fs25_mapContextBoxBg" /> diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 604e1d3a6..b6bd14ef3 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -230,8 +230,8 @@ function CpInGameMenu:onMenuOpened() -- end end -function CpInGameMenu:onButtonBack() - if self.currentPage:requestClose(self.clickBackCallback) then +function CpInGameMenu:onButtonBack(_, _, force) + if force or self.currentPage:requestClose(self.clickBackCallback) then CpInGameMenu:superClass().onButtonBack(self) end end diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 78808e701..9c655daaf 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -85,7 +85,7 @@ function CpCourseGeneratorFrame.new(target, custom_mt) self.loadAiTargetMapHotspot = AITargetHotspot.new() self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot - + self.updateTime = 0 self.mapSelectorTexts = { g_i18n:getText("ui_mapOverviewHotspots"), g_i18n:getText("button_createJob"), @@ -142,7 +142,9 @@ function CpCourseGeneratorFrame:initialize(menu) FocusManager:removeElement(self.createPositionTemplate) self.createPositionRotationTemplate:unlinkElement() FocusManager:removeElement(self.createPositionRotationTemplate) - + self.createButtonTemplate:unlinkElement() + FocusManager:removeElement(self.createButtonTemplate) + for key = 1, CpCourseGeneratorFrame.NUM_CATEGORIES do self.subCategoryPaging:addText(tostring(key)) self.subCategoryTabs[key] = self.selectorPrefab:clone(self.subCategoryBox) @@ -180,6 +182,33 @@ function CpCourseGeneratorFrame:initialize(menu) self.currentHotspot = nil end +function CpCourseGeneratorFrame:update(dt) + if self.updateTime < g_time then + for i = 1, self.activeWorkerList:getItemCount() do + local element = self.activeWorkerList:getElementAtSectionIndex(1, i) + if element ~= nil then + local job = g_currentMission.aiSystem:getJobByIndex(i) + + if job ~= nil then + element:getAttribute("text"):setText(job:getDescription()) + end + end + end + self.updateTime = g_time + 1000 + end + local hasChanged = false + for i = 1, #self.statusMessages do + local removeTime = self.statusMessages[1].removeTime + if removeTime < g_time then + table.remove(self.statusMessages, 1) + hasChanged = true + end + end + if hasChanged then + self:updateStatusMessages() + end +end + function CpCourseGeneratorFrame:onFrameOpen() local vehicle = CpUtil.getCurrentVehicle() if not vehicle then @@ -208,11 +237,20 @@ function CpCourseGeneratorFrame:onFrameOpen() g_messageCenter:subscribe(MessageType.AI_JOB_STARTED, function(self) self.activeWorkerList:reloadData() end, self) - -- g_messageCenter:subscribe(MessageType.AI_JOB_STOPPED, function() - - -- end, self) - g_messageCenter:subscribe(MessageType.AI_JOB_REMOVED, function() + + g_messageCenter:subscribe(MessageType.AI_JOB_STOPPED, function(self, job, aiMessage) + if aiMessage ~= nil and job ~= nil and g_localPlayer ~= nil then + if job.startedFarmId and g_localPlayer.farmId then + local helperName = job:getHelperName() + local text = aiMessage:getMessage() + self:addStatusMessage(string.format(text, helperName or "Unknown")) + end + end + end, self) + g_messageCenter:subscribe(MessageType.AI_JOB_REMOVED, function(self, jobId) self.activeWorkerList:reloadData() + + -- InGameMenuMapUtil.hideContextBox(self.contextBox) end, self) -- g_messageCenter:subscribe(MessageType.AI_TASK_SKIPPED, self.onAITaskSkipped, self) local hotspotValue = g_gameSettings:getValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER) @@ -239,10 +277,7 @@ function CpCourseGeneratorFrame:onFrameOpen() end self.filterList:reloadData() self.ingameMapBase:restoreDefaultFilter() - self:generateJobTypes() - - self.mode = self.AI_MODE_OVERVIEW self.currentJob = nil self.currentJobVehicle = nil @@ -287,23 +322,25 @@ function CpCourseGeneratorFrame:onFrameClose() g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) self.ingameMapBase:restoreDefaultFilter() - -- self.isOpen = false + if not self:getIsPicking() then + g_inGameMenu.pageMapOverview.executePickingCallback(self, false) + end self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) self:setMapSelectionItem(nil) + self.statusMessages = {} + self:updateStatusMessages() end function CpCourseGeneratorFrame:requestClose() if self.mode == self.AI_MODE_CREATE then if self:getIsPicking() then - self:executePickingCallback(false) + g_inGameMenu.pageMapOverview.executePickingCallback(self, false) self:updateContextActions() return false end self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) - FocusManager:setFocus(self.activeWorkerList) - self:setMapSelectionItem(nil) return false elseif self.currentHotspot then self:setMapSelectionItem(nil) @@ -372,13 +409,14 @@ function CpCourseGeneratorFrame:onDrawPostIngameMap() end -function CpCourseGeneratorFrame:onClickMap() +function CpCourseGeneratorFrame:onClickMap(element ,worldX, worldZ) if self.isPickingLocation then - + g_inGameMenu.pageMapOverview.executePickingCallback(self, true, worldX, worldZ) return end if self.isPickingRotation then - + local angle = math.atan2(worldX - self.pickingRotationOrigin[1], worldZ - self.pickingRotationOrigin[2]) + g_inGameMenu.pageMapOverview.executePickingCallback(self, true, angle) return end self:setMapSelectionItem(nil) @@ -386,12 +424,12 @@ end function CpCourseGeneratorFrame:onClickHotspot(element, hotspot) if self.isPickingLocation then - --- g_inGameMenu.pageMapOverview.executePickingCallback(self, true, hotspot.worldX, hotspot.worldZ) + g_inGameMenu.pageMapOverview.executePickingCallback(self, true, hotspot.worldX, hotspot.worldZ) return end if self.isPickingRotation then - --- g_inGameMenu.pageMapOverview.executePickingCallback(self, true, worldX, worldZ) - --- g_inGameMenu.pageMapOverview.executePickingCallback(self, true, math.atan2(hotspot.worldX - self.pickingRotationOrigin[0], hotspot.worldZ - self.pickingRotationOrigin[1])) + g_inGameMenu.pageMapOverview.executePickingCallback(self, true, worldX, worldZ) + g_inGameMenu.pageMapOverview.executePickingCallback(self, true, math.atan2(hotspot.worldX - self.pickingRotationOrigin[0], hotspot.worldZ - self.pickingRotationOrigin[1])) return end self:setMapSelectionItem(hotspot) @@ -439,31 +477,52 @@ function CpCourseGeneratorFrame:onClickDeselectAll() self.filterList:reloadData() end -function CpCourseGeneratorFrame:onJobTypeChanged() - +function CpCourseGeneratorFrame:onJobTypeChanged(index) + local jobTypeIndex = self.currentJobTypes[index] + self:setActiveJobTypeSelection(jobTypeIndex) end -function CpCourseGeneratorFrame:onClickMultiTextOptionParameter() - +function CpCourseGeneratorFrame:onClickMultiTextOptionParameter(index, element) + if self.currentJob ~= nil then + self.currentJob:onParameterValueChanged(element.aiParameter) + self:updateParameterValueTexts() + end + self:validateParameters() end function CpCourseGeneratorFrame:onClickPositionParameter(element) local parameter = element.aiParameter - + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + if parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.LOAD then + self.aiTargetMapHotspot = self.loadAiTargetMapHotspot + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then + self.aiTargetMapHotspot = self.fieldSiloAiTargetMapHotspot + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then + self.aiTargetMapHotspot = self.unloadAiTargetMapHotspot + else + self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot + end + self.contextBox:setVisible(false) g_inGameMenu.pageMapOverview.startPickPosition(self, parameter, function (success, x, z) if success then element:setText(parameter:getString()) end + self:updateParameterValueTexts() end) end function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) + self.contextBox:setVisible(false) local parameter = element.aiParameter g_inGameMenu.pageMapOverview.startPickPositionAndRotation(self, parameter, function (success, x, z, angle) if success then element:setText(parameter:getString()) end + self:updateParameterValueTexts() end) end @@ -472,19 +531,96 @@ function CpCourseGeneratorFrame:getIsPicking() end function CpCourseGeneratorFrame:validateParameters() - + local isValid = true + local errorText = "" + if self.currentJob ~= nil then + self.currentJob:setValues() + errorText = self.currentJob:validate() + self:updateWarnings() + end + self.errorMessage:setText(errorText) + self.errorMessage:setVisible(not isValid) +end + +function CpCourseGeneratorFrame:updateWarnings() + for _, element in ipairs(self.currentJobElements) do + local parameter = element.aiParameter + local invalidElement = element:getDescendantByName("invalid") + if invalidElement ~= nil then + invalidElement:setVisible(not parameter:getIsValid() and not parameter:getIsDisabled()) + end + end end function CpCourseGeneratorFrame:updateParameterValueTexts() - + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + local addedPositionHotspot = false + for _, element in ipairs(self.currentJobElements) do + local parameter = element.aiParameter + local invalidElement = element:getDescendantByName("invalid") + if invalidElement ~= nil then + invalidElement:setVisible(not parameter:getIsValid() and not parameter:getIsDisabled()) + end + + local parameterType = parameter:getType() + if parameterType == AIParameterType.TEXT then + local title = element:getDescendantByName("title") + + title:setText(parameter:getString()) + elseif parameter.is_a and parameter:is_a(CpAIParameterPosition) then + element:setText(parameter:getString()) + if parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO then + if parameter:applyToMapHotspot(self.driveToAiTargetMapHotspot) then + g_currentMission:addMapHotspot(self.driveToAiTargetMapHotspot) + end + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then + if parameter:applyToMapHotspot(self.fieldSiloAiTargetMapHotspot) then + g_currentMission:addMapHotspot(self.fieldSiloAiTargetMapHotspot) + end + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then + if parameter:applyToMapHotspot(self.unloadAiTargetMapHotspot) then + g_currentMission:addMapHotspot(self.unloadAiTargetMapHotspot) + end + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.LOAD then + if parameter:applyToMapHotspot(self.loadAiTargetMapHotspot) then + g_currentMission:addMapHotspot(self.loadAiTargetMapHotspot) + end + end + elseif element.updateTitle then + element:updateTitle() + end + end + end -function CpCourseGeneratorFrame:showActionMessage() - +function CpCourseGeneratorFrame:showActionMessage(localKey) + if localKey ~= nil then + self.actionMessage:setVisible(true) + self.actionMessage:setLocaKey(localKey) + return + end + self.actionMessage:setVisible(false) +end + +function CpCourseGeneratorFrame:getCanCancelJob() + return self.mode == self.AI_MODE_OVERVIEW and self.canCancel and + not self:getIsPicking() and g_currentMission:getHasPlayerPermission("hireAssistant") +end + +function CpCourseGeneratorFrame:getCanStartJob() + return self.mode == self.AI_MODE_CREATE and not self:getIsPicking() and + g_currentMission:getHasPlayerPermission("hireAssistant") end function CpCourseGeneratorFrame:onStartCancelJob() - + if self:getCanCancelJob() then + self:cancelJob() + elseif self:getCanStartJob() then + self:startJob() + end end function CpCourseGeneratorFrame:getNumberOfSections(list) @@ -598,6 +734,32 @@ function CpCourseGeneratorFrame:onListSelectionChanged(list, section, index) end +function CpCourseGeneratorFrame:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) + if self.isPickingRotation then + local localX, localY = self.ingameMap:getLocalPosition(posX, posY) + local worldX, worldZ = self.ingameMap:localToWorldPos(localX, localY) + local angle = math.atan2(worldX - self.pickingRotationOrigin[1], worldZ - self.pickingRotationOrigin[2]) + angle = angle + math.pi + + if self.pickingRotationSnapAngle > 0 then + local numSteps = MathUtil.round(angle / self.pickingRotationSnapAngle, 0) + angle = numSteps * self.pickingRotationSnapAngle + end + + self.aiTargetMapHotspot:setWorldRotation(angle) + end + + self.lastMousePoxY = posY + self.lastMousePosX = posX + + if self.isPickingLocation then + local localX, localY = self.ingameMap:getLocalPosition(self.lastMousePosX, self.lastMousePoxY) + local worldX, worldZ = self.ingameMap:localToWorldPos(localX, localY) + self.aiTargetMapHotspot:setWorldPosition(worldX, worldZ) + end + return CpCourseGeneratorFrame:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) +end + function CpCourseGeneratorFrame:updateInputGlyphs() local moveActions, moveText = nil @@ -656,17 +818,32 @@ function CpCourseGeneratorFrame:generateJobTypes() end end +function CpCourseGeneratorFrame:addStatusMessage(message) + table.insert(self.statusMessages, { + removeTime = g_time + 5000, + text = message + }) + self:updateStatusMessages() +end + +function CpCourseGeneratorFrame:updateStatusMessages() + local text = "" + for _, message in ipairs(self.statusMessages) do + text = text .. message.text .. "\n" + end + self.statusMessage:setText(text) +end function CpCourseGeneratorFrame:initializeContextActions() self.contextActions = { [self.CONTEXT_ACTIONS.ENTER_VEHICLE] = { - text = "button_enterVehicle", + text = g_i18n:getText("button_enterVehicle"), callback = function() if self.currentHotspot then local vehicle = self.currentHotspot:getVehicle() if vehicle then if vehicle.getIsEnterableFromMenu ~= nil and vehicle:getIsEnterableFromMenu() then - self:onClickBackCallback() + self.onClickBackCallback(nil, nil, true) g_localPlayer:requestToEnterVehicle(vehicle) end end @@ -675,70 +852,24 @@ function CpCourseGeneratorFrame:initializeContextActions() isActive = false }, [self.CONTEXT_ACTIONS.CREATE_JOB] = { - text = "button_createJob", - callback = function() - if self.currentHotspot then - local vehicle = self.currentHotspot:getVehicle() - if vehicle then - local currentJobTypesTexts = {} - for index, job in pairs(self.jobTypeInstances) do - if job:getIsAvailableForVehicle(vehicle, true) then - table.insert(self.currentJobTypes, index) - table.insert(currentJobTypesTexts, g_currentMission.aiJobTypeManager:getJobTypeByIndex(index).title) - end - end - self.jobTypeElement:setTexts(currentJobTypesTexts) - self.jobTypeElement:setState(1) - self.mode = self.AI_MODE_CREATE - self.currentJobVehicle = vehicle - self.currentJob = nil - self:setJobMenuVisible(true) - FocusManager:setFocus(self.jobTypeElement) - self:setActiveJobTypeSelection(self.currentJobTypes[1]) - end - end - end, + text = g_i18n:getText("button_createJob"), + callback = self.onCreateJob, isActive = false }, [self.CONTEXT_ACTIONS.START_JOB] = { - text = "button_startJob", - callback = function() - if self.currentHotspot then - local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) - if vehicle then - if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then - self:onClickBackCallback() - g_localPlayer:requestToEnterVehicle(vehicle) - end - end - end - end, + text = g_i18n:getText("button_startJob"), + callback = self.onStartCancelJob, isActive = false }, [self.CONTEXT_ACTIONS.STOP_JOB] = { - text = "button_cancelJob", - callback = function() - if self.currentHotspot then - local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) - if vehicle and vehicle:getIsAIActive() then - vehicle:stopCurrentAIJob() - end - end - end, + text = g_i18n:getText("button_cancelJob"), + callback = self.onStartCancelJob, isActive = false }, [self.CONTEXT_ACTIONS.GENERATE_COURSE] = { - text = "CP_ai_page_generate_course", + text = g_i18n:getText("CP_ai_page_generate_course"), callback = function() - if self.currentHotspot then - local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) - if vehicle then - if vehicle.getIsEnterableFromMenu ~= nil and not vehicle:getIsEnterableFromMenu() then - self:onClickBackCallback() - g_localPlayer:requestToEnterVehicle(vehicle) - end - end - end + end, isActive = false } @@ -748,21 +879,20 @@ end function CpCourseGeneratorFrame:updateContextActions() local vehicle = self.currentHotspot and self.currentHotspot:getVehicle() self.contextActions[self.CONTEXT_ACTIONS.ENTER_VEHICLE].isActive = vehicle and vehicle:getIsEnterableFromMenu() - local canCreateJob = false - if not canCreateJob and not self.currentJobVehicle then + self.canCreateJob = false + if not self.canCreateJob and not self.currentJobVehicle then for _, job in pairs(self.jobTypeInstances) do if job:getIsAvailableForVehicle(vehicle, true) then - canCreateJob = true + self.canCreateJob = true end end end - self.contextActions[self.CONTEXT_ACTIONS.CREATE_JOB].isActive = canCreateJob - self.contextActions[self.CONTEXT_ACTIONS.START_JOB].isActive = false - self.contextActions[self.CONTEXT_ACTIONS.STOP_JOB].isActive = false + self.canCancel = vehicle and vehicle.spec_aiJobVehicle and vehicle:getIsAIActive() + self.contextActions[self.CONTEXT_ACTIONS.CREATE_JOB].isActive = self.canCreateJob + self.contextActions[self.CONTEXT_ACTIONS.START_JOB].isActive = self:getCanStartJob() + self.contextActions[self.CONTEXT_ACTIONS.STOP_JOB].isActive = self:getCanCancelJob() self.contextActions[self.CONTEXT_ACTIONS.GENERATE_COURSE].isActive = false - - - self.contextButtonList:reloadData() + self.contextButtonList:reloadData() end function CpCourseGeneratorFrame:toggleMapInput(isActive) @@ -780,6 +910,10 @@ function CpCourseGeneratorFrame:toggleMapInput(isActive) end function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) + if self.mode == self.AI_MODE_CREATE then + return + end + if hotspot ~= nil then local x, _ = hotspot:getWorldPosition() @@ -840,10 +974,33 @@ function CpCourseGeneratorFrame:setAIVehicle(vehicle) local hotspot = vehicle:getMapHotspot() self:setMapSelectionItem(hotspot) self.ingameMap:panToHotspot(hotspot) - -- self:onCreateJob() + self:onCreateJob() self.createJobEmptyText:setVisible(false) end +function CpCourseGeneratorFrame:onCreateJob() + if self.currentHotspot then + local vehicle = self.currentHotspot:getVehicle() + if vehicle then + local currentJobTypesTexts = {} + for index, job in pairs(self.jobTypeInstances) do + if job:getIsAvailableForVehicle(vehicle, true) then + table.insert(self.currentJobTypes, index) + table.insert(currentJobTypesTexts, g_currentMission.aiJobTypeManager:getJobTypeByIndex(index).title) + end + end + self.jobTypeElement:setTexts(currentJobTypesTexts) + self.jobTypeElement:setState(1) + self.mode = self.AI_MODE_CREATE + self.currentJobVehicle = vehicle + self.currentJob = nil + self:setJobMenuVisible(true) + FocusManager:setFocus(self.jobTypeElement) + self:setActiveJobTypeSelection(self.currentJobTypes[1]) + end + end +end + function CpCourseGeneratorFrame:resetUIDeadzones() self.ingameMap:clearCursorDeadzones() self.ingameMap:addCursorDeadzone(self.rightBackground.absPosition[1], self.rightBackground.absPosition[2], self.rightBackground.size[1], self.rightBackground.size[2]) @@ -862,6 +1019,8 @@ function CpCourseGeneratorFrame:setJobMenuVisible(isVisible) self.jobTypeElement:setVisible(isVisible) self.jobMenuLayout:setVisible(isVisible) if not isVisible then + self.currentJob = nil + self.currentJobVehicle = nil self:setMapSelectionItem(self.currentHotspot) FocusManager:setFocus(self.mapOverviewSelector) self.mapOverviewSelector:setState(self.MAP_SELECTOR_ACTIVE_JOBS, true) From 4f8febb3107732f1abac7e94e3b363170eea2dee Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 4 Dec 2024 18:59:53 +0100 Subject: [PATCH 060/158] Some more fix --- config/gui/pages/CourseGeneratorFrame.xml | 5 ++-- scripts/gui/CpInGameMenu.lua | 1 - .../gui/elements/CpOptionToggleElement.lua | 23 ++++++++--------- scripts/gui/pages/CpCourseGeneratorFrame.lua | 25 ++++++++++++++++--- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index fd379b471..5dc227b63 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -68,7 +68,7 @@ onClick="onJobTypeChanged" /> <BoxLayout profile="fs25_aiCreateJobLayout" id="jobMenuLayout"> <CpOptionToggle profile="fs25_aiCreateJobMultiTextOption" - id="createMultiOptionTemplate" onClick="onClickMultiTextOptionParameter"> + id="createMultiOptionTemplate" onClick="onClickMultiTextOptionParameter" onClickCenter="onClickMultiTextOptionCenterParameter"> <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </CpOptionToggle> <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" id="createTextTemplate"> @@ -76,7 +76,7 @@ <Text profile="fs25_multiTextOptionText" name="title" /> <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </ThreePartBitmap> - <Button profile="fs25_aiCreateJobParameterBg" id="createButtonTemplate"> + <Button profile="fs25_aiCreateJobParameterBg" id="createButtonTemplate" onClick="onClickMultiTextOptionCenterParameter"> <Bitmap profile="fs25_aiCreateJobParameterVehicleIcon" /> <Text profile="fs25_multiTextOptionText" name="title" /> <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> @@ -97,6 +97,7 @@ <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </Button> </BoxLayout> + <GuiElement profile="fs25_mapButtonContainer" id="buttonStartJobContainer" width="340px" position="-10px 0px"> <ThreePartBitmap profile="fs25_mapButtonBgLight" /> diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index b6bd14ef3..d2f74a58e 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -89,7 +89,6 @@ function CpInGameMenu:initializePages() self.pageCourseGenerator:setInGameMap( g_inGameMenu.baseIngameMap, - g_currentMission.terrainSize, g_currentMission.hud) self.pageGlobalSettings:initialize(self) diff --git a/scripts/gui/elements/CpOptionToggleElement.lua b/scripts/gui/elements/CpOptionToggleElement.lua index 32b2bad71..ea34dc548 100644 --- a/scripts/gui/elements/CpOptionToggleElement.lua +++ b/scripts/gui/elements/CpOptionToggleElement.lua @@ -19,7 +19,6 @@ function CpOptionToggleElement:copyAttributes(src) self.onClickCenterCallback = src.onClickCenterCallback end - function CpOptionToggleElement:onCenterButtonClicked() self:raiseCallback("onClickCenterCallback", self) if self.dataSource ~= nil then @@ -61,17 +60,17 @@ function CpOptionToggleElement:setLabelElement(element) self:updateTitle() end -function CpOptionToggleElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) - if self.parent then - -- Fixes giants bug, where the scrolling layout is not disabling the mouse event for invisible child elements. - local _, clipY1 , _, clipY2 = self.parent:getClipArea(0,0,1,1) - if (clipY1 - self.absPosition[2] * 0.02) > (self.absPosition[2]) or - (clipY2 + self.absPosition[2] * 0.02) < ( self.absPosition[2] + self.absSize[2]) then - return eventUsed - end - end - return CpOptionToggleElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) -end +-- function CpOptionToggleElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) +-- if self.parent then +-- -- Fixes giants bug, where the scrolling layout is not disabling the mouse event for invisible child elements. +-- local _, clipY1 , _, clipY2 = self.parent:getClipArea(0,0,1,1) +-- if (clipY1 - self.absPosition[2] * 0.02) > (self.absPosition[2]) or +-- (clipY2 + self.absPosition[2] * 0.02) < ( self.absPosition[2] + self.absSize[2]) then +-- return eventUsed +-- end +-- end +-- return CpOptionToggleElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) +-- end function CpOptionToggleElement:inputEvent(action, value, eventUsed) if self:getIsActive() then diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 9c655daaf..823482e7b 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -80,7 +80,7 @@ function CpCourseGeneratorFrame.new(target, custom_mt) self.isInputContextActive = false self.driveToAiTargetMapHotspot = AITargetHotspot.new() self.fieldSiloAiTargetMapHotspot = AITargetHotspot.new() - -- self.fieldSiloAiTargetMapHotspot.icon:setUVs(POSITION_UVS) --- TODO_25 + self.fieldSiloAiTargetMapHotspot.icon:setUVs(GuiUtils.getUVs({760, 111, 100, 100}, AIPlaceableMarkerHotspot.FILE_RESOLUTION)) --- TODO_25 self.unloadAiTargetMapHotspot = AITargetHotspot.new() self.loadAiTargetMapHotspot = AITargetHotspot.new() @@ -110,10 +110,9 @@ function CpCourseGeneratorFrame.createFromExistingGui(gui, guiName) return newGui end -function CpCourseGeneratorFrame:setInGameMap(ingameMap, terrainSize, hud) +function CpCourseGeneratorFrame:setInGameMap(ingameMap, hud) self.ingameMapBase = ingameMap self.ingameMap:setIngameMap(ingameMap) - self.ingameMap:setTerrainSize(terrainSize) self.hud = hud end @@ -180,6 +179,7 @@ function CpCourseGeneratorFrame:initialize(menu) self.currentContextBox = self.contextBox self.currentHotspot = nil + self.ingameMap:setTerrainSize(g_currentMission.terrainSize) end function CpCourseGeneratorFrame:update(dt) @@ -403,6 +403,13 @@ function CpCourseGeneratorFrame:onDrawPostIngameMapHotspots() if self.currentContextBox ~= nil then InGameMenuMapUtil.updateContextBoxPosition(self.currentContextBox, self.currentHotspot) end + if self.aiTargetMapHotspot ~= nil then + + local icon = self.aiTargetMapHotspot.icon + + --Lx: [952, 952], [19, 35], 1 + self.actionMessage:setAbsolutePosition(icon.x + icon.width * 0.5, icon.y + icon.height * 0.5) + end end function CpCourseGeneratorFrame:onDrawPostIngameMap() @@ -490,6 +497,10 @@ function CpCourseGeneratorFrame:onClickMultiTextOptionParameter(index, element) self:validateParameters() end +function CpCourseGeneratorFrame:onClickMultiTextOptionCenterParameter() + +end + function CpCourseGeneratorFrame:onClickPositionParameter(element) local parameter = element.aiParameter g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) @@ -507,6 +518,7 @@ function CpCourseGeneratorFrame:onClickPositionParameter(element) end self.contextBox:setVisible(false) g_inGameMenu.pageMapOverview.startPickPosition(self, parameter, function (success, x, z) + print(success, x, z) if success then element:setText(parameter:getString()) end @@ -517,7 +529,10 @@ end function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) self.contextBox:setVisible(false) local parameter = element.aiParameter - + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) g_inGameMenu.pageMapOverview.startPickPositionAndRotation(self, parameter, function (success, x, z, angle) if success then element:setText(parameter:getString()) @@ -756,6 +771,7 @@ function CpCourseGeneratorFrame:mouseEvent(posX, posY, isDown, isUp, button, eve local localX, localY = self.ingameMap:getLocalPosition(self.lastMousePosX, self.lastMousePoxY) local worldX, worldZ = self.ingameMap:localToWorldPos(localX, localY) self.aiTargetMapHotspot:setWorldPosition(worldX, worldZ) + CpUtil.info("%s, %s, %s, %s, %s, %s", self.lastMousePosX, self.lastMousePoxY, localX, localY, worldX, worldZ) end return CpCourseGeneratorFrame:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) end @@ -982,6 +998,7 @@ function CpCourseGeneratorFrame:onCreateJob() if self.currentHotspot then local vehicle = self.currentHotspot:getVehicle() if vehicle then + self.currentJobTypes = {} local currentJobTypesTexts = {} for index, job in pairs(self.jobTypeInstances) do if job:getIsAvailableForVehicle(vehicle, true) then From 07881feaab88b67db82a9cdf60a47b6104ab9d19 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 4 Dec 2024 22:12:44 +0100 Subject: [PATCH 061/158] Some more fixes .. --- config/gui/pages/CourseGeneratorFrame.xml | 16 ++- scripts/gui/pages/CpCourseGeneratorFrame.lua | 117 ++++++++++++++++--- 2 files changed, 116 insertions(+), 17 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 5dc227b63..51b6284b3 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -97,15 +97,22 @@ <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </Button> </BoxLayout> - - <GuiElement profile="fs25_mapButtonContainer" id="buttonStartJobContainer" width="340px" + <SmoothList profile="cpMapContextButtonList" id="createJobButtonList" + onClick="onClickList" visible="true"> + <ListItem profile="fs25_mapContextButtonListItem"> + <ThreePartBitmap profile="fs25_mapContextButtonListItemBg" /> + <Text profile="fs25_mapButtonText" name="text" /> + <Button profile="fs25_mapContextButtonListItemButton" /> + </ListItem> + </SmoothList> + <!-- <GuiElement profile="fs25_mapButtonContainer" id="buttonStartJobContainer" width="340px" position="-10px 0px"> <ThreePartBitmap profile="fs25_mapButtonBgLight" /> <Text profile="fs25_mapButtonText" id="buttonStartJobText" text="$l10n_button_startJob" textOffset="15px 0px" /> <Button profile="fs25_mapButtonAction1" id="buttonStartJob" onClick="onStartCancelJob" /> - </GuiElement> + </GuiElement> --> <Text profile="fs25_aiContainerEmptyText" id="createJobEmptyText" text="$l10n_ui_createJobEmpty"/> </GuiElement> @@ -629,6 +636,9 @@ <Profile name="cpSubCategorySelectorTabbedContainer" extends="fs25_subCategorySelectorTabbedContainer"> <!-- <absoluteSizeOffset value="56px 0px" /> --> </Profile> + <Profile name="cpMapContextButtonList" extends="fs25_mapContextButtonList" with="anchorBottomStretchingX"> + <!-- <height value="25px" /> --> + </Profile> </GUIProfiles> </GUI> diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 823482e7b..8afec82fb 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -80,7 +80,7 @@ function CpCourseGeneratorFrame.new(target, custom_mt) self.isInputContextActive = false self.driveToAiTargetMapHotspot = AITargetHotspot.new() self.fieldSiloAiTargetMapHotspot = AITargetHotspot.new() - self.fieldSiloAiTargetMapHotspot.icon:setUVs(GuiUtils.getUVs({760, 111, 100, 100}, AIPlaceableMarkerHotspot.FILE_RESOLUTION)) --- TODO_25 + self.fieldSiloAiTargetMapHotspot.icon:setUVs(GuiUtils.getUVs({760, 0, 100, 100}, AIPlaceableMarkerHotspot.FILE_RESOLUTION)) self.unloadAiTargetMapHotspot = AITargetHotspot.new() self.loadAiTargetMapHotspot = AITargetHotspot.new() @@ -179,7 +179,6 @@ function CpCourseGeneratorFrame:initialize(menu) self.currentContextBox = self.contextBox self.currentHotspot = nil - self.ingameMap:setTerrainSize(g_currentMission.terrainSize) end function CpCourseGeneratorFrame:update(dt) @@ -214,6 +213,7 @@ function CpCourseGeneratorFrame:onFrameOpen() if not vehicle then return end + self.ingameMap:setTerrainSize(g_currentMission.terrainSize) local settings = vehicle:getCourseGeneratorSettings() local settingsBySubTitle, pageTitle = CpCourseGeneratorSettings.getSettingSetup() local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) @@ -284,6 +284,7 @@ function CpCourseGeneratorFrame:onFrameOpen() self.currentHotspot = nil self:setMapSelectionItem(nil) self:setJobMenuVisible(false) + self.startJobPending = false end function CpCourseGeneratorFrame:saveHotspotFilter() @@ -330,6 +331,7 @@ function CpCourseGeneratorFrame:onFrameClose() self:setMapSelectionItem(nil) self.statusMessages = {} self:updateStatusMessages() + self.startJobPending = false end function CpCourseGeneratorFrame:requestClose() @@ -502,6 +504,7 @@ function CpCourseGeneratorFrame:onClickMultiTextOptionCenterParameter() end function CpCourseGeneratorFrame:onClickPositionParameter(element) + self.contextBox:setVisible(false) local parameter = element.aiParameter g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) @@ -516,9 +519,7 @@ function CpCourseGeneratorFrame:onClickPositionParameter(element) else self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot end - self.contextBox:setVisible(false) g_inGameMenu.pageMapOverview.startPickPosition(self, parameter, function (success, x, z) - print(success, x, z) if success then element:setText(parameter:getString()) end @@ -533,6 +534,15 @@ function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + if parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.LOAD then + self.aiTargetMapHotspot = self.loadAiTargetMapHotspot + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then + self.aiTargetMapHotspot = self.fieldSiloAiTargetMapHotspot + elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then + self.aiTargetMapHotspot = self.unloadAiTargetMapHotspot + else + self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot + end g_inGameMenu.pageMapOverview.startPickPositionAndRotation(self, parameter, function (success, x, z, angle) if success then element:setText(parameter:getString()) @@ -552,6 +562,8 @@ function CpCourseGeneratorFrame:validateParameters() self.currentJob:setValues() errorText = self.currentJob:validate() self:updateWarnings() + self:updateContextActions() + self:updateParameterValueTexts() end self.errorMessage:setText(errorText) self.errorMessage:setVisible(not isValid) @@ -608,7 +620,6 @@ function CpCourseGeneratorFrame:updateParameterValueTexts() element:updateTitle() end end - end function CpCourseGeneratorFrame:showActionMessage(localKey) @@ -630,6 +641,10 @@ function CpCourseGeneratorFrame:getCanStartJob() g_currentMission:getHasPlayerPermission("hireAssistant") end +function CpCourseGeneratorFrame:getCanGenerateFieldWorkCourse() + return self.mode == self.AI_MODE_CREATE and self.currentJob and + self.currentJob:getCanGenerateFieldWorkCourse() +end function CpCourseGeneratorFrame:onStartCancelJob() if self:getCanCancelJob() then self:cancelJob() @@ -638,6 +653,66 @@ function CpCourseGeneratorFrame:onStartCancelJob() end end +function CpCourseGeneratorFrame:startJob() + if self.startJobPending then + return + end + self.currentJob:setValues() + local success, errorMessage = self.currentJob:validate(g_localPlayer.farmId) + if success then + local function callback(state) + if state == AIJob.START_SUCCESS then + self.mode = self.AI_MODE_OVERVIEW + self:setJobMenuVisible(false) + end + end + self:tryStartJob(self.currentJob, + g_localPlayer.farmId, callback) + else + InfoDialog.show(tostring(errorMessage), nil, nil, DialogElement.TYPE_WARNING) + self:updateWarnings() + end +end + +function CpCourseGeneratorFrame:onStartedJob(args, state, jobTypeIndex) + local callback = args[1] + self.startJobPending = false + g_messageCenter:unsubscribe(AIJobStartRequestEvent, self) + if state == AIJob.START_SUCCESS then + self.mapOverviewSelector:setState(InGameMenuMapFrame.AI_WORKER_LIST, true) + else + local jobType = g_currentMission.aiJobTypeManager:getJobTypeByIndex(jobTypeIndex) + local text = jobType.classObject.getIsStartErrorText(state) + + InfoDialog.show(text, nil, nil, DialogElement.TYPE_INFO) + end + + callback(state) + +end + +function CpCourseGeneratorFrame:tryStartJob(job, farmId, callback) + self.startJobPending = true + + g_messageCenter:subscribe(AIJobStartRequestEvent, + self.onStartedJob, self, {callback}) + g_client:getServerConnection():sendEvent( + AIJobStartRequestEvent.new(job, farmId)) +end + +function CpCourseGeneratorFrame:cancelJob() + local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) + if vehicle then + if vehicle:getIsAIActive() then + vehicle:stopCurrentAIJob() + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) + end + end +end + function CpCourseGeneratorFrame:getNumberOfSections(list) if list == self.filterList then return 2 @@ -659,15 +734,15 @@ function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) return #self.hotspotFilterCategories[section] end if list == self.contextButtonList then - if self.mode == self.AI_MODE_CREATE then - return 0 - end self.contextActionMapping = {} for index, action in pairs(self.contextActions) do if action.isActive then table.insert(self.contextActionMapping, index) end end + if self.mode == self.AI_MODE_CREATE then + return 0 + end return #self.contextActionMapping end if list == self.activeWorkerList then @@ -683,6 +758,11 @@ function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) end self.activeWorkerListEmpty:setVisible(count == 0) return count + elseif list == self.createJobButtonList then + if self.mode ~= self.AI_MODE_CREATE then + return 0 + end + return #self.contextActionMapping end return 0 end @@ -722,6 +802,10 @@ function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, inde cell:getAttribute("title"):setText(currentJob:getTitle()) cell:getAttribute("helper"):setText(currentJob:getHelperName()) end + elseif list == self.createJobButtonList then + local buttonInfo = self.contextActions[self.contextActionMapping[index]] + cell:getAttribute("text"):setText(buttonInfo.text) + cell.onClickCallback = buttonInfo.callback end end @@ -742,6 +826,8 @@ function CpCourseGeneratorFrame:onClickList(list, section, index, listElement) end elseif list == self.contextButtonList then listElement.onClickCallback(self) + elseif list == self.createJobButtonList then + listElement.onClickCallback(self) end end @@ -771,7 +857,6 @@ function CpCourseGeneratorFrame:mouseEvent(posX, posY, isDown, isUp, button, eve local localX, localY = self.ingameMap:getLocalPosition(self.lastMousePosX, self.lastMousePoxY) local worldX, worldZ = self.ingameMap:localToWorldPos(localX, localY) self.aiTargetMapHotspot:setWorldPosition(worldX, worldZ) - CpUtil.info("%s, %s, %s, %s, %s, %s", self.lastMousePosX, self.lastMousePoxY, localX, localY, worldX, worldZ) end return CpCourseGeneratorFrame:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) end @@ -885,7 +970,9 @@ function CpCourseGeneratorFrame:initializeContextActions() [self.CONTEXT_ACTIONS.GENERATE_COURSE] = { text = g_i18n:getText("CP_ai_page_generate_course"), callback = function() - + if self.currentJob then + CpUtil.try(self.currentJob.onClickGenerateFieldWorkCourse, self.currentJob) + end end, isActive = false } @@ -894,7 +981,7 @@ end function CpCourseGeneratorFrame:updateContextActions() local vehicle = self.currentHotspot and self.currentHotspot:getVehicle() - self.contextActions[self.CONTEXT_ACTIONS.ENTER_VEHICLE].isActive = vehicle and vehicle:getIsEnterableFromMenu() + self.contextActions[self.CONTEXT_ACTIONS.ENTER_VEHICLE].isActive = vehicle and vehicle:getIsEnterableFromMenu() and self.mode ~= self.AI_MODE_CREATE self.canCreateJob = false if not self.canCreateJob and not self.currentJobVehicle then for _, job in pairs(self.jobTypeInstances) do @@ -904,11 +991,12 @@ function CpCourseGeneratorFrame:updateContextActions() end end self.canCancel = vehicle and vehicle.spec_aiJobVehicle and vehicle:getIsAIActive() - self.contextActions[self.CONTEXT_ACTIONS.CREATE_JOB].isActive = self.canCreateJob + self.contextActions[self.CONTEXT_ACTIONS.CREATE_JOB].isActive = self.canCreateJob and self.mode ~= self.AI_MODE_CREATE self.contextActions[self.CONTEXT_ACTIONS.START_JOB].isActive = self:getCanStartJob() - self.contextActions[self.CONTEXT_ACTIONS.STOP_JOB].isActive = self:getCanCancelJob() - self.contextActions[self.CONTEXT_ACTIONS.GENERATE_COURSE].isActive = false + 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() self.contextButtonList:reloadData() + self.createJobButtonList:reloadData() end function CpCourseGeneratorFrame:toggleMapInput(isActive) @@ -1016,6 +1104,7 @@ function CpCourseGeneratorFrame:onCreateJob() self:setActiveJobTypeSelection(self.currentJobTypes[1]) end end + self:updateContextActions() end function CpCourseGeneratorFrame:resetUIDeadzones() From 091278b9b50c98d64143ef77ee19da690c67f23e Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Thu, 5 Dec 2024 21:25:59 +0100 Subject: [PATCH 062/158] Multiple stupid bug fixes ... --- config/gui/pages/CourseGeneratorFrame.xml | 44 +--- config/gui/pages/CourseManagerFrame.xml | 24 +- .../gui/elements/CpBinaryOptionElement.lua | 48 ++-- .../gui/elements/CpOptionToggleElement.lua | 22 +- scripts/gui/pages/CpCourseGeneratorFrame.lua | 238 ++++++++++++------ 5 files changed, 215 insertions(+), 161 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 51b6284b3..80c7da2a9 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8" standalone="no" ?> <GUI name="cpInGameMenuCourseGenerator"> - <InGameMap id="ingameMap" profile="cpIngameMapOverview" cursorId="mapCursor" + <InGameMap id="ingameMap" profile="ingameMapOverview" cursorId="mapCursor" onClickMap="onClickMap" onDrawPostIngameMap="onDrawPostIngameMap" onDrawPostIngameMapHotspots="onDrawPostIngameMapHotspots" onClickHotspot="onClickHotspot" /> <Bitmap profile="ingameMapCursor" id="mapCursor" visible="false"/> @@ -67,10 +67,10 @@ <MultiTextOption profile="fs25_aiJobTypeMultiTextOption" id="jobTypeElement" onClick="onJobTypeChanged" /> <BoxLayout profile="fs25_aiCreateJobLayout" id="jobMenuLayout"> - <CpOptionToggle profile="fs25_aiCreateJobMultiTextOption" + <OptionToggle profile="fs25_aiCreateJobMultiTextOption" id="createMultiOptionTemplate" onClick="onClickMultiTextOptionParameter" onClickCenter="onClickMultiTextOptionCenterParameter"> <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> - </CpOptionToggle> + </OptionToggle> <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" id="createTextTemplate"> <Bitmap profile="fs25_aiCreateJobParameterVehicleIcon" /> <Text profile="fs25_multiTextOptionText" name="title" /> @@ -97,7 +97,7 @@ <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </Button> </BoxLayout> - <SmoothList profile="cpMapContextButtonList" id="createJobButtonList" + <SmoothList profile="fs25_mapContextButtonList" id="createJobButtonList" onClick="onClickList" visible="true"> <ListItem profile="fs25_mapContextButtonListItem"> <ThreePartBitmap profile="fs25_mapContextButtonListItemBg" /> @@ -202,7 +202,6 @@ </GuiElement> <!-- Prefabs --> - <Button profile="fs25_subCategorySelectorTabbedTab" id="selectorPrefab"> <ThreePartBitmap profile="fs25_subCategorySelectorTabbedTabBg" name="background" /> </Button> @@ -219,28 +218,17 @@ <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> - <!-- <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> --> + <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> <Text profile="fs25_settingsMultiOptionTitle" name="label"/> </CpBinaryyOption> </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> - <!-- <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> --> + <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> <Text profile="fs25_settingsMultiOptionTitle" name="label"/> </CpOptionToggle> </Bitmap> - - <!-- <GuiElement profile="fs25_aiSettingsMapContainer" id="mapBox"> - <InGameMapPreview profile="fs25_aiSettingsIngameMap" id="ingameMapElement" - onDrawPostIngameMapHotspots="onDrawPostIngameMapHotspots" /> - <Text profile="fs25_aiSettingsNoFieldFound" id="noFieldFoundText" - text="$l10n_ui_aiSettingsFieldNotFound" /> - <Animation profile="fs25_aiSettingsLoadingAnimation" id="loadingAnimation" - visible="false" /> - <Bitmap profile="fs25_aiSettingsWorkDirection" id="workDirectionArrow" /> - </GuiElement> --> - </GuiElement> <ThreePartBitmap profile="fs25_sliderDockedBg" id="settingsSliderBox"> <ThreePartBitmap profile="fs25_sliderDockedBox"> @@ -604,19 +592,6 @@ <text2Offset value="-1px -1px" /> <text2Color value="$preset_colorBlack" /> </Profile> - - - <Profile name="cpIngameMapOverview" extends="ingameMapOverview" with="anchorStretchingYStretchingX pivotBottomCenter"> - <absoluteSizeOffset value="0px 80px" /> - <size></size> - <!-- <absoluteSizeOffset value="536px 40px" /> - <mapAlpha value="0.9" /> - <limitMapWidth value="true" /> - <Variant name="mobile"> - <limitMapWidth value="true" /> - <mapAlpha value="1" /> - </Variant> --> - </Profile> <Profile name="cpLeftSideBackground" extends="fs25_fullScreenBackground" with="pivotMiddleLeft"> <height value="100%"/> <width value="250px" /> @@ -633,12 +608,5 @@ <width value="100%"/> <height value="80px" /> </Profile> - <Profile name="cpSubCategorySelectorTabbedContainer" extends="fs25_subCategorySelectorTabbedContainer"> - <!-- <absoluteSizeOffset value="56px 0px" /> --> - </Profile> - <Profile name="cpMapContextButtonList" extends="fs25_mapContextButtonList" with="anchorBottomStretchingX"> - <!-- <height value="25px" /> --> - </Profile> - </GUIProfiles> </GUI> diff --git a/config/gui/pages/CourseManagerFrame.xml b/config/gui/pages/CourseManagerFrame.xml index 9c4615aaf..fe21fe33c 100644 --- a/config/gui/pages/CourseManagerFrame.xml +++ b/config/gui/pages/CourseManagerFrame.xml @@ -42,14 +42,14 @@ <Text profile="fs25_statisticsHeaderText" size="340px 104px" id="leftColumnHeader" /> </BoxLayout> --> - <SmoothList profile="fs25_pricesProductList" id="leftList" focusChangeTop="nil" focusChangeBottom="nil" selectedWithoutFocus="true"> + <SmoothList profile="cpLeftList" id="leftList" focusChangeTop="nil" focusChangeBottom="nil" selectedWithoutFocus="true"> <ListItem profile="fs25_pricesProductListItem"> <Bitmap name="icon" profile="fs25_pricesProductListItemIcon" /> <Text name="title" profile="fs25_pricesProductListItemName" /> </ListItem> </SmoothList> - <ThreePartBitmap profile="fs25_pricesProductListSliderBox"> + <ThreePartBitmap profile="cpLeftListSliderBox"> <Slider profile="fs25_listSlider" dataElementId="leftList" id="leftListSlider" /> </ThreePartBitmap> </GuiElement> @@ -59,14 +59,14 @@ <Text profile="fs25_statisticsHeaderText" size="330px 104px" id="rightColumnHeader" /> </BoxLayout> --> - <SmoothList profile="fs25_pricesPriceList" id="rightList" focusChangeTop="nil" focusChangeBottom="nil"> + <SmoothList profile="cpRightList" id="rightList" focusChangeTop="nil" focusChangeBottom="nil"> <ListItem profile="fs25_pricesPriceListItem"> <Bitmap name="icon" profile="fs25_pricesPriceListItemHotspot" /> <Text name="title" profile="fs25_pricesPriceListItemName" /> </ListItem> </SmoothList> - <ThreePartBitmap profile="fs25_pricesPriceSliderBox"> + <ThreePartBitmap profile="cpRightListSliderBox"> <Slider profile="fs25_listSlider" dataElementId="rightList" id="rightListSlider" /> </ThreePartBitmap> </GuiElement> @@ -93,9 +93,9 @@ <imageColor value="$preset_colorWhite_50" /> <imageSliceId value="gui.prices_buying" /> </Profile> - <Profile name="fs25_pricesProductList" extends="emptyPanel" + <Profile name="cpLeftList" extends="emptyPanel" with="anchorStretchingYLeft pivotTopLeft"> - <width value="396px" /> + <width value="695px" /> <absoluteSizeOffset value="0px 56px" /> <position value="25px -56px" /> </Profile> @@ -122,13 +122,13 @@ <textOffset value="-10px 0px" /> <textAlignment value="right" /> </Profile> - <Profile name="fs25_pricesProductListSliderBox" extends="fs25_listSliderBox" + <Profile name="cpLeftListSliderBox" extends="fs25_listSliderBox" with="anchorTopLeft"> <absoluteSizeOffset value="0px 56px" /> - <position value="434px -56px" /> + <position value="733px -56px" /> </Profile> - <Profile name="fs25_pricesPriceList" extends="emptyPanel" with="anchorTopRight"> - <size value="1025px 240px" /> + <Profile name="cpRightList" extends="emptyPanel" with="anchorStretchingYLeft pivotTopRight"> + <width value="695px" /> <absoluteSizeOffset value="0px 56px" /> <position value="-63px -56px" /> <selectedWithoutFocus value="false" /> @@ -172,8 +172,8 @@ <format value="currency" /> <formatDecimalPlaces value="0" /> </Profile> - <Profile name="fs25_pricesPriceSliderBox" extends="fs25_listSliderBox" with="anchorTopRight"> - <height value="240px" /> + <Profile name="cpRightListSliderBox" extends="fs25_listSliderBox" with="anchorTopRight"> + <absoluteSizeOffset value="0px 56px" /> <position value="-41px -56px" /> </Profile> <Profile name="fs25_pricesNoSellpointsText" extends="textDefault" with="anchorTopCenter"> diff --git a/scripts/gui/elements/CpBinaryOptionElement.lua b/scripts/gui/elements/CpBinaryOptionElement.lua index 9275d9e43..d68f84cc6 100644 --- a/scripts/gui/elements/CpBinaryOptionElement.lua +++ b/scripts/gui/elements/CpBinaryOptionElement.lua @@ -8,21 +8,23 @@ function CpBinaryOptionElement.new(target, custom_mt) return self end --- Lines 19-23 function CpBinaryOptionElement:delete() self.dataSource = nil CpBinaryOptionElement:superClass().delete(self) end --- Lines 25-28 function CpBinaryOptionElement:setDataSource(dataSource) self.dataSource = dataSource - - self:updateTitle() + self.useYesNoTexts = false + self:setTexts({self.dataSource.texts[1], self.dataSource.texts[2]}) + if self.dataSource:getValue() then + self:setState(BinaryOptionElement.STATE_RIGHT, true) + else + self:setState(BinaryOptionElement.STATE_LEFT, true) + end end - function CpBinaryOptionElement:addElement(element, ...) CpBinaryOptionElement:superClass().addElement(self, element, ...) -- if self.textElement then @@ -36,41 +38,27 @@ function CpBinaryOptionElement:addElement(element, ...) end end --- Lines 30-33 function CpBinaryOptionElement:updateTitle() - if self.dataSource:getValue() then - self:setState(2) - else - self:setState(1) - end if self.labelElement then self.labelElement:setText(self.dataSource:getTitle()) end if self.toolTipElement then self.toolTipElement:setText(self.dataSource:getTooltip()) end - -- self:setState(1) end --- Lines 35-42 -function CpBinaryOptionElement:onRightButtonClicked(steps, noFocus) - if self.dataSource ~= nil then - self.dataSource:setNextItem() - - -- self.texts[1] = self.dataSource:getString() +function CpBinaryOptionElement:setState(state, ...) + if state == BinaryOptionElement.STATE_RIGHT then + self.dataSource:setValue(true) + else + self.dataSource:setValue(false) end - - CpBinaryOptionElement:superClass().onRightButtonClicked(self, steps, noFocus) -end - --- Lines 44-51 -function CpBinaryOptionElement:onLeftButtonClicked(steps, noFocus) - if self.dataSource ~= nil then - self.dataSource:setPreviousItem() - - -- self.texts[1] = self.dataSource:getString() + self:updateTitle() + if self.dataSource:getValue() then + CpBinaryOptionElement:superClass().setState(self, BinaryOptionElement.STATE_RIGHT, true) + else + CpBinaryOptionElement:superClass().setState(self, BinaryOptionElement.STATE_LEFT, true) end - - CpBinaryOptionElement:superClass().onLeftButtonClicked(self, steps, noFocus) end + Gui.registerGuiElement("CpBinaryyOption", CpBinaryOptionElement) diff --git a/scripts/gui/elements/CpOptionToggleElement.lua b/scripts/gui/elements/CpOptionToggleElement.lua index ea34dc548..4b0dc6b4f 100644 --- a/scripts/gui/elements/CpOptionToggleElement.lua +++ b/scripts/gui/elements/CpOptionToggleElement.lua @@ -60,17 +60,17 @@ function CpOptionToggleElement:setLabelElement(element) self:updateTitle() end --- function CpOptionToggleElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) --- if self.parent then --- -- Fixes giants bug, where the scrolling layout is not disabling the mouse event for invisible child elements. --- local _, clipY1 , _, clipY2 = self.parent:getClipArea(0,0,1,1) --- if (clipY1 - self.absPosition[2] * 0.02) > (self.absPosition[2]) or --- (clipY2 + self.absPosition[2] * 0.02) < ( self.absPosition[2] + self.absSize[2]) then --- return eventUsed --- end --- end --- return CpOptionToggleElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) --- end +function CpOptionToggleElement:mouseEvent(posX, posY, isDown, isUp, button, eventUsed) + if self.parent then + -- Fixes giants bug, where the scrolling layout is not disabling the mouse event for invisible child elements. + local _, clipY1 , _, clipY2 = self.parent:getClipArea(0,0,1,1) + if (clipY1 - self.absPosition[2] * 0.02) > (self.absPosition[2]) or + (clipY2 + self.absPosition[2] * 0.02) < ( self.absPosition[2] + self.absSize[2]) then + return eventUsed + end + end + return CpOptionToggleElement:superClass().mouseEvent(self, posX, posY, isDown, isUp, button, eventUsed) +end function CpOptionToggleElement:inputEvent(action, value, eventUsed) if self:getIsActive() then diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 8afec82fb..c2f3f3141 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -163,7 +163,6 @@ function CpCourseGeneratorFrame:initialize(menu) FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) end end - self:resetUIDeadzones() self.mapOverviewSelector:setTexts(self.mapSelectorTexts) self.mapOverviewSelector:setState(1, true) @@ -206,6 +205,7 @@ function CpCourseGeneratorFrame:update(dt) if hasChanged then self:updateStatusMessages() end + CpCourseGeneratorFrame:superClass().update(self, dt) end function CpCourseGeneratorFrame:onFrameOpen() @@ -227,10 +227,6 @@ function CpCourseGeneratorFrame:onFrameOpen() layout, self.multiTextPrefab, self.booleanPrefab, self.sectionHeaderPrefab, settings) CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) - - self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) - FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) - -- g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self.onAIVehicleStateChanged, self) self.activeWorkerList:reloadData() @@ -285,6 +281,12 @@ function CpCourseGeneratorFrame:onFrameOpen() self:setMapSelectionItem(nil) self:setJobMenuVisible(false) self.startJobPending = false + self.ingameMap:onOpen() + + self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) + FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) + + CpCourseGeneratorFrame:superClass().onFrameOpen(self) end function CpCourseGeneratorFrame:saveHotspotFilter() @@ -321,10 +323,10 @@ function CpCourseGeneratorFrame:onFrameClose() g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) - + self.ingameMap:onClose() self.ingameMapBase:restoreDefaultFilter() if not self:getIsPicking() then - g_inGameMenu.pageMapOverview.executePickingCallback(self, false) + self:executePickingCallback(false) end self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) @@ -332,12 +334,14 @@ function CpCourseGeneratorFrame:onFrameClose() self.statusMessages = {} self:updateStatusMessages() self.startJobPending = false + + CpCourseGeneratorFrame:superClass().onFrameClose(self) end function CpCourseGeneratorFrame:requestClose() if self.mode == self.AI_MODE_CREATE then if self:getIsPicking() then - g_inGameMenu.pageMapOverview.executePickingCallback(self, false) + self:executePickingCallback(false) self:updateContextActions() return false end @@ -359,10 +363,6 @@ function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) end end -function CpCourseGeneratorFrame:isMapVisible() - return self.ingameMap:getIsVisible() -end - function CpCourseGeneratorFrame:updateSubCategoryPages(state) for i, _ in ipairs(self.subCategoryPages) do self.subCategoryPages[i]:setVisible(false) @@ -372,11 +372,12 @@ function CpCourseGeneratorFrame:updateSubCategoryPages(state) self.subCategoryTabs[state]:setSelected(true) local layout = self.subCategoryPages[state]:getDescendantByName("layout") if layout then + self:closeMap() self.settingsSliderBox:setVisible(true) self.ingameMap:setVisible(false) layout:invalidateLayout() self.settingsSlider:setDataElement(layout) - self:closeMap() + FocusManager:setFocus(layout) else self.settingsSliderBox:setVisible(false) self.ingameMap:setVisible(true) @@ -414,18 +415,66 @@ function CpCourseGeneratorFrame:onDrawPostIngameMapHotspots() end end -function CpCourseGeneratorFrame:onDrawPostIngameMap() - +function CpCourseGeneratorFrame:onDrawPostIngameMap(element, ingameMap) + local areCursePlotsAlwaysVisible = g_Courseplay.globalSettings:getSettings().showsAllActiveCourses:getValue() + local vehicle = self.currentHotspot and self.currentHotspot:getVehicle() + if areCursePlotsAlwaysVisible then + local vehicles = g_assignedCoursesManager:getRegisteredVehicles() + for _, v in pairs(vehicles) do + if g_currentMission.accessHandler:canPlayerAccess(v) then + v:drawCpCoursePlot(ingameMap) + end + end + elseif vehicle and vehicle.drawCpCoursePlot then + vehicle:drawCpCoursePlot(ingameMap) + end + -- show the custom fields on the AI map + g_customFieldManager:draw(ingameMap) + -- show the selected field on the AI screen map when creating a job + if self.currentJob and self.currentJob.draw then + self.currentJob:draw(ingameMap, self.mode == self.MODE_OVERVIEW) + end + + --- Draws the current progress, while creating a custom field. + -- if pageAI.mode == CpInGameMenuAIFrameExtended.MODE_DRAW_FIELD_BORDER and next(CpInGameMenuAIFrameExtended.curDrawPositions) then + -- local localX, localY = pageAI.ingameMap:getLocalPosition(g_inputBinding.mousePosXLast, g_inputBinding.mousePosYLast) + -- local worldX, worldZ = pageAI.ingameMap:localToWorldPos(localX, localY) + + -- if #CpInGameMenuAIFrameExtended.curDrawPositions > 0 then + -- local pos = CpInGameMenuAIFrameExtended.curDrawPositions[#CpInGameMenuAIFrameExtended.curDrawPositions] + -- local dx, dz, length = CpMathUtil.getPointDirection( pos, {x = worldX, z = worldZ}) + -- if Input.isKeyPressed(Input.KEY_lshift) then + -- if math.abs(dx) > math.abs(dz) then + -- dz = 0 + -- dx = math.sign(dx) + -- else + -- dx = 0 + -- dz = math.sign(dz) + -- end + -- worldX = pos.x + dx * length + -- worldZ = pos.z + dz * length + -- end + -- pageAI.customFieldPlot:setNextTargetPoint(worldX, worldZ) + -- else + -- pageAI.customFieldPlot:setNextTargetPoint() + -- end + + + -- pageAI.customFieldPlot:setWaypoints(CpInGameMenuAIFrameExtended.curDrawPositions) + -- pageAI.customFieldPlot:draw(self,true) + -- pageAI.customFieldPlot:setVisible(true) + -- end + end function CpCourseGeneratorFrame:onClickMap(element ,worldX, worldZ) if self.isPickingLocation then - g_inGameMenu.pageMapOverview.executePickingCallback(self, true, worldX, worldZ) + self:executePickingCallback(true, worldX, worldZ) return end if self.isPickingRotation then local angle = math.atan2(worldX - self.pickingRotationOrigin[1], worldZ - self.pickingRotationOrigin[2]) - g_inGameMenu.pageMapOverview.executePickingCallback(self, true, angle) + self:executePickingCallback(true, angle) return end self:setMapSelectionItem(nil) @@ -433,12 +482,12 @@ end function CpCourseGeneratorFrame:onClickHotspot(element, hotspot) if self.isPickingLocation then - g_inGameMenu.pageMapOverview.executePickingCallback(self, true, hotspot.worldX, hotspot.worldZ) + self:executePickingCallback(true, hotspot.worldX, hotspot.worldZ) return end if self.isPickingRotation then - g_inGameMenu.pageMapOverview.executePickingCallback(self, true, worldX, worldZ) - g_inGameMenu.pageMapOverview.executePickingCallback(self, true, math.atan2(hotspot.worldX - self.pickingRotationOrigin[0], hotspot.worldZ - self.pickingRotationOrigin[1])) + self:executePickingCallback(true, worldX, worldZ) + self:executePickingCallback(true, math.atan2(hotspot.worldX - self.pickingRotationOrigin[0], hotspot.worldZ - self.pickingRotationOrigin[1])) return end self:setMapSelectionItem(hotspot) @@ -500,7 +549,19 @@ function CpCourseGeneratorFrame:onClickMultiTextOptionParameter(index, element) end function CpCourseGeneratorFrame:onClickMultiTextOptionCenterParameter() - + +end + +function CpCourseGeneratorFrame:executePickingCallback(...) + self.ingameMap:setHotspotSelectionActive(true) + self.isPickingLocation = false + self.isPickingRotation = false + self.ingameMap:unlockMapMovement() + local cb = self.pickingCallback + self.pickingCallback = nil + if cb ~= nil then + cb(...) + end end function CpCourseGeneratorFrame:onClickPositionParameter(element) @@ -519,12 +580,21 @@ function CpCourseGeneratorFrame:onClickPositionParameter(element) else self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot end - g_inGameMenu.pageMapOverview.startPickPosition(self, parameter, function (success, x, z) + g_currentMission:addMapHotspot(self.aiTargetMapHotspot) + self.ingameMap:setHotspotSelectionActive(false) + self.ingameMap:setIsCursorAvailable(false) + self.isPickingLocation = true + self:showActionMessage("ui_ai_pickTargetLocation") + function self.pickingCallback(success, x, z) + self:showActionMessage() + self.ingameMap:setIsCursorAvailable(true) if success then - element:setText(parameter:getString()) + parameter:setValue(x, z) + self.aiTargetMapHotspot:setWorldPosition(x, z) end + self:validateParameters() self:updateParameterValueTexts() - end) + end end function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) @@ -543,12 +613,45 @@ function CpCourseGeneratorFrame:onClickPositionRotationParameter(element) else self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot end - g_inGameMenu.pageMapOverview.startPickPositionAndRotation(self, parameter, function (success, x, z, angle) + g_currentMission:addMapHotspot(self.aiTargetMapHotspot) + self.isPickingLocation = true + self.ingameMap:setHotspotSelectionActive(false) + self.ingameMap:setIsCursorAvailable(false) + self:showActionMessage("ui_ai_pickTargetLocation") + function self.pickingCallback(success, x, z) + self:showActionMessage() + self.ingameMap:setIsCursorAvailable(true) + self.contextBox:setVisible(true) + if success then - element:setText(parameter:getString()) + self.contextBox:setVisible(false) + self.ingameMap:setHotspotSelectionActive(false) + self.ingameMap:setIsCursorAvailable(false) + self.ingameMap:lockMapMovement() + self.aiTargetMapHotspot:setWorldPosition(x, z) + self.isPickingRotation = true + self.pickingRotationOrigin = {x, z} + self.pickingRotationSnapAngle = parameter:getSnappingAngle() + self:showActionMessage("ui_ai_pickTargetRotation") + + function self.pickingCallback(successRotation, angle) + self:showActionMessage() + self.ingameMap:setIsCursorAvailable(true) + self.contextBox:setVisible(true) + if successRotation then + parameter:setPosition(x, z) + parameter:setAngle(angle) + local convertedAngle = parameter:getAngle() + self.aiTargetMapHotspot:setWorldRotation(convertedAngle + math.pi) + end + self:validateParameters() + self:updateParameterValueTexts() + end + else + self:validateParameters() + self:updateParameterValueTexts() end - self:updateParameterValueTexts() - end) + end end function CpCourseGeneratorFrame:getIsPicking() @@ -563,7 +666,6 @@ function CpCourseGeneratorFrame:validateParameters() errorText = self.currentJob:validate() self:updateWarnings() self:updateContextActions() - self:updateParameterValueTexts() end self.errorMessage:setText(errorText) self.errorMessage:setVisible(not isValid) @@ -679,16 +781,13 @@ function CpCourseGeneratorFrame:onStartedJob(args, state, jobTypeIndex) self.startJobPending = false g_messageCenter:unsubscribe(AIJobStartRequestEvent, self) if state == AIJob.START_SUCCESS then - self.mapOverviewSelector:setState(InGameMenuMapFrame.AI_WORKER_LIST, true) + self.mapOverviewSelector:setState(self.MAP_SELECTOR_ACTIVE_JOBS, true) else local jobType = g_currentMission.aiJobTypeManager:getJobTypeByIndex(jobTypeIndex) local text = jobType.classObject.getIsStartErrorText(state) - InfoDialog.show(text, nil, nil, DialogElement.TYPE_INFO) end - callback(state) - end function CpCourseGeneratorFrame:tryStartJob(job, farmId, callback) @@ -778,7 +877,9 @@ function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, inde cell:getAttribute("iconBg").getIsSelected = function () return self.hotspotStateFilter[section][index] end - g_inGameMenu.pageMapOverview.assignItemColors(self, cell:getAttribute("iconBg"), status.color, cell:getAttribute("colorTemplate")) + g_inGameMenu.pageMapOverview.assignItemColors(self, + cell:getAttribute("iconBg"), status.color, + cell:getAttribute("colorTemplate")) elseif list == self.contextButtonList then local buttonInfo = self.contextActions[self.contextActionMapping[index]] cell:getAttribute("text"):setText(buttonInfo.text) @@ -890,7 +991,7 @@ end -- Lines 992-1005 function CpCourseGeneratorFrame:registerInput() - self:unregisterInput() + -- self:unregisterInput() -- g_inputBinding:registerActionEvent(InputAction.MENU_ACTIVATE, self, self.onStartCancelJob, false, true, false, true) -- g_inputBinding:registerActionEvent(InputAction.MENU_CANCEL, self, self.onStartGoToJob, false, true, false, true) -- g_inputBinding:registerActionEvent(InputAction.MENU_ACCEPT, self, self.onCreateJob, false, true, false, true) @@ -983,7 +1084,7 @@ function CpCourseGeneratorFrame:updateContextActions() local vehicle = self.currentHotspot and self.currentHotspot:getVehicle() self.contextActions[self.CONTEXT_ACTIONS.ENTER_VEHICLE].isActive = vehicle and vehicle:getIsEnterableFromMenu() and self.mode ~= self.AI_MODE_CREATE self.canCreateJob = false - if not self.canCreateJob and not self.currentJobVehicle then + if vehicle and not self.currentJobVehicle then for _, job in pairs(self.jobTypeInstances) do if job:getIsAvailableForVehicle(vehicle, true) then self.canCreateJob = true @@ -1000,17 +1101,17 @@ function CpCourseGeneratorFrame:updateContextActions() end function CpCourseGeneratorFrame:toggleMapInput(isActive) - if self.isInputContextActive ~= isActive then - self.isInputContextActive = isActive + -- if self.isInputContextActive ~= isActive then + -- self.isInputContextActive = isActive - self:toggleCustomInputContext(isActive, self.INPUT_CONTEXT_NAME) + -- -- self:toggleCustomInputContext(isActive, self.INPUT_CONTEXT_NAME) - if not isActive then - self:registerInput() - else - self:unregisterInput(true) - end - end + -- if isActive then + -- self:registerInput() + -- else + -- self:unregisterInput(true) + -- end + -- end end function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) @@ -1107,12 +1208,15 @@ function CpCourseGeneratorFrame:onCreateJob() self:updateContextActions() end -function CpCourseGeneratorFrame:resetUIDeadzones() +function CpCourseGeneratorFrame:resetUIDeadzones(enable) self.ingameMap:clearCursorDeadzones() - self.ingameMap:addCursorDeadzone(self.rightBackground.absPosition[1], self.rightBackground.absPosition[2], self.rightBackground.size[1], self.rightBackground.size[2]) - self.ingameMap:addCursorDeadzone(self.topBackground.absPosition[1], self.topBackground.absPosition[2], self.topBackground.size[1], self.topBackground.size[2]) - self.ingameMap:addCursorDeadzone(self.bottomBackground.absPosition[1], self.bottomBackground.absPosition[2], self.bottomBackground.size[1], self.bottomBackground.size[2]) - self.ingameMap:addCursorDeadzone(0, 0, self.leftBox.absPosition[1] + self.leftBox.absSize[1], 1) + if enable then + self.ingameMap:addCursorDeadzone(self.rightBackground.absPosition[1], self.rightBackground.absPosition[2], self.rightBackground.size[1], self.rightBackground.size[2]) + self.ingameMap:addCursorDeadzone(self.topBackground.absPosition[1], self.topBackground.absPosition[2], self.topBackground.size[1], self.topBackground.size[2]) + self.ingameMap:addCursorDeadzone(self.bottomBackground.absPosition[1], self.bottomBackground.absPosition[2], self.bottomBackground.size[1], self.bottomBackground.size[2]) + self.ingameMap:addCursorDeadzone(0, 0, self.leftBox.absPosition[1] + self.leftBox.absSize[1], 1) + else + end end function CpCourseGeneratorFrame:setJobMenuVisible(isVisible) @@ -1189,17 +1293,17 @@ function CpCourseGeneratorFrame:setActiveJobTypeSelection(jobTypeIndex) end function CpCourseGeneratorFrame:openMap() - ---------------------- - --- Ingame map - ---------------------- - --- - -- self:setJobMenuVisible(false) - - -- self.activeWorkerList:reloadData() self:toggleMapInput(true) - self.ingameMap:onOpen() + self.ingameMap:setDisabled(false) self.ingameMap:registerActionEvents() self.ingameMapBase:restoreDefaultFilter() + self:updateContextActions() + self:resetUIDeadzones(true) + if self.mode == self.AI_MODE_OVERVIEW and self.currentHotspot then + FocusManager:setFocus(self.currentContextBox) + else + FocusManager:setFocus(self.mapOverviewSelector) + end -- if g_localPlayer ~= nil then -- local x, _, z = g_localPlayer:getPosition() -- self.ingameMap:setCenterToWorldPosition(x, z) @@ -1207,18 +1311,12 @@ function CpCourseGeneratorFrame:openMap() end function CpCourseGeneratorFrame:closeMap() - self.ingameMap:onClose() + self.ingameMap:removeActionEvents() + self.ingameMap:setDisabled(true) self:toggleMapInput(false) - - -- self.startJobPending = false - - -- if self:getIsPicking() then - -- self:executePickingCallback(false) - -- self:refreshContextInput() - -- end - + if self:getIsPicking() then + self:executePickingCallback(false) + end + self:resetUIDeadzones(false) -- g_inputBinding:setContextEventsActive(self.INPUT_CONTEXT_NAME, InputAction.MENU_AXIS_LEFT_RIGHT, true) - - -- self.statusMessages = {} - -- self:updateStatusMessages() end \ No newline at end of file From 53811077b55b3a815783d26c0c5d90a78face278 Mon Sep 17 00:00:00 2001 From: schwiti6190 <schwiti6190@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:30:31 +0000 Subject: [PATCH 063/158] Updated translations --- translations/translation_br.xml | 7 ++++++- translations/translation_cs.xml | 7 ++++++- translations/translation_ct.xml | 7 ++++++- translations/translation_cz.xml | 7 ++++++- translations/translation_da.xml | 7 ++++++- translations/translation_de.xml | 7 ++++++- translations/translation_ea.xml | 7 ++++++- translations/translation_en.xml | 7 ++++++- translations/translation_es.xml | 7 ++++++- translations/translation_fc.xml | 7 ++++++- translations/translation_fi.xml | 7 ++++++- translations/translation_fr.xml | 7 ++++++- translations/translation_hu.xml | 7 ++++++- translations/translation_it.xml | 7 ++++++- translations/translation_jp.xml | 7 ++++++- translations/translation_kr.xml | 7 ++++++- translations/translation_nl.xml | 7 ++++++- translations/translation_no.xml | 7 ++++++- translations/translation_pl.xml | 7 ++++++- translations/translation_pt.xml | 7 ++++++- translations/translation_ro.xml | 7 ++++++- translations/translation_ru.xml | 7 ++++++- translations/translation_sv.xml | 7 ++++++- translations/translation_tr.xml | 7 ++++++- 24 files changed, 144 insertions(+), 24 deletions(-) diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 5a9fcfd2f..7ec959036 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Configuração do veículo para (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Configuração Básica"/> <text name="CP_vehicle_setting_subTitle_implement" text="Configuração do Implemento"/> <text name="CP_vehicle_setting_subTitle_combine" text="Configuração da Colheitadeira"/> @@ -516,6 +517,10 @@ A rota é salva automaticamente ao fechar o editor e substituir a rota seleciona <text name="CP_controllerGui_pauseRecording" text="Pausar gravação do campo customizado."/> <text name="CP_controllerGui_unpauseRecording" text="Continuar a gravação do campo customizado."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1037,7 +1042,7 @@ Agora sua seleção deve ser semelhante à imagem. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Mostrar canal de debug sim/não."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir mini GUI"/> <text name="input_CP_START_STOP" text="CP: Dirigir/stop motorista"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Alterar trabalho selecionado atual"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Mudar ponto inicial"/> <text name="input_CP_CLEAR_COURSE" text="CP: Limpa a rota atual"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Ecibir a rota atual do veículo"/> diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index c4ff42489..b933d5f1a 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="车辆设置(%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="基本设置"/> <text name="CP_vehicle_setting_subTitle_implement" text="工具设置"/> <text name="CP_vehicle_setting_subTitle_combine" text="收割机设置"/> @@ -516,6 +517,10 @@ <text name="CP_controllerGui_pauseRecording" text="暂停自定义田地记录。"/> <text name="CP_controllerGui_unpauseRecording" text="继续自定义田地记录。"/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="CP自动作业"/> @@ -1006,7 +1011,7 @@ hud还显示助手工作时堆或思洛存储器的剩余填充水平。 <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: 打开迷你HUB"/> <text name="input_CP_START_STOP" text="CP: 启动/停止司机 "/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: 更改当前选定的任务路线"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: 更改起始点"/> <text name="input_CP_CLEAR_COURSE" text="CP: 清除当前加载的任务路线"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="显示车辆的当前路线"/> diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml index b158f6dde..427174bc6 100644 --- a/translations/translation_ct.xml +++ b/translations/translation_ct.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="車輛設置(%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="基本設置"/> <text name="CP_vehicle_setting_subTitle_implement" text="工具設置"/> <text name="CP_vehicle_setting_subTitle_combine" text="收割機設置"/> @@ -516,6 +517,10 @@ <text name="CP_controllerGui_pauseRecording" text="暫停自訂田地記錄。"/> <text name="CP_controllerGui_unpauseRecording" text="取消暫停自訂田地記錄。"/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="CP自動作業"/> @@ -1006,7 +1011,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: 顯示除錯通道菜單 是/否。"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: 打開迷你HUB"/> <text name="input_CP_START_STOP" text="CP: 啟動/停止司機"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: 改變起始點"/> <text name="input_CP_CLEAR_COURSE" text="CP: 清除目前載入的任務"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="顯示車輛的目前路線"/> diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index e32cb9529..fe9c3b671 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Nastavení pro vozidlo (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Základní nastavení"/> <text name="CP_vehicle_setting_subTitle_implement" text="Nastavení nástroje"/> <text name="CP_vehicle_setting_subTitle_combine" text="Nastavení kombajnu"/> @@ -516,6 +517,10 @@ Trasa se automaticky uloží při zavření editoru a přepíše vybranou trasu. <text name="CP_controllerGui_pauseRecording" text="Pozastavit nahrávání vlastního pole."/> <text name="CP_controllerGui_unpauseRecording" text="Znovu spustit nahrávání vlastního pole."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1004,7 +1009,7 @@ Nyní by váš výběr měl vypadat podobně jako na obrázku. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Zobrazit nabídku kanálu ladění ano/ne"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Otevřt/zavřít nastavení vozidla"/> <text name="input_CP_START_STOP" text="CP: Spustit mini GUI"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Změnit aktuální úlohu"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Změnit startovní bod"/> <text name="input_CP_CLEAR_COURSE" text="CP: Vymazat aktuálně nahranou trasu"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Zobrazit aktuální trasu vozidla"/> diff --git a/translations/translation_da.xml b/translations/translation_da.xml index 1ff794fb8..5d9b4a0b5 100644 --- a/translations/translation_da.xml +++ b/translations/translation_da.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Køretøjsinstillinger (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Grundinstillinger"/> <text name="CP_vehicle_setting_subTitle_implement" text="Redskabsinstillinger"/> <text name="CP_vehicle_setting_subTitle_combine" text="Instillinger til mejetærsker"/> @@ -516,6 +517,10 @@ Ruten gemmes automatisk ved lukning af editoren og tilsidesætter den valgte rut <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1017,7 +1022,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Vis debug channels menu ja/nej."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Åbne mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop CP hjælper"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Skift start punkt"/> <text name="input_CP_CLEAR_COURSE" text="CP: Fjern nuværende aktive rute"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Vis den aktive rute for køretøjet"/> diff --git a/translations/translation_de.xml b/translations/translation_de.xml index e7ea5cf2f..f00662c9c 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Fahrzeugeinstellungen für (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Fahrzeugeinstellungen"/> <text name="CP_vehicle_setting_subTitle_basic" text="Grundeinstellungen"/> <text name="CP_vehicle_setting_subTitle_implement" text="Geräteeinstellungen"/> <text name="CP_vehicle_setting_subTitle_combine" text="Einstellungen für Drescher"/> @@ -516,6 +517,10 @@ Der Kurs wird beim Schließen automatisch gespeichert und überschrieben. <text name="CP_controllerGui_pauseRecording" text="Feldrandaufnahme pausieren"/> <text name="CP_controllerGui_unpauseRecording" text="Feldrandaufnahme fortsetzen"/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Karte"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1037,7 +1042,7 @@ Das Kreuz sollte jetzt, wie im Bild dargestellt, gelb sein. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Debugkanäle-Menü anzeigen/ausblenden"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Mini-GUI öffnen"/> <text name="input_CP_START_STOP" text="CP: CP-Helfer starten/stoppen"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Aktuell ausgewählten Job ändern"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Kurs generieren"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Startpunkt ändern"/> <text name="input_CP_CLEAR_COURSE" text="CP: Aktuell geladenen Kurs löschen"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Kurs Sichtbarkeit ändern"/> diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml index 863dfda43..e154678db 100644 --- a/translations/translation_ea.xml +++ b/translations/translation_ea.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1014,7 +1019,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 1bc6e3f45..24971fa29 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording"/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording"/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1052,7 +1057,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Show/hide debug channel menu"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Change starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clear currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Toggle course visibility"/> diff --git a/translations/translation_es.xml b/translations/translation_es.xml index 861893038..459a91efb 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Configuración del vehículo (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Configuración Básica"/> <text name="CP_vehicle_setting_subTitle_implement" text="Configuración de Herramientas"/> <text name="CP_vehicle_setting_subTitle_combine" text="Configuración de Cosechadoras"/> @@ -516,6 +517,10 @@ El curso se guarda automáticamente al cerrar el editor y anula el curso selecci <text name="CP_controllerGui_pauseRecording" text="Pausar grabación del campo."/> <text name="CP_controllerGui_unpauseRecording" text="Continuar grbación del campo."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1015,7 +1020,7 @@ Ahora su selección debería verse similar a la imagen. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Muestra el menú del canal de depuración Sí/No."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir/Cerrar GUI del vehículo."/> <text name="input_CP_START_STOP" text="CP: Iniciar/Detener conductor."/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Cambia el punto de inicio."/> <text name="input_CP_CLEAR_COURSE" text="CP: Borra el curso cargado actualmente"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Muestra el curso actual del vehículo"/> diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml index dbf158946..0622fc9b0 100644 --- a/translations/translation_fc.xml +++ b/translations/translation_fc.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1014,7 +1019,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml index c3092f90a..50d397d02 100644 --- a/translations/translation_fi.xml +++ b/translations/translation_fi.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1014,7 +1019,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index 1056efbc6..1c4e01fa7 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Paramètres du véhicule pour (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Paramètres de base"/> <text name="CP_vehicle_setting_subTitle_implement" text="Paramètres outil"/> <text name="CP_vehicle_setting_subTitle_combine" text="Paramètres moissonneuse-batteuse"/> @@ -516,6 +517,10 @@ La course est automatiquement sauvegardée à la fermeture de l'éditeur et remp <text name="CP_controllerGui_pauseRecording" text="Suspendre l'enregistrement du champ personnalisé"/> <text name="CP_controllerGui_unpauseRecording" text="Reprendre l'enregistrement du champ personnalisé"/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -999,7 +1004,7 @@ Votre sélection devrait ressembler à l'illustration ci-contre. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Affiche/masque les canaux de debogage."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Affiche mini ATH"/> <text name="input_CP_START_STOP" text="CP: Ouvrier Courseplay"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Change le point de départ"/> <text name="input_CP_CLEAR_COURSE" text="CP: Efface la course actuellement sélectionnée"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Affiche la course actuelle du véhicule"/> diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index d18c2d9f1..181723932 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="CoursePlay jármű beállítások"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Alapbeállítások"/> <text name="CP_vehicle_setting_subTitle_implement" text="Eszközök beállításai"/> <text name="CP_vehicle_setting_subTitle_combine" text="Kombájn beállításai"/> @@ -516,6 +517,10 @@ Az útvonal automatikusan mentésre kerül a szerkesztő bezárásakor és felü <text name="CP_controllerGui_pauseRecording" text="Egyedi tábla rögzítés szüneteltetése."/> <text name="CP_controllerGui_unpauseRecording" text="Egyedi tábla rögzítés folytatása."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1023,7 +1028,7 @@ A kijelölésnek hasonlónak kell lennie, mint a képen. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Megjeleníti a hibakeresés menüjét igen/nem."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Nyissa meg a mini GUI-t"/> <text name="input_CP_START_STOP" text="CP: Segítő indítás/leállítás"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Kiindulópont módosítása"/> <text name="input_CP_CLEAR_COURSE" text="CP: Törli az aktuálisan betöltött útvonalat"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mutassa a jármű aktuális irányát"/> diff --git a/translations/translation_it.xml b/translations/translation_it.xml index bb919755a..b02212c9f 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Impostazioni del veicolo (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Generali"/> <text name="CP_vehicle_setting_subTitle_implement" text="Attrezzi"/> <text name="CP_vehicle_setting_subTitle_combine" text="Mietitrebbia"/> @@ -516,6 +517,10 @@ Il percorso viene salvato automaticamente alla chiusura dell'editor e sovrascriv <text name="CP_controllerGui_pauseRecording" text="Sospendi la registrazione del campo personalizzato."/> <text name="CP_controllerGui_unpauseRecording" text="Riattiva la registrazione del campo personalizzato."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1019,7 +1024,7 @@ Ora la tua selezione dovrebbe essere simile all'immagine. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Mostra il menù del canale di debug si/no"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Apri mini GUI"/> <text name="input_CP_START_STOP" text="CP: Avvia/Ferma autista"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Cambia punto di partenza"/> <text name="input_CP_CLEAR_COURSE" text="CP: Cancella il percorso attualmente caricato"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mostra il percorso attuale del veicolo"/> diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index 6379f1ed5..71ce2e52c 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="[%s]の車両設定"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="一般設定"/> <text name="CP_vehicle_setting_subTitle_implement" text="ツール(作業機)設定"/> <text name="CP_vehicle_setting_subTitle_combine" text="コンバイン(収穫機)設定"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1013,7 +1018,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="コースプレイ:デバッグメニューを表示/非表示"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="コースプレイ:ミニGUIを開く"/> <text name="input_CP_START_STOP" text="コースプレイ:開始/停止"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml index c69a4f73f..a4c4aaa20 100644 --- a/translations/translation_kr.xml +++ b/translations/translation_kr.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1014,7 +1019,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index 551ad119c..94049ca87 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Voertuiginstellingen voor (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basisinstellingen"/> <text name="CP_vehicle_setting_subTitle_implement" text="Werktuig instellingen"/> <text name="CP_vehicle_setting_subTitle_combine" text="Maaidorser instellingen"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1013,7 +1018,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Toont debug kanaalmenu ja/nee."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: mini GUI openen"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_no.xml b/translations/translation_no.xml index f1bb544b5..1bee07ec0 100644 --- a/translations/translation_no.xml +++ b/translations/translation_no.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1014,7 +1019,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index 954b1838d..1e5dcae9f 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Ustawienia pojazdu dla (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Ustawienia podstawowe"/> <text name="CP_vehicle_setting_subTitle_implement" text="Ustawienia narzędzi"/> <text name="CP_vehicle_setting_subTitle_combine" text="Ustawienia kombajnu"/> @@ -516,6 +517,10 @@ Kurs jest automatycznie zapisywany po zamknięciu edytora i nadpisuje wybrany ku <text name="CP_controllerGui_pauseRecording" text="Wstrzymaj nagrywanie nowego pola."/> <text name="CP_controllerGui_unpauseRecording" text="Wznów nagrywanie nowego pola."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -983,7 +988,7 @@ Twój wybór powinien wyglądać podobnie do tego zdjęcia. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Menu kanałów debugowania"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Otwórz mini GUI"/> <text name="input_CP_START_STOP" text="CP: Uruchom/Zatrzymaj kierowcę"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Zmień obecnie wybraną pracę"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Zmień punkt startowy"/> <text name="input_CP_CLEAR_COURSE" text="CP: Wyczyść aktualnie załadowany kurs"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Pokaż aktualny kurs pojazdu"/> diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index f62d6417f..634f30740 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Configuração do veículo para (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Configurações básicas"/> <text name="CP_vehicle_setting_subTitle_implement" text="Configurações das alfaias"/> <text name="CP_vehicle_setting_subTitle_combine" text="Configurações da Ceifeira"/> @@ -516,6 +517,10 @@ A rota é guardada automaticamente ao fechar o editor e sobrepõe-se a qualquer <text name="CP_controllerGui_pauseRecording" text="Pausa a gravação de campo personalizado."/> <text name="CP_controllerGui_unpauseRecording" text="Continua a gravação de campo personalizado."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Gestor de Rotas"/> @@ -1008,7 +1013,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Mostra o menu do canal de depuração sim/não."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir mini GUI"/> <text name="input_CP_START_STOP" text="CP: Iniciar/parar Condutor"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Altera Ponto de Ínicio"/> <text name="input_CP_CLEAR_COURSE" text="CP: Limpa actual rotas"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mostra as rotas actuais do veículo"/> diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml index 2669f47a5..cdbf7c45b 100644 --- a/translations/translation_ro.xml +++ b/translations/translation_ro.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Vehicle settings for (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_setting_subTitle_implement" text="Tools settings"/> <text name="CP_vehicle_setting_subTitle_combine" text="Combine settings"/> @@ -516,6 +517,10 @@ The course is saved automatically on closing of the editor and overrides the sel <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1014,7 +1019,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index 4ea80862c..bcc676a1b 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Настройки техники CoursePlay"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Основные настройки"/> <text name="CP_vehicle_setting_subTitle_implement" text="Настройки инструментов"/> <text name="CP_vehicle_setting_subTitle_combine" text="Настройки комбайна"/> @@ -516,6 +517,10 @@ <text name="CP_controllerGui_pauseRecording" text="Приостановить запись пользовательского поля."/> <text name="CP_controllerGui_unpauseRecording" text="Продолжить запись пользовательского поля."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="CoursePlay"/> @@ -1023,7 +1028,7 @@ HUD также показывает оставшийся уровень запо <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Показать/Скрыть каналы отладки"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Открыть mini GUI"/> <text name="input_CP_START_STOP" text="CP: Отправить/Остановить водителя"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Изменить текущую выбранную работу"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Изменить точку отправки"/> <text name="input_CP_CLEAR_COURSE" text="CP: Очистить текущий загруженный курс"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Показать текущий курс на технике"/> diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml index b6881d40d..37136dc99 100644 --- a/translations/translation_sv.xml +++ b/translations/translation_sv.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Fordons inställningar för (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Grundläggande inställningar"/> <text name="CP_vehicle_setting_subTitle_implement" text="Redskaps inställningar"/> <text name="CP_vehicle_setting_subTitle_combine" text="Tröskans inställningar"/> @@ -516,6 +517,10 @@ Banan sparas automatiskt vid stängning av editorn och åsidosätter den valda b <text name="CP_controllerGui_pauseRecording" text="Pausa anpassad fältinspelning."/> <text name="CP_controllerGui_unpauseRecording" text="Återuppta anpassad fältinspelning."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1011,7 +1016,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Visa felsökningskanalsmeny Ja/Nej."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Öppna mini GUI"/> <text name="input_CP_START_STOP" text="CP: Starta/Stoppa förare"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Ändrar startpunkt"/> <text name="input_CP_CLEAR_COURSE" text="CP: Tabort den aktuella inlästa kursen"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Visa fordonets aktuella kurs"/> diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml index 4619b668f..75c929cbe 100644 --- a/translations/translation_tr.xml +++ b/translations/translation_tr.xml @@ -156,6 +156,7 @@ <!--Vehicle settings--> <!----> <text name="CP_vehicle_setting_title" text="Araç ayarları (%s)"/> + <text name="CP_vehicle_setting_subTitle_vehicle" text="Vehicle settings"/> <text name="CP_vehicle_setting_subTitle_basic" text="Temel ayarlar"/> <text name="CP_vehicle_setting_subTitle_implement" text="Araçlar ayarları"/> <text name="CP_vehicle_setting_subTitle_combine" text="Ayarları birleştir"/> @@ -516,6 +517,10 @@ Editör kapatıldığında kurs otomatik olarak kaydedilir ve seçilen kursu ge <text name="CP_controllerGui_pauseRecording" text="Pause custom field recording."/> <text name="CP_controllerGui_unpauseRecording" text="Unpause custom field recording."/> <!----> + <!--CP ingame menu--> + <!----> + <text name="CP_ingameMenu_map_title" text="Map"/> + <!----> <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> @@ -1011,7 +1016,7 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Shows debug channel menu yes/no."/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> From 68164ea499e4a386be00ccf0b1f44390985533f2 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 6 Dec 2024 13:50:25 +0100 Subject: [PATCH 064/158] Divide by zero fix and vine generator settings added --- config/MasterTranslations.xml | 4 +++ config/VehicleSettingsSetup.xml | 4 +-- scripts/gui/CoursePlot.lua | 2 +- scripts/gui/pages/CpCourseGeneratorFrame.lua | 26 ++++++++++++++----- .../CpCourseGeneratorSettings.lua | 6 ++--- 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index da3b6ab31..a6d2c666d 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -877,6 +877,10 @@ <Text language="de"><![CDATA[Grundeinstellungen]]></Text> <Text language="en"><![CDATA[Basic settings]]></Text> </Translation> + <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_vineField"> + <Text language="de"><![CDATA[Weinfeld Einstellungen]]></Text> + <Text language="en"><![CDATA[Vine field settings]]></Text> + </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_headland"> <Text language="de"><![CDATA[Einstellungen Vorgewende]]></Text> <Text language="en"><![CDATA[Settings headland]]></Text> diff --git a/config/VehicleSettingsSetup.xml b/config/VehicleSettingsSetup.xml index 386445ff1..4608d3264 100644 --- a/config/VehicleSettingsSetup.xml +++ b/config/VehicleSettingsSetup.xml @@ -123,9 +123,9 @@ </Texts> </Setting> <Setting classType="AIParameterSettingList" name="pipeOffsetX" min="-100" max="100" incremental="0.1" default="0" unit="2" precision="3" - isVisible="isPipeOffsetSettingsVisible"/> + isVisible="isPipeOffsetSettingsVisible" title="CP_deactivated"/> <Setting classType="AIParameterSettingList" name="pipeOffsetZ" min="-100" max="100" incremental="0.1" default="0" unit="2" precision="3" - isVisible="isPipeOffsetSettingsVisible"/> + isVisible="isPipeOffsetSettingsVisible" title="CP_deactivated"/> </SettingSubTitle> <SettingSubTitle title="seeder" isVisible="areSowingMachineSettingsVisible"> diff --git a/scripts/gui/CoursePlot.lua b/scripts/gui/CoursePlot.lua index d28c770c8..84cbb94ed 100644 --- a/scripts/gui/CoursePlot.lua +++ b/scripts/gui/CoursePlot.lua @@ -113,8 +113,8 @@ function CoursePlot:drawLineBetween(map, x, z, nx, nz, isHudMap, lineThickness, local startX, startY, _, sv = CpGuiUtil.worldToScreen(map, x, z, isHudMap) local endX, endY, _, ev = CpGuiUtil.worldToScreen(map, nx, nz, isHudMap) local dx, dz = nx - x, nz - z - local dirX, dirZ = MathUtil.vector2Normalize(dx, dz) local length = MathUtil.vector2Length(dx, dz) + local dirX, dirZ = length > 0 and MathUtil.vector2Normalize(dx, dz) or 0, 1 if startX and startY and endX and endY then local dx2D = endX - startX local dy2D = ( endY - startY ) / g_screenAspectRatio diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index c2f3f3141..d0e32a6ea 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -5,13 +5,15 @@ CpCourseGeneratorFrame = { CATEGRORIES = { - BASIC_SETTINGS = 1, - IN_GAME_MAP = 2, + IN_GAME_MAP = 1, + BASIC_SETTINGS = 2, + Vine_SETTINGS = 3 }, INPUT_CONTEXT_NAME = "CP_COURSE_GENERATOR_MENU", CATEGRORY_TEXTS = { - "CP_vehicle_courseGeneratorSetting_subTitle_basic", "CP_ingameMenu_map_title", + "CP_vehicle_courseGeneratorSetting_subTitle_basic", + "CP_vehicle_courseGeneratorSetting_subTitle_vineField", }, CLEAR_INPUT_ACTIONS = { InputAction.MENU_ACTIVATE, @@ -155,7 +157,7 @@ function CpCourseGeneratorFrame:initialize(menu) self.subCategoryTabs[key].onClickCallback = function () self:updateSubCategoryPages(key) end - if key == 2 then + if key == self.CATEGRORIES.IN_GAME_MAP then self.subCategoryPages[key] = self.containerMap else self.subCategoryPages[key] = self.containerPrefab:clone(self) @@ -219,7 +221,7 @@ function CpCourseGeneratorFrame:onFrameOpen() local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) self.categoryHeaderText:setText(title) - local layout = self.subCategoryPages[1]:getDescendantByName("layout") + local layout = self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout") for i = #layout.elements, 1, -1 do layout.elements[i]:delete() end @@ -228,6 +230,17 @@ function CpCourseGeneratorFrame:onFrameOpen() self.sectionHeaderPrefab, settings) CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) + settings = vehicle:getCpVineSettings() + settingsBySubTitle = CpCourseGeneratorSettings.getVineSettingSetup() + layout = self.subCategoryPages[self.CATEGRORIES.Vine_SETTINGS]:getDescendantByName("layout") + for i = #layout.elements, 1, -1 do + layout.elements[i]:delete() + end + CpSettingsUtil.generateAndBindGuiElementsToSettings(settingsBySubTitle, + layout, self.multiTextPrefab, self.booleanPrefab, + self.sectionHeaderPrefab, settings) + CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) + -- g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self.onAIVehicleStateChanged, self) self.activeWorkerList:reloadData() g_messageCenter:subscribe(MessageType.AI_JOB_STARTED, function(self) @@ -283,8 +296,7 @@ function CpCourseGeneratorFrame:onFrameOpen() self.startJobPending = false self.ingameMap:onOpen() - self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) - FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) + self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) CpCourseGeneratorFrame:superClass().onFrameOpen(self) end diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua index 3dbeab48f..0e1f2d97d 100644 --- a/scripts/specializations/CpCourseGeneratorSettings.lua +++ b/scripts/specializations/CpCourseGeneratorSettings.lua @@ -199,10 +199,8 @@ function CpCourseGeneratorSettings.getSettingSetup() end function CpCourseGeneratorSettings.getVineSettingSetup(vehicle) - local title = g_i18n:getText(CpCourseGeneratorSettings.vineSettings.pageTitle) - return CpCourseGeneratorSettings.vineSettings.settingsBySubTitle, - vehicle and string.format(title, vehicle:getName()) - or title + return CpCourseGeneratorSettings.vineSettings.settingsBySubTitle, + CpCourseGeneratorSettings.vineSettings.pageTitle end function CpCourseGeneratorSettings:loadSettings(savegame) From a23021f41d7f97fe4609efb7ac53dba1f341705d Mon Sep 17 00:00:00 2001 From: schwiti6190 <schwiti6190@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:50:48 +0000 Subject: [PATCH 065/158] Updated translations --- translations/translation_br.xml | 1 + translations/translation_cs.xml | 1 + translations/translation_ct.xml | 1 + translations/translation_cz.xml | 1 + translations/translation_da.xml | 1 + translations/translation_de.xml | 1 + translations/translation_ea.xml | 1 + translations/translation_en.xml | 1 + translations/translation_es.xml | 1 + translations/translation_fc.xml | 1 + translations/translation_fi.xml | 1 + translations/translation_fr.xml | 1 + translations/translation_hu.xml | 1 + translations/translation_it.xml | 1 + translations/translation_jp.xml | 1 + translations/translation_kr.xml | 1 + translations/translation_nl.xml | 1 + translations/translation_no.xml | 1 + translations/translation_pl.xml | 1 + translations/translation_pt.xml | 1 + translations/translation_ro.xml | 1 + translations/translation_ru.xml | 1 + translations/translation_sv.xml | 1 + translations/translation_tr.xml | 1 + 24 files changed, 24 insertions(+) diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 7ec959036..801f96eb8 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Configuração do gerador de rota de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Configuração do gerador de rota de (%s)"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="cabeceira"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="centro"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index b933d5f1a..ddb5cb96c 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="工作路线设置 (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="基本设置"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="田间地头设置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="田地中心路线设置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="障碍绕行设置"/> diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml index 427174bc6..c52881c74 100644 --- a/translations/translation_ct.xml +++ b/translations/translation_ct.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="工作路線設置 (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="基本設置"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="田間地頭設置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="中心路線設置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index fe9c3b671..bcd516a80 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Nastavení generátoru tras (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Základní"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Souvrať"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Střed"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Nastavení ostrova"/> diff --git a/translations/translation_da.xml b/translations/translation_da.xml index 5d9b4a0b5..d686bf494 100644 --- a/translations/translation_da.xml +++ b/translations/translation_da.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Rutegenerator indstilling for (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Rutegenerator indstilling for (%s)"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Forager"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Mitte"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_de.xml b/translations/translation_de.xml index f00662c9c..5c4aebd8f 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Kursgenerator-Einstellungen von (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Grundeinstellungen"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Weinfeld Einstellungen"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Einstellungen Vorgewende"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Einstellungen Mitte"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Einstellungen Insel"/> diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml index e154678db..59ebaa33a 100644 --- a/translations/translation_ea.xml +++ b/translations/translation_ea.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 24971fa29..26a786407 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Basic settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Settings headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Settings center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_es.xml b/translations/translation_es.xml index 459a91efb..17c04c9b0 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Configuración del generador de rutas de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Básicos"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Cabeceras"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Centro"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Islas"/> diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml index 0622fc9b0..ed65c28d2 100644 --- a/translations/translation_fc.xml +++ b/translations/translation_fc.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml index 50d397d02..a459aad6c 100644 --- a/translations/translation_fi.xml +++ b/translations/translation_fi.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index 1c4e01fa7..ce644b726 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Paramètres du générateur pour la course (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Paramètres principaux"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Paramètres des fourrières"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Paramètres des rangs"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Paramètres îlots"/> diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index 181723932..c6b5b3ebc 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Az útvonaltervező beállításai (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Általános"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Fordulósáv"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Sorok"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_it.xml b/translations/translation_it.xml index b02212c9f..35370492b 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Impostazioni percorso veicolo (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Base"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="testa campo"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="centro"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index 71ce2e52c..5c01b9673 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="[%s]のコース生成設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="一般設定"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="まくら地設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="フィールド内設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml index a4c4aaa20..1a00b2e15 100644 --- a/translations/translation_kr.xml +++ b/translations/translation_kr.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index 94049ca87..f6c1bbf06 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Instellingen koersgenerator van (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basis"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="wendakker"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="midden"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_no.xml b/translations/translation_no.xml index 1bee07ec0..728234d63 100644 --- a/translations/translation_no.xml +++ b/translations/translation_no.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index 1e5dcae9f..69f51f670 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Ustawienia generatora kursu dla (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Podstawowe"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Uwrocia"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Środek"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Ustawienia przeszkód"/> diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index 634f30740..4acfc78a9 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Configurações do gerador de rota de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Configuração geral"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Configuração de cabeceiras"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Configuração de faixas"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml index cdbf7c45b..29693329e 100644 --- a/translations/translation_ro.xml +++ b/translations/translation_ro.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index bcc676a1b..beb1c0b11 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Настройки генератора курсов (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="основные"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="поворотная полоса"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="метод движения"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Настройки работ вокруг островков"/> diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml index 37136dc99..5f9baa879 100644 --- a/translations/translation_sv.xml +++ b/translations/translation_sv.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Kursskapandeinställningar för (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Grundläggande"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Vändteg"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Centrum"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml index 75c929cbe..53dd790ed 100644 --- a/translations/translation_tr.xml +++ b/translations/translation_tr.xml @@ -272,6 +272,7 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Kurs oluşturucu ayarları (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Temel"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Sürülmemiş toprak"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="merkez"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> From 0464112c68bc6f701a293d5fa0201184eabcc62e Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 6 Dec 2024 14:08:02 +0100 Subject: [PATCH 066/158] Fixes getIsMotorStarted() --- scripts/ai/controllers/MotorController.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/scripts/ai/controllers/MotorController.lua b/scripts/ai/controllers/MotorController.lua index d74ccda95..f29909af5 100644 --- a/scripts/ai/controllers/MotorController.lua +++ b/scripts/ai/controllers/MotorController.lua @@ -26,7 +26,7 @@ function MotorController:update() return end if not self.settings.fuelSave:getValue() then - if not self.vehicle:getIsMotorStarted() then + if not self:getIsStarted() then self:startMotor() self.vehicle:raiseAIEvent('onAIFieldWorkerContinue', 'onAIImplementContinue') end @@ -35,7 +35,7 @@ function MotorController:update() end if self:isFuelSaveDisabled() or self.driveStrategy:getMaxSpeed() > self.speedThreshold then - if not self.vehicle:getIsMotorStarted() then + if not self:getIsStarted() then self:startMotor() self.vehicle:raiseAIEvent("onAIFieldWorkerContinue", "onAIImplementContinue") end @@ -47,7 +47,7 @@ function MotorController:update() self.timerSet = true end if self.timer:get() then - if self.vehicle:getIsMotorStarted() then + if self:getIsStarted() then self.vehicle:raiseAIEvent('onAIFieldWorkerBlock', 'onAIImplementBlock') self:stopMotor() end @@ -113,12 +113,8 @@ end function MotorController:startMotor() self.vehicle.spec_cpAIWorker.motorDisabled = false - -- TODO 25 for whatever reason, vehicle:getIsMotorStarted() returns true only much later after the motor was started - -- so we call this and log for quite a few seconds when the motor was not running when the helper was started - if self.vehicle:getCanBeTurnedOn() then - self.implement:startMotor() - self:debug('Started motor after fuel save.') - end + self.implement:startMotor() + self:debug('Started motor after fuel save.') end function MotorController:stopMotor() @@ -133,3 +129,7 @@ function MotorController:onFinished() self.implement:setFillUnitIsFilling(false) end end + +function MotorController:getIsStarted() + return self.vehicle:getMotorState() ~= MotorState.OFF +end \ No newline at end of file From 13852fa2ede944ce30ab156c882082060b425fd1 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 6 Dec 2024 15:49:23 +0100 Subject: [PATCH 067/158] Stupid map fix .. --- scripts/gui/CoursePlot.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/gui/CoursePlot.lua b/scripts/gui/CoursePlot.lua index 84cbb94ed..5c2a1953e 100644 --- a/scripts/gui/CoursePlot.lua +++ b/scripts/gui/CoursePlot.lua @@ -114,7 +114,11 @@ function CoursePlot:drawLineBetween(map, x, z, nx, nz, isHudMap, lineThickness, local endX, endY, _, ev = CpGuiUtil.worldToScreen(map, nx, nz, isHudMap) local dx, dz = nx - x, nz - z local length = MathUtil.vector2Length(dx, dz) - local dirX, dirZ = length > 0 and MathUtil.vector2Normalize(dx, dz) or 0, 1 + local dirX, dirZ = 0, 1 + if length <= 0 then + return + end + dirX, dirZ = MathUtil.vector2Normalize(dx, dz) if startX and startY and endX and endY then local dx2D = endX - startX local dy2D = ( endY - startY ) / g_screenAspectRatio From 922e4edaabe0f5beee7378e58fae6f101a8f613b Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 6 Dec 2024 16:02:40 +0100 Subject: [PATCH 068/158] Added Building Collision Flag to the proximity sensor --- scripts/ai/ProximitySensor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index da17ae61c..91ab425eb 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -114,7 +114,7 @@ function ProximitySensor:update() self.objectId = nil self.hitTerrain = false if self.enabled then - local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.BUILDING raycastClosest(x, y1 + self.height, z, nx, ny, nz, self.range, 'raycastCallback', self, raycastMask) if CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then DebugUtil.drawDebugLine(x, y1 + self.height, z, x + 5 * nx, y1 + self.height + 5 * ny, z + 5 * nz, 0, 1, 0) From d75af2dfa062efb9c169d59ae502d84ba880697c Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 6 Dec 2024 16:06:49 +0100 Subject: [PATCH 069/158] Added Collision flag building to pathfinder and veritcal proximity sensor --- scripts/ai/ProximitySensor.lua | 2 +- scripts/dev/DevHelper.lua | 2 +- scripts/pathfinder/PathfinderUtil.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index 91ab425eb..ef9e00625 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -460,7 +460,7 @@ function VerticalProximitySensor:update() self.objectId = nil self.hitTerrain = false if self.enabled then - local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.BUILDING -- straight up from 10 cm above the ground to height raycastClosest(x, y + self.minHeightAboveGround, z, 0, 1, 0, self.height - self.minHeightAboveGround, diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 9255f50fe..06e011cca 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -83,7 +83,7 @@ function DevHelper:update() self.data.isOnFieldArea, self.data.onFieldArea, self.data.totalOnFieldArea = CpFieldUtil.isOnFieldArea(self.data.x, self.data.z) self.data.nx, self.data.ny, self.data.nz = getTerrainNormalAtWorldPos(g_currentMission.terrainRootNode, self.data.x, y, self.data.z) - local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + CollisionFlag.BUILDING self.data.collidingShapes = '' overlapBox(self.data.x, self.data.y + 0.2, self.data.z, 0, self.yRot, 0, 1.6, 1, 8, "overlapBoxCallback", self, collisionMask, true, true, true) diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index 9f189a248..164552fb2 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -343,7 +343,7 @@ function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, self.collidingShapes = 0 self.collidingShapesText = 'unknown' - local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + CollisionFlag.BUILDING overlapBox(x, y + 0.2, z, xRot, yRot, zRot, width, 1, length, 'overlapBoxCallback', self, collisionMask, true, true, true) From 388d1f7b892e236d69a17666b139b08a6dcd6d53 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 6 Dec 2024 16:17:38 +0100 Subject: [PATCH 070/158] Added nil check for shovel mode --- scripts/specializations/CpShovelPositions.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/specializations/CpShovelPositions.lua b/scripts/specializations/CpShovelPositions.lua index aa3bc0979..401fd0b04 100644 --- a/scripts/specializations/CpShovelPositions.lua +++ b/scripts/specializations/CpShovelPositions.lua @@ -358,7 +358,6 @@ function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, height local ax, ay, az = localToLocal(armTool.node, armVehicle.rootNode, 0, 0, 0) local wx, _, wz = getWorldTranslation(armVehicle.rootNode) - local function draw(x1, y1, z1, x2, y2, z2, r, g, b) if CpUtil.getCurrentVehicle() == shovelVehicle.rootVehicle and CpDebug:isChannelActive(CpDebug.DBG_SILO, shovelVehicle.rootVehicle) then @@ -445,7 +444,7 @@ function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, height end local alpha, oldRotRelativeArmRot = 0, 0 - if hasIntersection then + if hasIntersection and i1y ~= nil then --- Controls the arm height setTranslation(armProjectionNode, 0, i1y, i1z) setTranslation(armToolRefNode, ax, ay, az) From 5e88dddcb77b944c4aebc89f2ffb6d86bda9c513 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 6 Dec 2024 16:57:18 -0500 Subject: [PATCH 071/158] fix: self unload pipe position --- scripts/ai/SelfUnloadHelper.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/ai/SelfUnloadHelper.lua b/scripts/ai/SelfUnloadHelper.lua index ae944a957..9c822dc4d 100644 --- a/scripts/ai/SelfUnloadHelper.lua +++ b/scripts/ai/SelfUnloadHelper.lua @@ -144,10 +144,11 @@ function SelfUnloadHelper:getTargetParameters(fieldPolygon, myVehicle, implement local _, _, dZ = localToLocal(trailerRootNode, targetNode, 0, 0, 0) - -- this should put the pipe's end 1.1 m from the trailer's edge towards the middle. We are not aiming for + -- this should put the pipe's end 1.6 m from the trailer's edge towards the middle (but not more than half the width + -- of the trailer, so the pipe always stays on the side closer to the harvester. We are not aiming for -- the centerline of the trailer to avoid bumping into very wide trailers, we don't want to get closer -- than what is absolutely necessary. - local offsetX = math.max(3.8, math.abs(objectWithPipeAttributes.pipeOffsetX)) + trailerWidth / 2 - 1.6 + local offsetX = math.max(3.8, math.abs(objectWithPipeAttributes.pipeOffsetX)) + trailerWidth / 2 - math.min(1.6, trailerWidth / 2) offsetX = objectWithPipeAttributes.pipeOnLeftSide and -offsetX or offsetX -- arrive near the trailer alignLength meters behind the target, from there, continue straight a bit local _, steeringLength = AIUtil.getSteeringParameters(myVehicle) From d94eeaf45422468078cbd4af59a347d1d448aba5 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 6 Dec 2024 18:47:43 -0500 Subject: [PATCH 072/158] feat: do not open pipe when turning --- scripts/ai/AIDriveStrategyCombineCourse.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index 338006c13..a6ac07f1d 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -1494,7 +1494,8 @@ function AIDriveStrategyCombineCourse:handlePipe(dt) end function AIDriveStrategyCombineCourse:handleCombinePipe(dt) - if self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe() then + -- don't open the pipe while turning + if not self.state == self.states.TURNING and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then self.pipeController:openPipe() else if not self.forcePipeOpen:get() then From ae41558fdca9dd7bad08362be7dd18f518744d25 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 6 Dec 2024 19:32:13 -0500 Subject: [PATCH 073/158] fix: do not wait for straw swath on headland --- scripts/ai/AIDriveStrategyCombineCourse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index a6ac07f1d..e6bc16aa3 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -1306,7 +1306,7 @@ function AIDriveStrategyCombineCourse:shouldHoldInTurnManeuver() local discharging = self:isDischarging() and not self:alwaysNeedsUnloader() local stillProcessingFruit = self:alwaysNeedsUnloader() and self:isProcessingFruit() local isFinishingRow = self.aiTurn and self.aiTurn:isFinishingRow() - local waitForStraw = self.combineController:isDroppingStrawSwath() and not isFinishingRow + local waitForStraw = self.combineController:isDroppingStrawSwath() and not isFinishingRow and not self:isOnHeadland() self:debugSparse('Turn maneuver=> Autoaim: %s, discharging: %s, wait for straw: %s, straw swath active: %s, processing: %s, finishing row: %s', tostring(self:hasAutoAimPipe()), tostring(discharging), tostring(waitForStraw), From 31d5eb30b9022a6fbefc4ac4e5d12e87bd50f7f2 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 7 Dec 2024 06:54:45 -0500 Subject: [PATCH 074/158] fix: stupid pipe open logic error --- scripts/ai/AIDriveStrategyCombineCourse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index e6bc16aa3..dfc576dd1 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -1495,7 +1495,7 @@ end function AIDriveStrategyCombineCourse:handleCombinePipe(dt) -- don't open the pipe while turning - if not self.state == self.states.TURNING and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then + if self.state ~= self.states.TURNING and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then self.pipeController:openPipe() else if not self.forcePipeOpen:get() then From 057284ab5a9e69acc3de3952ab315d40b946b8fa Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 7 Dec 2024 07:10:32 -0500 Subject: [PATCH 075/158] feat: self unload pipe open Don't open the pipe until we are close enough to the target trailer --- scripts/ai/AIDriveStrategyCombineCourse.lua | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/AIDriveStrategyCombineCourse.lua index dfc576dd1..f490c7da4 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/AIDriveStrategyCombineCourse.lua @@ -1495,7 +1495,7 @@ end function AIDriveStrategyCombineCourse:handleCombinePipe(dt) -- don't open the pipe while turning - if self.state ~= self.states.TURNING and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then + if self:isPipeOpenEnabled() and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then self.pipeController:openPipe() else if not self.forcePipeOpen:get() then @@ -1507,6 +1507,18 @@ function AIDriveStrategyCombineCourse:handleCombinePipe(dt) end end +--- we don't want random triggers to open the pipe while turning or driving to another trailer +function AIDriveStrategyCombineCourse:isPipeOpenEnabled() + if self:isTurning() then + return false + elseif self.state == self.states.UNLOADING_ON_FIELD and + self:isUnloadStateOneOf(self.drivingToSelfUnloadStates) and not self:isCloseToCourseEnd(10) then + return false + else + return true + end +end + function AIDriveStrategyCombineCourse:isAGoodTrailerInRange() local _, trailer = self.pipeController:isFillableTrailerInRange() local unloaderVehicle = trailer and trailer:getRootVehicle() From 7cdc9d8aa435cbc78772bfc6c8fbb4d5e9de0a79 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 7 Dec 2024 16:35:23 -0500 Subject: [PATCH 076/158] wip --- scripts/Course.lua | 12 -- scripts/Waypoint.lua | 1 - scripts/ai/AIDriveStrategyBunkerSilo.lua | 2 +- scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 2 +- scripts/ai/AIDriveStrategyUnloadCombine.lua | 2 +- scripts/ai/AIUtil.lua | 82 ++++++++++--- scripts/ai/turns/AITurn.lua | 13 ++- scripts/ai/turns/TurnManeuver.lua | 110 ++++++++---------- scripts/pathfinder/PathfinderUtil.lua | 8 +- 9 files changed, 131 insertions(+), 101 deletions(-) diff --git a/scripts/Course.lua b/scripts/Course.lua index c8fb4c419..75f395a0e 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -559,18 +559,6 @@ function Course:getYRotationCorrectedForDirectionChanges(ix) end end --- This is the radius from the course generator. For now ony island bypass waypoints nodes have a --- radius. -function Course:getRadiusAtIx(ix) - local r = self.waypoints[ix].radius - if r ~= r then - -- radius can be nan - return nil - else - return r - end -end - -- This is the radius calculated when the course is created. function Course:getCalculatedRadiusAtIx(ix) local r = self.waypoints[ix].calculatedRadius diff --git a/scripts/Waypoint.lua b/scripts/Waypoint.lua index 834e976d1..a96b1ff8c 100644 --- a/scripts/Waypoint.lua +++ b/scripts/Waypoint.lua @@ -34,7 +34,6 @@ function Waypoint:init(wp) ---@type CourseGenerator.WaypointAttributes self.attributes = wp.attributes and wp.attributes:clone() or CourseGenerator.WaypointAttributes() self.angle = wp.angle or nil - self.radius = wp.radius or nil self.rev = wp.rev or wp.reverse or false self.rev = self.rev or wp.gear and wp.gear == Gear.Backward -- dynamically added/calculated properties diff --git a/scripts/ai/AIDriveStrategyBunkerSilo.lua b/scripts/ai/AIDriveStrategyBunkerSilo.lua index 3c93eb83c..ead8452d9 100644 --- a/scripts/ai/AIDriveStrategyBunkerSilo.lua +++ b/scripts/ai/AIDriveStrategyBunkerSilo.lua @@ -415,7 +415,7 @@ function AIDriveStrategyBunkerSilo:startTransitionToNextLane() end local path = PathfinderUtil.findAnalyticPath(ReedsSheppSolver(), self.vehicle:getAIDirectionNode(), - 0, self.turnNode, 0, 0, self.turningRadius) + 0, 0, self.turnNode, 0, 0, self.turningRadius) if not path or #path == 0 then self:debug("No valid turn was found!") self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new()) diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index c4ec2224f..d865284d6 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -775,7 +775,7 @@ function AIDriveStrategyFieldWorkCourse:calculateTightTurnOffset() if self.state == self.states.WORKING or self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then -- when rounding small islands or to start on a course with curves self.tightTurnOffset = AIUtil.calculateTightTurnOffset(self.vehicle, self.turningRadius, self.course, - self.tightTurnOffset, true) + self.tightTurnOffset) else self.tightTurnOffset = 0 end diff --git a/scripts/ai/AIDriveStrategyUnloadCombine.lua b/scripts/ai/AIDriveStrategyUnloadCombine.lua index 9ef447033..25b58a0ef 100644 --- a/scripts/ai/AIDriveStrategyUnloadCombine.lua +++ b/scripts/ai/AIDriveStrategyUnloadCombine.lua @@ -2829,7 +2829,7 @@ function AIDriveStrategyUnloadCombine:onFieldUnloadPositionReached() self:debug("Starting pathfinding to the reverse unload turn end node with align length: %.2f and steering length: %.2f, turn radius: %.2f", alignLength, steeringLength, self.turningRadius) local path = PathfinderUtil.findAnalyticPath(PathfinderUtil.dubinsSolver, self.fieldUnloadTurnStartNode, - 0, self.fieldUnloadTurnEndNode, 0, 3, self.turningRadius) + 0, 0, self.fieldUnloadTurnEndNode, 0, 3, self.turningRadius) if not path or #path == 0 then self:debug("Reverse alignment course creation failed!") else diff --git a/scripts/ai/AIUtil.lua b/scripts/ai/AIUtil.lua index 190336d7e..a3907555c 100644 --- a/scripts/ai/AIUtil.lua +++ b/scripts/ai/AIUtil.lua @@ -50,7 +50,7 @@ end --- making sure that the towed implement's trajectory remains closer to the --- course. ---@param course Course -function AIUtil.calculateTightTurnOffset(vehicle, vehicleTurningRadius, course, previousOffset, useCalculatedRadius) +function AIUtil.calculateTightTurnOffset(vehicle, vehicleTurningRadius, course, previousOffset) local tightTurnOffset local function smoothOffset(offset) @@ -58,12 +58,7 @@ function AIUtil.calculateTightTurnOffset(vehicle, vehicleTurningRadius, course, end -- first of all, does the current waypoint have radius data? - local r - if useCalculatedRadius then - r = course:getCalculatedRadiusAtIx(course:getCurrentWaypointIx()) - else - r = course:getRadiusAtIx(course:getCurrentWaypointIx()) - end + local r = course:getCalculatedRadiusAtIx(course:getCurrentWaypointIx()) if not r then return smoothOffset(0) end @@ -104,13 +99,53 @@ function AIUtil.calculateTightTurnOffset(vehicle, vehicleTurningRadius, course, -- smooth the offset a bit to avoid sudden changes tightTurnOffset = smoothOffset(offset) - CpUtil.debugVehicle(CpDebug.DBG_AI_DRIVER, vehicle, + CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, 'Tight turn, r = %.1f, tow bar = %.1f m, currentAngle = %.0f, nextAngle = %.0f, offset = %.1f, smoothOffset = %.1f', r, towBarLength, currentAngle, nextAngle, offset, tightTurnOffset ) -- remember the last value for smoothing return tightTurnOffset end +function AIUtil.calculateTightTurnOffsetForTurnManeuver(vehicle, steeringLength, course, ix, previousOffset) + local tightTurnOffset + + local function smoothOffset(offset) + return (offset + 4 * (previousOffset or 0 )) / 5 + end + + -- first of all, does the current waypoint have radius data? + local r = course:getCalculatedRadiusAtIx(ix) + if not r then + return smoothOffset(0) + end + + -- Ok, looks like a tight turn, so we need to move a bit left or right of the course + -- to keep the tool on the course. Use a little less than the calculated, this is purely empirical and should probably + -- be reviewed why the calculated one seems to overshoot. + local offset = 1 * AIUtil.getTractorRadiusFromImplementRadius(r, steeringLength) - r + if offset ~= offset then + -- check for nan + return smoothOffset(0) + end + -- figure out left or right now? + local nextAngle = course:getWaypointAngleDeg(ix + 1) + local currentAngle = course:getWaypointAngleDeg(ix) + if not nextAngle or not currentAngle then + return smoothOffset(0) + end + + if CpMathUtil.getDeltaAngle(math.rad(nextAngle), math.rad(currentAngle)) > 0 then offset = -offset end + + -- smooth the offset a bit to avoid sudden changes + tightTurnOffset = smoothOffset(offset) + CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, + 'Tight turn, r = %.1f, tow bar = %.1f m, currentAngle = %.0f, nextAngle = %.0f, offset = %.1f, smoothOffset = %.1f', + r, steeringLength, currentAngle, nextAngle, offset, tightTurnOffset ) + -- remember the last value for smoothing + return tightTurnOffset +end + + function AIUtil.getTowBarLength(vehicle) -- is there a wheeled implement behind the tractor and is it on a pivot? local implement = AIUtil.getFirstReversingImplementWithWheels(vehicle, true) @@ -141,8 +176,17 @@ function AIUtil.getSteeringParameters(vehicle) end function AIUtil.getOffsetForTowBarLength(r, towBarLength) + return AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) - r +end + +function AIUtil.getImplementRadiusFromTractorRadius(r, towBarLength) + local rImplement = math.sqrt( r * r - towBarLength * towBarLength ) -- the radius the tractor should be on + return rImplement +end + +function AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) local rTractor = math.sqrt( r * r + towBarLength * towBarLength ) -- the radius the tractor should be on - return rTractor - r + return rTractor end function AIUtil.getArticulatedAxisVehicleReverserNode(vehicle) @@ -359,9 +403,9 @@ function AIUtil.getFirstAttachedImplement(vehicle, suppressLog) -- the distance from the vehicle's root node to the front of the implement local _, _, d = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, implement.object.size.length / 2 + implement.object.size.lengthOffset) - if implement.object.spec_leveler then + if implement.object.spec_leveler then local nodeData = ImplementUtil.getLevelerNode(implement.object) - if nodeData then + if nodeData then _, _, d = localToLocal(nodeData.node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) end end @@ -388,9 +432,9 @@ function AIUtil.getLastAttachedImplement(vehicle,suppressLog) -- the distance from the vehicle's root node to the back of the implement local _, _, d = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, - implement.object.size.length / 2 + implement.object.size.lengthOffset) - if implement.object.spec_leveler then + if implement.object.spec_leveler then local nodeData = ImplementUtil.getLevelerNode(implement.object) - if nodeData then + if nodeData then _, _, d = localToLocal(nodeData.node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) end end @@ -452,7 +496,7 @@ function AIUtil.getNumberOfChildVehiclesWithSpecialization(vehicle, specializati return #vehicles end ---- Gets all child vehicles with a given specialization. +--- Gets all child vehicles with a given specialization. --- This can include the rootVehicle and implements --- that are not directly attached to the rootVehicle. ---@param vehicle table @@ -462,7 +506,7 @@ end ---@return boolean at least one vehicle/implement was found function AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, specializationReference) if vehicle == nil then - printCallstack() + printCallstack() CpUtil.info("Vehicle is nil!") return {}, false end @@ -661,7 +705,7 @@ function AIUtil.getLength(vehicle) if vehicle.getAIAgentSize then vehicle:updateAIAgentAttachments() local width, length, lengthOffset, frontOffset, height = vehicle:getAIAgentSize() - for _, attachment in ipairs(vehicle.spec_aiDrivable.attachments) do + for _, attachment in ipairs(vehicle.spec_aiDrivable.attachments) do length = length + attachment.length end return length @@ -697,7 +741,7 @@ function AIUtil.hasCutterOnTrailerAttached(vehicle) end --- Checks if a cutter is attached and it's not registered as a valid combine cutter. ---- A Example is the New Holland Superflex header, when it is attached as transport trailer. +--- A Example is the New Holland Superflex header, when it is attached as transport trailer. function AIUtil.hasCutterAsTrailerAttached(vehicle) local cutters, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Cutter) if not found then @@ -705,12 +749,12 @@ function AIUtil.hasCutterAsTrailerAttached(vehicle) return false end local combines, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Combine) - if not found then + if not found then --- No valid combine object was found. return false end local spec = combines[1].spec_combine - if spec.numAttachedCutters <= 0 then + if spec.numAttachedCutters <= 0 then --- The cutter is not available for threshing in this combination. return true end diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index f9524b0da..45c896a0a 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -651,8 +651,8 @@ function CourseTurn:onWaypointChange(ix) if self.turnCourse then if self.forceTightTurnOffset or (self.enableTightTurnOffset and self.turnCourse:useTightTurnOffset(ix)) then -- adjust the course a bit to the outside in a curve to keep a towed implement on the course - self.tightTurnOffset = AIUtil.calculateTightTurnOffset(self.vehicle, self.turningRadius, self.turnCourse, - self.tightTurnOffset, true) + self.tightTurnOffset = AIUtil.calculateTightTurnOffsetForTurnManeuver(self.vehicle, self.steeringLength, + self.turnCourse, self.turnCourse:getCurrentWaypointIx(), self.tightTurnOffset) self.turnCourse:setOffset(self.tightTurnOffset, 0) else -- reset offset to 0 if tight turn offset is not on @@ -728,8 +728,13 @@ function CourseTurn:generateCalculatedTurn() -- TODO: the generated Dubins turn may not fit on the field and we we'll move it back, forcing the -- vehicle to reverse at the start and at the end of the turn. This will be very slow, and if the -- vehicle is able to reverse, a Reeds-Shepp would be lot faster - turnManeuver = DubinsTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), - self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) + if self.steeringLength > 0 then + turnManeuver = TowedDubinsTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), + self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) + else + turnManeuver = DubinsTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), + self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) + end else turnManeuver = ReedsSheppTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 3dbf1d598..39672621c 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -17,7 +17,7 @@ TurnManeuver.CHANGE_TO_FWD_WHEN_REACHED = 'changeToFwdWhenReached' -- making sure it is lowered when we reach the start of the next row) TurnManeuver.LOWER_IMPLEMENT_AT_TURN_END = 'lowerImplementAtTurnEnd' -- Mark waypoints for dynamic tight turn offset -TurnManeuver.applyTightTurnOffset = true +TurnManeuver.tightTurnOffsetEnabled = false ---@param course Course function TurnManeuver.hasTurnControl(course, ix, control) @@ -44,6 +44,7 @@ function TurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRa -- how far the furthest point of the maneuver is from the vehicle's direction node, used to -- check if we can turn on the field self.dzMax = -math.huge + self.turnEndXOffset = self.turnEndXOffset or 0 end function TurnManeuver:getCourse() @@ -284,8 +285,9 @@ function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSe self:debug('Reverse to work start (implement in back)') -- vehicle in front of the work start node at turn end local forwardAfterTurn = Course.createFromNode(self.vehicle, self.turnContext.vehicleAtTurnEndNode, 0, - dFromTurnEnd + 1, dFromTurnEnd + 1 + self.steeringLength, 0.8, false) + dFromTurnEnd + 1 + self.steeringLength / 2, dFromTurnEnd + 1 + self.steeringLength, 0.8, false) courseWithReversing:append(forwardAfterTurn) + --self:applyTightTurnOffset(forwardAfterTurn:getLength()) -- allow early direction change when aligned TurnManeuver.setTurnControlForLastWaypoints(courseWithReversing, forwardAfterTurn:getLength(), TurnManeuver.CHANGE_DIRECTION_WHEN_ALIGNED, true, true) @@ -306,6 +308,7 @@ function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSe local forwardAfterTurn = Course.createFromNode(self.vehicle, self.turnContext.workStartNode, 0, dFromWorkStart, 1, 0.8, false) courseWithReversing:append(forwardAfterTurn) + --self:applyTightTurnOffset(forwardAfterTurn:getLength()) TurnManeuver.setTurnControlForLastWaypoints(courseWithReversing, forwardAfterTurn:getLength(), TurnManeuver.CHANGE_DIRECTION_WHEN_ALIGNED, true, true) end @@ -315,45 +318,43 @@ function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSe endingTurnLength = reverseAfterTurn:getLength() else self:debug('Reverse to work start not needed') - endingTurnLength = self.turnContext:appendEndingTurnCourse(courseWithReversing, self.steeringLength, self.applyTightTurnOffset) + endingTurnLength = self.turnContext:appendEndingTurnCourse(courseWithReversing, self.steeringLength, self.tightTurnOffsetEnabled) end return courseWithReversing, endingTurnLength end +function TurnManeuver:applyTightTurnOffset(length) + if self.tightTurnOffsetEnabled then + -- use the default length (a quarter circle) unless there is a configured value + length = length or self.turningRadius * math.pi + self.course:setUseTightTurnOffsetForLastWaypoints( + g_vehicleConfigurations:getRecursively(self.vehicle, 'tightTurnOffsetDistanceInTurns') or length) + end +end + ---@class AnalyticTurnManeuver : TurnManeuver AnalyticTurnManeuver = CpObject(TurnManeuver) function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) TurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength) self:debug('Start generating') - self:debug('r=%.1f, w=%.1f, steeringLength=%.1f, distanceToFieldEdge=%.1f', - turningRadius, workWidth, steeringLength, distanceToFieldEdge) - local turnEndNode, goalOffset = self.turnContext:getTurnEndNodeAndOffsets(self.steeringLength) - self.course = self:findAnalyticPath(vehicleDirectionNode, 0, turnEndNode, 0, goalOffset, self.turningRadius) + local turnEndNode, endZOffset = self.turnContext:getTurnEndNodeAndOffsets(self.steeringLength) + self:debug('r=%.1f, w=%.1f, steeringLength=%.1f, distanceToFieldEdge=%.1f, goalOffset=%.1f', + turningRadius, workWidth, steeringLength, distanceToFieldEdge, endZOffset) + self.course = self:findAnalyticPath(vehicleDirectionNode, 0, 0, turnEndNode, self.turnEndXOffset, endZOffset, self.turningRadius) local endingTurnLength local dBack = self:getDistanceToMoveBack(self.course, workWidth, distanceToFieldEdge) local canReverse = AIUtil.canReverse(vehicle) if dBack > 0 and canReverse then dBack = dBack < 2 and 2 or dBack self:debug('Not enough space on field, regenerating course back %.1f meters', dBack) - self.course = self:findAnalyticPath(vehicleDirectionNode, -dBack, turnEndNode, 0, goalOffset + dBack, self.turningRadius) - if self.applyTightTurnOffset then - self.course:setUseTightTurnOffsetForLastWaypoints( - g_vehicleConfigurations:getRecursively(vehicle, 'tightTurnOffsetDistanceInTurns') or 10) - end + self.course = self:findAnalyticPath(vehicleDirectionNode, 0, -dBack, turnEndNode, self.turnEndXOffset, endZOffset + dBack, self.turningRadius) + self:applyTightTurnOffset(1000) local ixBeforeEndingTurnSection = self.course:getNumberOfWaypoints() self.course, endingTurnLength = self:adjustCourseToFitField(self.course, dBack, ixBeforeEndingTurnSection) else - if self.applyTightTurnOffset then - self.course:setUseTightTurnOffsetForLastWaypoints( - g_vehicleConfigurations:getRecursively(vehicle, 'tightTurnOffsetDistanceInTurns') or 10) - end - endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, steeringLength, self.applyTightTurnOffset) - end - if self.applyTightTurnOffset then - -- make sure we use tight turn offset towards the end of the course so a towed implement is aligned with the new row - self.course:setUseTightTurnOffsetForLastWaypoints( - g_vehicleConfigurations:getRecursively(vehicle, 'tightTurnOffsetDistanceInTurns') or 10) + self:applyTightTurnOffset(1000) + endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, steeringLength, self.tightTurnOffsetEnabled) end TurnManeuver.setLowerImplements(self.course, endingTurnLength, true) end @@ -363,18 +364,17 @@ function AnalyticTurnManeuver:getDistanceToMoveBack(course, workWidth, distanceT local dzMax = self:getDzMax(course) local spaceNeededOnFieldForTurn = dzMax + workWidth / 2 distanceToFieldEdge = distanceToFieldEdge or 500 -- if not given, assume we have a lot of space - if self.turnContext:getTurnEndForwardOffset() < 0 then - -- in an offset turn, where the turn start (and thus, the vehicle) is on the longer leg, - -- so the turn end is behind the turn start, we have in reality less space, as we measured the - -- distance to the field edge from the turn start, but we need to measure it from the turn end, - -- where there's less space - distanceToFieldEdge = distanceToFieldEdge + self.turnContext:getTurnEndForwardOffset() - end + local turnEndForwardOffset = self.turnContext:getTurnEndForwardOffset() + -- in an offset turn, where the turn start (and thus, the vehicle) is on the longer leg, + -- so the turn end is behind the turn start, we have in reality less space, as we measured the + -- distance to the field edge from the turn start, but we need to measure it from the middle of the turn, + -- where there's less space + distanceToFieldEdge = distanceToFieldEdge + turnEndForwardOffset / 2 -- with a headland at angle, we have to move further back, so the left/right edge of the swath also stays on -- the field, not only the center distanceToFieldEdge = distanceToFieldEdge - (workWidth / 2 / math.abs(math.tan(self.turnContext:getHeadlandAngle()))) - self:debug('dzMax=%.1f, workWidth=%.1f, spaceNeeded=%.1f, distanceToFieldEdge=%.1f', dzMax, workWidth, - spaceNeededOnFieldForTurn, distanceToFieldEdge) + self:debug('dzMax=%.1f, workWidth=%.1f, spaceNeeded=%.1f, turnEndForwardOffset=%.1f, distanceToFieldEdge=%.1f', dzMax, workWidth, + spaceNeededOnFieldForTurn, turnEndForwardOffset, distanceToFieldEdge) return spaceNeededOnFieldForTurn - distanceToFieldEdge end @@ -383,46 +383,41 @@ DubinsTurnManeuver = CpObject(AnalyticTurnManeuver) function DubinsTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) self.debugPrefix = '(DubinsTurn): ' + self.turnEndXOffset = 0 AnalyticTurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) end -function DubinsTurnManeuver:findAnalyticPath(vehicleDirectionNode, startOffset, turnEndNode, - xOffset, goalOffset, turningRadius) +function DubinsTurnManeuver:findAnalyticPath(startNode, startXOffset, startZOffset, endNode, + endXOffset, endZOffset, turningRadius) local path = PathfinderUtil.findAnalyticPath(PathfinderUtil.dubinsSolver, - vehicleDirectionNode, startOffset, turnEndNode, 0, goalOffset, self.turningRadius) + startNode, startXOffset, startZOffset, endNode, endXOffset, endZOffset, self.turningRadius) return Course.createFromAnalyticPath(self.vehicle, path, true) end ---@class TowedDubinsTurnManeuver : DubinsTurnManeuver TowedDubinsTurnManeuver = CpObject(DubinsTurnManeuver) -TowedDubinsTurnManeuver.applyTightTurnOffset = false function TowedDubinsTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) self.debugPrefix = '(TowedDubinsTurn): ' - local offset = AIUtil.getOffsetForTowBarLength(turningRadius, steeringLength) - turningRadius = turningRadius - offset - self:debug('Towed implement, adjusting radius to %.1f to accommodate tight turn offset', turningRadius) + local xOffset = turningRadius - AIUtil.getImplementRadiusFromTractorRadius(turningRadius, steeringLength) + self.turnEndXOffset = turnContext:isLeftTurn() and -xOffset or xOffset + self:debug('Towed implement, offsetting turn end %.1f to accommodate tight turn ', xOffset) AnalyticTurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) + --self:calculateTractorCourse(self.course) end function TowedDubinsTurnManeuver:calculateTractorCourse(course) - for _, wp in ipairs(course.waypoints) do - local v = PathfinderUtil.getWaypointAsState3D(wp, 0, self.steeringLength) - wp.x, wp.z = v.x, -v.y + local offsetX = 0 + for ix, wp in ipairs(course.waypoints) do + offsetX = AIUtil.calculateTightTurnOffsetForTurnManeuver(self.vehicle, self.steeringLength, course, ix, offsetX) + wp:setOffsetPosition(offsetX, 0) end course:enrichWaypointData() return course end -function TowedDubinsTurnManeuver:findAnalyticPath(vehicleDirectionNode, startOffset, turnEndNode, - xOffset, goalOffset, turningRadius) - local path = PathfinderUtil.findAnalyticPath(PathfinderUtil.dubinsSolver, - vehicleDirectionNode, startOffset, turnEndNode, 0, goalOffset, self.turningRadius) - return self:calculateTractorCourse(Course.createFromAnalyticPath(self.vehicle, path, true)) -end - ---@class LeftTurnReedsSheppSolver : ReedsSheppSolver LeftTurnReedsSheppSolver = CpObject(ReedsSheppSolver) function LeftTurnReedsSheppSolver:solve(start, goal, turnRadius) @@ -451,8 +446,8 @@ function ReedsSheppTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, workWidth, steeringLength, distanceToFieldEdge) end -function ReedsSheppTurnManeuver:findAnalyticPath(vehicleDirectionNode, startOffset, turnEndNode, - xOffset, goalOffset, turningRadius) +function ReedsSheppTurnManeuver:findAnalyticPath(vehicleDirectionNode, startXOffset, startZOffset, turnEndNode, + endXOffset, endZOffset, turningRadius) local solver if self.turnContext:isLeftTurn() then self:debug('using LeftTurnReedsSheppSolver') @@ -461,12 +456,12 @@ function ReedsSheppTurnManeuver:findAnalyticPath(vehicleDirectionNode, startOffs self:debug('using RightTurnReedsSheppSolver') solver = RightTurnReedsSheppSolver() end - local path = PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, startOffset, turnEndNode, - 0, goalOffset, self.turningRadius) + local path = PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, startXOffset, startZOffset, turnEndNode, + 0, endZOffset, self.turningRadius) if not path or #path == 0 then self:debug('Could not find ReedsShepp path, retry with Dubins') - path = PathfinderUtil.findAnalyticPath(PathfinderUtil.dubinsSolver, vehicleDirectionNode, startOffset, - turnEndNode, 0, goalOffset, self.turningRadius) + path = PathfinderUtil.findAnalyticPath(PathfinderUtil.dubinsSolver, vehicleDirectionNode, startXOffset, startZOffset, + turnEndNode, 0, endZOffset, self.turningRadius) end local course = Course.createFromAnalyticPath(self.vehicle, path, true) course:adjustForTowedImplements(1.5 * self.steeringLength + 1) @@ -503,10 +498,7 @@ function TurnEndingManeuver:init(vehicle, turnContext, vehicleDirectionNode, tur self:generateStraightSection(endArc, endStraight) myCorner:delete() self.course = Course(vehicle, self.waypoints, true) - if self.applyTightTurnOffset then - self.course:setUseTightTurnOffsetForLastWaypoints( - g_vehicleConfigurations:getRecursively(vehicle, 'tightTurnOffsetDistanceInTurns') or 20) - end + self:applyTightTurnOffset() TurnManeuver.setLowerImplements(self.course, math.max(math.abs(turnContext.frontMarkerDistance), steeringLength)) end @@ -627,7 +619,7 @@ function VineTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turni turningRadius, workWidth, dz, startOffset, goalOffset) local path = PathfinderUtil.findAnalyticPath(PathfinderUtil.dubinsSolver, -- always move the goal a bit backwards to let the vehicle align - vehicleDirectionNode, startOffset, turnEndNode, 0, goalOffset - turnContext.frontMarkerDistance, self.turningRadius) + vehicleDirectionNode, startOffset, 0, turnEndNode, 0, goalOffset - turnContext.frontMarkerDistance, self.turningRadius) self.course = Course.createFromAnalyticPath(self.vehicle, path, true) local endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, 0, false) TurnManeuver.setLowerImplements(self.course, endingTurnLength, true) diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index 164552fb2..f98009814 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -604,7 +604,9 @@ end ------------------------------------------------------------------------------------------------------------------------ ---@param solver AnalyticSolver for instance PathfinderUtil.dubinsSolver or PathfinderUtil.reedsSheppSolver ---@param vehicleDirectionNode number Giants node ----@param startOffset number offset in meters relative to the vehicle position (forward positive, backward negative) where +---@param startXOffset number offset in meters relative to the vehicle position (left positive, right negative) where +--- we want the turn to start +---@param startZOffset number offset in meters relative to the vehicle position (forward positive, backward negative) where --- we want the turn to start ---@param goalReferenceNode table node used to determine the goal ---@param xOffset number offset in meters relative to the goal node (left positive, right negative) @@ -613,9 +615,9 @@ end ---@param turnRadius number vehicle turning radius ---@return table|nil path ---@return number length -function PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, startOffset, goalReferenceNode, +function PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, startXOffset, startZOffset, goalReferenceNode, xOffset, zOffset, turnRadius) - local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(vehicleDirectionNode, 0, startOffset or 0) + local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(vehicleDirectionNode, startXOffset, startZOffset or 0) local start = State3D(x, -z, CpMathUtil.angleFromGame(yRot)) x, z, yRot = PathfinderUtil.getNodePositionAndDirection(goalReferenceNode, xOffset or 0, zOffset or 0) local goal = State3D(x, -z, CpMathUtil.angleFromGame(yRot)) From d884fe5adb93642596ec39370cfd7d4f91432655 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 7 Dec 2024 18:07:02 -0500 Subject: [PATCH 077/158] fix: auto aim pipe detection for FS25 The combine unloader did not detect the chopper as a chopper because of this. --- Courseplay.lua | 10 ---------- scripts/ai/ProximityController.lua | 2 +- scripts/ai/ProximitySensor.lua | 2 +- scripts/ai/controllers/PipeController.lua | 2 +- scripts/dev/DevHelper.lua | 9 --------- scripts/specializations/CpVehicleSettings.lua | 3 --- 6 files changed, 3 insertions(+), 25 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index f365a9d85..2a9f89a02 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -175,16 +175,6 @@ function Courseplay.saveToXMLFile(missionInfo) end FSCareerMissionInfo.saveToXMLFile = Utils.prependedFunction(FSCareerMissionInfo.saveToXMLFile, Courseplay.saveToXMLFile) --- TODO 25 -function Courseplay.raiseAIEvent(vehicle, event1, event2, something, ...) - if event1 ~= 'onAIFieldWorkerActive' and event1 ~= 'onAIFieldWorkerTurnProgress' then - CpUtil.infoVehicle(vehicle, "raiseAIEvent: %s %s %s", tostring(event1), tostring(event2), tostring(something)) - g_devHelper:showFs25Debug(vehicle) - end -end - -AIVehicle.raiseAIEvent = Utils.prependedFunction(AIVehicle.raiseAIEvent, Courseplay.raiseAIEvent) - function Courseplay:update(dt) g_devHelper:update() g_bunkerSiloManager:update(dt) diff --git a/scripts/ai/ProximityController.lua b/scripts/ai/ProximityController.lua index f6e057c53..534e87930 100644 --- a/scripts/ai/ProximityController.lua +++ b/scripts/ai/ProximityController.lua @@ -21,7 +21,6 @@ function ProximityController:init(vehicle, width) self.blockingVehicle = CpTemporaryObject(nil) self.blockingObject = CpTemporaryObject(nil) self.ignoreObjectCallbackRegistrations = {} - self:setState(self.states.NO_OBSTACLE, 'proximity controller initialized') local frontMarker, backMarker = Markers.getMarkerNodes(self.vehicle) self.forwardLookingProximitySensorPack = WideForwardLookingProximitySensorPack( self.vehicle, frontMarker, self.sensorRange, 1, width) @@ -29,6 +28,7 @@ function ProximityController:init(vehicle, width) self.vehicle, backMarker, self.sensorRange, 1, self.vehicle.size.width) self.showBlockedByObjectMessageTimer = CpTemporaryObject(false) + self:setState(self.states.NO_OBSTACLE, 'proximity controller initialized') end function ProximityController:setState(state, debugString) diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index ef9e00625..c97e3a617 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -182,7 +182,7 @@ function ProximitySensor:showDebugInfo() end end end - renderText(0.6, 0.4 + self.yRotation / 10, 0.018, text .. string.format(' %d', math.deg(self.yRotation))) + renderText(0.6, 0.4 + self.yRotation / 5, 0.012, text .. string.format(' %d', math.deg(self.yRotation))) end ---@class ProximitySensorPack diff --git a/scripts/ai/controllers/PipeController.lua b/scripts/ai/controllers/PipeController.lua index b12ccb806..7a4b38586 100644 --- a/scripts/ai/controllers/PipeController.lua +++ b/scripts/ai/controllers/PipeController.lua @@ -209,7 +209,7 @@ function PipeController:getClosestObject() end function PipeController:isAutoAimPipe() - return #self.pipeSpec.autoAimingStates > 0 + return self.pipeSpec.numAutoAimingStates > 0 end function PipeController:handleChopperPipe() diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 06e011cca..0bcdb4895 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -312,15 +312,6 @@ function DevHelper:showDriveData() strategy.reverser:getDriveData() end -function DevHelper:showFs25Debug(vehicle) - local a, b = vehicle:getCanAIFieldWorkerContinueWork() - local c, d = vehicle:getAttachedAIImplements()[1] and vehicle:getAttachedAIImplements()[1].object:getCanAIImplementContinueWork() - local e, f = vehicle:getIsMotorStarted() - local g, h = vehicle.getCanBeTurnedOn and vehicle:getCanBeTurnedOn() - CpUtil.infoVehicle(vehicle, 'fw can work: %s %s | imp1 can work: %s %s | motorstarted: %s %s | canbeturnedon: %s %s', tostring(a), tostring(b), tostring(c), tostring(d), tostring(e), tostring(f), tostring(g), tostring(h)) - --printCallstack() -end - -- make sure to recreate the global dev helper whenever this script is (re)loaded g_devHelper = DevHelper() diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua index a84227ce6..99036a50e 100644 --- a/scripts/specializations/CpVehicleSettings.lua +++ b/scripts/specializations/CpVehicleSettings.lua @@ -125,9 +125,6 @@ end --- Changes the sprayer work width on fill type change, as it might depend on the loaded fill type. --- For example Lime and Fertilizer might have a different work width. function CpVehicleSettings:onStateChange(state, data) - -- TODO 25 - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, self, 'onStateChange %s', tostring(state)) - g_devHelper:showFs25Debug(self) local spec = self.spec_cpVehicleSettings if state == VehicleStateChange.FILLTYPE_CHANGE and self:getIsSynchronized() then local _, hasSprayer = AIUtil.getAllChildVehiclesWithSpecialization(self, Sprayer, nil) From 5394a6bf6bafc837286449bcc3ef146478768c6c Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 8 Dec 2024 13:58:07 -0500 Subject: [PATCH 078/158] wip --- scripts/ai/AIUtil.lua | 16 ++++++++++++++-- scripts/ai/turns/TurnContext.lua | 1 + scripts/ai/turns/TurnManeuver.lua | 24 ++++++++++++++++-------- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/scripts/ai/AIUtil.lua b/scripts/ai/AIUtil.lua index a3907555c..cfa6c6e2c 100644 --- a/scripts/ai/AIUtil.lua +++ b/scripts/ai/AIUtil.lua @@ -179,13 +179,25 @@ function AIUtil.getOffsetForTowBarLength(r, towBarLength) return AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) - r end +--- When a tractor is towing an implement in a turn, on what radius will the implement be if +--- the radius the tractor is driving is known? +---@param r number the radius the tractor is on +---@param towBarLength number the length of the tow bar +---@return number the radius the implement will be on. Can be negative, meaning the implement will be +--- moving backwards in the turn function AIUtil.getImplementRadiusFromTractorRadius(r, towBarLength) - local rImplement = math.sqrt( r * r - towBarLength * towBarLength ) -- the radius the tractor should be on + local rSquared = r * r - towBarLength * towBarLength + local rImplement = rSquared > 0 and math.sqrt(rSquared) or -math.sqrt(-rSquared) return rImplement end +--- When a tractor is towing an implement in a turn, on what radius will the tractor be if +--- the radius the implement is known? +---@param r number the radius the implement is following +---@param towBarLength number the length of the tow bar +---@return number the radius the tractor will be on function AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) - local rTractor = math.sqrt( r * r + towBarLength * towBarLength ) -- the radius the tractor should be on + local rTractor = math.sqrt( r * r + towBarLength * towBarLength ) return rTractor end diff --git a/scripts/ai/turns/TurnContext.lua b/scripts/ai/turns/TurnContext.lua index ae6d3e9af..df2c92099 100644 --- a/scripts/ai/turns/TurnContext.lua +++ b/scripts/ai/turns/TurnContext.lua @@ -230,6 +230,7 @@ end ---@return number angle (radian) between the row and the headland, 90 degrees means the headland is perpendicular to the row function TurnContext:getHeadlandAngle() + print(self.turnEndWp.angle, self.turnStartWp.angle) return math.abs(CpMathUtil.getDeltaAngle(math.rad(self.turnEndWp.angle), math.rad(self.turnStartWp.angle))) end diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 39672621c..2a6baa158 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -339,8 +339,13 @@ function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, t self:debug('Start generating') local turnEndNode, endZOffset = self.turnContext:getTurnEndNodeAndOffsets(self.steeringLength) - self:debug('r=%.1f, w=%.1f, steeringLength=%.1f, distanceToFieldEdge=%.1f, goalOffset=%.1f', - turningRadius, workWidth, steeringLength, distanceToFieldEdge, endZOffset) + local _, _, dz = localToLocal(vehicleDirectionNode, turnEndNode, 0, 0, 0) + -- zOffset from the turn end (work start). If there is a zOffset in the turn, that is, the turn end is behind the + -- turn start due to an angled headland, we still want to make the complete 180 turn es close to the field edge + -- as we can, so a towed implement, with an offset arc is turned 180 as soon as possible and has time to align + endZOffset = math.min(dz, endZOffset) + self:debug('r=%.1f, w=%.1f, steeringLength=%.1f, distanceToFieldEdge=%.1f, goalOffset=%.1f, dz=%.1f', + turningRadius, workWidth, steeringLength, distanceToFieldEdge, endZOffset, dz) self.course = self:findAnalyticPath(vehicleDirectionNode, 0, 0, turnEndNode, self.turnEndXOffset, endZOffset, self.turningRadius) local endingTurnLength local dBack = self:getDistanceToMoveBack(self.course, workWidth, distanceToFieldEdge) @@ -372,9 +377,11 @@ function AnalyticTurnManeuver:getDistanceToMoveBack(course, workWidth, distanceT distanceToFieldEdge = distanceToFieldEdge + turnEndForwardOffset / 2 -- with a headland at angle, we have to move further back, so the left/right edge of the swath also stays on -- the field, not only the center - distanceToFieldEdge = distanceToFieldEdge - (workWidth / 2 / math.abs(math.tan(self.turnContext:getHeadlandAngle()))) - self:debug('dzMax=%.1f, workWidth=%.1f, spaceNeeded=%.1f, turnEndForwardOffset=%.1f, distanceToFieldEdge=%.1f', dzMax, workWidth, - spaceNeededOnFieldForTurn, turnEndForwardOffset, distanceToFieldEdge) + local headlandAngle = self.turnContext:getHeadlandAngle() + distanceToFieldEdge = distanceToFieldEdge - + (headlandAngle > 0.0001 and (workWidth / 2 / math.abs(math.tan(headlandAngle))) or 0) + self:debug('dzMax=%.1f, workWidth=%.1f, spaceNeeded=%.1f, turnEndForwardOffset=%.1f, headlandAngle=%.1f, distanceToFieldEdge=%.1f', dzMax, workWidth, + spaceNeededOnFieldForTurn, turnEndForwardOffset, math.deg(headlandAngle), distanceToFieldEdge) return spaceNeededOnFieldForTurn - distanceToFieldEdge end @@ -400,12 +407,13 @@ TowedDubinsTurnManeuver = CpObject(DubinsTurnManeuver) function TowedDubinsTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) self.debugPrefix = '(TowedDubinsTurn): ' - local xOffset = turningRadius - AIUtil.getImplementRadiusFromTractorRadius(turningRadius, steeringLength) + self.vehicle = vehicle + local implementRadius = AIUtil.getImplementRadiusFromTractorRadius(turningRadius, steeringLength) + local xOffset = turningRadius - implementRadius self.turnEndXOffset = turnContext:isLeftTurn() and -xOffset or xOffset - self:debug('Towed implement, offsetting turn end %.1f to accommodate tight turn ', xOffset) + self:debug('Towed implement, offsetting turn end %.1f to accommodate tight turn, implement radius %.1f ', xOffset, implementRadius) AnalyticTurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) - --self:calculateTractorCourse(self.course) end function TowedDubinsTurnManeuver:calculateTractorCourse(course) From 197f52422fcedea57e625a13fc0c26314e58973c Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Mon, 9 Dec 2024 17:17:52 +0100 Subject: [PATCH 079/158] add piros to config --- config/VehicleConfigurations.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index fb9794ab1..388b1cea2 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -270,6 +270,9 @@ You can define the following custom settings: toolOffsetX = "-1.8" noReverse = "true" /> + <Vehicle name="prios440.xml" + implementWheelAlwaysOnGround = "true" + /> <!--\vehicles\ropa--> <Vehicle name="keiler2.xml" toolOffsetX = "-2.0" From 7896a2d75852a44cf3d897878a3caa584262c4f4 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 9 Dec 2024 12:02:09 -0500 Subject: [PATCH 080/158] wip --- scripts/ai/turns/TurnManeuver.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 2a6baa158..c23ae4700 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -17,7 +17,7 @@ TurnManeuver.CHANGE_TO_FWD_WHEN_REACHED = 'changeToFwdWhenReached' -- making sure it is lowered when we reach the start of the next row) TurnManeuver.LOWER_IMPLEMENT_AT_TURN_END = 'lowerImplementAtTurnEnd' -- Mark waypoints for dynamic tight turn offset -TurnManeuver.tightTurnOffsetEnabled = false +TurnManeuver.tightTurnOffsetEnabled = true ---@param course Course function TurnManeuver.hasTurnControl(course, ix, control) @@ -354,11 +354,11 @@ function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, t dBack = dBack < 2 and 2 or dBack self:debug('Not enough space on field, regenerating course back %.1f meters', dBack) self.course = self:findAnalyticPath(vehicleDirectionNode, 0, -dBack, turnEndNode, self.turnEndXOffset, endZOffset + dBack, self.turningRadius) - self:applyTightTurnOffset(1000) + self:applyTightTurnOffset() local ixBeforeEndingTurnSection = self.course:getNumberOfWaypoints() self.course, endingTurnLength = self:adjustCourseToFitField(self.course, dBack, ixBeforeEndingTurnSection) else - self:applyTightTurnOffset(1000) + self:applyTightTurnOffset() endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, steeringLength, self.tightTurnOffsetEnabled) end TurnManeuver.setLowerImplements(self.course, endingTurnLength, true) @@ -411,6 +411,7 @@ function TowedDubinsTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode local implementRadius = AIUtil.getImplementRadiusFromTractorRadius(turningRadius, steeringLength) local xOffset = turningRadius - implementRadius self.turnEndXOffset = turnContext:isLeftTurn() and -xOffset or xOffset + self.turnEndXOffset = 0 self:debug('Towed implement, offsetting turn end %.1f to accommodate tight turn, implement radius %.1f ', xOffset, implementRadius) AnalyticTurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) From 1bdde1eb414f34071cdba0ce06dba0d6a0ef9845 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 9 Dec 2024 13:02:16 -0500 Subject: [PATCH 081/158] fix: callstack in shop while selecting other variant --- scripts/specializations/CpCourseManager.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index f941826fe..35ea92200 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -217,8 +217,6 @@ end function CpCourseManager:addCourse(course,noEventSend) local spec = self.spec_cpCourseManager - -- reset temporary offset field course, this will be regenerated based on the current settings when the job starts - spec.offsetFieldWorkCourse = nil course:setVehicle(self) table.insert(spec.courses,course) SpecializationUtil.raiseEvent(self,"onCpCourseChange",course,noEventSend) @@ -226,10 +224,11 @@ end function CpCourseManager:resetCourses() local spec = self.spec_cpCourseManager - spec.offsetFieldWorkCourse = nil - spec.courses = {} - spec.assignedCoursesID = nil - SpecializationUtil.raiseEvent(self,"onCpCourseChange") + if spec.courses then + spec.courses = {} + spec.assignedCoursesID = nil + SpecializationUtil.raiseEvent(self,"onCpCourseChange") + end end function CpCourseManager:resetCpCoursesFromGui() From 114b54f0f5ea1b6102260d2622a89272e72f1506 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 9 Dec 2024 13:02:16 -0500 Subject: [PATCH 082/158] fix: callstack in shop while selecting other variant --- scripts/specializations/CpCourseManager.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index f941826fe..35ea92200 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -217,8 +217,6 @@ end function CpCourseManager:addCourse(course,noEventSend) local spec = self.spec_cpCourseManager - -- reset temporary offset field course, this will be regenerated based on the current settings when the job starts - spec.offsetFieldWorkCourse = nil course:setVehicle(self) table.insert(spec.courses,course) SpecializationUtil.raiseEvent(self,"onCpCourseChange",course,noEventSend) @@ -226,10 +224,11 @@ end function CpCourseManager:resetCourses() local spec = self.spec_cpCourseManager - spec.offsetFieldWorkCourse = nil - spec.courses = {} - spec.assignedCoursesID = nil - SpecializationUtil.raiseEvent(self,"onCpCourseChange") + if spec.courses then + spec.courses = {} + spec.assignedCoursesID = nil + SpecializationUtil.raiseEvent(self,"onCpCourseChange") + end end function CpCourseManager:resetCpCoursesFromGui() From 98cf8b5b0d2dede235812f0fa5b26828e3620cc5 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Thu, 12 Dec 2024 13:52:44 -0500 Subject: [PATCH 083/158] fix: apply tight turn offset to end of course --- scripts/ai/turns/AITurn.lua | 13 +++++------- scripts/ai/turns/TurnContext.lua | 4 ++-- scripts/ai/turns/TurnManeuver.lua | 35 ++++++++++++++----------------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 45c896a0a..885f90710 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -728,13 +728,8 @@ function CourseTurn:generateCalculatedTurn() -- TODO: the generated Dubins turn may not fit on the field and we we'll move it back, forcing the -- vehicle to reverse at the start and at the end of the turn. This will be very slow, and if the -- vehicle is able to reverse, a Reeds-Shepp would be lot faster - if self.steeringLength > 0 then - turnManeuver = TowedDubinsTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), - self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) - else - turnManeuver = DubinsTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), - self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) - end + turnManeuver = DubinsTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), + self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) else turnManeuver = ReedsSheppTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) @@ -774,7 +769,8 @@ function CourseTurn:onPathfindingDone(path) self.turnCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) -- make sure we use tight turn offset towards the end of the course so a towed implement is aligned with the new row self.turnCourse:setUseTightTurnOffsetForLastWaypoints(15) - local endingTurnLength = self.turnContext:appendEndingTurnCourse(self.turnCourse, nil, true) + local endingTurnLength = self.turnContext:appendEndingTurnCourse(self.turnCourse, nil) + self.turnCourse:setUseTightTurnOffsetForLastWaypoints(endingTurnLength) local x = AIUtil.getDirectionNodeToReverserNodeOffset(self.vehicle) self:debug('Extending course at direction switch for reversing to %.1f m (or at least 1m)', -x) self.turnCourse:adjustForReversing(math.max(1, -x)) @@ -1024,6 +1020,7 @@ function StartRowOnly:init(vehicle, driveStrategy, ppc, turnContext, startRowCou self.turnCourse:setUseTightTurnOffsetForLastWaypoints(15) -- add a turn ending section into the row to make sure the implements are lowered correctly local endingTurnLength = self.turnContext:appendEndingTurnCourse(self.turnCourse, 3, true) + self.turnCourse:setUseTightTurnOffsetForLastWaypoints(endingTurnLength) TurnManeuver.setLowerImplements(self.turnCourse, endingTurnLength, true) self.turnCourse:adjustForReversing(2) self.state = self.states.DRIVING_TO_ROW diff --git a/scripts/ai/turns/TurnContext.lua b/scripts/ai/turns/TurnContext.lua index df2c92099..c00846c0e 100644 --- a/scripts/ai/turns/TurnContext.lua +++ b/scripts/ai/turns/TurnContext.lua @@ -383,7 +383,7 @@ end ---@param extraLength number add so many meters to the calculated course (for example to allow towed implements to align --- before reversing) ---@return number length added to the course in meters -function TurnContext:appendEndingTurnCourse(course, extraLength, useTightTurnOffset) +function TurnContext:appendEndingTurnCourse(course, extraLength) -- make sure course reaches the front marker node so end it well behind that node local _, _, dzFrontMarker = course:getWaypointLocalPosition(self.vehicleAtTurnEndNode, course:getNumberOfWaypoints()) local _, _, dzWorkStart = course:getWaypointLocalPosition(self.workStartNode, course:getNumberOfWaypoints()) @@ -399,7 +399,7 @@ function TurnContext:appendEndingTurnCourse(course, extraLength, useTightTurnOff dzFrontMarker, dzWorkStart, extraLength) for d = math.min(dzFrontMarker, dzWorkStart) + 1, extraLength, 1 do local x, y, z = localToWorld(startNode, 0, 0, d) - table.insert(waypoints, {x = x, y = y, z = z, useTightTurnOffset = useTightTurnOffset or nil}) + table.insert(waypoints, {x = x, y = y, z = z}) end local oldLength = course:getLength() course:appendWaypoints(waypoints) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index c23ae4700..bdf58d785 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -287,7 +287,7 @@ function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSe local forwardAfterTurn = Course.createFromNode(self.vehicle, self.turnContext.vehicleAtTurnEndNode, 0, dFromTurnEnd + 1 + self.steeringLength / 2, dFromTurnEnd + 1 + self.steeringLength, 0.8, false) courseWithReversing:append(forwardAfterTurn) - --self:applyTightTurnOffset(forwardAfterTurn:getLength()) + self:applyTightTurnOffset(forwardAfterTurn:getLength()) -- allow early direction change when aligned TurnManeuver.setTurnControlForLastWaypoints(courseWithReversing, forwardAfterTurn:getLength(), TurnManeuver.CHANGE_DIRECTION_WHEN_ALIGNED, true, true) @@ -308,7 +308,7 @@ function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSe local forwardAfterTurn = Course.createFromNode(self.vehicle, self.turnContext.workStartNode, 0, dFromWorkStart, 1, 0.8, false) courseWithReversing:append(forwardAfterTurn) - --self:applyTightTurnOffset(forwardAfterTurn:getLength()) + self:applyTightTurnOffset(forwardAfterTurn:getLength()) TurnManeuver.setTurnControlForLastWaypoints(courseWithReversing, forwardAfterTurn:getLength(), TurnManeuver.CHANGE_DIRECTION_WHEN_ALIGNED, true, true) end @@ -318,14 +318,15 @@ function TurnManeuver:adjustCourseToFitField(course, dBack, ixBeforeEndingTurnSe endingTurnLength = reverseAfterTurn:getLength() else self:debug('Reverse to work start not needed') - endingTurnLength = self.turnContext:appendEndingTurnCourse(courseWithReversing, self.steeringLength, self.tightTurnOffsetEnabled) + endingTurnLength = self.turnContext:appendEndingTurnCourse(courseWithReversing, self.steeringLength) + self:applyTightTurnOffset(endingTurnLength) end return courseWithReversing, endingTurnLength end function TurnManeuver:applyTightTurnOffset(length) if self.tightTurnOffsetEnabled then - -- use the default length (a quarter circle) unless there is a configured value + -- use the default length (a half circle) unless there is a configured value length = length or self.turningRadius * math.pi self.course:setUseTightTurnOffsetForLastWaypoints( g_vehicleConfigurations:getRecursively(self.vehicle, 'tightTurnOffsetDistanceInTurns') or length) @@ -340,9 +341,12 @@ function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, t local turnEndNode, endZOffset = self.turnContext:getTurnEndNodeAndOffsets(self.steeringLength) local _, _, dz = localToLocal(vehicleDirectionNode, turnEndNode, 0, 0, 0) - -- zOffset from the turn end (work start). If there is a zOffset in the turn, that is, the turn end is behind the - -- turn start due to an angled headland, we still want to make the complete 180 turn es close to the field edge - -- as we can, so a towed implement, with an offset arc is turned 180 as soon as possible and has time to align + -- zOffset from the turn end (work start). If there is a negative zOffset in the turn, that is, the turn end is behind the + -- turn start due to an angled headland, we still want to make the complete 180 turn as close to the field edge + -- as we can, so a towed implement, with an offset arc is turned 180 as soon as possible and has time to align. + -- This way, the tight turn offset can make its magic during the 180 turn. Otherwise, the Dubins generated will split + -- the 180 into two turns, one over 120 at the turn start, and one less than 60 at the turn end. This latter one + -- is not enough direction change for the tight turn offset to work. endZOffset = math.min(dz, endZOffset) self:debug('r=%.1f, w=%.1f, steeringLength=%.1f, distanceToFieldEdge=%.1f, goalOffset=%.1f, dz=%.1f', turningRadius, workWidth, steeringLength, distanceToFieldEdge, endZOffset, dz) @@ -359,7 +363,8 @@ function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, t self.course, endingTurnLength = self:adjustCourseToFitField(self.course, dBack, ixBeforeEndingTurnSection) else self:applyTightTurnOffset() - endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, steeringLength, self.tightTurnOffsetEnabled) + endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, steeringLength) + self:applyTightTurnOffset(endingTurnLength) end TurnManeuver.setLowerImplements(self.course, endingTurnLength, true) end @@ -402,6 +407,9 @@ function DubinsTurnManeuver:findAnalyticPath(startNode, startXOffset, startZOffs return Course.createFromAnalyticPath(self.vehicle, path, true) end +-- This is an experiment to create turns with towed implements that better align with the next row. +-- Instead of relying on the dynamic tight turn offset, we offset the turn end already while generating the turn +-- to get the implement closer to the next row. ---@class TowedDubinsTurnManeuver : DubinsTurnManeuver TowedDubinsTurnManeuver = CpObject(DubinsTurnManeuver) function TowedDubinsTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, @@ -411,22 +419,11 @@ function TowedDubinsTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode local implementRadius = AIUtil.getImplementRadiusFromTractorRadius(turningRadius, steeringLength) local xOffset = turningRadius - implementRadius self.turnEndXOffset = turnContext:isLeftTurn() and -xOffset or xOffset - self.turnEndXOffset = 0 self:debug('Towed implement, offsetting turn end %.1f to accommodate tight turn, implement radius %.1f ', xOffset, implementRadius) AnalyticTurnManeuver.init(self, vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) end -function TowedDubinsTurnManeuver:calculateTractorCourse(course) - local offsetX = 0 - for ix, wp in ipairs(course.waypoints) do - offsetX = AIUtil.calculateTightTurnOffsetForTurnManeuver(self.vehicle, self.steeringLength, course, ix, offsetX) - wp:setOffsetPosition(offsetX, 0) - end - course:enrichWaypointData() - return course -end - ---@class LeftTurnReedsSheppSolver : ReedsSheppSolver LeftTurnReedsSheppSolver = CpObject(ReedsSheppSolver) function LeftTurnReedsSheppSolver:solve(start, goal, turnRadius) From 6810659eb93edf53337349e844cf7bc77359cacd Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 13 Dec 2024 02:00:37 -0500 Subject: [PATCH 084/158] feat: rice field detection For now, you'll need 0.8m field margin. --- scripts/field/CpFieldUtil.lua | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index 4d3793ad1..b5ed2a7a4 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -130,6 +130,10 @@ function CpFieldUtil.isField(x, z, widthX, widthZ) return isField, area, totalArea end +--- Get the field polygon vertices from the map. These determine the field boundary in the game's +--- initial state. These do not reflect changes made by terrain modification or plowing, to get a +--- field polygon with those changes, use the FieldScanner +---@return table [{x, y, z}] field polygon vertices function CpFieldUtil.getFieldPolygon(field) local unpackedVertices = field:getDensityMapPolygon():getVerticesList() local vertices = {} @@ -140,6 +144,17 @@ function CpFieldUtil.getFieldPolygon(field) return vertices end +--- Rice fields work differently, just defined by their polygon, no density map, so the scanner +--- wouldn't work +function CpFieldUtil.getRiceFieldPolygon(riceField) + local vertices = {} + for i = 1, riceField.polygon:getNumVertices() do + local x, z = riceField.polygon:getVertex(i) + table.insert(vertices, { x = x, y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z), z = z }) + end + return vertices +end + --- Get the field polygon (field edge vertices) at the world position. --- If there is also a custom field at the position it may return that, depending on the user's preference set. ---@return {x, y, z}[], boolean the field polygon, nil if not on field. True if a custom field was selected @@ -176,8 +191,15 @@ function CpFieldUtil.detectFieldBoundary(x, z, detect, useGiantsDetector) -- not implemented return nil else - local valid, points = g_fieldScanner:findContour(x, z) - return valid and points or nil + local y = getTerrainHeightAtWorldPos(g_terrainNode, x, 0, z) + local _, _, _, riceField = PlaceableRiceField.getRiceFieldAtPosition(x, y, z) + if riceField then + -- rice fields are somewhat special, so always use the Giants method + return CpFieldUtil.getRiceFieldPolygon(riceField) + else + local valid, points = g_fieldScanner:findContour(x, z) + return valid and points or nil + end end else local field = CpFieldUtil.getFieldAtWorldPosition(x, z) From 2d8ec0e898897d37a6f7bd3b08ea1ee77a58bc18 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 13 Dec 2024 02:11:36 -0500 Subject: [PATCH 085/158] feat: rice field detection Apply 0.8 offset to rice fields to accurately place the boundary. --- scripts/courseGenerator/FieldworkCourse.lua | 1 - scripts/field/CpFieldUtil.lua | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/courseGenerator/FieldworkCourse.lua b/scripts/courseGenerator/FieldworkCourse.lua index 2bdfbbf71..4484c2349 100644 --- a/scripts/courseGenerator/FieldworkCourse.lua +++ b/scripts/courseGenerator/FieldworkCourse.lua @@ -455,7 +455,6 @@ function FieldworkCourse:_getHeadlandOffset(n) return self:_getHeadlandWorkingWidth(1) / 2 else -- for n > 1, the headland width is with the overlap - print(n) return self:_getHeadlandWorkingWidth(1) + (n - 1 - 0.5) * self:_getHeadlandWorkingWidth(n) end end diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index b5ed2a7a4..317fb48c2 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -147,12 +147,15 @@ end --- Rice fields work differently, just defined by their polygon, no density map, so the scanner --- wouldn't work function CpFieldUtil.getRiceFieldPolygon(riceField) - local vertices = {} + local boundary = Polygon() for i = 1, riceField.polygon:getNumVertices() do local x, z = riceField.polygon:getVertex(i) - table.insert(vertices, { x = x, y = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, x, 1, z), z = z }) + boundary:append({ x = x, y = -z }) end - return vertices + boundary:calculateProperties() + -- for some reason, the rice field boundary is wider than the actual field, so shrink it a bit + local offsetBoundary = CourseGenerator.Headland(boundary, boundary:isClockwise(), 0, 0.8, false):getPolygon() + return CpMathUtil.pointsToGameInPlace(offsetBoundary) end --- Get the field polygon (field edge vertices) at the world position. From c730aab18f7c0143229714a9d5cf593c4d8a2072 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Fri, 13 Dec 2024 10:45:55 -0500 Subject: [PATCH 086/158] feat: tight turn offset for articulated vehicles Enable tight turn offset for these, just use more smoothing. --- scripts/ai/AIUtil.lua | 11 ++++++----- scripts/ai/turns/AITurn.lua | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/ai/AIUtil.lua b/scripts/ai/AIUtil.lua index cfa6c6e2c..bf0e6dc48 100644 --- a/scripts/ai/AIUtil.lua +++ b/scripts/ai/AIUtil.lua @@ -110,7 +110,11 @@ function AIUtil.calculateTightTurnOffsetForTurnManeuver(vehicle, steeringLength, local tightTurnOffset local function smoothOffset(offset) - return (offset + 4 * (previousOffset or 0 )) / 5 + -- smooth more for articulated axis or track vehicle + -- as those usually have a very small turn radius anyway, causing jackknifing + -- TODO: use the vehicle's solo radius instead? + local factor = AIUtil.hasArticulatedAxis(vehicle) and 6 or 4 + return (offset + factor * (previousOffset or 0 )) / (factor + 1) end -- first of all, does the current waypoint have radius data? @@ -119,10 +123,7 @@ function AIUtil.calculateTightTurnOffsetForTurnManeuver(vehicle, steeringLength, return smoothOffset(0) end - -- Ok, looks like a tight turn, so we need to move a bit left or right of the course - -- to keep the tool on the course. Use a little less than the calculated, this is purely empirical and should probably - -- be reviewed why the calculated one seems to overshoot. - local offset = 1 * AIUtil.getTractorRadiusFromImplementRadius(r, steeringLength) - r + local offset = AIUtil.getTractorRadiusFromImplementRadius(r, steeringLength) - r if offset ~= offset then -- check for nan return smoothOffset(0) diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 885f90710..e08c9d7cd 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -734,9 +734,8 @@ function CourseTurn:generateCalculatedTurn() turnManeuver = ReedsSheppTurnManeuver(self.vehicle, self.turnContext, self.vehicle:getAIDirectionNode(), self.turningRadius, self.workWidth, self.steeringLength, distanceToFieldEdge) end - -- only use tight turn offset if we are towing something and not an articulated axis or track vehicle - -- as those usually have a very small turn radius anyway, causing jackknifing - if self.steeringLength > 0 and not AIUtil.hasArticulatedAxis(self.vehicle) then + -- only use tight turn offset if we are towing something + if self.steeringLength > 0 then self:debug('Enabling tight turn offset') self.enableTightTurnOffset = true end @@ -1014,8 +1013,7 @@ function StartRowOnly:init(vehicle, driveStrategy, ppc, turnContext, startRowCou self.forceTightTurnOffset = false local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) - self.enableTightTurnOffset = steeringLength > 0 and not AIUtil.hasArticulatedAxis(self.vehicle) - + self.enableTightTurnOffset = steeringLength > 0 -- TODO: do we need tight turn offset here? self.turnCourse:setUseTightTurnOffsetForLastWaypoints(15) -- add a turn ending section into the row to make sure the implements are lowered correctly From d5141bfd9889fb348f4d848f3c26827a830d5741 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 8 Dec 2024 12:18:17 +0100 Subject: [PATCH 087/158] Some more adjustments --- scripts/gui/CpInGameMenu.lua | 43 ++++++++- .../gui/elements/CpBinaryOptionElement.lua | 19 +--- .../gui/elements/CpOptionToggleElement.lua | 11 ++- scripts/gui/pages/CpCourseGeneratorFrame.lua | 93 +++++++++++++------ scripts/gui/pages/CpGlobalSettingsFrame.lua | 20 ++-- scripts/gui/pages/CpVehicleSettingsFrame.lua | 11 +-- 6 files changed, 128 insertions(+), 69 deletions(-) diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index d2f74a58e..398805b15 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -10,6 +10,7 @@ function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager, c self.defaultMenuButtonInfo = {} self.backButtonInfo = {} + self.currentVehicle = nil self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN, function (menu) g_gui:showGui("CpInGameMenu") @@ -41,6 +42,8 @@ function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager, c local index = self.pagingElement:getPageMappingIndexByElement(self.pageCourseManager) self.pageSelector:setState(index, true) end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED, + self.onCurrentVehicleChanged, self) return self end @@ -72,6 +75,7 @@ function CpInGameMenu.setupGui(courseStorage) MessageType.GUI_CP_INGAME_OPEN_VEHICLE_SETTINGS = nextMessageTypeId() MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR = nextMessageTypeId() MessageType.GUI_CP_INGAME_OPEN_COURSE_MANAGER = nextMessageTypeId() + MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED = nextMessageTypeId() CpCourseGeneratorFrame.setupGui() CpGlobalSettingsFrame.setupGui() @@ -112,14 +116,14 @@ function CpInGameMenu:setupMenuPages() { self.pageVehicleSettings, function () - return CpUtil.getCurrentVehicle() ~= nil + return self.currentVehicle ~= nil end, {896, 0, 128, 128} }, { self.pageCourseGenerator, function () - return CpUtil.getCurrentVehicle() ~= nil + return true end, {128, 0, 128, 128} }, @@ -229,16 +233,24 @@ function CpInGameMenu:onMenuOpened() -- end end -function CpInGameMenu:onButtonBack(_, _, force) - if force or self.currentPage:requestClose(self.clickBackCallback) then - CpInGameMenu:superClass().onButtonBack(self) +function CpInGameMenu:onButtonBack() + if self.currentPage.onClickBack then + if not self.currentPage:onClickBack() then + return + end end + CpInGameMenu:superClass().onButtonBack(self) end -- Lines 559-578 function CpInGameMenu:onClose(element) CpInGameMenu:superClass().onClose(self) + self:unlockCurrentVehicle() +end +function CpInGameMenu:onOpen() + CpInGameMenu:superClass().onOpen(self) + self:lockCurrentVehicle(CpUtil.getCurrentVehicle()) end -- Lines 650-699 @@ -271,6 +283,27 @@ function CpInGameMenu:getPageButtonInfo(page) return buttonInfo end +function CpInGameMenu:lockCurrentVehicle(vehicle) + if vehicle ~= self.currentVehicle then + g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED, vehicle) + end + self.currentVehicle = vehicle +end + +function CpInGameMenu:unlockCurrentVehicle() + self.currentVehicle = nil +end + +function CpInGameMenu:getCurrentVehicle() + return self.currentVehicle +end + +function CpInGameMenu:onCurrentVehicleChanged() + if self:getIsOpen() then + self:updatePages() + end +end + CpInGameMenu.TAB_UV = { MAP = { diff --git a/scripts/gui/elements/CpBinaryOptionElement.lua b/scripts/gui/elements/CpBinaryOptionElement.lua index d68f84cc6..a58f661a3 100644 --- a/scripts/gui/elements/CpBinaryOptionElement.lua +++ b/scripts/gui/elements/CpBinaryOptionElement.lua @@ -24,26 +24,13 @@ function CpBinaryOptionElement:setDataSource(dataSource) self:setState(BinaryOptionElement.STATE_LEFT, true) end end - -function CpBinaryOptionElement:addElement(element, ...) - CpBinaryOptionElement:superClass().addElement(self, element, ...) - -- if self.textElement then - -- self.textElement.forceHighlight = true - -- self.textElement:setHandleFocus(false) - -- self.textElement.target = self - -- self.textElement:setCallback("onClickCallback", "onCenterButtonClicked") - -- end - if element.name == "tooltip" then - self.toolTipElement = element - end -end - function CpBinaryOptionElement:updateTitle() if self.labelElement then self.labelElement:setText(self.dataSource:getTitle()) end - if self.toolTipElement then - self.toolTipElement:setText(self.dataSource:getTooltip()) + self.toolTipElement = self:getDescendantByName("tooltip") + if self.toolTipElement then + self.toolTipText = self.dataSource:getTooltip() end end diff --git a/scripts/gui/elements/CpOptionToggleElement.lua b/scripts/gui/elements/CpOptionToggleElement.lua index 4b0dc6b4f..89e64ab00 100644 --- a/scripts/gui/elements/CpOptionToggleElement.lua +++ b/scripts/gui/elements/CpOptionToggleElement.lua @@ -37,9 +37,6 @@ function CpOptionToggleElement:addElement(element, ...) self.textElement.target = self self.textElement:setCallback("onClickCallback", "onCenterButtonClicked") end - if element.name == "tooltip" then - self.toolTipElement = element - end end function CpOptionToggleElement:updateTitle() @@ -50,8 +47,12 @@ function CpOptionToggleElement:updateTitle() if self.labelElement and self.labelElement.setText then self.labelElement:setText(self.dataSource:getTitle()) end - if self.toolTipElement then - self.toolTipElement:setText(self.dataSource:getTooltip()) + -- if self.toolTipElement then + -- self.toolTipElement:setText(self.dataSource:getTooltip()) + -- end + self.toolTipElement = self:getDescendantByName("tooltip") + if self.toolTipElement then + self.toolTipText = self.dataSource:getTooltip() end end diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index d0e32a6ea..9b119ba2e 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -119,6 +119,7 @@ function CpCourseGeneratorFrame:setInGameMap(ingameMap, hud) end function CpCourseGeneratorFrame:initialize(menu) + self.cpMenu = menu self.onClickBackCallback = menu.clickBackCallback self:initializeContextActions() @@ -161,8 +162,11 @@ function CpCourseGeneratorFrame:initialize(menu) self.subCategoryPages[key] = self.containerMap else self.subCategoryPages[key] = self.containerPrefab:clone(self) - self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" + local layout = self.subCategoryPages[key]:getDescendantByName("layout") + layout.scrollDirection = "vertical" FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) + -- FocusManager:linkElements(self.subCategoryPages[key], FocusManager.BOTTOM, layout) + -- FocusManager:linkElements(layout, FocusManager.TOP, self.subCategoryPages[key]) end end self.mapOverviewSelector:setTexts(self.mapSelectorTexts) @@ -180,6 +184,26 @@ function CpCourseGeneratorFrame:initialize(menu) self.currentContextBox = self.contextBox self.currentHotspot = nil + g_messageCenter:subscribe(MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED, + function(self, vehicle) + if vehicle then + self.subCategoryPaging:setDisabled(false) + for ix=1, self.NUM_CATEGORIES do + if ix ~= self.CATEGRORIES.IN_GAME_MAP then + self.subCategoryTabs[ix]:setDisabled(false) + end + end + self:updateSettings(vehicle) + else + self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) + self.subCategoryPaging:setDisabled(true) + for ix=1, self.NUM_CATEGORIES do + if ix ~= self.CATEGRORIES.IN_GAME_MAP then + self.subCategoryTabs[ix]:setDisabled(true) + end + end + end + end, self) end function CpCourseGeneratorFrame:update(dt) @@ -210,12 +234,7 @@ function CpCourseGeneratorFrame:update(dt) CpCourseGeneratorFrame:superClass().update(self, dt) end -function CpCourseGeneratorFrame:onFrameOpen() - local vehicle = CpUtil.getCurrentVehicle() - if not vehicle then - return - end - self.ingameMap:setTerrainSize(g_currentMission.terrainSize) +function CpCourseGeneratorFrame:updateSettings(vehicle) local settings = vehicle:getCourseGeneratorSettings() local settingsBySubTitle, pageTitle = CpCourseGeneratorSettings.getSettingSetup() local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) @@ -229,7 +248,7 @@ function CpCourseGeneratorFrame:onFrameOpen() layout, self.multiTextPrefab, self.booleanPrefab, self.sectionHeaderPrefab, settings) CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) - + settings = vehicle:getCpVineSettings() settingsBySubTitle = CpCourseGeneratorSettings.getVineSettingSetup() layout = self.subCategoryPages[self.CATEGRORIES.Vine_SETTINGS]:getDescendantByName("layout") @@ -240,9 +259,12 @@ function CpCourseGeneratorFrame:onFrameOpen() layout, self.multiTextPrefab, self.booleanPrefab, self.sectionHeaderPrefab, settings) CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) +end +function CpCourseGeneratorFrame:onFrameOpen() + self.ingameMap:setTerrainSize(g_currentMission.terrainSize) -- g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self.onAIVehicleStateChanged, self) - self.activeWorkerList:reloadData() + g_messageCenter:subscribe(MessageType.AI_JOB_STARTED, function(self) self.activeWorkerList:reloadData() end, self) @@ -284,6 +306,7 @@ function CpCourseGeneratorFrame:onFrameOpen() else self.buttonDeselectAllText:setText(g_i18n:getText(InGameMenuMapFrame.L10N_SYMBOL.DESELECT_ALL)) end + self.activeWorkerList:reloadData() self.filterList:reloadData() self.ingameMapBase:restoreDefaultFilter() self:generateJobTypes() @@ -291,7 +314,6 @@ function CpCourseGeneratorFrame:onFrameOpen() self.currentJob = nil self.currentJobVehicle = nil self.currentHotspot = nil - self:setMapSelectionItem(nil) self:setJobMenuVisible(false) self.startJobPending = false self.ingameMap:onOpen() @@ -299,6 +321,17 @@ function CpCourseGeneratorFrame:onFrameOpen() self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) CpCourseGeneratorFrame:superClass().onFrameOpen(self) + + local vehicle = self.cpMenu:getCurrentVehicle() + if vehicle and self.currentHotspot == nil then + self.subCategoryPaging:setDisabled(false) + self:setAIVehicle(vehicle) + elseif vehicle == nil then + self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) + self.subCategoryPaging:setDisabled(true) + self:setMapSelectionItem(nil) + end + end function CpCourseGeneratorFrame:saveHotspotFilter() @@ -329,7 +362,10 @@ end function CpCourseGeneratorFrame:onFrameClose() self:closeMap() - g_messageCenter:unsubscribeAll(self) + g_messageCenter:unsubscribe(MessageType.AI_JOB_STARTED, self) + g_messageCenter:unsubscribe(MessageType.AI_JOB_STOPPED, self) + g_messageCenter:unsubscribe(MessageType.AI_JOB_REMOVED, self) + g_messageCenter:unsubscribe(AIJobStartRequestEvent, self) self.jobTypeInstances = {} g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) @@ -342,7 +378,6 @@ function CpCourseGeneratorFrame:onFrameClose() end self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) - self:setMapSelectionItem(nil) self.statusMessages = {} self:updateStatusMessages() self.startJobPending = false @@ -350,7 +385,10 @@ function CpCourseGeneratorFrame:onFrameClose() CpCourseGeneratorFrame:superClass().onFrameClose(self) end -function CpCourseGeneratorFrame:requestClose() +function CpCourseGeneratorFrame:onClickBack() + if self.startJobPending then + return + end if self.mode == self.AI_MODE_CREATE then if self:getIsPicking() then self:executePickingCallback(false) @@ -359,6 +397,7 @@ function CpCourseGeneratorFrame:requestClose() end self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) + self:setMapSelectionItem(self.currentHotspot) return false elseif self.currentHotspot then self:setMapSelectionItem(nil) @@ -389,7 +428,7 @@ function CpCourseGeneratorFrame:updateSubCategoryPages(state) self.ingameMap:setVisible(false) layout:invalidateLayout() self.settingsSlider:setDataElement(layout) - FocusManager:setFocus(layout) + FocusManager:setFocus(self.subCategoryPages[state]) else self.settingsSliderBox:setVisible(false) self.ingameMap:setVisible(true) @@ -778,6 +817,7 @@ function CpCourseGeneratorFrame:startJob() if state == AIJob.START_SUCCESS then self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) + self:setMapSelectionItem(self.currentHotspot) end end self:tryStartJob(self.currentJob, @@ -1130,15 +1170,13 @@ function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) if self.mode == self.AI_MODE_CREATE then return end - if hotspot ~= nil then - local x, _ = hotspot:getWorldPosition() if x == nil then hotspot = nil end end - self.ingameMapBase:setSelectedHotspot(nil) + self.ingameMapBase:setSelectedHotspot(hotspot) self.selectedFarmland = nil g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) @@ -1185,6 +1223,7 @@ function CpCourseGeneratorFrame:setMapSelectionItem(hotspot) else InGameMenuMapUtil.hideContextBox(self.contextBox) end + self.cpMenu:lockCurrentVehicle(self.currentHotspot and self.currentHotspot:getVehicle()) end function CpCourseGeneratorFrame:setAIVehicle(vehicle) @@ -1192,7 +1231,6 @@ function CpCourseGeneratorFrame:setAIVehicle(vehicle) self:setMapSelectionItem(hotspot) self.ingameMap:panToHotspot(hotspot) self:onCreateJob() - self.createJobEmptyText:setVisible(false) end function CpCourseGeneratorFrame:onCreateJob() @@ -1207,14 +1245,16 @@ function CpCourseGeneratorFrame:onCreateJob() table.insert(currentJobTypesTexts, g_currentMission.aiJobTypeManager:getJobTypeByIndex(index).title) end end - self.jobTypeElement:setTexts(currentJobTypesTexts) - self.jobTypeElement:setState(1) - self.mode = self.AI_MODE_CREATE - self.currentJobVehicle = vehicle - self.currentJob = nil - self:setJobMenuVisible(true) - FocusManager:setFocus(self.jobTypeElement) - self:setActiveJobTypeSelection(self.currentJobTypes[1]) + if #self.currentJobTypes > 0 then + self.jobTypeElement:setTexts(currentJobTypesTexts) + self.jobTypeElement:setState(1) + self.mode = self.AI_MODE_CREATE + self.currentJobVehicle = vehicle + self.currentJob = nil + self:setJobMenuVisible(true) + FocusManager:setFocus(self.jobTypeElement) + self:setActiveJobTypeSelection(self.currentJobTypes[1]) + end end end self:updateContextActions() @@ -1243,7 +1283,6 @@ function CpCourseGeneratorFrame:setJobMenuVisible(isVisible) if not isVisible then self.currentJob = nil self.currentJobVehicle = nil - self:setMapSelectionItem(self.currentHotspot) FocusManager:setFocus(self.mapOverviewSelector) self.mapOverviewSelector:setState(self.MAP_SELECTOR_ACTIVE_JOBS, true) else diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua index 75f3da8c4..aa12cc851 100644 --- a/scripts/gui/pages/CpGlobalSettingsFrame.lua +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -55,30 +55,30 @@ function CpGlobalSettingsFrame:initialize(menu) for key = 1, CpGlobalSettingsFrame.NUM_CATEGORIES do self.subCategoryPaging:addText(tostring(key)) self.subCategoryPages[key] = self.containerPrefab:clone(self) - self.subCategoryPages[key]:getDescendantByName("layout").scrollDirection = "vertical" + local layout = self.subCategoryPages[key]:getDescendantByName("layout") + layout.scrollDirection = "vertical" FocusManager:loadElementFromCustomValues(self.subCategoryPages[key]) self.subCategoryTabs[key] = self.selectorPrefab:clone(self.subCategoryBox) FocusManager:loadElementFromCustomValues(self.subCategoryTabs[key]) - self.subCategoryBox:invalidateLayout() self.subCategoryTabs[key]:setText(g_i18n:getText(self.CATEGRORY_TEXTS[key])) self.subCategoryTabs[key]:getDescendantByName("background"):setSize( self.subCategoryTabs[key].size[1], self.subCategoryTabs[key].size[2]) self.subCategoryTabs[key].onClickCallback = function () self:updateSubCategoryPages(key) end - end + end + self.subCategoryBox:invalidateLayout() local settings = g_Courseplay.globalSettings:getSettings() local settingsBySubTitle, pageTitle = g_Courseplay.globalSettings:getSettingSetup() self.categoryHeaderText:setText(g_i18n:getText(pageTitle)) local ix = 1 for _, data in pairs(settingsBySubTitle) do - CpSettingsUtil.generateAndBindGuiElements(data, - self.subCategoryPages[ix]:getDescendantByName("layout"), + local layout = self.subCategoryPages[ix]:getDescendantByName("layout") + CpSettingsUtil.generateAndBindGuiElements(data, layout, self.multiTextPrefab, self.booleanPrefab, settings) - CpSettingsUtil.updateGuiElementsBoundToSettings( - self.subCategoryPages[ix]:getDescendantByName("layout")) + CpSettingsUtil.updateGuiElementsBoundToSettings(layout) if ix >= 2 then break end @@ -87,7 +87,9 @@ function CpGlobalSettingsFrame:initialize(menu) end function CpGlobalSettingsFrame:onFrameOpen() + CpGlobalSettingsFrame:superClass().onFrameOpen(self) self:updateSubCategoryPages(self.CATEGRORIES.BASIC_SETTINGS) + FocusManager:setFocus(self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout")) end function CpGlobalSettingsFrame:onClickCpMultiTextOption(_, guiElement) @@ -102,6 +104,6 @@ function CpGlobalSettingsFrame:updateSubCategoryPages(state) end self.subCategoryPages[state]:setVisible(true) self.subCategoryTabs[state]:setSelected(true) - self.settingsSlider:setDataElement(self.subCategoryPages[state]:getDescendantByName("layout")) - FocusManager:setFocus(self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout")) + local layout = self.subCategoryPages[state]:getDescendantByName("layout") + self.settingsSlider:setDataElement(layout) end diff --git a/scripts/gui/pages/CpVehicleSettingsFrame.lua b/scripts/gui/pages/CpVehicleSettingsFrame.lua index 0fbae0287..c9c45ed54 100644 --- a/scripts/gui/pages/CpVehicleSettingsFrame.lua +++ b/scripts/gui/pages/CpVehicleSettingsFrame.lua @@ -39,7 +39,7 @@ function CpVehicleSettingsFrame.createFromExistingGui(gui, guiName) end function CpVehicleSettingsFrame:initialize(menu) - + self.cpMenu = menu self.booleanPrefab:unlinkElement() FocusManager:removeElement(self.booleanPrefab) self.multiTextPrefab:unlinkElement() @@ -70,10 +70,8 @@ function CpVehicleSettingsFrame:initialize(menu) end function CpVehicleSettingsFrame:onFrameOpen() - local vehicle = CpUtil.getCurrentVehicle() - if not vehicle then - return - end + CpVehicleSettingsFrame:superClass().onFrameOpen(self) + local vehicle = self.cpMenu:getCurrentVehicle() local settings = vehicle:getCpSettings() local settingsBySubTitle, pageTitle = CpVehicleSettings.getSettingSetup() local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) @@ -93,9 +91,8 @@ function CpVehicleSettingsFrame:onFrameOpen() end function CpVehicleSettingsFrame:onClickCpMultiTextOption(_, guiElement) - local vehicle = CpUtil.getCurrentVehicle() CpSettingsUtil.updateGuiElementsBoundToSettings( - self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout"), vehicle) + self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout"), self.cpMenu:getCurrentVehicle()) end function CpVehicleSettingsFrame:updateSubCategoryPages(state) From fe2699a146ff6766070ce1affabf9217cbbf49cc Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 8 Dec 2024 12:59:31 +0100 Subject: [PATCH 088/158] Some more adjustments --- config/gui/pages/CourseGeneratorFrame.xml | 6 +++--- scripts/gui/elements/CpOptionToggleElement.lua | 3 --- scripts/gui/pages/CpCourseGeneratorFrame.lua | 10 +++------- scripts/gui/pages/CpGlobalSettingsFrame.lua | 3 +-- scripts/gui/pages/CpVehicleSettingsFrame.lua | 3 +-- 5 files changed, 8 insertions(+), 17 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 80c7da2a9..12bdfd9a4 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -27,9 +27,9 @@ <ThreePartBitmap profile="fs25_subCategoryContainerBg" id="leftBox"> <Bitmap profile="fs25_subCategoryContainerArrow" /> </ThreePartBitmap> - <Bitmap profile="fs25_subCategoryContainer" id="filterBox"> + <Bitmap profile="fs25_subCategoryContainer" id="filterBox" absoluteSizeOffset="0 -75px"> <MultiTextOption profile="fs25_subCategorySelector" id="mapOverviewSelector" - onClick="onClickMapOverviewSelector" /> + onClick="onClickMapOverviewSelector"/> <BoxLayout profile="fs25_subCategorySelectorBox" id="subCategoryDotBox"> <RoundCorner profile="fs25_subCategorySelectorDot" /> <RoundCorner profile="fs25_subCategorySelectorDot" /> @@ -82,7 +82,7 @@ <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </Button> <Text profile="fs25_aiCreateJobParameterTitle" id="createTitleTemplate" - text="Group title" /> + text="Group title" size="100% 30px"/> <Button profile="fs25_aiCreateJobParameterButton" id="createPositionTemplate" text="<400, 300>" onClick="onClickPositionParameter"> <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" /> diff --git a/scripts/gui/elements/CpOptionToggleElement.lua b/scripts/gui/elements/CpOptionToggleElement.lua index 89e64ab00..c03fa3ef9 100644 --- a/scripts/gui/elements/CpOptionToggleElement.lua +++ b/scripts/gui/elements/CpOptionToggleElement.lua @@ -47,9 +47,6 @@ function CpOptionToggleElement:updateTitle() if self.labelElement and self.labelElement.setText then self.labelElement:setText(self.dataSource:getTitle()) end - -- if self.toolTipElement then - -- self.toolTipElement:setText(self.dataSource:getTooltip()) - -- end self.toolTipElement = self:getDescendantByName("tooltip") if self.toolTipElement then self.toolTipText = self.dataSource:getTooltip() diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 9b119ba2e..f24bf5943 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -407,11 +407,7 @@ function CpCourseGeneratorFrame:onClickBack() end function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) - local vehicle = CpUtil.getCurrentVehicle() - local layout = self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout") - if layout then - CpSettingsUtil.updateGuiElementsBoundToSettings(layout, vehicle) - end + CpSettingsUtil.updateGuiElementsBoundToSettings(guiElement.parent.parent, self.cpMenu:getCurrentVehicle()) end function CpCourseGeneratorFrame:updateSubCategoryPages(state) @@ -600,7 +596,7 @@ function CpCourseGeneratorFrame:onClickMultiTextOptionParameter(index, element) end function CpCourseGeneratorFrame:onClickMultiTextOptionCenterParameter() - + end function CpCourseGeneratorFrame:executePickingCallback(...) @@ -744,7 +740,7 @@ function CpCourseGeneratorFrame:updateParameterValueTexts() if invalidElement ~= nil then invalidElement:setVisible(not parameter:getIsValid() and not parameter:getIsDisabled()) end - + element:setDisabled(not parameter:getCanBeChanged()) local parameterType = parameter:getType() if parameterType == AIParameterType.TEXT then local title = element:getDescendantByName("title") diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua index aa12cc851..485cbe8a7 100644 --- a/scripts/gui/pages/CpGlobalSettingsFrame.lua +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -93,8 +93,7 @@ function CpGlobalSettingsFrame:onFrameOpen() end function CpGlobalSettingsFrame:onClickCpMultiTextOption(_, guiElement) - CpSettingsUtil.updateGuiElementsBoundToSettings( - self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout")) + CpSettingsUtil.updateGuiElementsBoundToSettings(guiElement.parent.parent) end function CpGlobalSettingsFrame:updateSubCategoryPages(state) diff --git a/scripts/gui/pages/CpVehicleSettingsFrame.lua b/scripts/gui/pages/CpVehicleSettingsFrame.lua index c9c45ed54..c17f9d458 100644 --- a/scripts/gui/pages/CpVehicleSettingsFrame.lua +++ b/scripts/gui/pages/CpVehicleSettingsFrame.lua @@ -91,8 +91,7 @@ function CpVehicleSettingsFrame:onFrameOpen() end function CpVehicleSettingsFrame:onClickCpMultiTextOption(_, guiElement) - CpSettingsUtil.updateGuiElementsBoundToSettings( - self.subCategoryPages[self.subCategoryPaging:getState()]:getDescendantByName("layout"), self.cpMenu:getCurrentVehicle()) + CpSettingsUtil.updateGuiElementsBoundToSettings(guiElement.parent.parent, self.cpMenu:getCurrentVehicle()) end function CpVehicleSettingsFrame:updateSubCategoryPages(state) From 8ac6379dcb6cad4e811b7c6fab177dad7225c292 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Mon, 9 Dec 2024 19:28:44 +0100 Subject: [PATCH 089/158] Translation --- config/MasterTranslations.xml | 8 ++++++-- scripts/gui/pages/CpCourseGeneratorFrame.lua | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index a6d2c666d..85d44e071 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -878,8 +878,12 @@ <Text language="en"><![CDATA[Basic settings]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_vineField"> - <Text language="de"><![CDATA[Weinfeld Einstellungen]]></Text> - <Text language="en"><![CDATA[Vine field settings]]></Text> + <Text language="de"><![CDATA[Reben Einstellungen]]></Text> + <Text language="en"><![CDATA[Vine settings]]></Text> + </Translation> + <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork"> + <Text language="de"><![CDATA[Feldarbeit Einstellungen]]></Text> + <Text language="en"><![CDATA[Fieldwork settings]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_headland"> <Text language="de"><![CDATA[Einstellungen Vorgewende]]></Text> diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index f24bf5943..b9c460305 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -12,7 +12,7 @@ CpCourseGeneratorFrame = { INPUT_CONTEXT_NAME = "CP_COURSE_GENERATOR_MENU", CATEGRORY_TEXTS = { "CP_ingameMenu_map_title", - "CP_vehicle_courseGeneratorSetting_subTitle_basic", + "CP_vehicle_courseGeneratorSetting_subTitle_fieldwork", "CP_vehicle_courseGeneratorSetting_subTitle_vineField", }, CLEAR_INPUT_ACTIONS = { From 0321f3df0d74530119094a32fa9cb1561c08f022 Mon Sep 17 00:00:00 2001 From: Tensuko <Tensuko@users.noreply.github.com> Date: Mon, 9 Dec 2024 18:29:04 +0000 Subject: [PATCH 090/158] Updated translations --- translations/translation_br.xml | 1 + translations/translation_cs.xml | 1 + translations/translation_ct.xml | 1 + translations/translation_cz.xml | 1 + translations/translation_da.xml | 1 + translations/translation_de.xml | 3 ++- translations/translation_ea.xml | 1 + translations/translation_en.xml | 3 ++- translations/translation_es.xml | 1 + translations/translation_fc.xml | 1 + translations/translation_fi.xml | 1 + translations/translation_fr.xml | 1 + translations/translation_hu.xml | 1 + translations/translation_it.xml | 1 + translations/translation_jp.xml | 1 + translations/translation_kr.xml | 1 + translations/translation_nl.xml | 1 + translations/translation_no.xml | 1 + translations/translation_pl.xml | 1 + translations/translation_pt.xml | 1 + translations/translation_ro.xml | 1 + translations/translation_ru.xml | 1 + translations/translation_sv.xml | 1 + translations/translation_tr.xml | 1 + 24 files changed, 26 insertions(+), 2 deletions(-) diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 801f96eb8..0f9e17cc1 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Configuração do gerador de rota de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Configuração do gerador de rota de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="cabeceira"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="centro"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index ddb5cb96c..916209ea3 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="工作路线设置 (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="基本设置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="田间地头设置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="田地中心路线设置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="障碍绕行设置"/> diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml index c52881c74..906a1091f 100644 --- a/translations/translation_ct.xml +++ b/translations/translation_ct.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="工作路線設置 (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="基本設置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="田間地頭設置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="中心路線設置"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index bcd516a80..91cc4ae3d 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Nastavení generátoru tras (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Základní"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Souvrať"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Střed"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Nastavení ostrova"/> diff --git a/translations/translation_da.xml b/translations/translation_da.xml index d686bf494..b1cc6a486 100644 --- a/translations/translation_da.xml +++ b/translations/translation_da.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Rutegenerator indstilling for (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Rutegenerator indstilling for (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Forager"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Mitte"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 5c4aebd8f..c418a938a 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -272,7 +272,8 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Kursgenerator-Einstellungen von (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Grundeinstellungen"/> - <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Weinfeld Einstellungen"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Reben Einstellungen"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Feldarbeit Einstellungen"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Einstellungen Vorgewende"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Einstellungen Mitte"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Einstellungen Insel"/> diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml index 59ebaa33a..a8fdcfffb 100644 --- a/translations/translation_ea.xml +++ b/translations/translation_ea.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 26a786407..8f58ed551 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -272,7 +272,8 @@ <!----> <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Basic settings"/> - <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Settings headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Settings center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_es.xml b/translations/translation_es.xml index 17c04c9b0..5f753ae87 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Configuración del generador de rutas de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Básicos"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Cabeceras"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Centro"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Islas"/> diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml index ed65c28d2..ef69c963d 100644 --- a/translations/translation_fc.xml +++ b/translations/translation_fc.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml index a459aad6c..315ba94df 100644 --- a/translations/translation_fi.xml +++ b/translations/translation_fi.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index ce644b726..f6022ed44 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Paramètres du générateur pour la course (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Paramètres principaux"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Paramètres des fourrières"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Paramètres des rangs"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Paramètres îlots"/> diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index c6b5b3ebc..6cebcc376 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Az útvonaltervező beállításai (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Általános"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Fordulósáv"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Sorok"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_it.xml b/translations/translation_it.xml index 35370492b..ad3098e72 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Impostazioni percorso veicolo (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Base"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="testa campo"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="centro"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index 5c01b9673..d2a256fdb 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="[%s]のコース生成設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="一般設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="まくら地設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="フィールド内設定"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml index 1a00b2e15..8133e94ed 100644 --- a/translations/translation_kr.xml +++ b/translations/translation_kr.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index f6c1bbf06..7f2d1b681 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Instellingen koersgenerator van (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basis"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="wendakker"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="midden"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_no.xml b/translations/translation_no.xml index 728234d63..e367c51d2 100644 --- a/translations/translation_no.xml +++ b/translations/translation_no.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index 69f51f670..d84ef34c3 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Ustawienia generatora kursu dla (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Podstawowe"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Uwrocia"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Środek"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Ustawienia przeszkód"/> diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index 4acfc78a9..4a8ccfca1 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Configurações do gerador de rota de (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Configuração geral"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Configuração de cabeceiras"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Configuração de faixas"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml index 29693329e..10c072570 100644 --- a/translations/translation_ro.xml +++ b/translations/translation_ro.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="basic"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="headland"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index beb1c0b11..c5cbc7a90 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Настройки генератора курсов (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="основные"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="поворотная полоса"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="метод движения"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Настройки работ вокруг островков"/> diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml index 5f9baa879..84ac7f3e5 100644 --- a/translations/translation_sv.xml +++ b/translations/translation_sv.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Kursskapandeinställningar för (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Grundläggande"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Vändteg"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Centrum"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml index 53dd790ed..9448c38e2 100644 --- a/translations/translation_tr.xml +++ b/translations/translation_tr.xml @@ -273,6 +273,7 @@ <text name="CP_vehicle_courseGeneratorSetting_title" text="Kurs oluşturucu ayarları (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Temel"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine field settings"/> + <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_headland" text="Sürülmemiş toprak"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="merkez"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> From d88162cd77547c4fa4299ab3ea661cdf54a251ec Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Mon, 9 Dec 2024 19:59:37 +0100 Subject: [PATCH 091/158] Help menu cosmetic.... --- img/helpmenu/minihudhelp_balecollect.dds | Bin 524416 -> 524416 bytes img/helpmenu/minihudhelp_combineunload.dds | Bin 524416 -> 524416 bytes img/helpmenu/minihudhelp_fieldwork.dds | Bin 524416 -> 524416 bytes img/helpmenu/minihudhelp_general.dds | Bin 524416 -> 524416 bytes img/helpmenu/minihudhelp_siloloader.dds | Bin 524416 -> 524416 bytes img/helpmenu/minihudhelp_siloworker.dds | Bin 524416 -> 524416 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/img/helpmenu/minihudhelp_balecollect.dds b/img/helpmenu/minihudhelp_balecollect.dds index b74e8baf66f496fcf6ba10f4c7762282a13dec75..14ef2731278f3401696f5a90bde918c2a9b23909 100644 GIT binary patch delta 49522 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%0Ag`CWXa=KB2<cR?Lx;sj#Za`sjz4nxHf}^m!_j z#xgPBhb+&V=1EB?mTife0L?s~it$PB!jF~FkBdbwL&kfGU;2LHy_e)i**_YRVAZi+ zT{ZQmFW{q$sav>$US>ZN6Z3e2s8#Uv<vNK~BRbfZ>VbxFVo|AZBPk<;2rS_;MFsP3 z_SfwGeX@<oWSTvP-1b#}C0m_TR6ySAuVnfqs*;ui+N``33Xzmabym>-nM$qYG+DhU z*YDX=Z0|WB8_&Ej0sdP$_(ph^NVCa+^3ZEnS!SiLl8Wp-kIAxgp6mwg<4*oftd;Kk zz6xF`d8ZTQbB%uRkmP2MKPJ1~<Oh;i%;ZPqgxuVN&K>ma63Qp&do92jxtXVR(!*7& z24#!)EVH_S<?ReA=`jSf^TQ_w52M|EB8{w+(#i9e$zRanjhR!SJl)t71DqL5+%V=0 z->%{g5CL^fIujb681|Y0iSKsTL0NWgj=Kn(7kYa^6A^d3wY89Nr+EB2U_f;FAX$gk zY<}^OY{ll0yHS*+Ni+B^cH@~B1u)LSpo)*biDW)!AQA!848|J)I|n=ld-);qt92;0 ziBZ3rhI__ebi-*#>U1U0_~#SHNx}Fi83$fy1#^wmo>X*3#Hq>N9kXss+YmA7(f|HF zX&(K!j>r(GuQljLQ7$S-VtQI%U(e1urQWL{)Nc#eKRG1(W;S}hL+ho51PA(Qr1rH` z854vrJTplA5p<>-!M@@j$jX<fGs*3wbm?@j{;*72MgQNOyMIus#jmDkwq$F~0iEs) zu~WJ(+52i`Z_|@|qgJDG`~<MJzJ3}(%`A5;N11*a!3|kB-SPMV(jZ0W35C7quRp)F zx|aXhRrE47u`juWy@wAIC6su^oC=6|;eoHv+URDpy?{<uC?pNhDI>iun~8eJ-bvrY zNF0e-Qu@*ilqa6w^a=V;c`xk3cu}FSK!tw$?i<snm!w7o`b8QK+RF~h?jkoz+m`VP zVk~tQd8Q_kulHVbE>b4)HYxIc4UEI*Gc_gb(gHS;S2Uky>cgcM!n+p53_2j6kN>U@ z<9WU(PEg>?_2mqIU=sN8ozWx&8nXA9!te|_NfOHl_T};vUf6R_nt(pimS8aSsiLa@ z1|s;R`#%NZsB7~uU^A~wYom7hbYrrNG{NnPj6h?KUeQqknk+;`(NAf=&_6#rgI8by zr|2ZOxkIkv@nOsEq3jwX_60ec@{>S-)K{>SQm_(hr&WWpL++(td!xAa{8O?`Cfe>1 z{d7GZsy>Bjn-5M~lZh-b_hc6oTnfLEi&6ax7lzeT?w>Sg8VP!OU0GO$<bvAK#SbrM z&U|`WB=sW)s$tPsyXou?j-bpboFU>@3ElUrP>%G|TVW}y?|~o6j7!qUF@IDg0go?# z_+RY(^|Cx={-twgs6y)N3)yGuW%=6aj#etCp8PHA$eF)=JHp`U58pVBfisp1weV*C zu#P4q```GDJk)df+hzgH9G5~+P-eMV!X|nA)%MzFWCi!>(M-QgkjUqUx~8GLL5nAR zfW)<D&%^}8my$eX1>LSSeXj$(Nw3>zfBzZT8dG~~Wvh0~S6$ai>Jpio9f$7f^(OjN zFQi-I4A8rW7fhHPhj)N)_Q=`KB@dn*tYOqH=nbe>xRvCsXftUZRzh3!qaExt@Zsvo z!7>UTZch=}QYpQYA87;}eN!<?0g7JosoL7?<_1}|3EOr$tmtC*JoK|6@fxP5&Wj{x za#R{`G0G<uBFSsN&|yw|pJnZge^N&63(M0Hz_is;yaW9xE*%6RllvwnFupL9N?&mQ zh`z>5T*Urjle`D@9Xy|oEW~AMhv3P>O^OnJ@<E>Opowto+nWc4YFy~AhyF&2i<|L! ztlf!^7_V)oXSVbcw4ZFJ-wtu%1D%=sGLrx&duG^=6Q((*ukWXFv%d~+eoK_;(vyU* zBtPFpib|(4+u~0_P=|`TN9I5=K4*b~ys_mC%4q4q$OJyZqmHilINIHG7s=ez!Di9` zVs$*yRz@s!c`lv+!h^MwCoy68WxpTgK11VSz=@u@YQUV?IeYq1*{W4!U9@}FByPx6 z)=d_)=F$tDqNDVLP4=Yyg!%s~>TAHFysmZU`%nXeLed<6k~DQ3D}xYzVvg0sDo9gE zrtK{u86gOaF^y2u7^<~UP-cQnA<$?}@4>H0PU0MLE#pjNEV*QG5QgCCbNxv*r!66k zf#KlQkU<%WrH~ot`|kVhY47oQc$RyA`)~GId+oK?T6=BjUc}GIH*2%|No?luOSH1$ z)D@oR;xV^K@;!uUU!BI{0LL4+0THy)?C;d;dE(ZDw-*k3wwt|dTvgxQNNB5Qm@#1~ z57oqO1o_hk!9%FIY8A8(nqSQJ1_!2pD0WwHLr^hx>kR)g!iv2U=h)Er3{TYgMQcBh zBr^7h$J5z_@4@zox(+~XuCJSO6%EeM5JGo=Q*ULF)^G*f7Bb+FS9mIG!?t1NwU?x9 z8*2^u{i2lR4~<zYRl1{a8*2r#`evO-e;J&_QiY8qK2_I*{LR`hk2>byq*lYx8J4c! zMyM+jivwPDYJllZ*}9Fi>vkJ;NSozD;k|?np^L4Y=7W~~5Btf3Tka~ga}}QMK21et zk>5QAnSkyagEqoT{=kq4Jfmhr14=>cI6Nt}jnbMpyFFfUG(z|CCg;=Z*JDw^btZTI zV>0>rx&|xL-C0@PY{#L~WfRIe^)}&jmk_OsT4j%)>BCd&ZUCCAcgBp3(=Hd#08$#Q zDd#w?h^Is}SS;3Wk>*{Kk<kLI4M^st+9LdCVt)SPYhdo$^X|!oApVH2H*^OUi<~}w zA8FK&cm-G#=CQG{FdK-@b*?5C4+gc%O7kJ<eq(Y>2akJCpbW6DAMM-E9@8GuA{>cL zJE8oaLz3a1Yy7>ox|u1iRezYeZ<PA?d%tq@=h8}PMlt+c`kH*D!khbD>AMSTx?enF zVfC!;5mbaDn?V*<=-2aKt765Y!;$yc;Hd3bCcpcRjh^8|s1uFrI5Bc!_Z){aed8ms zTN~~1K9=+QJJb0+Cr}|K5NZfu8rNGJ!oo}z45~J?zXpB;e&{2Bk%nR{QjmmRHZUD6 z_kx6=m3K#dO7<&~VXZg<HR`iT2erM|Z#qF2-6u2LjE(DRPirTE!WV3=N%`3;Qn@X2 z0F;WW<d*L-970Xafntsu4tvQg7zueo9N)kIB5BYjGwBlPE$-N1T%BjE!%DN>Wgcfe z+=16PoSB}_Dq?3bpKyVkYCW}6w4+O2%61c6+bm5OSfQ=37Z@mjIa$Su2Ir`&Z)8L3 zH#3eklzCXmge@&D11Vhdn9{|9JmziY5(<Ah8fe0To~nIvR{`o9;#>SImvN%Ep8M0{ z4`(e5R#{g!uVNz2es6jb5v6y9>)7tyjr!CW>JK%hb3(DMmCswG`xmF(=3P|2NMLX& zceY9^R+7+M?wn4xb9L20D+zI8dR}<qhWaB)L91lAi!w^yRHw_yuNUHs^Ga>2wDPXQ z6j^#lo&))V-1<CM6lbgSSfL3`j#^6v-_7YzF+`}4T+VoQ;b^vibWI3G<4)$t0>#7q z%rDkD#UgUcjjwW1Y0){*6!g0|qj+<FDSdCj>hnWGl!3eP>|fr?tAAqA>8kTyjw4)X ze0dRKpO#cBr(TmXBv~1KO<eAvxiQ$unaAC$0GXu4a`QaXxO3J9h<kmLEu7)7sv0ZN z?MY9%@SMr~{Pn+a#wXeZ(=w+Hh=5h*#LSBaS<fg(El!~PIO6;_m+mTZICt!<0uvk_ z87UXXcFU}7h}INi`+R^EbYn5cKpfi>n~+XRZxzP)%y)AHYMm)2H~~PiuXuBmTyXYe zn($TY4$inx-<<l{UNS$kE@$YiCUkUg_g<`BN<JG_vc2~&W^yNoUHxztH9e}|=E3Ty zEV_0xG|mq_gAYLQ0!a&^5}-LeX6J5Y?nh*1!oM2ge@Pnr-CWEmZoW)wZ9@6kg-a6I zjy7VOtHJ<x`~Zsv`Sr!#eld{!bNiUSh9{Z`)euE-h?H@gABj`!zk*65@zO7uV-56j zbInVG`xnyY@#OVb<?ynT<IkFagQ1bPKIiG&U#w{)iQbice$uL5pDIYFb1>!suLWX3 zJcv`WPumq+hy>WHjkd5IpVQIFb~LM%)SUMtUA$gLVWU{}^)SO(TLS4Hrk=!fRD?}t zUS6MXUTDs=8|uh^5hG5}ZnVZKA``l%>MOVaa2GVl`a!L6oM~s!*-N44!bA|l(Dkiu zkN=or|DRHI0Uk#%Z*(rYgQT_VjcsjWIGPnl03I!d1f@?RR2F*=X*Y)`2Ec%gpAA_g zOA|pt!e@Tu-SDr{6AMz=SFdlfmudn&dYSj~bLPSl2NY3{L7hN_)GkQ6C1uK~UrWQb zciBjiQHMF><}8zZ5E5n3SO6?q-fDY}*bMErkR|?u8sM^b_?u2(*WKr7C0!SXf)_}R z@NX?6uAGTk8(1&eHz@?{eZ}Phcb-jChsp9boxXXE)G(kK14C@MXCQTe&>?>qVnf)( zH!-ctH5!!EH>6w<lF-<!{~>cXp6bdZOv;quMN(PRdM&E4J4E@^VtUJ9xC7s95xR`R z*dj;>C=N|Q&czmYHe@5z0=*XrZF8n%!jswsFj`21HN``Oecj#&G2iK%S3ZA3TJc~U zQ$GLgQ9{+1F4$mH8=Yq9iouJ(@1nf29d-lZiG+St$$nE>zUm^AY1*7Z7}mD%fe>@= z^hXs;x2qmisd`gdALsLLQXFqeuYSd^MTIB1uRoI|H+3EI=KLE```;-2wpr&WDWrx- zm)6||9V0bDJ1Q}ZtWe;2rN<%lnFj~*FiYl)^Ur)h*q@k?%vqb8oYy!zBL|~i&h-~3 za4!qT4P9zx8ozJxPlzSibGwVI0CC}E#sBZ{khsjRIvt~9;fa=D%;qrFtErr0m#ejf zD76eN0pr9q*Uvt}Zi&eJSuU~ND#bQ(AiTO!OcE*j^dnsyd$(V_`~`7$6R~nd`?(p7 zgyR<nZxiOYlllS8^X<mijevt8*Can^y{YR&!NC|O!RKtn-Y%^GCFAnaiCsq(M>`yf z_wMUb2HK&+IZq^Mn)fu3H!-$z%!Ik!R`EQMWqx)2`bIA1(A?YUCVU@jT)l$jySt-U zW+~<)CM`f19{dimW=`Frb#kG3TrM}U$XmmN>M`E{zx#+~xQ=<kx99WC*B^Jae8mN% zf?G_h>jl&To0O_|q*E(+aH^_}m-F#;#2Sm*c2e`J-t7Mct1z^H|5(Or%vi-@8F#}U z0fsXy<Jt=4lvA27Ap+wKRW3}adC%_PyPUEm39^tET6_I-O_(t_(}3(Da=Yp60hGC0 zT39X}4!=Z2Z`bw;ZYJXnql|o^J~`@=ObL{C5oj(hNy&EM`iidW9J@<}fW=~})+vg2 zVoI3qfwLB&tZVG4#FWje4SP&zK*M7^oWhBjbC}9S=92mf5r3k4<U}j;8#5x82#YNh z)8u@9Na7-0c22CX6Y?t}ZS(sZQbh$h8MaDvfCCMM19v3U!F1O9vFMm{Z(kJ4xuGEz zjJntFew*%mJ@0-B27GO+eXPO8b>@nMzTAb$kM@nsvg2aQ{)40ix7*VJq~z7rRRHJL z@fwE51=K0q-BQAWiGEE}s@&4Ya#n^n_g$#mHh%plh+=_&6<_}mR3HLkxd8sA)MWmN zl}C4Y56>OjR0(Jc*I~v}%qV|ZId@9Jqd5(=oE4Yn#7S;Tfww=OVu7?nnS-3o=)#4G zv00toOITW1v7a#RYWTKd_^q^NlRRx@hAwyCKA}?@o`p&uM_G58-if|WT=)@|naOO> zWo2}rob5&0pm1>8KW9?xzm-f67L%8HMky@Pa;~XZzeT&#^ukl`r`xBlviX0#Ymwj% zQ|EMBluv#urQS)+n#Fbyf(@*idwRQ+JG-QmlJRkN+U^TMHG)S;oVbUBay64J@hn%T z1zo)D8jtI9Y_)TpLC1CuT5WSkn_W3cpdAz9vR8*WUCrr{E1Zq_bIRc^Y3*8tnGJ6D z5oW?+9a;yR=$hku0P_sZ`ertA>vz#CWvokDx_Vq^Uxb3dMH}b1-P3^+)b*`8yn7CW z;v{w9va;em>1z)%Ec7Dt#h?~ozTUqnj<8l0FXce*VV~flcOd{6^ELH7rSd(=koGB4 zVeR%b;Ylrr@&hihOu9{gtK*w1kVY-PKVSLnd(xwE)ANVD^Y2N&TCg<VyjYh_hFNs* zU_F+H$!vtj;;|kn?GCKugHel3`KU))wlz1Gzw`@v@)mJAGV6zwMNdMPgVVNuztzfV zt^8T<HwZ10xDJUnpBt@xlQYpG$1A4OFqAHCLhh@r?YQ|{S(N<2lHV&kPD^}RX=zTj z)&z@GyHQS6UOO!<y$jbsIjkOCXB^>$9m@NsrIj-7x(%O(jBn&d+jH?7Znlr8;x;AT zC#6DCtBhGKHzEa}4(K{VyMt^%)_B8gmpMH%P0!yl)2K@(7O%h!lBgaDGr*zA>}-$U z!BwaY)As~#h-mI~md}zKam)f%v$IhT>-au(1EED76r#h@47r-=3wA9)*z&A)5BLnO z-tM%6JW|XF8Alq4m!2!z-c;s&l1cI^hF)09GcfPawa)uG1(E!;?Jb}&Z@$jzC)Bz< zV??V@(^NuzP7ZcutZ@8e`VlRX!}|T9jx0hoY(o?#_%qFl2=jRnwQ&pE(LapSDxh=T zF6zm0=Kxi+=?~>-7MipiGnb=eJM+H&V;rw-``&Go^t6L>b4+jF4o@q558oQ_DHFXE z7&5mghBG*$tDWB5Gty%Vc9<|hnk72S2}n=8i5(S={|Hu7iTakKt>DY~!bVDVd8>|> z-gz%(=~tM)JfIA*+$SY<_3W=NBeI<kj>5}LKpb>+L1kh9x3v~7Tu3U*LDX&1(UA+| z<LuBbL$?n&ftev}Xc&J05Emc@upkiS$)T@fzUqx)LqLPtWIG3#>G$+9U)Q)vXtH#M zb+<5}qJdMN6f^(AbQx4d5LC{$xJm8G&p(jvdUl}4--M6haO|Qgv^i-ke5or0bW$E^ z?AnFK!Lva|Fr6pbj}WTbMZp}>6D9p)J8=Z7DX-y=@DEoMI|0=r8D3{;6SQ8JzpBiC z00E-$MzgYFP|A8xESi92f1nEG+@X*vLdCdfg2In4`K+ICsx=Xt&Mq87+&-mtP+Ivk z!V2Sn);)KP)UAJcO*<!bw4&%L)1HN|CUCeD@OWmm7Q5cSX7&yo@iX7)Y#%E?cW*p5 zV&UFwJ1`+OL%r;lmFYoTIt5L`UheTfqTG2_T9$C>B7>fU!!1hIS=jN&u6&bW;b8}- zS;6k&b%*;&NCurQNH}pj@;8?2?fTKPZ0z`wJH$m4&Ema6iq_QH84~7bu+_!#yRMb| zn)x1g$BX$OP^&gKOCbhN$jEo#x2l?k6<{(C1uo<iF~g&2aQ3_d(b#=&qk%kJgb_<1 ztR0&m9}YX(na&&XOkky=L;vLSOb4fxMgM`D;AsfZa&2pacD0rIvB^l*7VpOYfQ58z zVI_v!ck=fkZoS~{OJZ&v79HF^%vKvE&3yBcxH~LL&p9dO;iU^%UVG8izW<~szy4qT zRluhharL|u2Sy0>{YD+m((roNka3u>r1pZc{Jga0Dg8GY_%+{bx7P_<bgx$2y}w&D zaGn*1_FHX)k(;kv0&er{a<hx+Kijk@p)A`I33c(8#>kzgo_)VkdF{Nkk~jpaTUuI_ zp7YX1?2zy=$y?K=%%2CB%2Es$q(Uoe{xAvFa;yYPAn0RyN>u}>irch&<_gkjLouWb zV8-Jyk-FwIMn!~UM#$Dc*qqY`YJ*U*<xgU%cV@M8u$}O9mvZ?6?4<Dqm1&!U7IR#C z+%&M4E3da}#KQDpIV;4W%I;1d`>EYAuaYACk)J9nFG`vBY9XfcTr(;pE5PYr7mDv& z;*`pZQtFCNS<B}?wz3j1I_UuV!O*NXl#Yv1%6;lBQTu&Wg>?WNvrov>D4%s$nYbvW zEy8ObOMiDzS@9u)9aMWad?-D=Aoc3c!C{8zcky{Cy!RY=FJCX!>Ga3*pyVkl90NhY z9z3`&N9p-U%Dv+Reob-BpB3#R>FC||M##MoI&9@a<ne2D8H)2iC7a|?QvU!!w{>rY z6W@cib#=s7d$>N#S;(#4nkQ!GRo<({arB>-V|HMDzWs@KRPMHFb?|p020=I1LECKR zLu9ypkx3z+ZY#Eq@H?WeR}kX$&_>qp?)H*}nUj3H_5<7eq|b#76()kbi%Q@RlBw$R z8WX+;Ix>e@PYw7TTz}=Wvj;ls|M3itXCI=IPIcnFE92E>BcG(OKk`dJlQ|dvIIrz$ zXB(lKnKaG$9~--E;`)w4uHkH*Oi=dxQMy;kS6=y}^!0UbdplUlu1ix{Dl>B(eg(Kj z_<|6EhSmN#=D&XPu#c^}R8<N55$sak#%dF4pic<Tp($uVu5V3BKa6m^i}Iwn!)b(e zUC$_)Pg<7CoAW0ea#Mt!Lg+1U_pqd-<ap^F<{W5m;tnn{57b-4Ug=L)-v30h#!a{D zlofxLn&5xs__Nd;Pj4-!oS3F$e=04NK3DQTmA-C6__%>-F{4Ifb2ewz<=hBa13MC~ z7*^u9%#YVKaIh`OiDD1m<UiKI-!!4tO>suPtml)8xOKE@p<DnC;F4+Q6?)Mw!WI#f z$%IhR4#*;Kip<Io5^9UHr=dXk;!|l!+BD8IkZWB@?+~N=uf4RCux-QgGh#sQMl^_p zX(-&O7_LY;_lz<*mG%2sLM?dqAYo4E%kxU@6)6*jrj9Gp2I&nYa7B91w8*>$;XU|l z<Mpk<d6!E}bYlBC{-#04_?MjM@VN%>hR?WS{(FM+%_Sv;j&}}FG#<*zd-*p-Pw#BB zb70o$x-z9_Osb0CRr-q}&X<z<U(&=1*UtPRf5bNL#5xM(xZ&MaS!Dlp$u=c*T)KaC zU}%`^!n4`$AUsfAT}m;v#T~BaCYUqyT)9#;E~UuJw<xcTODlF%p{wIiF;=0OS&gnf zxHj7}f17MJZlG_!m>X9k7BME>x2~lUlTZ_IwTns7f?{!NpL_JoNAv_gVJIB#XFEB) zk&qI9Rmzt}mG57bmdBqt#rX(-sO-5a-5bv)jxV!RE3aLZ{)bek1g=Us(v-6D|H20c zg1tBQ|H7bW?6g0N*;zltho|vog`9XY@8w5-lC0Cgkpd=x!m?uJ)P%H4n({9H0%(CU zIX@^F<0)6Q6}8Apbx>MwOTxo&7?7^9bSHb&8yEhD1FQ`P$C>tfG8+l|BZCcujXK6> zh9~^Y9Jo2AFM0DU`G-~+wleKo=kF(9nd+KGeUteO<IYuVZ!RYt@{`FN6)y)s*3eDs zJoWiS-^KG-mj7l5|6mL;=&3ItW7xrl-_;)y<gZP+TJFG%YZ0}DQ-)_}dng)jvv{{D z)sxZ>;?~SARE8&|Z=}skzso(@H$R4jwa9#;yA4CfBnRIXtX%WWmy!5d`un{bZovEZ z+1T-b_$@BG6$es-nIjoWbehqvV55+v_NfCDbtY!$9EcDue+-oHXPxx$pn%P9ssAFO zXR)wG7ln0knPksH-7YutFDN&sr4@IdyCk;C<K2IDlSsS!;h?<YFOu>8eDfcRze(Lx zk@MbETo$S<8Y<s&fi>e7%S!QIq=)Zdg}Jw873D9|suh*^<^R@m5mFvD(zGBPHcdr3 z<ECrMM}LvBOGv@@$aZ|ir4tc1kJ`*>P~c;DdqNh5|4}t8j$>1&58;<q7A<@^VXz71 z=d0lc4>)`Lc$8^d-T2i1m$XY}VBgob3t7@g-(ehuLM>RFkl^R0b;Q@CZW=2Ezx>$j z8Zo2QW&21FiI@6MnOH9HxtElv4YdKU5Y%j;F!3oTIqZaw<ZKf8-5E=g3xJ7TufP5r z%e4&V?j;tTiSO_XmT&&?kE0eP{uWH6w?w^fI>tC2_hj@1mXDV;L8#vDrR$k?z#ogy z66gcl#L+4ecCp@tVO3*V`vPWve2*TCPExWvcWMk96GDCIl03UmPV4&|8yt>&iL~m# z(x}Zsct(3xrSp~)|M)@?UeGd311xOzejE_74a&M)ZfAMUv9STR);~IrUkf$Qp;ueU z5T_i2_|@08CT3-&vz%i}RkT|W@E=KjM_Kt-X;~t!BXF3HXg5@E?q6|h$W~Q!d7w}n zLU5&b8gq?fpRf)VW#1m|=G+%6%Z28na_UKJ=DYG-FdH(zHS4CI=|%%_#^^Ef;wIpJ zzu)5}9J)Supb70Az0xVDx1nwnR1P6A&KOh1h3lSg&P0Kp(-uJ_Ju2=vKxLBdI+f}K zeV*7IQORUSwGLsX{MmTBlJljs3c6g_i&EoLl&UYG)X7DoW<#Xa*n!5QeW54`zP#AF zpPfy0#XHE!yEpfpB+c)rD#2~IFQs)U;sBM(x>@N-iL5i%O$F^gmM_F)gB^Qs7)08o zRpC3rN#mG`klv1U2G%Q%Tar@=q#fJnaF8f}U1(?_64T<Y;_eAtGW<NK?ZTGW%n34k zBGw9yNsdd{Oo+83*uyA}lT4o;91y>yF)@0O?S@OOf;jq>zOc0N(eMoxgYa_l_)T() zu#YYyFnadqXp7K%q`R-r1mxO%qm#@p`f)Uo!&)n+V2$B8zhmdqiXj5~lT7E16th~J zhl=@TU+)ERo_{+3U+_Di9kfzLfFH)TvH`*do3MiB&7~)0GTqnx`-5WfWcmXpkY`EB z7;>*~z23S`X!CpLX7L6jq&MwNndOgfj(HJkb-xmOk}W5zMGT-PzMrcqPwyIhhxC18 z2rjUo+cC$+WWwUzc0FOpuTc=obE}g=Vd1x0gQWNQpbC1YoypUS?`Hj)NDj+EHm+d) z^nAaz1~3>_ZPy=PnCEv-qTKbwt^Pjm5G(j@dAHn_N*2fi_O@TkUrVdrL0&Lgv2O{- zt!v!!5%FeRcn6pd%m#ng&tE(09d8?xp@!cH15or!%TY;QdiTJou}W0Z`qRYfNKZ`E zDXEg2w=CbBtb0<dumN2%E;K{2vwu9K*d-aa^Zqo>mcdrVl6bgnq>YrQ&5gpa-VC2t zpAjP}8mlgZw|zcl-cn9+1Cfz!gu$6oK_RKhOV+a;tX-KSmd6H9H4(F(lP>1Nq#>5J z<3fXtY5hZ&PZHYTgCh9r{31AJmNvmz@x(9qVSY>}`1;c!>O`^e_QwwGQ(MlUA!sQN z90+lWI&<c1gy~711k^K^m*0ef8P5Ud_`&~RmxApP5DLq7Bh$NGje54PJ7)t4<(K0{ zM1PJbXw(bZ8=m`{O4<rkGyugs*}EpGBATm#cRCK(o$ipr4uf&BoA9!0%YjNjt<T-f zj&y0pM&{cZ>h>Ltlh>6Om=8QP{8tmk=K76K&!+wAo9Qf)xTJRrtIpZ>2)%TR+w&^F z&)4;1tWb-2H_0q#X|5_`zSik#r;y@v2gRhn^~iv7Bu-xXqg44!FB{5Cqe2l<&+cFi z$+>O4Wi#PiadBQg$g(yZs9^-hQuxd8x5>IxAIUWoifDowHEoRPmZZ#h!hVBzur}{& z{o{hHGeb!ml{**68Pa9%mIZRff(82ItV{(a&3mC`U2^N$S&pJ4;<0nsiL$I*j+ZyA zm2;{<bGw^U!98H8PnHd2W|Oe+)7QiA_V~yc>vtbkvJ&JCwn0C2>j5nSaxN%3bUq?; z1OMp@Z>RI<(c?0vIZ?d3<M>HZXt~V-hwx^23@XJGX^h4Pgc^K1RBx>JwaJVmQmVr- zhP9Pq9t(Crf-5ke_#hU$yZF{XGSe2@hXb5kcidrZyE2g=uaReJm1MoV($=+?i&FPc zEW*LPP`z;t-FW-+_c_3xQ~fi9cHQAk?4TjY`}LFryudb{$2!-UP(R`eBdULj`PDT` zH}HGr;JGs-Jfkz41e2F)-{@o7pljY36Gdz4SuXWKdo0MRPu5{xA^6RQoy1PsmZWvW zn$C&%a)6%D<eRK8c~KeF%Vu!8n0u&_ohaYGSpOQll3^>X54!S|rxP*HCbSio*1b@4 zR`Ge5bv>HJN{Z=U-Y0Xgr8`#S^Zbi&{!96Nral5i5j3~Wyo|TVeDDgc5uxvv#jaC= zM0<NZtQQ!@ArkehmZ4dWZP)faSIH0`fhUp}d(!0x1FYzK@$Fl5!=37V;%A(#*X0Bu z-vo8MeF^u(8RvyX821^~54Y>Z@=jiEn_cn&J|d<_WCk11F0-ufOjUaBl-FT*i=M5< zdc?WGF^v>BOt?Rta7|;p@KbH;o3k}xkKOhM4F{=8m{nrezoS}54$UZOjRx%3l|`K$ zF)#m9^9|5kR2G#5Ml8x>cgZVP2?x@N>0phBDH_849ej>jE8t{}?f!MIUTMBdzW>40 z07Ppj77#X_?e%5cgc204NQL!JS7L^iv|ds<Q;ucb|H5($vy_kTk_~@<Qcafxdm%<? z3(Sr1E25X!38&P^8A7nxQ-td6cg3Po{Sgq+F0*|+7-asySWF)r4C=tY!A?8Z1J9M_ zXoyi#D0!TN9R~tkW6Td;9iHVzXbwFbibnY#e<;gO`0?=YGD1(}Fgj#JP`3*WLV<A= zs{$cCFPycCgVJ85^DFW_kN<|hsl42(vC{0x^EOaLo@+vFAT>nG%ggy)i$;e8v{?mD zozgxPRHVOZ)Uh1m6-Jdi@0N2CI_|*A(X>{dQgXMPB1@8Dzgu3WuLfnrJTMf;-P{M! zXq&FFMH#qTUYZ!FQA`+`ds4M3V<^6LW`?a67Y9gkd8*ojO?y#ZPAIJMz1jiK`n4uB zSYK6wIay@xYwH%AJtE7~Y`8H#$GhR5<R=ygM~J|Ft3`Qjg}m(HmUi)V#*Z1sZwgIf z<}q=78KAdHVWGi>W;V1q^yFJ>Wj&1ah$MLJEhVr*-fDmh31X91Gqc&H)`5w^Hz^j~ z?H;{-r;>Y*yxyiu&LDZE{pvU@jLk(E@Gf!gbe39cETBqf5uVIzaiW2`+mP`9ue~38 z)y=RGmuyil+@1x=wV-G?JmDm|=G%4iWM;PI!z-&qxHwmP6X+wWLlZF@<|iX?4uR_Z zT#>bk^rwb`o(>eKiOz+%Y=F5THdw!BpIBZ;m%r*~N7mcl7b0?03yAA@!QFLr=&)$b z()A0)x_EX^1qoEIH@fmM5LfW<B%@X|HaBC^E6qI@8in9ctWEFWwBi^;60*Tzz7v}u zop6j|SJ`4@Y-6xRdt)N!J01ScCN$vp`&}rPXKzm<J2g+r#x2BVc*5ZztwD7y7zzT< zU<Ker|HM7uV4#X&JOT%5D65^;ks)1;jPfO*yfq7t*cTP0pduuBval&_ILrq9o+E-h z5Jws#N8mf%LvhNA`{bOpf}zP%(=9BvXMNjIw%@kw8)Cc{&fM;2x-oC-HpPCQyds~e zQe*5=v&CAwm%ZwA?ZVSGdU7*F0hB&h_!S|^hIV%{-I+66Lw?=iS47&Kqm14sFMoXR zKzj;=lZoju9g7Ej+Ax1iRpFS*!K^*e=V$egvo_5TD$D)DEH^e0#CO=I4z1nHX8pt7 zEnk&)EWi`ZMBb%ohk)AgrHaQOr>EcuQh28@Oj*N02M7sAl3^<eaQ0+!x)Wq>v6Yn_ zT_vyBNSc@3K_{uHuRi8@V&A9&2i!6RvT>In2jVD*=glB=;JEDh*Wp>EkhMWF=BKB6 zC)jlN$yT9WNccs{&q-=_=e8mzV2isC_hLGVT<4spSi9Nr*bfxPDmn8`y6nTfrT%YX z^eu79=qh=YRDIM4R>6{M<J(_X3`jjji2<*^*;!ug?ZSG;2CS#S9Na3f=SBf1C34{+ zd8dDiD~+rUnnG|jY~X%vjvIU5&5lvUZj`_JXj*bCe%|EexgpY6#I{G+AB{`8i&<l$ zx<YW-jD+>GOxtzIIh8<9aQ6&C07CD(>GhA5F{7NJf8evPVn}A|3x83Tua>jY`jd!8 zS5PgX3_A77jTWLe)KDg9HTmX{I4`(Akm8i;)$;NfDL(SvU@N;xSoL36xi}Vj-a@y~ zHBFBNSv@eBqAI6W%PG56jl?HUsxtYi3Wpok(@tCzdqHp}Dx5!hBe4hwb=5O7<jX-D zBhTRY=}TJ0pF5!&Jj11?4r`;#2W^;4=+8d!b_vsyiA!Gf=6+qaEJ$x`h7?b8&8b6# zy1|ATPQANb2dcusH`JF*6rb;`w<rUt@(Zg|2cS-Zmz=*-(8ABY+z&QXiFc%J;8*bE z|HidmrEsl$@3zXK!0b~$slqq6cs&=?_M5-G&qm|>)f)xKw*+e76pYjT3o+a3-!vP- zgI5-flpQE5fY0b$;9!8|{bw0V3WJM8W|V=oa<+|frYlVr)U~5Q)v>j0Ki!5C5#2}q zeh#_T&1b<8k724*#G_1~iry6I(1^&tlG%NlBd@Q}uup*M&IH9&X#Pt%<YUp0`#nyU zJUCZO>T>ylVKJF|XC4RZh7mu~9yEbAv`W6UhJZ5jpPVAJg)R&dtNPV;p_D##MolJB z^=W}1zvt%tMPRwM;@n21Cr#e`eAl>vAN(z`s}^=AEjhao&UH}iXfBU`1S2%(<bJ=N zRO;-9g$o@7!&aPKuWj{b>h-LjB@f<2exA!WEM&cTTX8wbDRHE23CYRl>eZAT>2k^w zZ-Q#!WWOaRll21KF}F)^R#qR#G7qlO;*dS6c_OUeuBnf(-0|NHttTv=t-y@HtFLxK zaO!q7qztFaCOIqX=mYZ7`_^}Z@tfd?u~=*X^M>g^u$m|9;!ED_2jr3k*$np+9h%iT zFz?oSdg>PxNib*;zCUAcCQRRScniOKqh3wv$(B=$Z4u&CK9SXd9k`Ts3F0r;xuS}2 zRZFA(=!5c#R94pObZdz0iQ4&3ssLNQvNpbnyJoTFDdzX*^nI@6J}9T=@_FVerz4Go zHyiz9Kf9QXAQl(kwr#%#n`$bGOa_JeX~JQzqbrD0?|z=G+}LxaO>sUbZxkMzRZas+ z!%@^12?&u9mmk<l+tAQ#6UGGJu}0QLSPUn|Xr8Zl>J!3Se<|P3`8f=i0z$+sodHKT z$HhH}r3LL;NSott@-3E;Ur@A6H-HJk#ko=IB+zA61EE#vtQhKBbA%JGZ)c1|9JyO) zp}|qy@)@pBt&{Iff}JjNlb(4ewl?Y&=Q=s{u@us_%jaSX2vK^7T^CvpevfItUv(Ft zv^sh5GD#j0U_c?zdo>zCu{bg{8nY=xcw9$Xl@$-kCaF}(eMnxhZ4okE?u&veMTe|{ zJR9?#t0mjE+3047^>C#37(3ay_pDGww&MaT*NtI(u}&QHwYeISpFOzc?_{QQJ|w3; zqt@o5RLITepBI_miSzjRqc7~$iG$McP#abwSpGXSTS0X;*o?)5?LXg$QJCP@)C?&V z<?{?es2uTX*a|_NC+BXmTwdN~?}mStw=GyLFCq7V!Z#M1)deCz8WR2V6wx~L`^_WD zsg3fob#2}zY`|)9C*(@x4@Hj<b`NzwK;jAcCp(=;I}ufg`GqB=?aKT{`5UE!@Iu0Q z#@U+LRAegZoY(=f$T$D<?9gTkY7W;YGpu)Vs(qZKTUhpNTodxA-kid*x3Wm>>c>S- zNS<je*(`VH<XnX7YGZgjPicM_$K?zbi~g){J)wWNw3e0!UGhRfm9-640j0EWzs~QD z#`ISSn@cLJ>^`&i^bmh;k1kuVMa1@yDTvk}#yN*`B=oEZ(qt<?Sj=(<4m^35!=GKc zc`2!OuJdtm)x~VRbps8gAj(kZ1s2TeTk|qu?cM+?LW30<EuqLWI$VB@>6i}`6-B9y zEN2-RR9<{UUT#ae$j|*_JL-5a-Zr#;r~qk?X%zT_%jV$rgO$K5oam+qZ<>zulSCWa zT}F<_vb`JQGnqfFyFJBvk(or<gz;*n;xcd1bKl;pWB&ZM;%^eVm&9~Fneko72dRH6 zrXDb-sZZ6A@az}-HV|Uoc<-)WZnjoy^evulUS2;7@<zI1&dPGVewWb%m}`8<pgguo z&XSBu?It-xHV!Hso8;7l4u0KU^4cfLC!6H2KbrQ^ka+8Q*cuG5V?%1}5uE;E?XR4t zNWDw^0JTfAv7hvuA@l=v$KFfyB!KJFt_g4MH)R;~W&}g^cJm5$LD3A>6=xzk`OZ~h z+PeSSzgJG>%B42MiWw@aX5JTZX<6i5X}al}U!ZLLjj7LBS5EXlKwY~oH7$kZwD90K z+v-S71E*{%@^?;7IgwvmwNLQAFTV5RD5>O|!!dpv)-*dVgP2>5Mmvc%&H0Ff#5ytF z+}vzKMaxhJ?)l*2o>~hL1%A7Z7V@d0;i2Cpv!k_rUL0qzTFT1stFbrAE|qB4pPdn_ z2Jz~buw0r;g@(SVD8Y!JKByn{Lk2~D$zxSJ*j{3WxPEVLmNz8}mcM_tJC(n<*kXB# z1z^V-pqeTWjK#3YiHw8ppjEo_CB2fJCub+I`<50bcrf;rv&x=4dBxMshDxA7Q#-Q= z%ZqD_M2Tl%UjhYIc#2Gr_B!1{H;TSyN!q*@l+0Fi@p~`=EQvvFgf!{$oQ7UMh3_jU zfjs%1jRnXIYOy$M4HXp?q!?f-6ZDnW;133o&O1B3ubSzstOSps8@)4I$^Mpn|9$7w zdX8!!qVD0Sy1T`jMdott>>;J<Te4-bwh5>GJ8;v-)t;}M|CXGw>hOmwsg2IJ686sV zCAfUpr0Necy=~h*W%;A>*AivA;6Tpo4PEr+{*(0hg70Cf&GIT7fk9t2mm!d0H|<sq z5;h!&ah&tf$Yx$nTZ^k$PUP3MI~@G}-0QNA>8RETnG+klq<s9C{LsRk1?C$eCFybb zfkn+lmbSH*l-$SVjd2~NRZ9Ki@@jc7PH{Xgr>@`(rYv&c$~{ES{V*UQeZh|CwquXW z%N`jBGM$`5WfPoxJhl+iC6_KG0>a6m8C(MO{0R4jnLpEWyN-BvZA^72*=BjEZQ)6c zAKHU;;R2viFWZ4)_?fuc4WKjgbJtFfv%#ED<PE}}siX%8D^AUvX#y+{8rx|k2o#Ti zdqD34NAyZeU0q%5UYVSfn8>M2_9iSrJ#+H<AuC~GyknTK?6ap1TxNw3?yV!E$nqtR zhG47OyHgwpuAj>!len`FJ;d+*+kd>CG#3nO_zw2aFfOz+(Le-WaU6HWqx{0W-4)Km zis3u*GTW>0%7>~b8fatr9Bm%t1o1cV>>9T7(_{2JfC$h&f53#9!Q*;i4s3rY0kn+3 z$#oNBWQU#;{$mtQogq9{3wXWv?KOm*o@1S4lRwn`>QS~kHyibnTtn(HC-d8s?(fKV z+FBE}N~A1!L7joAU7pa}ER9VSL=EmgT6~G_cg;QH!tX#YHNMW)b-InCoCW>h{7IIx zZ`xnX{Mzl;-vU$2Q<P%{!p_^bdC$o#%SqyV-VcQr%YPX~bRun`+56~s<tG;`9^?$l zgCBXTN-LFj3gv=DO+{tZe+($og?~S}wyjC{jdJG`@-m}Y9J||H{!!|M{O&p2erqn( z{V*o{4ey?xL9j0Q{=~%bE2Oa3zb+>z)lbMP4CI5d%LUGXuv7z|e8xUAd&zzHs=zBR zE6yk6Z{+!K^oCIJR`<hXS5@Yxs#q?3Dt08=0>4J?%EdK9Vz*9`4T~$0hH4zt5rc@@ zC2v>(GS4?hmoCj=Ga(on*vwRqPI<9NUKu~Fau!nU%2A|jgJ-dLu$3=aFKDr0dIAR< z(5zhs&4NWH_x-sN#nl5-FS1&CV!wv(dG`32R5<41$qZX7{+aOOw9-HY%hfIhX#gG> zmk$w!_r|Q6#i}Zh<#DOvAhozrv&^zU8~CS^`=q>b?YktsC7w_V`<JDKAh{ItbhXn- zI8q<}lthtw%FwJdqc{~x2)~n=DR8TWs~bpR=63xWW#CB|=*|u7-G%o0I!=minsxTQ z%EXg$8lL>LSYP-ayVkUjn=sJ3x{(kL8)6w1kCbPbuzapI59++RPhpp}#lp&3>%)}h z@p{7BR>BrTf>1YSbho`l4E7X;HRPLhJq&qYS@g>BQVTaI-P+@Be(&rW74q0?C9gw% z!l_|t3hF~)91b^-W3BEVx<=HUO!ps$lxE&3%Z;p``K_$1tS0>De=pV_X2pb;){2I1 zRFvZ~I*uJ1Xfua)rlq|d5cgY+%{)K5!#*J(9$%A?pcFnW-!G@HQ(k;p-kQo*9&;^# z-tvc{bO%bWwLNWfVqY!PYDDXQDzjB9pFAyZPLvaqX)kPcAtJk>R5tFaEV^~}Kg&4> zOVk59IrihnkIym<*ZKQmNB8C(Om{-LUVz3o^^ROe+WfcCQEY9y%z;p(g_C~U72|Qy z$)HQL_lhJ4vcfuOP@JWp)CVeA>~F_V90*Tyony6>MYPX=?y?tu2&RyybLv6;@Kv4k zcN11jH{T|+@addjfd~Pz&hI6hEG#UcXgORQuo1`2I^%J{x_Vc3u)IIhZa1M_UnnGa z^1<)h;%<TWnf)658Nl-p844XB0^#Me>}YKgy>MZKHN?#MX#*EA53twoR7330_I&aK zgx%MYF<^?k#&*`zv?~GrAV)i%MM3=+PTp**M7bLGJL&vxS(=+mn56t}yL{(;O0;be zzHYDIUSEJw)K*lKGVPljtx=}8|6MCznw#6M+*yW0Ags`Fx6YcdKqn`sY~GD!u;t`4 zP|58$(u-$pIy5`Zq88Vo3r%1lZ<IY%Lc%*9{aKWhp&@`JJNHdz;^B4pR~)ohnAXWj z$;#Lc7zHLl=qctTum8%?XXIs4m$LjBIaM+!xzGHadD`2}yObTz$Sb8>#r}+J{2GQd zJ3K7NS?e}A(Q4*f!_e|L@lc`$W5P4C>6rjKX&yW!_@nLMHL*%Oo<Ktt2E7fpDamq% zw8gp(l&hIL7-ISHvwN%8K)*kNkgZHFLEJ3SP46B-3aQy$IGo^~{%6)=<*}Xe%7Pd8 zxyKcp<!`rItX7Hv#>REcMARmjA&D5BYbuTT`ZSkJ1O<v~C?=ct?$gBPnJz1xJLP*< za44F;JK6+zyx%^~uF9$p+)&1LVxAUn<dEugy0b^0mH+vBsmTl1u#-p5@D;pDb8>nM zr~)EFpkGJ0<{lpBp}f>;ZW3l~7{LK5-~3q_46neus@6^r|N2Jv$$aIdXXUTj?8!sS zaZLCG2ZZnXHBs}VU%&xZxSlQRGLzRApd}cB$C(a3WE4FP!RtsUTC@C_tT(Go>Pf$I zhg;H^uIuk|aHK}3u8Z|Nv)A^sKlub=hGQWqU;Tk>lrIi8<^Dj%UmX4x;BO)R7I|}j Ja7XUH{XcN=5v>3K delta 49527 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 Lo^Ri_=WqWXZq;hP diff --git a/img/helpmenu/minihudhelp_combineunload.dds b/img/helpmenu/minihudhelp_combineunload.dds index 181aa1aa60773c0b0ec10d06709e8ae607f60ac1..0f136fa88984f16f60c886b86c93cd1465968aad 100644 GIT binary patch delta 35424 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 Ef3>j-UjP6A delta 35417 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 diff --git a/img/helpmenu/minihudhelp_fieldwork.dds b/img/helpmenu/minihudhelp_fieldwork.dds index e64bc87a6944a281dcf7b6753b768a23fce9feef..c1fb7050588ed4b8968e66224e951752f81851f9 100644 GIT binary patch delta 48074 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 delta 48068 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 AI{*Lx diff --git a/img/helpmenu/minihudhelp_general.dds b/img/helpmenu/minihudhelp_general.dds index 4f2c47e80d457bbb4a56ffce3a1062bcc194dc56..614a52f44f2145c350c6948aca0974b3042aeb89 100644 GIT binary patch delta 42553 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 L;)O*|{jdKEBykrk delta 42554 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 diff --git a/img/helpmenu/minihudhelp_siloloader.dds b/img/helpmenu/minihudhelp_siloloader.dds index 71cd5b83b14db8ba213525491c376d821d42fc1c..547913f027309bcae3d341fbaa6ba4e049ae178b 100644 GIT binary patch delta 37899 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 delta 37893 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 JuB`p={{Ud?*W~~J diff --git a/img/helpmenu/minihudhelp_siloworker.dds b/img/helpmenu/minihudhelp_siloworker.dds index bf41c59161c74069840eb9a402de1b7eb917c7b0..8dfcd0df0f594ccf143048a086128e76fc169746 100644 GIT binary patch delta 43705 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 delta 43702 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 From 9be35ac6d7dfc1e046ac21fe52ac949620ac3530 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Mon, 9 Dec 2024 20:14:53 +0100 Subject: [PATCH 092/158] Fixes open vehicle menu short cut --- scripts/gui/CpInGameMenu.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 398805b15..753f99b19 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -15,30 +15,35 @@ function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager, c self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN, function (menu) g_gui:showGui("CpInGameMenu") self:changeScreen(CpInGameMenu) + self:updatePages() -- local index = self.pagingElement:getPageMappingIndexByElement(self.page) -- self.pageSelector:setState(pageAIIndex, true) end, self) self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_GLOBAL_SETTINGS, function (menu) g_gui:showGui("CpInGameMenu") self:changeScreen(CpInGameMenu) + self:updatePages() local index = self.pagingElement:getPageMappingIndexByElement(self.pageGlobalSettings) self.pageSelector:setState(index, true) end, self) self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_VEHICLE_SETTINGS, function (menu) g_gui:showGui("CpInGameMenu") self:changeScreen(CpInGameMenu) + self:updatePages() local index = self.pagingElement:getPageMappingIndexByElement(self.pageVehicleSettings) self.pageSelector:setState(index, true) end, self) self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR, function (menu) g_gui:showGui("CpInGameMenu") self:changeScreen(CpInGameMenu) + self:updatePages() local index = self.pagingElement:getPageMappingIndexByElement(self.pageCourseGenerator) self.pageSelector:setState(index, true) end, self) self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_COURSE_MANAGER, function (menu) g_gui:showGui("CpInGameMenu") self:changeScreen(CpInGameMenu) + self:updatePages() local index = self.pagingElement:getPageMappingIndexByElement(self.pageCourseManager) self.pageSelector:setState(index, true) end, self) From 9bf49bdd8400413d049bf40b53b22b11d2667e75 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 9 Dec 2024 15:50:05 -0500 Subject: [PATCH 093/158] fix: vine course generation typo --- scripts/ai/jobs/CpAIJobFieldWork.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/ai/jobs/CpAIJobFieldWork.lua b/scripts/ai/jobs/CpAIJobFieldWork.lua index 61bdf0b50..b4a897d83 100644 --- a/scripts/ai/jobs/CpAIJobFieldWork.lua +++ b/scripts/ai/jobs/CpAIJobFieldWork.lua @@ -222,8 +222,10 @@ function CpAIJobFieldWork:onClickGenerateFieldWorkCourse() vineSettings.vineCenterOffset:getValue(), tx, tz ) - ok, course = CourseGeneratorInterface.generateVineCourse(vertices, + ok, course = CourseGeneratorInterface.generateVineCourse( + vertices, startingPoint, + vehicle, width, AIUtil.getTurningRadius(vehicle), rowAngleDeg, From 035396da78b79824a643b3b448122faf4f0f6961 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 10 Dec 2024 20:33:06 +0100 Subject: [PATCH 094/158] Added helpmenu and removed redundant xml gui profiles ... --- Courseplay.lua | 11 - config/HelpMenu.xml | 4 +- config/gui/CpInGameMenu.xml | 5 +- config/gui/pages/CourseGeneratorFrame.xml | 359 +----------------- config/gui/pages/CourseManagerFrame.xml | 419 ---------------------- config/gui/pages/GlobalSettingsFrame.xml | 16 +- config/gui/pages/HelpFrame.xml | 123 +++++++ config/gui/pages/VehicleSettingsFrame.xml | 16 +- modDesc.xml | 1 + scripts/gui/CpInGameMenu.lua | 24 +- scripts/gui/pages/CpHelpFrame.lua | 202 +++++++++++ 11 files changed, 358 insertions(+), 822 deletions(-) create mode 100644 config/gui/pages/HelpFrame.xml create mode 100644 scripts/gui/pages/CpHelpFrame.lua diff --git a/Courseplay.lua b/Courseplay.lua index 2a9f89a02..2032ddd58 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -147,17 +147,6 @@ function Courseplay.drawHudMap(map) end end ---- Adds cp help info to the in game help menu. -function Courseplay:loadMapDataHelpLineManager(superFunc, ...) - local ret = superFunc(self, ...) - if ret then - self:loadFromXML(Utils.getFilename("config/HelpMenu.xml", Courseplay.BASE_DIRECTORY)) - return true - end - return false -end -HelpLineManager.loadMapData = Utils.overwrittenFunction( HelpLineManager.loadMapData, Courseplay.loadMapDataHelpLineManager) - --- Saves all global data, for example global settings. function Courseplay.saveToXMLFile(missionInfo) if missionInfo.isValid then diff --git a/config/HelpMenu.xml b/config/HelpMenu.xml index aa3981b1e..83e693f78 100644 --- a/config/HelpMenu.xml +++ b/config/HelpMenu.xml @@ -26,14 +26,14 @@ <page title="$l10n_CP_help_page_extendedJobMenu_title"> <paragraph> <text text="$l10n_CP_help_page_startJobMenuBase_text"/> - <image filename="img/helpmenu/startjobmenuhelp.dds" size="1024 1024" uvs="0px 0px 1020px 895px"/> + <image filename="img/helpmenu/startjobmenuhelp.dds" size="1024 1024" uvs="0px 0px 1024px 895px" aspectRatio="0.87"/> </paragraph> <paragraph> <text text="$l10n_CP_help_page_startJobMenuFunctions_text"/> </paragraph> <paragraph> <text text="$l10n_CP_help_page_readyJobMenuBase_text"/> - <image filename="img/helpmenu/readyjobmenuhelp.dds" size="1024 1024" uvs="0px 0px 765px 510px"/> + <image filename="img/helpmenu/readyjobmenuhelp.dds" size="1024 1024" uvs="0px 0px 765px 510px" aspectRatio="0.66"/> </paragraph> <paragraph> <text text="$l10n_CP_help_page_readyJobMenuFunctions_text"/> diff --git a/config/gui/CpInGameMenu.xml b/config/gui/CpInGameMenu.xml index 42c5dc03c..399995d20 100644 --- a/config/gui/CpInGameMenu.xml +++ b/config/gui/CpInGameMenu.xml @@ -7,6 +7,7 @@ <FrameReference ref="cpInGameMenuVehicleSettings" name="cpInGameMenuVehicleSettings" id="pageVehicleSettings" /> <FrameReference ref="cpInGameMenuCourseGenerator" name="cpInGameMenuCourseGenerator" id="pageCourseGenerator" /> <FrameReference ref="cpInGameMenuCourseManager" name="cpInGameMenuCourseManager" id="pageCourseManager"/> + <FrameReference ref="cpInGameMenuHelpLine" name="cpInGameMenuHelpLine" id="pageHelpLine"/> </Paging> <Bitmap profile="fs25_tabListContainer" id="header"> <MultiTextOption profile="uiInGameMenuHeaderSelector" onClick="onClickPageSelection" @@ -41,8 +42,6 @@ </Button> </BoxLayout> <GUIProfiles> - <Profile name="uiInGameMenuHeaderDark" extends="uiInGameMenuHeader"> - <imageColor value="$preset_colorGlass" /> - </Profile> + </GUIProfiles> </GUI> \ No newline at end of file diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 12bdfd9a4..4d5cce383 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -219,14 +219,14 @@ <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> - <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpBinaryyOption> </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> - <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpOptionToggle> </Bitmap> </GuiElement> @@ -236,362 +236,9 @@ </ThreePartBitmap> </ThreePartBitmap> <GUIProfiles> - <Profile name="fs25_settingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> + <Profile name="cpSettingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> <position value="-500px 0px" /> </Profile> - <Profile name="fs25_settingsNoPermissionText" extends="fs25_textDefault" - with="anchorMiddleStretchingX"> - <size value="100% 40px" /> - <textSize value="20px" /> - <textAlignment value="center" /> - <textColor value="$preset_fs25_colorMainLight" /> - </Profile> - <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> - <position value="-41px 0px" /> - </Profile> - <Profile name="fs25_inGameMapLoadingBg" extends="emptyPanel" with="pivotTopRight"> - <absoluteSizeOffset value="540px 40px" /> - <position value="-125px 0px" /> - <visible value="false" /> - </Profile> - <Profile name="fs25_mapList" extends="fs25_subCategoryList" with="anchorTopCenter"> - <absoluteSizeOffset value="0px 60px" /> - </Profile> - <Profile name="fs25_mapListItem" extends="fs25_subCategoryListItem"> - <height value="70px" /> - <hotspot value="0px 0px 0px 15px" /> - <imageSliceId value="gui.map_box" /> - <imageSelectedSliceId value="gui.map_box_selected" /> - <imageHighlightedSliceId value="gui.map_box_hover" /> - </Profile> - <Profile name="fs25_mapListItemIconBg" extends="baseReference" with="anchorTopLeft"> - <size value="46px 46px" /> - <position value="3px -3px" /> - <imageSliceId value="gui.map_box_colorOverlay" /> - <imageColor value="$preset_colorBlack" /> - <imageSelectedColor value="$preset_colorWhite" /> - </Profile> - <Profile name="fs25_mapListColorTemplate" extends="baseReference"> - <size value="1px 46px" /> - <imageColor value="$preset_colorTransparent" /> - <imageSelectedColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_mapListItemIcon" extends="fs25_subCategoryListItemIcon" - with="anchorTopLeft"> - <size value="40px 40px" /> - <position value="6px -6px" /> - <imageColor value="$preset_colorWhite_25" /> - <imageSelectedColor value="$preset_colorWhite" /> - </Profile> - <Profile name="fs25_mapListItemName" extends="fs25_subCategoryListItemName" - with="anchorTopLeft"> - <width value="230px" /> - <position value="65px -16px" /> - </Profile> - <Profile name="fs25_workerListItemIcon" extends="fs25_subCategoryListItemIcon"> - <imageSliceId value="gui.ingameMap_helper" /> - </Profile> - <Profile name="fs25_workerListItemHelper" extends="fs25_textDefault" with="anchorMiddleLeft"> - <size value="10px 10px" /> - <position value="38px 9px" /> - <textSize value="10px" /> - <textAlignment value="center" /> - <textSelectedColor value="$preset_fs25_colorMainLight" /> - </Profile> - <Profile name="fs25_workerListItemDescription" extends="fs25_subCategoryListItemName"> - <width value="220px" /> - <position value="100px -50px" /> - </Profile> - <Profile name="fs25_mapButtonContainer" extends="emptyPanel" with="anchorBottomStretchingX"> - <height value="25px" /> - </Profile> - <Profile name="fs25_mapButton" extends="emptyPanel" with="anchorStretchingYStretchingX"> - <handleFocus value="false" /> - <textAlignment value="right" /> - <iconSize value="25px 25px" /> - <iconColor value="$preset_fs25_colorMainHighlight" /> - <iconFocusedColor value="$preset_fs25_colorMainDark" /> - <iconHighlightedColor value="$preset_fs25_colorMainDark" /> - <iconSelectedColor value="$preset_fs25_colorMainDark" /> - <iconDisabledColor value="$preset_colorDisabled" /> - <iconBgColor value="$preset_fs25_colorGreenDark" /> - <iconBgFocusedColor value="$preset_fs25_colorMainHighlight" /> - <iconBgHighlightedColor value="$preset_fs25_colorMainHighlight" /> - <iconBgSelectedColor value="$preset_fs25_colorMainHighlight" /> - <iconBgDisabledColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_mapButtonText" extends="fs25_textDefault" - with="anchorStretchingYStretchingX"> - <width value="100%" /> - <textSize value="16px" /> - <textBold value="true" /> - <textUpperCase value="true" /> - <textOffset value="10px 0px" /> - <textSelectedColor value="$preset_fs25_colorMainHighlight" /> - <textFocusedColor value="$preset_fs25_colorMainHighlight" /> - <textHighlightedColor value="$preset_fs25_colorMainHighlight" /> - </Profile> - <Profile name="fs25_mapButtonBg" extends="baseReference" with="anchorStretchingYStretchingX"> - <imageColor value="$preset_fs25_colorMainDark_90" /> - <imageSliceId value="gui.button_middle" /> - <startImageSize value="6px 0" /> - <startImageSliceId value="gui.button_left" /> - <endImageSize value="6px 0" /> - <endImageSliceId value="gui.button_right" /> - </Profile> - <Profile name="fs25_mapButtonBgLight" extends="fs25_mapButtonBg"> - <imageColor value="$preset_fs25_colorMainDark_40" /> - </Profile> - <Profile name="fs25_mapButtonAction1" extends="fs25_mapButton"> - <inputAction value="MENU_MAP_ACTION_1" /> - </Profile> - <Profile name="fs25_mapMoneyBoxBg" extends="fs25_shopMoneyBoxBg"> - <color value="$preset_fs25_colorMainDark_90" /> - </Profile> - <Profile name="fs25_mapContextBoxContainer" extends="emptyPanel" with="anchorTopRight"> - <absoluteSizeOffset value="540px 40px" /> - <clipping value="true" /> - </Profile> - <Profile name="fs25_mapContextBox" extends="emptyPanel"> - <size value="276px 330px" /> - <visible value="false" /> - <handleFocus value="false" /> - </Profile> - <Profile name="fs25_mapContextBoxBg" extends="baseReference" with="anchorTopLeft"> - <absoluteSizeOffset value="-34px -20px" /> - <position value="-18px 10px" /> - <isHorizontal value="false" /> - <imageSliceId value="gui.map_selectedVehicle_middle" /> - <startImageSize value="0 210px" /> - <startImageSliceId value="gui.map_selectedVehicle_top" /> - <endImageSize value="0 40px" /> - <endImageSliceId value="gui.map_selectedVehicle_bottom" /> - </Profile> - <Profile name="fs25_mapContextBoxBgFarmland" extends="baseReference" with="anchorTopLeft"> - <absoluteSizeOffset value="-29px -20px" /> - <position value="-10px 10px" /> - <imageSliceId value="gui.map_fieldInfo" /> - </Profile> - <Profile name="fs25_mapContextImageVehicle" extends="baseReference" with="anchorTopCenter"> - <size value="220px 220px" /> - <position value="0px -10px" /> - <visible value="false" /> - </Profile> - <Profile name="fs25_mapContextImage" extends="baseReference" with="anchorTopCenter"> - <size value="200px 200px" /> - <position value="0px -22px" /> - </Profile> - <Profile name="fs25_mapContextText" extends="fs25_textDefault" - with="anchorBottomStretchingX"> - <size value="100% 40px" /> - <absoluteSizeOffset value="40px" /> - <position value="0px 45px" /> - <textSize value="18px" /> - <textBold value="true" /> - <textMaxNumLines value="2" /> - <textAlignment value="center" /> - <textVerticalAlignment value="bottom" /> - </Profile> - <Profile name="fs25_mapContextTextTop" extends="fs25_mapContextText" - with="anchorTopStretchingX" /> - <Profile name="fs25_mapContextFarm" extends="fs25_textDefault" - with="anchorBottomStretchingX"> - <width value="100%" /> - <absoluteSizeOffset value="20px" /> - <position value="0px 19px" /> - <textAlignment value="center" /> - <textLayoutMode value="resize" /> - </Profile> - <Profile name="fs25_mapContextFarmTop" extends="fs25_mapContextFarm" - with="anchorTopStretchingX" /> - <Profile name="fs25_mapContextFarmlandTitle" extends="fs25_textDefault" - with="anchorTopStretchingX"> - <width value="100%" /> - <absoluteSizeOffset value="20px" /> - <textBold value="true" /> - </Profile> - <Profile name="fs25_mapContextFarmlandValue" extends="fs25_mapContextFarmlandTitle" - with="anchorTopStretchingX"> - <width value="100%" /> - <absoluteSizeOffset value="20px" /> - <textBold value="false" /> - <textAlignment value="right" /> - </Profile> - <Profile name="fs25_mapContextSeparator" extends="baseReference" - with="anchorBottomStretchingX"> - <height value="1dp" /> - <position value="0px 90px" /> - <imageColor value="$preset_fs25_colorGreyLight_20" /> - </Profile> - <Profile name="fs25_mapContextButtonList" extends="emptyPanel" - with="anchorBottomStretchingX"> - <height value="200px" /> - <position value="0px -200px" /> - <handleFocus value="true" /> - <listItemSpacing value="3px" /> - <showHighlights value="true" /> - <canReceiveFocusWhileEmpty value="true" /> - </Profile> - <Profile name="fs25_mapContextButtonListItem" extends="emptyPanel" - with="anchorTopStretchingX"> - <height value="25px" /> - </Profile> - <Profile name="fs25_mapContextButtonListItemBg" extends="baseReference" - with="anchorStretchingYStretchingX"> - <imageSliceId value="gui.button_middle" /> - <imageColor value="$preset_fs25_colorMainDark_90" /> - <imageSelectedColor value="$preset_fs25_colorMainDark" /> - <startImageSize value="6px 0" /> - <startImageSliceId value="gui.button_left" /> - <startImageSelectedColor value="$preset_fs25_colorMainDark" /> - <endImageSize value="6px 0" /> - <endImageSliceId value="gui.button_right" /> - <endImageSelectedColor value="$preset_fs25_colorMainDark" /> - </Profile> - <Profile name="fs25_mapContextButtonListItemButton" extends="emptyPanel" - with="anchorStretchingYStretchingX"> - <handleFocus value="false" /> - <textAlignment value="right" /> - <inputAction value="MENU_ACCEPT" /> - <iconSize value="25px 25px" /> - <iconColor value="$preset_colorTransparent" /> - <iconFocusedColor value="$preset_fs25_colorMainDark" /> - <iconHighlightedColor value="$preset_colorTransparent" /> - <iconSelectedColor value="$preset_fs25_colorMainDark" /> - <iconBgColor value="$preset_colorTransparent" /> - <iconBgFocusedColor value="$preset_fs25_colorMainHighlight" /> - <iconBgHighlightedColor value="$preset_colorTransparent" /> - <iconBgSelectedColor value="$preset_fs25_colorMainHighlight" /> - </Profile> - <Profile name="fs25_aiJobTypeMultiTextOption" extends="fs25_multiTextOption" - with="anchorTopStretchingX"> - <width value="100%" /> - </Profile> - <Profile name="fs25_aiCreateJobLayout" extends="emptyPanel" - with="anchorStretchingYLeft pivotTopLeft"> - <absoluteSizeOffset value="0px 40px" /> - <position value="0px -40px" /> - <flowDirection value="vertical" /> - </Profile> - <Profile name="fs25_aiCreateJobMultiTextOption" extends="fs25_multiTextOption" - with="anchorTopStretchingX"> - <size value="100% 40px" /> - </Profile> - <Profile name="fs25_aiCreateJobParameterInvalid" extends="baseReference" - with="anchorTopRight"> - <size value="30px 24px" /> - <position value="-5px 26px" /> - <imageSliceId value="gui.contextAction_icon_alert" /> - </Profile> - <Profile name="fs25_aiCreateJobParameterBg" extends="fs25_multiTextOptionBg" - with="anchorTopStretchingX"> - <size value="100% 40px" /> - </Profile> - <Profile name="fs25_aiCreateJobParameterIcon" extends="baseReference" - with="anchorMiddleLeft"> - <size value="22px 22px" /> - <position value="17px 0px" /> - <imageSliceId value="gui.aiParameterPosition" /> - <imageColor value="$preset_colorWhite_25" /> - <imageFocusedColor value="$preset_colorWhite" /> - <imageHighlightedColor value="$preset_colorWhite" /> - <imageDisabledColor value="$preset_colorWhite" /> - </Profile> - <Profile name="fs25_aiCreateJobParameterVehicleIcon" extends="fs25_aiCreateJobParameterIcon"> - <size value="36px 36px" /> - <position value="10px 0px" /> - <imageSliceId value="gui.icon_vehicleDealer_machines" /> - </Profile> - <Profile name="fs25_aiCreateJobParameterTitle" extends="fs25_textDefault" - with="anchorTopStretchingX"> - <size value="100% 40px" /> - <textBold value="true" /> - <textOffset value="0px 5px" /> - <textUpperCase value="true" /> - <textVerticalAlignment value="bottom" /> - </Profile> - <Profile name="fs25_aiCreateJobParameterButton" extends="fs25_multiTextOptionText" - with="anchorTopStretchingX"> - <size value="100% 40px" /> - <textBold value="true" /> - <textMaxWidth value="215px" /> - <textAutoWidth value="false" /> - <imageSliceId value="noSlice" /> - <imageColor value="$preset_colorTransparent" /> - <imageFocusedColor value="$preset_colorTransparent" /> - <imageHighlightedColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_aiContainerEmptyText" extends="fs25_textDefault" - with="anchorMiddleStretchingX"> - <width value="300px" /> - <textSize value="20px" /> - <textMaxNumLines value="3" /> - <textAlignment value="center" /> - </Profile> - <Profile name="ingameMenuMapDynamicLoadingText" extends="fs25_textDefault" - with="anchorBottomCenter"> - <size value="600px 30px" /> - <position value="0px 50px" /> - <textSize value="24px" /> - <textAlignment value="center" /> - <textBold value="true" /> - <text2Size value="24px" /> - <text2Bold value="true" /> - <text2Offset value="1px -1px" /> - <text2Alignment value="center" /> - <text2Color value="$preset_colorBlack" /> - </Profile> - <Profile name="ingameMenuAILimitReached" extends="fs25_textDefault" with="anchorBottomRight"> - <size value="316px 28px" /> - <position value="-42px 150px" /> - <textMaxNumLines value="4" /> - <textWrapWidth value="300px" /> - </Profile> - <Profile name="ingameMenuAIActionText" extends="fs25_textDefault" with="anchorBottomLeft"> - <textAutoWidth value="true" /> - <textSize value="24px" /> - <textBold value="true" /> - <textOffset value="25px 5px" /> - <text2Size value="24px" /> - <text2Bold value="true" /> - <text2Offset value="24px 4px" /> - <text2Color value="$preset_colorBlack" /> - </Profile> - <Profile name="ingameMenuAIErrorText" extends="fs25_textDefault" with="anchorBottomCenter"> - <position value="0px 50px" /> - <textSize value="24px" /> - <textBold value="true" /> - <textAutoWidth value="true" /> - <text2Size value="24px" /> - <text2Bold value="true" /> - <text2Offset value="-1px -1px" /> - <text2Color value="$preset_colorBlack" /> - </Profile> - <Profile name="fs25_ingameMenuAIStatusText" extends="fs25_textDefault" - with="anchorBottomRight"> - <position value="-20px -35px" /> - <textSize value="20px" /> - <textBold value="true" /> - <textAutoWidth value="true" /> - <textAlignment value="right" /> - <text2Size value="20px" /> - <text2Bold value="true" /> - <text2Offset value="-1px -1px" /> - <text2Color value="$preset_colorBlack" /> - </Profile> - - <Profile name="fs25_ingameMenuAIStatusText" extends="fs25_textDefault" - with="anchorBottomRight"> - <position value="-20px -35px" /> - <textSize value="20px" /> - <textBold value="true" /> - <textAutoWidth value="true" /> - <textAlignment value="right" /> - <text2Size value="20px" /> - <text2Bold value="true" /> - <text2Offset value="-1px -1px" /> - <text2Color value="$preset_colorBlack" /> - </Profile> <Profile name="cpLeftSideBackground" extends="fs25_fullScreenBackground" with="pivotMiddleLeft"> <height value="100%"/> <width value="250px" /> diff --git a/config/gui/pages/CourseManagerFrame.xml b/config/gui/pages/CourseManagerFrame.xml index fe21fe33c..f1be34df3 100644 --- a/config/gui/pages/CourseManagerFrame.xml +++ b/config/gui/pages/CourseManagerFrame.xml @@ -73,55 +73,12 @@ </GuiElement> </GuiElement> <GUIProfiles> - <Profile name="fs25_statisticsHeaderBox" extends="emptyPanel" with="anchorTopStretchingX"> - <height value="32px" /> - </Profile> - <Profile name="fs25_statisticsHeaderText" extends="fs25_textDefault" - with="anchorStretchingYLeft"> - <textSize value="16px" /> - <textBold value="true" /> - <textUpperCase value="true" /> - <textOffset value="40px 0px" /> - <textColor value="$preset_colorWhite_50" /> - </Profile> - <Profile name="fs25_statisticsHeaderName" extends="fs25_statisticsHeaderText" - with="anchorStretchingYLeft"> - <width value="500px" /> - </Profile> - <Profile name="fs25_statisticsHeaderIcon" extends="baseReference" with="anchorMiddleLeft"> - <size value="20px 20px" /> - <imageColor value="$preset_colorWhite_50" /> - <imageSliceId value="gui.prices_buying" /> - </Profile> <Profile name="cpLeftList" extends="emptyPanel" with="anchorStretchingYLeft pivotTopLeft"> <width value="695px" /> <absoluteSizeOffset value="0px 56px" /> <position value="25px -56px" /> </Profile> - <Profile name="fs25_pricesProductListItem" extends="baseReference" - with="anchorTopStretchingX alternating"> - <height value="32px" /> - <imageColor value="$preset_fs25_colorGrey" /> - <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <alternateBackgroundColor value="$preset_fs25_colorGreyDark_50" /> - </Profile> - <Profile name="fs25_pricesProductListItemIcon" extends="baseReference" - with="anchorMiddleLeft"> - <size value="24px 24px" /> - <position value="8px 0px" /> - <imageSliceId value="noSlice" /> - </Profile> - <Profile name="fs25_pricesProductListItemName" extends="fs25_textDarkHighlight" - with="anchorStretchingYLeft"> - <textBold value="true" /> - <textOffset value="40px 0px" /> - </Profile> - <Profile name="fs25_pricesProductListItemStorage" extends="fs25_textDarkHighlight" - with="anchorStretchingYRight"> - <textOffset value="-10px 0px" /> - <textAlignment value="right" /> - </Profile> <Profile name="cpLeftListSliderBox" extends="fs25_listSliderBox" with="anchorTopLeft"> <absoluteSizeOffset value="0px 56px" /> @@ -133,385 +90,9 @@ <position value="-63px -56px" /> <selectedWithoutFocus value="false" /> </Profile> - <Profile name="fs25_pricesPriceListItem" extends="baseReference" - with="anchorTopStretchingX alternating"> - <height value="32px" /> - <imageColor value="$preset_fs25_colorGrey" /> - <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <alternateBackgroundColor value="$preset_fs25_colorGreyDark_50" /> - </Profile> - <Profile name="fs25_pricesPriceListItemHotspot" extends="baseReference" - with="anchorMiddleLeft"> - <size value="24px 24px" /> - <position value="10px 0px" /> - <imageSliceId value="gui.visit" /> - <imageColor value="$preset_colorWhite_08" /> - <imageSelectedColor value="$preset_colorWhite" /> - </Profile> - <Profile name="fs25_pricesPriceListItemName" extends="fs25_textDarkHighlight" - with="anchorStretchingYLeft"> - <position value="40px 0px" /> - <textBold value="true" /> - <textAutoWidth value="true" /> - <textMaxWidth value="370px" /> - <textLayoutMode value="scrolling" /> - </Profile> - <Profile name="fs25_pricesPriceListItemIconTrain" extends="baseReference" - with="anchorMiddleRight"> - <size value="20px 20px" /> - <position value="30px 0px" /> - <imageSliceId value="gui.sellingStation_train" /> - </Profile> - <Profile name="fs25_pricesPriceListItemIconPallet" - extends="fs25_pricesPriceListItemIconTrain"> - <imageSliceId value="gui.sellingStation_pallet" /> - </Profile> - <Profile name="fs25_pricesPriceListItemInfo" extends="fs25_textDarkHighlight" - with="anchorStretchingYRight"> - <position value="-62px 0px" /> - <format value="currency" /> - <formatDecimalPlaces value="0" /> - </Profile> <Profile name="cpRightListSliderBox" extends="fs25_listSliderBox" with="anchorTopRight"> <absoluteSizeOffset value="0px 56px" /> <position value="-41px -56px" /> </Profile> - <Profile name="fs25_pricesNoSellpointsText" extends="textDefault" with="anchorTopCenter"> - <position value="200px -150px" /> - <textSize value="20px" /> - <textAlignment value="center" /> - <textAutoWidth value="true" /> - <textMaxNumLines value="2" /> - <textMaxWidth value="450px" /> - </Profile> - <Profile name="fs25_pricesPriceListArrow" extends="baseReference" with="anchorMiddleRight"> - <size value="18px 18px" /> - <position value="-83px 0px" /> - <imageSliceId value="gui.priceTrend" /> - <imageColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_pricesPriceListArrowClimbing" extends="fs25_pricesPriceListArrow"> - <imageColor value="$preset_colorGreen" /> - <imageRotation value="0" /> - </Profile> - <Profile name="fs25_pricesPriceListArrowFalling" extends="fs25_pricesPriceListArrow"> - <imageColor value="$preset_colorRed" /> - <imageInvertY value="true" /> - </Profile> - <Profile name="fs25_pricesPriceListArrowGreatDemand" extends="fs25_pricesPriceListArrow"> - <imageColor value="$preset_colorBlue" /> - <imageSelectedColor value="$preset_colorWhite" /> - <imageRotation value="90" /> - </Profile> - <Profile name="fs25_pricesFluctuationsLayoutBg" extends="emptyPanel" - with="anchorBottomRight"> - <size value="1045px 405px" /> - <position value="-43px 0px" /> - <handleFocus value="false" /> - </Profile> - <Profile name="fs25_pricesSeparator" extends="baseReference" with="anchorTopLeft"> - <size value="1dp 375px" /> - <margin value="0px 30px 0px 0px" /> - <imageColor value="$preset_fs25_colorGreyLight_50" /> - </Profile> - <Profile name="fs25_pricesMonthText" extends="fs25_textDefault" with="anchorTopLeft"> - <width value="86px" /> - <margin value="0px 5px 0px 0px" /> - <textBold value="true" /> - <textUpperCase value="true" /> - <textAlignment value="center" /> - <textColor value="$preset_colorWhite_50" /> - </Profile> - <Profile name="fs25_pricesFluctuationsContainer" extends="emptyPanel" - with="anchorBottomRight"> - <size value="1045px 375px" /> - <position value="-43px 0px" /> - </Profile> - <Profile name="fs25_pricesFluctuationsTodayBar" extends="emptyPanel" - with="anchorStretchingYLeft"> - <width value="2dp" /> - </Profile> - <Profile name="fs25_pricesFluctuationsPrice" extends="fs25_textDefault"> - <position value="0px 20px" /> - <textAlignment value="center" /> - <format value="currency" /> - <formatDecimalPlaces value="0" /> - </Profile> - <Profile name="fs25_pricesFluctuationsPriceBg" extends="baseReference" - with="anchorMiddleCenter"> - <size value="70px 25px" /> - <position value="0px -1px" /> - <imageColor value="$preset_fs25_colorGrey" /> - <imageSliceId value="gui.button_middle" /> - <startImageSize value="6px 0" /> - <startImageSliceId value="gui.button_left" /> - <endImageSize value="6px 0" /> - <endImageSliceId value="gui.button_right" /> - </Profile> - <Profile name="fs25_vehiclesHeaderName" extends="fs25_statisticsHeaderText"> - <size value="450px 32px" /> - <imageColor value="0 0 0 0" /> - <textFocusedColor value="$preset_fs25_colorMainHighlight" /> - <textHighlightedColor value="$preset_fs25_colorMainHighlight" /> - </Profile> - <Profile name="fs25_vehiclesHeaderTextContainer" extends="emptyPanel"> - <size value="180px 32px" /> - </Profile> - <Profile name="fs25_vehiclesHeaderText" extends="fs25_vehiclesHeaderName" - with="anchorMiddleCenter"> - <size value="160px 32px" /> - <textOffset value="0px 0px" /> - <textAutoWidth value="true" /> - <textAlignment value="center" /> - </Profile> - <Profile name="fs25_vehiclesHeaderIconSortAscending" extends="baseReference" - with="anchorMiddleLeft"> - <size value="7px 7px" /> - <position value="-15.5px 0px" /> - <imageColor value="$preset_colorWhite" /> - <imageSliceId value="gui.tourdialogue_arrow" /> - <imageFocusedColor value="$preset_fs25_colorMainHighlight" /> - <imageHighlightedColor value="$preset_fs25_colorMainHighlight" /> - </Profile> - <Profile name="fs25_vehiclesHeaderIconSortDescending" - extends="fs25_vehiclesHeaderIconSortAscending"> - <imageRotation value="180" /> - </Profile> - <Profile name="fs25_vehiclesList" extends="emptyPanel" - with="anchorStretchingYStretchingX pivotTopCenter"> - <position value="0px -34px" /> - <itemizedScrollDelta value="3" /> - <absoluteSizeOffset value="0px 249px" /> - </Profile> - <Profile name="fs25_vehiclesListItem" extends="baseReference" with="anchorTopStretchingX"> - <height value="45px" /> - <alternateChildren value="true" /> - <imageSliceId value="gui.empty" /> - <imageColor value="$preset_fs25_colorGreyListItem" /> - <alternateBackgroundColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_vehiclesListItemBg" extends="baseReference" - with="anchorStretchingYStretchingX"> - <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <startImageSize value="100px 30px" /> - <startImageSliceId value="gui.list_gradient" /> - <startImageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <endImageSize value="100px 30px" /> - <endImageSliceId value="gui.list_gradient" /> - <endImageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <endImageInvertX value="true" /> - </Profile> - <Profile name="fs25_vehiclesListItemName" extends="fs25_textDarkHighlight" - with="anchorStretchingYLeft"> - <width value="450px" /> - <textSize value="18px" /> - <textBold value="true" /> - <textOffset value="40px 0px" /> - <Variant name="mobile"> - <size value="748px 104px" /> - <textSize value="33px" /> - </Variant> - </Profile> - <Profile name="fs25_vehiclesListItemInfo" extends="fs25_vehiclesListItemName" - with="anchorStretchingYLeft"> - <width value="200px" /> - <position value="-100px 0px" /> - <textBold value="false" /> - <textAlignment value="center" /> - <textOffset value="0px 0px" /> - </Profile> - <Profile name="fs25_vehiclesListItemInfoLarge" extends="fs25_vehiclesListItemInfo"> - <width value="500px" /> - <textAlignment value="left" /> - </Profile> - <Profile name="fs25_vehiclesDetailsImageBg" extends="baseReference"> - <size value="250px 250px" /> - <position value="0px -35px" /> - <imageSliceId value="gui.shopMods" /> - </Profile> - <Profile name="fs25_vehiclesDetailsImage" extends="baseReference" with="anchorMiddleCenter"> - <size value="210px 210px" /> - <imageSliceId value="noSlice" /> - </Profile> - <Profile name="fs25_vehiclesDetailsName" extends="fs25_textDefault" with="anchorTopLeft"> - <size value="100% 35px" /> - <absoluteSizeOffset value="270px 0px" /> - <position value="280px -30px" /> - <textSize value="32px" /> - <textBold value="true" /> - <textUpperCase value="true" /> - <textVerticalAlignment value="top" /> - </Profile> - <Profile name="fs25_vehiclesDetailsInfoBox" extends="emptyPanel" with="anchorTopLeft"> - <size value="100% 140px" /> - <absoluteSizeOffset value="300px 0px" /> - <position value="280px -75px" /> - <numFlows value="3" /> - <fitFlowToElements value="true" /> - </Profile> - <Profile name="fs25_financesHeaderToday" extends="fs25_statisticsHeaderName" - with="anchorStretchingYRight"> - <width value="190px" /> - <position value="-100px 0px" /> - <textSize value="16px" /> - <textAlignment value="right" /> - <textOffset value="-20px 0px" /> - <Variant name="mobile"> - <textOffset value="-30px 0px" /> - <textSize value="33px" /> - <size value="300px 100px" /> - </Variant> - </Profile> - <Profile name="fs25_financesHeaderTodayMinusOne" extends="fs25_financesHeaderToday"> - <position value="-290px 0px" /> - <Variant name="mobile"> - <position value="-300px 0px" /> - </Variant> - </Profile> - <Profile name="fs25_financesHeaderTodayMinusTwo" extends="fs25_financesHeaderToday"> - <position value="-480px 0px" /> - <Variant name="mobile"> - <position value="-600px 0px" /> - </Variant> - </Profile> - <Profile name="fs25_financesHeaderTodayMinusThree" extends="fs25_financesHeaderToday"> - <position value="-670px 0px" /> - <Variant name="mobile"> - <position value="-900px 0px" /> - </Variant> - </Profile> - <Profile name="fs25_financesHeaderTodayMinusFour" extends="fs25_financesHeaderToday"> - <position value="-860px 0px" /> - <Variant name="mobile"> - <visible value="false" /> - </Variant> - </Profile> - <Profile name="fs25_financesList" extends="emptyPanel" - with="anchorStretchingYStretchingX pivotTopCenter"> - <position value="0px -34px" /> - <itemizedScrollDelta value="3" /> - <absoluteSizeOffset value="0px 154px" /> - </Profile> - <Profile name="fs25_financesListItem" extends="baseReference" with="anchorTopStretchingX"> - <height value="30px" /> - <alternateChildren value="true" /> - <imageSliceId value="gui.empty" /> - <imageColor value="$preset_fs25_colorGreyListItem" /> - <alternateBackgroundColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_financesListItemBg" extends="baseReference" - with="anchorStretchingYStretchingX"> - <imageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <startImageSize value="100px 30px" /> - <startImageSliceId value="gui.list_gradient" /> - <startImageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <endImageSize value="100px 30px" /> - <endImageSliceId value="gui.list_gradient" /> - <endImageSelectedColor value="$preset_fs25_colorMainHighlight" /> - <endImageInvertX value="true" /> - </Profile> - <Profile name="fs25_financesListItemName" extends="fs25_textDarkHighlight" - with="anchorStretchingYLeft"> - <width value="500px" /> - <textSize value="18px" /> - <textBold value="true" /> - <textOffset value="40px 0px" /> - <Variant name="mobile"> - <size value="748px 104px" /> - <textSize value="33px" /> - </Variant> - </Profile> - <Profile name="fs25_financesListItemToday" extends="fs25_financesListItemName" - with="anchorStretchingYRight"> - <width value="190px" /> - <position value="-100px 0px" /> - <textBold value="false" /> - <textAlignment value="right" /> - <textOffset value="-20px 0px" /> - </Profile> - <Profile name="fs25_financesFooterBox" extends="baseReference" with="anchorTopStretchingX"> - <size value="100% 122px" /> - <position value="0px -630px" /> - <Variant name="mobile"> - <hasFrame value="false" /> - <size value="1768px 180px" /> - <position value="0px -500px" /> - </Variant> - </Profile> - <Profile name="fs25_financesTotalContainer" extends="fs25_financesListItem" - with="anchorTopLeft"> - <position value="0px -10px" /> - <Variant name="mobile"> - <position value="0px -20px" /> - </Variant> - </Profile> - <Profile name="fs25_financesBalanceContainer" extends="fs25_financesListItem" - with="anchorTopLeft"> - <position value="0px -40px" /> - </Profile> - <Profile name="fs25_financesLoanContainer" extends="fs25_financesListItem" - with="anchorTopLeft"> - <position value="0px -70px" /> - </Profile> - <Profile name="fs25_financesTotalName" extends="fs25_financesListItemName"> - <textSize value="20px" /> - <textBold value="true" /> - <textUpperCase value="true" /> - </Profile> - <Profile name="fs25_statisticsTextWhite"> - <textColor value="$preset_colorWhite" /> - <textSelectedColor value="$preset_fs25_colorMainDark" /> - <textFocusedColor value="$preset_colorWhite" /> - <textHighlightedColor value="$preset_colorWhite" /> - </Profile> - <Profile name="fs25_statisticsTextRed"> - <textColor value="$preset_colorRed" /> - <textSelectedColor value="$preset_colorDarkRed" /> - <textFocusedColor value="$preset_colorRed" /> - <textHighlightedColor value="$preset_colorRed" /> - </Profile> - <Profile name="fs25_statisticsHeaderTime" extends="fs25_statisticsHeaderText" - with="anchorStretchingYLeft"> - <width value="190px" /> - <textOffset value="0px 0px" /> - <textAlignment value="right" /> - </Profile> - <Profile name="fs25_statisticsList" extends="emptyPanel" - with="anchorStretchingYStretchingX pivotTopLeft"> - <width value="50%" /> - <absoluteSizeOffset value="0px 55px" /> - <position value="0px -34px" /> - <soundDisabled value="true" /> - </Profile> - <Profile name="fs25_statisticsListItem" extends="baseReference" with="anchorTopStretchingX"> - <height value="45px" /> - <alternateChildren value="true" /> - <imageSliceId value="gui.empty" /> - <imageColor value="$preset_fs25_colorGreyListItem" /> - <alternateBackgroundColor value="$preset_colorTransparent" /> - </Profile> - <Profile name="fs25_statisticsListItemBg" extends="baseReference" - with="anchorStretchingYStretchingX"> - <startImageSize value="100px 30px" /> - <startImageSliceId value="gui.list_gradient" /> - <endImageSize value="100px 30px" /> - <endImageSliceId value="gui.list_gradient" /> - <endImageInvertX value="true" /> - </Profile> - <Profile name="fs25_statisticsListItemName" extends="fs25_textDefault" - with="anchorStretchingYLeft"> - <width value="500px" /> - <textSize value="18px" /> - <textBold value="true" /> - <textOffset value="40px 0px" /> - </Profile> - <Profile name="fs25_statisticsListItemValue" extends="fs25_textDefault" - with="anchorStretchingYLeft"> - <width value="200px" /> - <textSize value="18px" /> - <textBold value="false" /> - <textOffset value="0px 0px" /> - <textAlignment value="right" /> - </Profile> </GUIProfiles> </GUI> diff --git a/config/gui/pages/GlobalSettingsFrame.xml b/config/gui/pages/GlobalSettingsFrame.xml index a63bd3570..6e224f2ed 100644 --- a/config/gui/pages/GlobalSettingsFrame.xml +++ b/config/gui/pages/GlobalSettingsFrame.xml @@ -33,14 +33,14 @@ <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> - <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpBinaryyOption> </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> - <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpOptionToggle> </Bitmap> @@ -51,18 +51,8 @@ </ThreePartBitmap> </ThreePartBitmap> <GUIProfiles> - <Profile name="fs25_settingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> + <Profile name="cpSettingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> <position value="-500px 0px" /> </Profile> - <Profile name="fs25_settingsNoPermissionText" extends="fs25_textDefault" - with="anchorMiddleStretchingX"> - <size value="100% 40px" /> - <textSize value="20px" /> - <textAlignment value="center" /> - <textColor value="$preset_fs25_colorMainLight" /> - </Profile> - <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> - <position value="-41px 0px" /> - </Profile> </GUIProfiles> </GUI> diff --git a/config/gui/pages/HelpFrame.xml b/config/gui/pages/HelpFrame.xml new file mode 100644 index 000000000..76c9c409f --- /dev/null +++ b/config/gui/pages/HelpFrame.xml @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="utf-8" standalone="no"?> +<GUI name="cpInGameMenuHelpLine"> + <ThreePartBitmap profile="fs25_subCategoryContainerBg"> + <Bitmap profile="fs25_subCategoryContainerArrow" /> + <GuiElement profile="fs25_subCategoryContainer"> + <MultiTextOption profile="fs25_subCategorySelector" id="helpLineSelector" onClick="onClickMultiTextOption"/> + <BoxLayout profile="fs25_subCategorySelectorBox" id="helpLineDotBox" /> + <RoundCorner profile="fs25_subCategorySelectorDot" id="helpLineDotTemplate" /> + <GuiElement profile="fs25_subCategoryListContainer"> + <Bitmap profile="fs25_subCategoryStartClipper" name="startClipper" /> + <Bitmap profile="fs25_subCategoryStopClipper" name="endClipper" /> + <SmoothList profile="fs25_subCategoryList" id="helpLineList" wrapAround="true" + focusInit="onOpen" startClipperElementName="startClipper" + endClipperElementName="endClipper" listSectionHeader="section"> + <ListItem profile="fs25_subCategoryListItem"> + <Bitmap profile="fs25_subCategoryListItemIconFull" name="icon" /> + <Text profile="fs25_helpListItemName" name="title" /> + </ListItem> + <ListItem profile="fs25_subCategoryListSectionHeader" name="section"> + <Text profile="fs25_subCategoryListSectionHeaderTitle" name="title" /> + </ListItem> + </SmoothList> + <ThreePartBitmap profile="fs25_subCategoryListSliderBox"> + <Slider profile="fs25_listSlider" dataElementId="helpLineList" + focusChangeTop="nil" /> + </ThreePartBitmap> + </GuiElement> + </GuiElement> + </ThreePartBitmap> + <GuiElement profile="fs25_menuContentContainer"> + <GuiElement profile="fs25_menuHeaderPanel" position="0px 74px"> + <Bitmap profile="fs25_menuHeaderIconBg"> + <Bitmap profile="fs25_menuHeaderIcon" imageSliceId="gui.icon_options_help2" /> + </Bitmap> + <Text profile="fs25_menuHeaderTitle" id="helpLineTitleElement" /> + </GuiElement> + <GuiElement profile="fs25_helpLinelayoutBox"> + <Bitmap profile="fs25_helpLineStartClipper" name="topClipper" /> + <Bitmap profile="fs25_helpLineStopClipper" name="bottomClipper" /> + <ScrollingLayout profile="fs25_helpLinelayout" id="helpLineContentBox" + topClipperElementName="topClipper" bottomClipperElementName="bottomClipper"> + <Text profile="fs25_helpLineItemTitle" id="helpLineContentItemTitle" /> + <BoxLayout profile="fs25_helpLineContentItem" id="helpLineContentItem"> + <Bitmap profile="fs25_helpLineImage" name="image" /> + <Text profile="fs25_helpLineText" name="text" /> + <Text profile="fs25_helpLineTextFullWith" name="textFullWidth" /> + </BoxLayout> + </ScrollingLayout> + </GuiElement> + </GuiElement> + <ThreePartBitmap profile="fs25_sliderDockedBg"> + <ThreePartBitmap profile="fs25_sliderDockedBox"> + <Slider profile="fs25_helpLineSlider" id="statisticsSlider" + dataElementId="helpLineContentBox" /> + </ThreePartBitmap> + </ThreePartBitmap> + <GUIProfiles> + <Profile name="fs25_helpListItemName" extends="fs25_subCategoryListItemName"> + <width value="208px" /> + <position value="100px -32px" /> + <textMaxNumLines value="2" /> + <textLayoutMode value="resize" /> + </Profile> + <Profile name="fs25_helpLineSlider" extends="fs25_sliderDocked"> + <handleFocus value="true" /> + <sliderImageColor value="$preset_fs25_colorGreyLight" /> + <sliderImageFocusedColor value="$preset_fs25_colorMainHighlight" /> + <startImageColor value="$preset_fs25_colorGreyLight" /> + <startImageFocusedColor value="$preset_fs25_colorMainHighlight" /> + <endImageColor value="$preset_fs25_colorGreyLight" /> + <endImageFocusedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="fs25_helpLineStartClipper" extends="baseReference" + with="anchorTopStretchingX"> + <height value="85px" /> + <absoluteSizeOffset value="-60px 0px" /> + <position value="0px 1dp" /> + <imageSliceId value="gui.divider_3" /> + </Profile> + <Profile name="fs25_helpLineStopClipper" extends="fs25_helpLineStartClipper" + with="anchorBottomStretchingX"> + <position value="0px -1dp" /> + <imageInvertY value="true" /> + </Profile> + <Profile name="fs25_helpLinelayoutBox" extends="emptyPanel" + with="anchorStretchingYStretchingX pivotTopLeft"> + <position value="44px -30px" /> + <absoluteSizeOffset value="115px 70px" /> + </Profile> + <Profile name="fs25_helpLinelayout" extends="emptyPanel" with="anchorStretchingYStretchingX"> + <flowDirection value="vertical" /> + </Profile> + <Profile name="fs25_helpLineContentItem" extends="emptyPanel"> + <size value="854px 200px" /> + <margin value="0px 0px 0px 25px" /> + </Profile> + <Profile name="fs25_helpLineImage" extends="baseReference"> + <size value="278px 200px" /> + <margin value="0px 0px 10px 0" /> + </Profile> + <Profile name="fs25_helpLineText" extends="fs25_textDefault"> + <size value="566px 200px" /> + <textSize value="16px" /> + <textMaxWidth value="566px" /> + <textMaxNumLines value="100" /> + <textVerticalAlignment value="top" /> + <textColor value="$preset_fs25_colorGreyText" /> + </Profile> + <Profile name="fs25_helpLineItemTitle" extends="fs25_helpLineText"> + <size value="854px 30px" /> + <margin value="0px 20px 0px 0px" /> + <textSize value="20px" /> + <textBold value="true" /> + <textMaxWidth value="854px" /> + <textColor value="$preset_fs25_colorMainLight" /> + </Profile> + <Profile name="fs25_helpLineTextFullWith" extends="fs25_helpLineText"> + <size value="854px 200px" /> + <margin value="0 0 0 0" /> + <textMaxWidth value="854px" /> + </Profile> + </GUIProfiles> +</GUI> \ No newline at end of file diff --git a/config/gui/pages/VehicleSettingsFrame.xml b/config/gui/pages/VehicleSettingsFrame.xml index 233a7ddd6..eb2b3c031 100644 --- a/config/gui/pages/VehicleSettingsFrame.xml +++ b/config/gui/pages/VehicleSettingsFrame.xml @@ -33,14 +33,14 @@ <Bitmap profile="fs25_multiTextOptionContainer" id="booleanPrefab"> <CpBinaryyOption profile="fs25_settingsBinaryOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> - <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpBinaryyOption> </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> - <Text profile="fs25_settingsMultiOptionTitle" name="label"/> + <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpOptionToggle> </Bitmap> @@ -51,18 +51,8 @@ </ThreePartBitmap> </ThreePartBitmap> <GUIProfiles> - <Profile name="fs25_settingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> + <Profile name="cpSettingsMultiOptionTitle" extends="fs25_settingsMultiTextOptionTitle"> <position value="-500px 0px" /> </Profile> - <Profile name="fs25_settingsNoPermissionText" extends="fs25_textDefault" - with="anchorMiddleStretchingX"> - <size value="100% 40px" /> - <textSize value="20px" /> - <textAlignment value="center" /> - <textColor value="$preset_fs25_colorMainLight" /> - </Profile> - <Profile name="fs25_settingsTextInput" extends="fs25_textInput" with="anchorMiddleRight"> - <position value="-41px 0px" /> - </Profile> </GUIProfiles> </GUI> diff --git a/modDesc.xml b/modDesc.xml index f69645814..70a4d6fa3 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -499,6 +499,7 @@ Changelog 7.1.0.0: <sourceFile filename="scripts/gui/pages/CpCourseGeneratorFrame.lua"/> <sourceFile filename="scripts/gui/pages/CpVehicleSettingsFrame.lua"/> <sourceFile filename="scripts/gui/pages/CpGlobalSettingsFrame.lua"/> + <sourceFile filename="scripts/gui/pages/CpHelpFrame.lua"/> <sourceFile filename="scripts/gui/CpInGameMenu.lua"/> <sourceFile filename="scripts/gui/hud/HudElements.lua"/> diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 753f99b19..a392c80c6 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -58,6 +58,7 @@ function CpInGameMenu.createFromExistingGui(gui, guiName) CpVehicleSettingsFrame.createFromExistingGui(g_gui.frames.cpInGameMenuVehicleSettings.target, "CpVehicleSettingsFrame") CpCourseGeneratorFrame.createFromExistingGui(g_gui.frames.cpInGameMenuCourseGenerator.target, "CpCourseGeneratorFrame") CpCourseManagerFrame.createFromExistingGui(g_gui.frames.cpInGameMenuCourseManager.target, "CpCourseManagerFrame") + CpHelpFrame.createFromExistingGui(g_gui.frames.cpInGameMenuHelpLine.target, "CpHelpFrame") local messageCenter = gui.messageCenter local l10n = gui.l10n @@ -86,6 +87,7 @@ function CpInGameMenu.setupGui(courseStorage) CpGlobalSettingsFrame.setupGui() CpVehicleSettingsFrame.setupGui() CpCourseManagerFrame.setupGui() + CpHelpFrame.setupGui() g_cpInGameMenu = CpInGameMenu.new(nil, nil, g_messageCenter, g_i18n, g_inputBinding, courseStorage) g_gui:loadGui(Utils.getFilename("config/gui/CpInGameMenu.xml", Courseplay.BASE_DIRECTORY), @@ -100,6 +102,7 @@ function CpInGameMenu:initializePages() g_inGameMenu.baseIngameMap, g_currentMission.hud) + self.pageHelpLine:initialize(self) self.pageGlobalSettings:initialize(self) self.pageVehicleSettings:initialize(self) self.pageCourseGenerator:initialize(self) @@ -138,18 +141,29 @@ function CpInGameMenu:setupMenuPages() return true end, {256, 0, 128, 128} + }, + { + self.pageHelpLine, + function () + return true + end, + nil, + "gui.icon_options_help2" } } for i, pageDef in ipairs(orderedDefaultPages) do - local page, predicate, iconUVs = unpack(pageDef) + local page, predicate, iconUVs, sliceId = unpack(pageDef) if page ~= nil then self:registerPage(page, i, predicate) - - local normalizedUVs = GuiUtils.getUVs(iconUVs) - - self:addPageTab(page, Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY), normalizedUVs) + local normalizedUVs = nil + local path = nil + if iconUVs then + normalizedUVs = GuiUtils.getUVs(iconUVs) + path = Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY) + end + self:addPageTab(page, path, normalizedUVs, sliceId) end end end diff --git a/scripts/gui/pages/CpHelpFrame.lua b/scripts/gui/pages/CpHelpFrame.lua new file mode 100644 index 000000000..deace94b8 --- /dev/null +++ b/scripts/gui/pages/CpHelpFrame.lua @@ -0,0 +1,202 @@ +CpHelpFrame = {} +local CpHelpFrame_mt = Class(CpHelpFrame, TabbedMenuFrameElement) +function CpHelpFrame.new(target, custom_mt) + local self = TabbedMenuFrameElement.new(target, custom_mt or CpHelpFrame_mt) + + self.helpLineManager = HelpLineManager.new() + self.helpLineManager:initDataStructures() + self.helpLineManager.customEnvironmentNames = {} + self.helpLineManager.customEnvironmentToCategory = {} + self.helpLineManager:loadFromXML(Utils.getFilename("config/HelpMenu.xml", g_Courseplay.BASE_DIRECTORY)) + return self +end + +function CpHelpFrame.setupGui() + local frame = CpHelpFrame.new() + g_gui:loadGui(Utils.getFilename("config/gui/pages/HelpFrame.xml", Courseplay.BASE_DIRECTORY), + "CpHelpFrame", frame, true) +end + +function CpHelpFrame.createFromExistingGui(gui, guiName) + local newGui = CpHelpFrame.new(nil, nil) + + g_gui.frames[gui.name].target:delete() + g_gui.frames[gui.name]:delete() + g_gui:loadGui(gui.xmlFilename, guiName, newGui, true) + + return newGui +end + +function CpHelpFrame:initialize(menu) + self.helpLineDotTemplate:unlinkElement() + FocusManager:removeElement(self.helpLineDotTemplate) +end + +function CpHelpFrame:onFrameOpen() + CpHelpFrame:superClass().onFrameOpen(self) + self.customEnvironments = self.helpLineManager:getCustomEnvironmentNames() + local texts = {} + for _, env in ipairs(self.customEnvironments) do + if string.isNilOrWhitespace(env) then + --table.insert(texts, g_i18n:getText("ui_helpLine_baseGame")) + else + local mod = g_modManager:getModByName(env) + if mod then + table.insert(texts, mod.title) + else + table.insert(texts, "Unknown") + end + end + end + self.helpLineSelector:setTexts(texts) + for i = 1, #self.helpLineSelector.texts do + local dot = self.helpLineDotTemplate:clone(self.helpLineDotBox) + dot.getIsSelected = function () + return self.helpLineSelector:getState() == i + end + end + self.helpLineDotBox:invalidateLayout() + self.helpLineList:reloadData() + self:setSoundSuppressed(true) + FocusManager:setFocus(self.helpLineList) + self:setSoundSuppressed(false) + self.helpLineContentBox:registerActionEvents() +end + +function CpHelpFrame:onFrameClose() + CpHelpFrame:superClass().onFrameClose(self) + self.helpLineContentBox:removeActionEvents() +end + +function CpHelpFrame:onClickMultiTextOption(_, guiElement) + self.helpLineList:reloadData() +end + +function CpHelpFrame:onListSelectionChanged(list, section, index) + if self.helpLineContentItem ~= nil then + self:updateContents(self:getPage(section, index)) + end +end + +function CpHelpFrame:getNumberOfSections(list) + local customEnvironment = self.customEnvironments[self.helpLineSelector:getState()] + return #self.helpLineManager:getCategories(customEnvironment) +end + +function CpHelpFrame:getNumberOfItemsInSection(list, section) + local customEnvironment = self.customEnvironments[self.helpLineSelector:getState()] + local category = self.helpLineManager:getCategory(customEnvironment, section) + return #category.pages +end + +function CpHelpFrame:getTitleForSectionHeader(list, section) + local customEnvironment = self.customEnvironments[self.helpLineSelector:getState()] + local category = self.helpLineManager:getCategory(customEnvironment, section) + return self.helpLineManager:convertText(category.title, customEnvironment) +end + +function CpHelpFrame:populateCellForItemInSection(list, section, index, cell) + local page = self:getPage(section, index) + if page then + cell:getAttribute("title"):setText(self.helpLineManager:convertText(page.title, page.customEnvironment)) + local icon = cell:getAttribute("icon") + if page.iconSliceId ~= nil then + icon:setVisible(true) + icon:setImageSlice(nil, page.iconSliceId) + else + icon:setVisible(false) + end + end +end + +function CpHelpFrame:getPage(categoryIndex, pageIndex) + local customEnvironment = self.customEnvironments[self.helpLineSelector:getState()] + local category = self.helpLineManager:getCategory(customEnvironment, categoryIndex) + return category.pages[pageIndex] +end + +function CpHelpFrame:openPage(categoryIndex, pageIndex) + self:setSoundSuppressed(true) + self.helpLineList:setSelectedItem(categoryIndex, pageIndex, true, 1) + self:setSoundSuppressed(false) +end + +function CpHelpFrame:updateContents(page) + self.helpLineContentItem:unlinkElement() + self.helpLineContentItemTitle:unlinkElement() + for i = #self.helpLineContentBox.elements, 1, -1 do + self.helpLineContentBox.elements[i]:delete() + end + if page == nil then + return + end + self.helpLineTitleElement:setText(self.helpLineManager:convertText(page.title, page.customEnvironment)) + for _,paragraph in ipairs(page.paragraphs) do + if paragraph.title ~= nil then + local titleElement = self.helpLineContentItemTitle:clone(self.helpLineContentBox) + titleElement:setText(self.helpLineManager:convertText(paragraph.title, page.customEnvironment)) + end + local row = self.helpLineContentItem:clone(self.helpLineContentBox) + local textElement = row:getDescendantByName("text") + local textFullElement = row:getDescendantByName("textFullWidth") + local imageElement = row:getDescendantByName("image") + local textHeightFullHeight, textHeight = 0, 0 + if paragraph.noSpacing then + row.margin = {0, 0, 0, 0} + end + if paragraph.image ~= nil then + textFullElement:setVisible(false) + if paragraph.text ~= nil then + textElement:setText(self.helpLineManager:convertText(paragraph.text, page.customEnvironment)) + textHeight = textElement:getTextHeight(true) + textElement:setSize(nil, textHeight) + end + local filename = Utils.getFilename(paragraph.image.filename, page.baseDirectory) + imageElement:setImageFilename(filename) + imageElement:setImageUVs(nil, unpack(paragraph.image.uvs)) + if imageElement.originalWidth == nil then + imageElement.originalWidth = imageElement.absSize[1] + end + if paragraph.image.displaySize ~= nil then + imageElement:setSize(paragraph.image.displaySize[1], paragraph.image.displaySize[2]) + else + if paragraph.text == nil then + imageElement:setSize(row.absSize[1], row.absSize[1] * paragraph.image.aspectRatio * g_screenAspectRatio) + else + imageElement:setSize(imageElement.originalWidth, nil) + end + end + if paragraph.text ~= nil and paragraph.alignToImage then + textElement:setSize(nil, imageElement.size[2]) + textElement.textVerticalAlignment = TextElement.VERTICAL_ALIGNMENT.MIDDLE + end + else + textElement:setVisible(false) + imageElement:setVisible(false) + if paragraph.text ~= nil then + textFullElement:setText(self.helpLineManager:convertText(paragraph.text, paragraph.customEnvironment)) + end + textHeightFullHeight = textFullElement:getTextHeight(true) + textFullElement:setSize(nil, textHeightFullHeight) + end + local imageHeight = 0 + if paragraph.image then + imageHeight = imageElement.absSize[2] or 0 + end + row:setSize(nil, math.max(textHeight, textHeightFullHeight, imageHeight)) + row:invalidateLayout() + end + self.helpLineContentBox:invalidateLayout() +end + +function CpHelpFrame:delete() + self.helpLineContentItem:delete() + self.helpLineContentItemTitle:delete() + for ix, clone in ipairs(self.helpLineDotBox.elements) do + clone:delete() + self.helpLineDotBox.elements[ix] = nil + end + self.helpLineDotTemplate:delete() + + CpHelpFrame:superClass().delete(self) +end \ No newline at end of file From 428daa2d9e3297bb7f93fca76b3a2137642e4356 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 10 Dec 2024 20:52:43 +0100 Subject: [PATCH 095/158] Fixes job start button gui position --- config/gui/pages/CourseGeneratorFrame.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 4d5cce383..65efe3429 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -98,7 +98,7 @@ </Button> </BoxLayout> <SmoothList profile="fs25_mapContextButtonList" id="createJobButtonList" - onClick="onClickList" visible="true"> + onClick="onClickList" visible="true" position="0px -165px"> <ListItem profile="fs25_mapContextButtonListItem"> <ThreePartBitmap profile="fs25_mapContextButtonListItemBg" /> <Text profile="fs25_mapButtonText" name="text" /> From 62341b595f6b615491010b8b2bbee27488d59a03 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 10 Dec 2024 21:34:52 +0100 Subject: [PATCH 096/158] Added Helpmenu button to the hud --- scripts/gui/CpGuiUtil.lua | 62 +++++++---------------------------- scripts/gui/CpInGameMenu.lua | 9 +++++ scripts/gui/hud/CpBaseHud.lua | 24 ++++++++++---- 3 files changed, 38 insertions(+), 57 deletions(-) diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua index 9ef10b013..f11860ba0 100644 --- a/scripts/gui/CpGuiUtil.lua +++ b/scripts/gui/CpGuiUtil.lua @@ -300,10 +300,15 @@ end ---@param iconData table filename, uvs ---@param color table r, g, b, alpha ---@param alignment table vertical, horizontal alignments -function CpGuiUtil.createOverlay(size, iconData, color, alignment) +---@param sliceId string|nil slice id +function CpGuiUtil.createOverlay(size, iconData, color, alignment, sliceId) local filename, uvs = unpack(iconData) local overlay = Overlay.new(filename, 0, 0, unpack(size)) - overlay:setUVs(uvs) + if sliceId then + overlay:setSliceId(sliceId) + else + overlay:setUVs(uvs) + end overlay:setColor(unpack(color)) overlay:setAlignment(unpack(alignment)) return overlay @@ -454,55 +459,6 @@ end function CpGuiUtil.openCourseGeneratorGui(vehicle) g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR) - --- TODO_25 - -- local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle) - -- local pageAI = inGameMenu.pageAI - -- --- Opens the ai inGame menu - -- inGameMenu:goToPage(pageAI) - -- local hotspot = vehicle:getMapHotspot() - -- pageAI:setMapSelectionItem(hotspot) - -- CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame menu.") - -- if vehicle:getIsCpActive() or not g_currentMission:getHasPlayerPermission("hireAssistant") then - -- pageAI:updateParameterValueTexts() - -- return - -- end - -- CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame job creation.") - -- vehicle:updateAIFieldWorkerImplementData() - -- pageAI.currentJobTypes = {} - -- local currentJobTypesTexts = {} - -- local currentJobTypeIndex, currentIndex = nil, nil - -- for index, jobType in ipairs(g_currentMission.aiJobTypeManager.jobTypes) do - -- if pageAI.jobTypeInstances[index]:getIsAvailableForVehicle(vehicle) then - -- table.insert(pageAI.currentJobTypes, index) - -- table.insert(currentJobTypesTexts, jobType.title) - -- if pageAI.jobTypeInstances[index]:isa(CpAIJob) then - -- currentJobTypeIndex = currentJobTypeIndex or index - -- currentIndex = currentIndex or #pageAI.currentJobTypes - -- end - -- end - -- end - -- if #pageAI.currentJobTypes == 0 then - -- return - -- end - - -- pageAI.jobTypeElement:setTexts(currentJobTypesTexts) - -- pageAI.jobTypeElement:setState(currentIndex or 1) - - -- pageAI.mode = InGameMenuAIFrame.MODE_CREATE - -- pageAI.currentJobVehicle = vehicle - -- pageAI.currentJob = nil - - -- pageAI:setJobMenuVisible(true) - -- pageAI:setActiveJobTypeSelection(currentJobTypeIndex or 1) - -- if not vehicle:hasCpCourse() then - -- if pageAI.currentJob:getCanGenerateFieldWorkCourse() then - -- CpUtil.debugVehicle(CpDebug.DBG_HUD, vehicle, "opened ai inGame menu course generator.") - -- pageAI:onClickOpenCloseCourseGenerator() - -- end - -- end - -- --- Moves the map, so the selected vehicle is directly visible. - -- local worldX, _, worldZ = getWorldTranslation(vehicle.rootNode) - -- CpGuiUtil.movesMapCenterTo(pageAI.ingameMap, worldX, worldZ) end function CpGuiUtil.openVehicleSettingsGui(vehicle) @@ -513,6 +469,10 @@ function CpGuiUtil.openGlobalSettingsGui(vehicle) g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_GLOBAL_SETTINGS) end +function CpGuiUtil.openHelpMenuGui() + g_messageCenter:publishDelayed(MessageType.GUI_CP_INGAME_OPEN_HELP_MENU) +end + CpGuiUtil.UNIT_EXTENSIONS = { "k", "M", diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index a392c80c6..9e15d73b7 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -47,6 +47,14 @@ function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager, c local index = self.pagingElement:getPageMappingIndexByElement(self.pageCourseManager) self.pageSelector:setState(index, true) end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_OPEN_HELP_MENU, function (menu) + g_gui:showGui("CpInGameMenu") + self:changeScreen(CpInGameMenu) + self:updatePages() + local index = self.pagingElement:getPageMappingIndexByElement(self.pageHelpLine) + self.pageSelector:setState(index, true) + end, self) + self.messageCenter:subscribe(MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED, self.onCurrentVehicleChanged, self) return self @@ -81,6 +89,7 @@ function CpInGameMenu.setupGui(courseStorage) MessageType.GUI_CP_INGAME_OPEN_VEHICLE_SETTINGS = nextMessageTypeId() MessageType.GUI_CP_INGAME_OPEN_COURSE_GENERATOR = nextMessageTypeId() MessageType.GUI_CP_INGAME_OPEN_COURSE_MANAGER = nextMessageTypeId() + MessageType.GUI_CP_INGAME_OPEN_HELP_MENU = nextMessageTypeId() MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED = nextMessageTypeId() CpCourseGeneratorFrame.setupGui() diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 00573c0a4..b68aa0b8f 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -214,8 +214,6 @@ function CpBaseHud:init(vehicle) local leftTopText = CpTextHudElement.new(self.baseHud, CpBaseHud.x + self.wMargin, CpBaseHud.y + self.hMargin/4 + self.height - headerHeight, self.headerFontSize) leftTopText:setTextDetails("Courseplay") - local rightTopText = CpTextHudElement.new(self.baseHud, CpBaseHud.x + self.width - 3*self.wMargin/2, CpBaseHud.y + self.hMargin/4 + self.height - headerHeight, self.headerFontSize, RenderText.ALIGN_RIGHT) - rightTopText:setTextDetails(g_Courseplay.currentVersion) -------------------------------------- --- Left side @@ -261,20 +259,34 @@ function CpBaseHud:init(vehicle) --- Right side -------------------------------------- --- Exit button - local width, height = getNormalizedScreenValues(18, 18) + local exitWidth, height = getNormalizedScreenValues(18, 18) local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local exitBtnOverlay = CpGuiUtil.createOverlay({width, height}, + local exitBtnOverlay = CpGuiUtil.createOverlay({exitWidth, height}, {imageFilename, GuiUtils.getUVs(unpack(self.uvs.exitSymbol))}, self.WHITE_COLOR, self.alignments.bottomRight) self.exitBtn = CpHudButtonElement.new(exitBtnOverlay, self.baseHud) - local x, y = CpBaseHud.x + self.width -width/3 , CpBaseHud.y + self.height - headerHeight + self.hMargin/12 + local x, y = CpBaseHud.x + self.width -exitWidth/3 , CpBaseHud.y + self.height - headerHeight + self.hMargin/12 self.exitBtn:setPosition(x, y) self.exitBtn:setCallback("onClickPrimary", self.vehicle, function (vehicle) vehicle:closeCpHud() end) - + local width, height = getNormalizedScreenValues(20, 20) + local helpMenuOverlay = CpGuiUtil.createOverlay({width, height}, + {"dataS/menu/gui.png", {nil}}, + self.WHITE_COLOR, + self.alignments.bottomRight, + "gui.icon_options_help2") + + self.helpMenuBtn = CpHudButtonElement.new(helpMenuOverlay, self.baseHud) + local x, y = CpBaseHud.x + self.width -width/3 - exitWidth - self.wMargin/2, CpBaseHud.y + self.height - headerHeight + self.hMargin/12 + self.helpMenuBtn:setPosition(x, y) + self.helpMenuBtn:setCallback("onClickPrimary", self.vehicle, function (vehicle) + CpGuiUtil.openHelpMenuGui() + end) + local rightTopText = CpTextHudElement.new(self.baseHud, x - 3*self.wMargin/2, CpBaseHud.y + self.hMargin/4 + self.height - headerHeight, self.headerFontSize, RenderText.ALIGN_RIGHT) + rightTopText:setTextDetails(g_Courseplay.currentVersion) --- Create start/stop button local onOffBtnWidth, height = getNormalizedScreenValues(20, 20) From 35d44073306457470236f1f16b0dcdfdd7f9790f Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Thu, 12 Dec 2024 20:53:14 +0100 Subject: [PATCH 097/158] Small gui adjustment and another nil check for shovel positions --- scripts/gui/hud/CpBaseHud.lua | 18 ++++++------------ scripts/specializations/CpShovelPositions.lua | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index b68aa0b8f..55abba1fa 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -56,14 +56,7 @@ CpBaseHud.uvs = { }, driveNowSymbol = { {0, 768, 128, 128} - }, - goalSymbol = GuiUtils.getUVs({ - 788, - 30, - 44, - 44 - }, AIPlaceableMarkerHotspot.FILE_RESOLUTION), - + }, exitSymbol = { {148, 184, 32, 32}, {256, 512} }, @@ -352,15 +345,16 @@ function CpBaseHud:init(vehicle) end) --- Goal button. - local width, height = getNormalizedScreenValues(34, 34) + local width, height = getNormalizedScreenValues(30, 30) local goalOverlay = CpGuiUtil.createOverlay({width, height}, - {AIPlaceableMarkerHotspot.FILENAME, self.uvs.goalSymbol}, + {"dataS/menu/gui.png", nil}, self.OFF_COLOR, - self.alignments.bottomRight) + self.alignments.bottomRight, + "gui.aiParameterPosition") self.goalBtn = CpHudButtonElement.new(goalOverlay, self.baseHud) local x, y = unpack(self.lines[7].right) - self.goalBtn:setPosition(x + self.wMargin/4, y - self.hMargin/4) + self.goalBtn:setPosition(x + self.wMargin/4, y - self.hMargin/3) self.goalBtn:setCallback("onClickPrimary", vehicle, function (vehicle) self:openCourseGeneratorGui(vehicle) end) diff --git a/scripts/specializations/CpShovelPositions.lua b/scripts/specializations/CpShovelPositions.lua index 401fd0b04..6de630f9f 100644 --- a/scripts/specializations/CpShovelPositions.lua +++ b/scripts/specializations/CpShovelPositions.lua @@ -444,7 +444,7 @@ function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, height end local alpha, oldRotRelativeArmRot = 0, 0 - if hasIntersection and i1y ~= nil then + if hasIntersection and i1y ~= nil and i1z ~= nil then --- Controls the arm height setTranslation(armProjectionNode, 0, i1y, i1z) setTranslation(armToolRefNode, ax, ay, az) From 44a43f45dc1e23e7b981f20e2c07eb3692d829e4 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Thu, 12 Dec 2024 21:18:19 +0100 Subject: [PATCH 098/158] InfoPanel Cosmetic --- img/helpmenu/infopanel.dds | Bin 524416 -> 524416 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/helpmenu/infopanel.dds b/img/helpmenu/infopanel.dds index d05de208b7f1fa3e88a6b86f79cfa66c502a8ea4..832ed3e6e48cb84124561bee725c75d30700e209 100644 GIT binary patch literal 524416 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ϖy(E(ei5gZ}yiBt88zUym_L_R?n^7$RCg% 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 z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ z2jBo4fCF#<4!{9800-az9DoCG01m(bH~<IW033h=Z~zX#0XP5$-~b$e18@KizyUY_ W2jBo4fCF#<4!{9800*|uf&UA9CQ|nR literal 524416 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 zj0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyi zz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T z2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAi zalklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G z7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4 zfN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw z4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462 z<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzW zFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&8 z0poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW z954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04 z#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$ zU>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF z1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C% zIA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8 zj0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyi zz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T z2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAi zalklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G z7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4 zfN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw z4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462 z<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzW zFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&8 z0poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW z954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04 z#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$ zU>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF z1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C% zIA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8 zj0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T2aE&80poyi zz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAialklW954<T z2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G7zd04#sTAi zalklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4fN{V$U>q<G z7zd04#sTAialklW954<T2aE&80poyiz&KzWFb)_8j0462<A8C%IA9zw4j2cF1I7X4 WfN{V$U>q<G7zd04#(`hv!2b{Z+KB)F From 68e41109694458408b798e55f089b4a40d3c87bc Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Thu, 12 Dec 2024 21:42:57 +0100 Subject: [PATCH 099/158] Fixes aspect ratio and uvs --- config/HelpMenu.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/HelpMenu.xml b/config/HelpMenu.xml index 83e693f78..226e29086 100644 --- a/config/HelpMenu.xml +++ b/config/HelpMenu.xml @@ -251,8 +251,7 @@ <!--Courseplay Info Panel--> <page title="$l10n_CP_help_page_infoPanel_title"> <paragraph> - <text text="$l10n_CP_help_page_blank"/> - <image filename="img/helpmenu/infopanel.dds" size="1024 1024" uvs="0px 0px 765px 510px" aspectRatio="0.66"/> + <image filename="img/helpmenu/infopanel.dds" size="1024 1024" uvs="0px 0px 480px 130px" aspectRatio="0.27"/> </paragraph> <paragraph> <text text="$l10n_CP_help_page_infoPanelBasic_text"/> From b771ee84312f73438d4cbc3c9cfa44716eedde33 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 13 Dec 2024 14:50:52 +0100 Subject: [PATCH 100/158] Some more gui fixes and improvements .. --- config/gui/pages/CourseGeneratorFrame.xml | 14 +++- config/gui/pages/GlobalSettingsFrame.xml | 2 +- config/gui/pages/HelpFrame.xml | 65 +-------------- config/gui/pages/VehicleSettingsFrame.xml | 2 +- scripts/ai/AIMessages.lua | 16 ++-- scripts/gui/CpStatus.lua | 16 ++++ scripts/gui/pages/CpCourseGeneratorFrame.lua | 84 +++++++++++++++----- 7 files changed, 103 insertions(+), 96 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 65efe3429..84e9f2214 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -140,7 +140,7 @@ </GuiElement> </Bitmap> </GuiElement> - <GuiElement profile="fs25_menuContainer"> + <GuiElement profile="cpMenuContainer"> <GuiElement profile="fs25_menuHeaderPanel"> <!-- <RoundCorner profile="fs25_mapMoneyBoxBg" id="shopMoneyBoxBg" /> <BoxLayout profile="fs25_shopMoneyBox" id="shopMoneyBox"> @@ -156,8 +156,9 @@ <Bitmap profile="fs25_mapContextImage" name="image" /> <Bitmap profile="fs25_mapContextImageVehicle" name="imageVehicle" /> <Bitmap profile="fs25_mapContextSeparator" /> - <Text profile="fs25_mapContextText" name="text" /> - <Text profile="fs25_mapContextFarm" name="farm" /> + <Text profile="fs25_mapContextTextTop" name="text" position="0px -5px"/> + <Text profile="fs25_mapContextFarmTop" name="farm" position="0px -50px"/> + <Text profile="fs25_mapContextText" name="statusText"/> <SmoothList profile="fs25_mapContextButtonList" id="contextButtonList" name="buttonBox" onClick="onClickList"> <ListItem profile="fs25_mapContextButtonListItem"> @@ -224,7 +225,7 @@ </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> - <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption" hideButtonOnLimitReached="False"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpOptionToggle> @@ -255,5 +256,10 @@ <width value="100%"/> <height value="80px" /> </Profile> + <Profile name="cpMenuContainer" extends="fs25_menuContainer"> + <position value="0px 0px"/> + <absoluteSizeOffset value="10px 10px" /> + <!-- <position value="0px 92px" /> --> + </Profile> </GUIProfiles> </GUI> diff --git a/config/gui/pages/GlobalSettingsFrame.xml b/config/gui/pages/GlobalSettingsFrame.xml index 6e224f2ed..1659c460a 100644 --- a/config/gui/pages/GlobalSettingsFrame.xml +++ b/config/gui/pages/GlobalSettingsFrame.xml @@ -38,7 +38,7 @@ </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> - <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption" hideButtonOnLimitReached="False"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpOptionToggle> diff --git a/config/gui/pages/HelpFrame.xml b/config/gui/pages/HelpFrame.xml index 76c9c409f..711b7460e 100644 --- a/config/gui/pages/HelpFrame.xml +++ b/config/gui/pages/HelpFrame.xml @@ -55,69 +55,6 @@ </ThreePartBitmap> </ThreePartBitmap> <GUIProfiles> - <Profile name="fs25_helpListItemName" extends="fs25_subCategoryListItemName"> - <width value="208px" /> - <position value="100px -32px" /> - <textMaxNumLines value="2" /> - <textLayoutMode value="resize" /> - </Profile> - <Profile name="fs25_helpLineSlider" extends="fs25_sliderDocked"> - <handleFocus value="true" /> - <sliderImageColor value="$preset_fs25_colorGreyLight" /> - <sliderImageFocusedColor value="$preset_fs25_colorMainHighlight" /> - <startImageColor value="$preset_fs25_colorGreyLight" /> - <startImageFocusedColor value="$preset_fs25_colorMainHighlight" /> - <endImageColor value="$preset_fs25_colorGreyLight" /> - <endImageFocusedColor value="$preset_fs25_colorMainHighlight" /> - </Profile> - <Profile name="fs25_helpLineStartClipper" extends="baseReference" - with="anchorTopStretchingX"> - <height value="85px" /> - <absoluteSizeOffset value="-60px 0px" /> - <position value="0px 1dp" /> - <imageSliceId value="gui.divider_3" /> - </Profile> - <Profile name="fs25_helpLineStopClipper" extends="fs25_helpLineStartClipper" - with="anchorBottomStretchingX"> - <position value="0px -1dp" /> - <imageInvertY value="true" /> - </Profile> - <Profile name="fs25_helpLinelayoutBox" extends="emptyPanel" - with="anchorStretchingYStretchingX pivotTopLeft"> - <position value="44px -30px" /> - <absoluteSizeOffset value="115px 70px" /> - </Profile> - <Profile name="fs25_helpLinelayout" extends="emptyPanel" with="anchorStretchingYStretchingX"> - <flowDirection value="vertical" /> - </Profile> - <Profile name="fs25_helpLineContentItem" extends="emptyPanel"> - <size value="854px 200px" /> - <margin value="0px 0px 0px 25px" /> - </Profile> - <Profile name="fs25_helpLineImage" extends="baseReference"> - <size value="278px 200px" /> - <margin value="0px 0px 10px 0" /> - </Profile> - <Profile name="fs25_helpLineText" extends="fs25_textDefault"> - <size value="566px 200px" /> - <textSize value="16px" /> - <textMaxWidth value="566px" /> - <textMaxNumLines value="100" /> - <textVerticalAlignment value="top" /> - <textColor value="$preset_fs25_colorGreyText" /> - </Profile> - <Profile name="fs25_helpLineItemTitle" extends="fs25_helpLineText"> - <size value="854px 30px" /> - <margin value="0px 20px 0px 0px" /> - <textSize value="20px" /> - <textBold value="true" /> - <textMaxWidth value="854px" /> - <textColor value="$preset_fs25_colorMainLight" /> - </Profile> - <Profile name="fs25_helpLineTextFullWith" extends="fs25_helpLineText"> - <size value="854px 200px" /> - <margin value="0 0 0 0" /> - <textMaxWidth value="854px" /> - </Profile> + </GUIProfiles> </GUI> \ No newline at end of file diff --git a/config/gui/pages/VehicleSettingsFrame.xml b/config/gui/pages/VehicleSettingsFrame.xml index eb2b3c031..438ddf639 100644 --- a/config/gui/pages/VehicleSettingsFrame.xml +++ b/config/gui/pages/VehicleSettingsFrame.xml @@ -38,7 +38,7 @@ </Bitmap> <Text profile="fs25_settingsSectionHeader" name="sectionHeader" id="sectionHeaderPrefab"/> <Bitmap profile="fs25_multiTextOptionContainer" id="multiTextPrefab"> - <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption"> + <CpOptionToggle profile="fs25_settingsMultiTextOption" name="setting" namedComponents="true" onClick="onClickCpMultiTextOption" hideButtonOnLimitReached="False"> <Text profile="fs25_multiTextOptionTooltip" name="tooltip"/> <Text profile="cpSettingsMultiOptionTitle" name="label"/> </CpOptionToggle> diff --git a/scripts/ai/AIMessages.lua b/scripts/ai/AIMessages.lua index 3e8aabfb8..993e44c74 100644 --- a/scripts/ai/AIMessages.lua +++ b/scripts/ai/AIMessages.lua @@ -9,7 +9,7 @@ function AIMessageErrorIsFull.new(customMt) return self end -function AIMessageErrorIsFull:getMessage() +function AIMessageErrorIsFull:getI18NText() return g_i18n:getText("CP_ai_messageErrorIsFull") end @@ -24,7 +24,7 @@ function AIMessageCpError.new(customMt) return self end -function AIMessageCpError:getMessage() +function AIMessageCpError:getI18NText() return g_i18n:getText("CP_ai_messageError") end @@ -39,7 +39,7 @@ function AIMessageCpErrorNoPathFound.new(customMt) return self end -function AIMessageCpErrorNoPathFound:getMessage() +function AIMessageCpErrorNoPathFound:getI18NText() return g_i18n:getText("CP_ai_messageErrorNoPathFound") end @@ -55,7 +55,7 @@ function AIMessageErrorWrongBaleWrapType.new(customMt) return self end -function AIMessageErrorWrongBaleWrapType:getMessage() +function AIMessageErrorWrongBaleWrapType:getI18NText() return g_i18n:getText("CP_ai_messageErrorWrongBaleWrapType") end @@ -71,7 +71,7 @@ function AIMessageErrorGroundUnloadNotSupported.new(customMt) return self end -function AIMessageErrorGroundUnloadNotSupported:getMessage() +function AIMessageErrorGroundUnloadNotSupported:getI18NText() return g_i18n:getText("CP_ai_messageErrorGroundUnloadNotSupported") end @@ -86,7 +86,7 @@ function AIMessageErrorCutterNotSupported.new(customMt) return self end -function AIMessageErrorCutterNotSupported:getMessage() +function AIMessageErrorCutterNotSupported:getI18NText() return g_i18n:getText("CP_ai_messageErrorCutterNotSupported") end @@ -101,7 +101,7 @@ function AIMessageErrorAutomaticCutterAttachNotActive.new(customMt) return self end -function AIMessageErrorAutomaticCutterAttachNotActive:getMessage() +function AIMessageErrorAutomaticCutterAttachNotActive:getI18NText() return g_i18n:getText("CP_ai_messageErrorAutomaticCutterAttachNotActive") end @@ -116,7 +116,7 @@ function AIMessageErrorWrongMissionFruitType.new(customMt) return self end -function AIMessageErrorWrongMissionFruitType:getMessage() +function AIMessageErrorWrongMissionFruitType:getI18NText() return g_i18n:getText("CP_ai_messageErrorWrongMissionFruitType") end diff --git a/scripts/gui/CpStatus.lua b/scripts/gui/CpStatus.lua index ce261d9a8..8b3e92072 100644 --- a/scripts/gui/CpStatus.lua +++ b/scripts/gui/CpStatus.lua @@ -143,6 +143,22 @@ function CpStatus:getTimeRemainingText() return self.remainingTimeText end +function CpStatus:getText() + if not self.isActive then + return "" + end + if self.fillLevelLeftOver ~= nil then + return self:getSiloFillLevelPercentageLeftOver() + end + if self.compactionPercentage ~= nil then + return self:getCompactionText() + end + if self.numBalesLeftOver ~= nil then + return self:getBalesText() + end + return self.remainingTimeText or "" +end + function CpStatus:getIsActive() return self.isActive end diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index b9c460305..53fd5ac84 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -219,6 +219,15 @@ function CpCourseGeneratorFrame:update(dt) end end self.updateTime = g_time + 1000 + local vehicle = self.currentHotspot and self.currentContextBox and self.currentHotspot:getVehicle() + if vehicle then + if vehicle.getIsCpActive and vehicle:getIsCpActive() then + local status = vehicle:getCpStatus() + self.currentContextBox:getDescendantByName("statusText"):setText(status:getText()) + else + self.currentContextBox:getDescendantByName("statusText"):setText("") + end + end end local hasChanged = false for i = 1, #self.statusMessages do @@ -263,18 +272,21 @@ end function CpCourseGeneratorFrame:onFrameOpen() self.ingameMap:setTerrainSize(g_currentMission.terrainSize) - -- g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self.onAIVehicleStateChanged, self) - + g_messageCenter:subscribe(MessageType.AI_VEHICLE_STATE_CHANGE, function(self, _, vehicle) + if self.currentHotspot and vehicle and InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) == vehicle then + self:updateContextActions() + end + end, self) + g_messageCenter:subscribe(MessageType.AI_JOB_STARTED, function(self) self.activeWorkerList:reloadData() end, self) g_messageCenter:subscribe(MessageType.AI_JOB_STOPPED, function(self, job, aiMessage) if aiMessage ~= nil and job ~= nil and g_localPlayer ~= nil then - if job.startedFarmId and g_localPlayer.farmId then - local helperName = job:getHelperName() - local text = aiMessage:getMessage() - self:addStatusMessage(string.format(text, helperName or "Unknown")) + if job.startedFarmId == g_localPlayer.farmId then + local text = aiMessage:getMessage(job) + self:addStatusMessage(text) end end end, self) @@ -450,8 +462,45 @@ end ------------------------------- function CpCourseGeneratorFrame:onDrawPostIngameMapHotspots() - if self.currentContextBox ~= nil then - InGameMenuMapUtil.updateContextBoxPosition(self.currentContextBox, self.currentHotspot) + if self.currentContextBox and self.currentHotspot and self.currentContextBox:getIsVisible() then + local posX, posY, _ = self.currentHotspot:getLastScreenPositionCenter() + local buttonBox = self.currentContextBox:getDescendantByName("buttonBox") + local bottomOffset = buttonBox == nil and 0 or (buttonBox.contentSize or 0) + local outRight = posX + self.currentContextBox.size[1] > self.rightBackground.absPosition[1] + local outLeft = posX - self.currentContextBox.size[1] < self.leftBox.absPosition[1] + self.leftBox.size[1] + local outTop = posY + self.currentContextBox.size[2] + bottomOffset > self.topBackground.absPosition[2] + -- local outBottom = posY + self.currentContextBox.size[2] + bottomOffset > self.cpTopSideBackground.absPosition[2] + local orientation = InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.TOP_RIGHT + if outRight then + if outTop then + orientation = InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.BOTTOM_LEFT + else + orientation = InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.TOP_LEFT + end + elseif outLeft then + if outTop then + orientation = InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.BOTTOM_RIGHT + else + orientation = InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.TOP_RIGHT + end + elseif outTop then + orientation = InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.BOTTOM_RIGHT + end + + local goLeft = orientation == InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.TOP_LEFT and true or orientation == InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.BOTTOM_LEFT + local goDown = orientation == InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.BOTTOM_LEFT and true or orientation == InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.BOTTOM_RIGHT + if goLeft then + posX = posX - self.currentContextBox.size[1] + end + if goDown then + posY = posY - self.currentContextBox.size[2] + end + self.currentContextBox:setAbsolutePosition(posX, posY + ((buttonBox == nil or + orientation ~= InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.TOP_RIGHT and + orientation ~= InGameMenuMapUtil.CONTEXT_BOX_ORIENTATION.TOP_LEFT) and + 0 or (buttonBox.contentSize or 0))) + + end if self.aiTargetMapHotspot ~= nil then @@ -538,7 +587,6 @@ function CpCourseGeneratorFrame:onClickHotspot(element, hotspot) return end self:setMapSelectionItem(hotspot) - -- self:refreshContextInput() end function CpCourseGeneratorFrame:showMapHotspot(self, hotspot) @@ -849,14 +897,12 @@ end function CpCourseGeneratorFrame:cancelJob() local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot) - if vehicle then - if vehicle:getIsAIActive() then - vehicle:stopCurrentAIJob() - g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) - g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) - g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) - g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) - end + if vehicle and vehicle:getIsAIActive() then + vehicle:stopCurrentAIJob(AIMessageSuccessStoppedByUser.new()) + g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot) + g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot) end end @@ -1226,7 +1272,9 @@ function CpCourseGeneratorFrame:setAIVehicle(vehicle) local hotspot = vehicle:getMapHotspot() self:setMapSelectionItem(hotspot) self.ingameMap:panToHotspot(hotspot) - self:onCreateJob() + if not vehicle:getIsAIActive() then + self:onCreateJob() + end end function CpCourseGeneratorFrame:onCreateJob() From be74d3edc6d01257e4f61984da7c3902370bf241 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 13 Dec 2024 17:33:36 +0100 Subject: [PATCH 101/158] Goal symbol a little bit smaller --- scripts/gui/hud/CpBaseHud.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 55abba1fa..67ce2d1a5 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -345,7 +345,7 @@ function CpBaseHud:init(vehicle) end) --- Goal button. - local width, height = getNormalizedScreenValues(30, 30) + local width, height = getNormalizedScreenValues(28, 28) local goalOverlay = CpGuiUtil.createOverlay({width, height}, {"dataS/menu/gui.png", nil}, self.OFF_COLOR, From b97b09569c43e2e20a455bcb2ca9a382c68e2b1b Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Thu, 12 Dec 2024 21:13:19 +0100 Subject: [PATCH 102/158] Only reset the discarge timer, if the filllevel is changing --- scripts/ai/controllers/TrailerController.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/ai/controllers/TrailerController.lua b/scripts/ai/controllers/TrailerController.lua index 88e5c70a7..1b5d24451 100644 --- a/scripts/ai/controllers/TrailerController.lua +++ b/scripts/ai/controllers/TrailerController.lua @@ -48,7 +48,11 @@ function TrailerController:update(dt) end if self.implement:getCanDischargeToGround(self.dischargeData.dischargeNode) then --- Update discharge timer - self.isDischargingTimer:set(true, 500) + local fillLevel = self.implement:getFillUnitFillLevelPercentage(self.dischargeData.dischargeNode) + if fillLevel ~= self.dischargeData.lastFillLevel then + self.isDischargingTimer:set(true, 500) + end + self.dischargeData.lastFillLevel = fillLevel if not self:isDischarging() then self.implement:setDischargeState(Dischargeable.DISCHARGE_STATE_GROUND) end @@ -108,6 +112,7 @@ function TrailerController:startDischargeToGround(dischargeNode) self.isDischargingToGround = true self.dischargeData = { dischargeNode = dischargeNode, + lastFillLevel = 0 } local tipSide = self.trailerSpec.dischargeNodeIndexToTipSide[dischargeNode.index] if tipSide ~= nil then From e6b39643a93890af72e857191cdd661608c5902a Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Thu, 12 Dec 2024 21:32:36 +0100 Subject: [PATCH 103/158] Increased wait for discharge timer --- scripts/ai/controllers/TrailerController.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/controllers/TrailerController.lua b/scripts/ai/controllers/TrailerController.lua index 1b5d24451..c9a1774ba 100644 --- a/scripts/ai/controllers/TrailerController.lua +++ b/scripts/ai/controllers/TrailerController.lua @@ -50,7 +50,7 @@ function TrailerController:update(dt) --- Update discharge timer local fillLevel = self.implement:getFillUnitFillLevelPercentage(self.dischargeData.dischargeNode) if fillLevel ~= self.dischargeData.lastFillLevel then - self.isDischargingTimer:set(true, 500) + self.isDischargingTimer:set(true, 10000) end self.dischargeData.lastFillLevel = fillLevel if not self:isDischarging() then From 800fadb086eebe284bd7652099a19820d6dfdd32 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 13 Dec 2024 17:30:04 +0100 Subject: [PATCH 104/158] Bug fix --- scripts/ai/controllers/TrailerController.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/ai/controllers/TrailerController.lua b/scripts/ai/controllers/TrailerController.lua index c9a1774ba..5c9d069ea 100644 --- a/scripts/ai/controllers/TrailerController.lua +++ b/scripts/ai/controllers/TrailerController.lua @@ -19,7 +19,7 @@ function TrailerController:getDriveData() maxSpeed = 0 self:debugSparse("Waiting for unloading!") end - if self.trailerSpec.tipState == Trailer.TIPSTATE_OPENING then + if self.implement:getDischargeNodeEmptyFactor(self.dischargeData.dischargeNode) < 0.1 then --- Trailer not yet ready to unload. maxSpeed = 0 self:debugSparse("Waiting for trailer animation opening!") @@ -39,22 +39,22 @@ function TrailerController:update(dt) if self.isDischargingToGround then if self:isEmpty() then if self:isDischarging() then - self.implement:setDischargeState(Dischargeable.DISCHARGE_STATE_OFF) + self.implement:setManualDischargeState(Dischargeable.DISCHARGE_STATE_OFF) end if self.implement:getAIHasFinishedDischarge(self.dischargeData.dischargeNode) then self:finishedDischarge() end return end + local fillLevel = self.implement:getFillUnitFillLevelPercentage(self.dischargeData.dischargeNode.fillUnitIndex) + if fillLevel ~= self.dischargeData.lastFillLevel then + self:debugSparse("Resetting the discharge timer") + self.isDischargingTimer:set(true, 250) + end + self.dischargeData.lastFillLevel = fillLevel if self.implement:getCanDischargeToGround(self.dischargeData.dischargeNode) then - --- Update discharge timer - local fillLevel = self.implement:getFillUnitFillLevelPercentage(self.dischargeData.dischargeNode) - if fillLevel ~= self.dischargeData.lastFillLevel then - self.isDischargingTimer:set(true, 10000) - end - self.dischargeData.lastFillLevel = fillLevel if not self:isDischarging() then - self.implement:setDischargeState(Dischargeable.DISCHARGE_STATE_GROUND) + self.implement:setManualDischargeState(Dischargeable.DISCHARGE_STATE_GROUND) end end end @@ -141,7 +141,7 @@ function TrailerController:prepareForUnload() end function TrailerController:onFinished() - self.implement:setDischargeState(Dischargeable.DISCHARGE_STATE_OFF) + self.implement:setManualDischargeState(Dischargeable.DISCHARGE_STATE_OFF) end function TrailerController:isDischarging() From 256a7a223267bd5b239d5463f77c509e6c37329d Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Fri, 13 Dec 2024 18:04:47 +0100 Subject: [PATCH 105/158] helpmenu screenshots may need to adjust size and maybe make them bigger at some point but better then having olf stuff... --- img/helpmenu/managerbasehelp.dds | Bin 524416 -> 524416 bytes img/helpmenu/manageredithelp.dds | Bin 524416 -> 524416 bytes img/helpmenu/readyjobmenuhelp.dds | Bin 524416 -> 524416 bytes img/helpmenu/startjobmenuhelp.dds | Bin 524416 -> 524416 bytes img/helpmenu/vineworkgen.dds | Bin 524416 -> 524416 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/img/helpmenu/managerbasehelp.dds b/img/helpmenu/managerbasehelp.dds index f0a4417db366b8016138e5a88338e0e095118c77..2b8dda787e8e11a7a9abe116404f6e69864e578d 100644 GIT binary patch literal 524416 zcmeI*4_H+9o$v9V18fgk*Lk{P=CSEIGcpO-w5e^;sND@ilWsJ3_o2vvSwMxs^tt)d zdkK-njZ$%xE|Sov+v_y+uBLX3){JYCW*=HjsxjDVtQ*^#77}-ZBxskeZbP<JNlb9= z_cy~yQxY@~XBgl;&trZ$XU;iu&hPxr>+?H@nR9NrWvO$mQtBi2$x5Zl|LA|pP?O}p z`rmL~PMKKGev7Yg?)%PXfA82ld%eu^(#u|>T*~<wUSnITa`xR38?G_)?CYl}HFc`r zzTOBog+s$FQ(b<0T3XuFOjoKa^42pEO6p-a?I6`vHM3lr?Y68NXPWMCcyM_z>&B0n z&Zf}ZjY`?fNY`hiRA!`aw99F7P`};ZMSJJCPiprf^3th5IWg<OW#pz7`kqi;<CWA; z%$I(L4BzqBO{&6hrtPV$m+`r&X;V|waC6_9p8O~8hm-g84{0y7m&BbXk^2)f_pSYI z<#fDC@*mIfJaL}Wf8ux>&hcjD0OiE&2gED)t>u#ccr+Z3$S>Lp?Im&jMRI>)=DxMx zt(=ZmN&e$Go+r+8`cE8h!#Uop9H5+-{eXDozO`KPACHFP5&1=Xp}i!ozew&+%-py3 zyOq=ND#?F5$MeK_PXCGHZ8*o9l>?L$vmX$z+_#oX{^QYbJR-knFSM7$^%u$giJAM> zez$TuUgc=#|Lm2iF4Ht~+;yj&X8RQ7u6x8`$}e93P|En?->8kH(di1wAM%I%NnHML zyp8VhX5|3o#Ow#eEBCGCj#mE9UYcf?Ha#n^;nz;NeSz{eJX<Pvk$E|<!L?HEc42xO zUaeFrCC%I5uM3BoLU|3hxP0aH>h#==j!19++qg8kE>Zu~KmD-y_ru8jiJAM>ez$Tu zUgc=zzfI2fGjcpR{swQur3>r&DjK>gUtaz8+KLAEO0%~90dIqCW!k2{{wr_8`lV`8 zN{XH?@}p<^+E$%*78RD2S^2ZD=aj=V+jCBI9CDa7wq2`_OKFx>ojAE&%1>=reLQ40 ziw{mdzD9Z#>pn5P<+z@{tn9~qx@^s9iB3On-o6gG&S0wWo6-5o{EMQ?^=#UwmuuF~ zInkk~_gvR<TwjM!;o`%3e9NC-is<=@%5IR}N+hyw)}kIgz9heJhf;mD56#-rtB1>c z#fSBD85yz9Eh>wxr^mB%?jc7ca_QW~=;t=4e|@e~g~N~j^UN+iU(gesu14+JdwjdY zY+s&#qC<baEfiai`ZKZhOJ5(`4mIcE$x||4aoNR_dj7=~oqhUq*Z4DC+Z~OD`?iHW zvK;fyFAkb7d|O^X`eWxV6(5%RT9z|)^>M$weMSA`;=_7*88Zg9dsnO;UOKrix*jTA z8m*^te{6j{n~Dzyq`u1orO|r0^Z8U)^e&l`Rv$kt%bB`opq>M<`O7cHwnyzMKCag@ z_paD{cUNY*PCLWl@a{_yy}rLa8e9I<O|ku_=ESBeF5A~3pVyuqh`nFl6kE@XjF#x~ z)UMe6@cC0+`YviJ_sx#&cGIkEiw)=IorvytTPRi^2Pf}~?&s$}(zC#8JpI~lS1ycg zhs>X6$ovOm^{U&IuAhUGKd1Neiq3f_^m<79o7tt;x6I!eZC}5Y?RUz2DeD*YNWGeO zS4#cKcBX7UG0?7luvw|D{@UrX9x|V8*PAExbgTWbdP$M>()+vqOl*B8*{pU?IWhWQ zRt`{3%zi+;a^G6+Xyw1YzVGqmc;x*49+jJ`Ty?gUU+?UkW~+0pTs|$M%vIM`x@Mbi zTSY^~((I?&>irF`u5vfd-lY2tW%9E8yLi%Tq-{ESRQp&o$!7Z>lEe4?Xx6OKo`AjD z=PP?be9ic7=ZX^@N^NQRW=pB$pPqkB?{<5Ryw>yv?DOQcufuN?7Z<C&9bUt`_={CD zyZlCF<@!%8>JjJ7;#W$0yvFq&kEi9h+`iU)OkLRTGBQ3f<67;0e))47TaE|p898Z3 zBawjJS5Q(gvnyoJ@iZ@9DDzcU9Bt6vXUI!`Ue%Is%$wQeHDqzuJ$9e&m#mO}gHp3U z_LB4qwmXUnefeu-zGiPlMKo8W<8fiX*H~wo<)yMd1qHveEtKsu&2<O$dM#dDk-ksX zyWI?`MKbL3`HJ-RR#)fyW_DE=Z2|Yk*2?Fa?GMT4N|NO+xm_Pki|>4P)0$qdVVaS8 zE2r1=`ek{|9=+U*;EigvZ13l%&zJs{*C;AdyJG!^BHv!!uQG!fEgdeypYJQw+f#X8 zb+xW{*{?ZzyBn^*e%=jtE7jkB>73p_`4XUdI~N~ae6*=R_RGyT-yB=tH<bQ7*`D$> zy;Dk`{odUFw_57cG`~H)wCAgLd-HDjtC?L&9X)z9z4&l!J9X~=!r_qjLv~+bQPD-o zO;0cwym(TndDFje?VHi{Et1cxFpfTdtm=f${a{8;sSG#XFz+Fs?x)lg6<$2)QU%r3 zwi6wGdq#$*{!Ll#yk8ZWTY4*u4u|{bo3h`0g~jH*4=T0pp;_0&+ErP!zn8Ht5_$8a z-+p~sbMqeAkA?mH{cEJZceKId`ElQN$Mf>i+r57M`bleLyDQ~2v&$*%sxn3IuV64o zE$ng`U--i27iGUn{jRocZgr`z7T3sr)BCU6rt9A{Uz7c#yDw^z)$S?Df88MH7mV+I zftAzoDn~W{b3Bs&u9XLKb923N-hba0QZwcJ|DOB)_m{pT=ll1h9IYwwN_p^n@!!hz zfwivL?%CaI2j{=|el*Q9U!S+un0s6Fd0G44RxRwQu={+~8+<yaIx{}ma@?uD+46Fc z&cU)WU-uR%b8<WnoRGY@Zr=5hcYgbv7mJH^S?H^-)cMnHuGp|oa&&S|#^e(nPIX=8 zb$@c>Mz8UhTmaB{JS!vP&sQI>FfylamK+h+t9&c2k$lR}&;RD4p6&KmZEuw6<=v>B zY1jFA&5Y@erE<PFeO2K(oy#{kCr#D;muu#yHP7q{*x#1Fd*~dfE<e_w^R&X-EV=Ku z?^tH;{c*3~UVYzv)jH39S5Od|w$^1_Ja5+Pd|2=GdiDHGo{Zq?<C2dZzmoiLDxWXk zccLR?f1#kDU`=nT>z~^ym+9#i%d7aXQqRhKi<boKsxu<@!9VTXeddgu&sP|mDk?VV z{JSw2yz$>;yMI`AZeB;L%P3xEW-XNUs;;i~t&zO;G#{<h*F9F%^+$AmSKn8;N#~>N z)(7<P{P`YFv*hBrbLTEtd8PAT;$QN=!k9H{)+ZLq@Ebngu4z)Q_t%)Y(l2w|C$AG7 zc}7;?**d+yel9s$D$_|f@2IZ7db7W?SgzY-WCW$Y0(R*yg%-+o1v5NNy5Do{=jY|< zT+S*gDq17`gR+9k#d<$@rXH=lDC3`zdfp=ao-$u`g|5$#bO7|{_?taBRg&|Onyz!v z^_0)cbRBZIHf`FZpBFIeyS3|NewnXoL+rXj`zLjOuDRj*hjjl>`V)KC%KCd6j{5a_ zJ>T5mS+z&0&KfgoO|Mh(zuH&Y<21HQ2SE2<+R}35I>jM5@9+5hyu&WzFHCb+i>!aS z*IT|w^8a_$)gj&9UR!3?>+3h}M#E**BPIDy{*V9uPrP#9TJEUk|G@cwt~6wQ-M?g0 z$2Q#`_-{v=d><h1o?nz7RBjpmWo?EGe{rQvuHSx9zAso<SY%rM>HL``=V9NG>$s{$ z{t~eNl*1=qD4f}K%Hff+`0#c|{R-36=TDR5bM$rI^mfzKWqGk&X_f2pIWk?i#A}?B z&h(4}ryNK1eDZgKbSd@uxSoHn4D03T>#=e|X12)ZwMhBG7RkT9uDVp0OL=Aaf3{UA zdtYsrzAn6~vro>pCn>#sde~l9+jpZp|K?m>r0@4K-$#lgo%0W;x)#fFbpAdn7rgcS zAL*>^dQJXb@riX^5q%w4Z<oF<t+z{mzD-Jfox5Jj-^p^k@_Bmu^yldL^W}3ENk5{X zvf|G3t*Nd8nJ(4kH8SOWy}r-N`s(TQe7YX{-+JaPt3JPT{FF1corj{^{SC?Qo7Y9U z^mTW=9@Av~SIKj8^xre8Wx3XT7iGQld^*o`z3S`To`&b|lzK62lO`!^x<8Y8c=pA0 zomqJs9J8dpt@>Oo+mkEv>-xAay4(%+=}udt9)4biFUWq?`$gxVtI=r8bp6E9-RO>9 zx7YjA`rH`@Qe7D`U9_INBDELxpLRCOaQpsL*SptO*MnE;K~Jyi=O5)d(X868{&uOa zLK(hh(0<YD|IAyFUj6rwsq((nE^f^Z**|_J9M;!&Y&E*Q$@KzxU+=$J>-uE-4ms2^ zX(zJ$m+Shv&bOX&RLc9&b}QRIUzYc>4D0n*=k;=9^XvVdBI9+b_p{Z0t$L&+|K)Jz z{9lft@mvp9PRFYp?fkE3$ZLr7tqbe&nMm!NcMt3F-iFGhvVY6-8csV8R+WkWKXGo# z3c2y`^8V@Ey;dr2wCYKJzGD<Wf3&un&%J!Rq<)51KN4@_I^Wp7%kSTK<-WDt(aQhK z+f(f~u9NG|w`bbrd9Xv4KgqkrUe!NveOk_s@nd*?P+!y+-_w6a{=PLf+X4A_`FtGj z^<(99yvk9_|1o+zj#Wok@3HAW`)y+HxAC5DY~SVkH}T4SYq_JF|B-8M%}#aI_O+J} zICmcJCboC%fA;?<T(4LWbI5E<HiyW^%je^GuOBO?<5ea)|C<Wr_ss1%Q;(J0n*AyH zLf_evJC}UT=?<MONp%&kex=?c{eeiN{~S3$4vaz$jM?kN952H=Uc%ZTf#_I?C%biu zcBrYK&KY(eE6H>%m-~z@UZUSGE*$N89q;+Z_FcYz6R+I2mYdl8U$;&lU+L*7DQ8QL zm8|Pq@rfrlA1jfsH&^T7NMBdK?gtbV{o(ihqka7G`NMOS`l7zZLci@;$!Vu&YI^$F zl5l~1t>~_MAB;NWkGa&<e@q;b-+5Mzc6%ltFQ1R&y?!!X6>?t`Q@+O?pE6aJn>IB~ zmLGq&)~$)<e@aS9dh^}KN-|`_e!t~dN!1$Dw2nL7FPGc)jC#L25%n<^oTMJ&e_YTG z694_YV<qzY-t_dTI{)vI-)~OeajYb7z@h%yu0EarRaN=E(Qg0aJ>S^A%lB`3^pDET zbu}N!(`9YvM;2b)GHZ#~c>VnO)<m`QvvU4F)e|(2l^A-9CJp3%uf7ha>T`WP3-s@= z+vRtDxJxc1J{M?r6XUqk=NX=yc5&#AY;j1g1L*6KUE<I!awm=Ys@?khzvvJ5w-~K< zK|WqSAIE$BY;aT<%W5KB@hS(d|EI;Neczb5Q2!n6@s!g`^!?q#>XxNe`G{kW4DTr2 z;W%T4ZHG#K;wYBkL#46zDX9nDUT6ojgZQ?CpE}P=JG(<13QKv*(w{n4NSS$itlm#Y zzXxz}TWZv?uUUT&xD+QZ-}C*G@t$vN-{t!^*5B!eD&_92${lhS4U{{Enr^7^@&Eh( zY3GV?xGB^1h@-gPE%$BP=rC2I5iJd8Ak~%WSRoy7U7D2C1MPt0V3Zu_m(R6a(q5T! zoN}MBN1XjeNWA)~bGJa7sNA<chkU$zK92YL87jA<dGGg!ez4hVZ2H*8CI$Dn3}2P6 zXi<+-$@5%nx5`y5shh$)`+6htbmYH=T2B1)e_~uuw2oIw_BVgG;qNwA%YpHpZ*1S? z`!_@7c659=G^bRaD^e5mc*^8?Ei0Z-YHQ!clk4SobzAyE_7^{`)TM~aFpGS?p_Vh+ z`R|eYr@I^H<a)9LcDb$lxAcALr5pZHc}ksJ*-!@to?E+Eucr*3yER*$(-WGL7n{%B zp6LqM<u>tuJ@EcoPquzeihHO2UZYGuZ(w=P42;hn7)~9S|HQy_dv2A_4>yH_vEKpL zyl}OSUzS6Efc^mefsr~uK3+Z_$9w$@mD|ztf4ARXCeJa;2!7JGwpY?l<(BK`%BkY^ zoUMKG#F<IYzr4Rso^w`osXjV=WA^t3FK@K+--teMe%+M6Yh7<Vn*GV}4_lq;x5s|A z%iOcw@%LZ+P2-cB<@sn?YV|L-q`Dq<Tzk`#n~yBm^t%l=-}~SxNA@K58{gX8RPc7g zgMk`J?uQ+r)E72?W$vcmeeAQ-cT^Z_?yK(p{Eo#-&fec$^2;r^W_M@R`=;***q4{R zyjPe1)P37Q{rsDqjiG-r_cRq;+`M=5FSk?}<v-gNvdi;rUV8f+_aET<mo9Rf+#YFe zU#<DZd%m%Km+#*U&TDH}=eghSckA4@**+QE(igCom*>`R>2(=7!6_+grC%UV94p$| z7i@iFZFNfXefs(8MTOS<R+*suFV@fP3AsbB>F3<Z?`=DK*PM3h=j+}3;A!VS%k$zM zTa)Tali{z*^Wr}riS&Ky?vOpdz03U8=2MOhGTth`Ect(fOs|J`$@tziryO69^2yDy z<$Y%3DTjVOUbVa*h`uk+3zkyPCw~};K6mX;q|w<orn>%HhM$z_wng7R?VQ$bnuq6} zcCPBHmERv6($^7cDX9nA!9+L?qAt}~K3U~YvohuWq_x)f^7^k<u7A+wkdK$o$MIf2 zR!$F9UW)kbLHQxemcBjmq*mqIBIo_-55I2L=lw;ylv6*4UA>-P_0_v84E<cw!OMwf z{y+0{r1$&sd%nk~m}d5pQ;s<{v-VE;K5bZn*eb&Xa@<K^>-%x&@s!j9?SSJT(GE0e zhoUZ>a!9#M-~Ue!8+oUlyXwu(opV!NUwb3*zXu%e`NsBLzJD_~uLloDuLH>UQkx3A zhJNn&mOiDn1^VT=;a;Of&Xd<lKVY?#gXgn`<C*_lKjr%A2R+YWI=&}?7w`3ge7t-< zj`#YpaynjRqVj*k(ljHoZq}?&Lz=zToTCEKvWb#<koNH2cCh`PG}U$frI+;mpv;=~ zoFmb)xa>;(gy{6<p0rpQR?+!P_dBIEo>JeJBdjKRJ;c86`**9N^-$C9ITCAUNpt_v z@t$vN-{t!^@ydN`xrxgEQ>AIftXbF0s%Yq5+xW_JuXr1tEmaY@F46F6sr*vW%xeg( zlKXD8<uy#YT`A-GyoQRpu)8TMufera{;rWaFdU+c*2}oB2Y1~g4xhZV%Q^BovLMq{ zD6cE!zn(5FEzQ%j;45?USLd((`rJFRfAZSXpVQ?{bDn9MfB47({oI$&?fAZw%YTuT zcVt0rpLzYAOLoqUbe&&%XTgyLHO1}IS0C2zM*=+yzP}*Uv^#zAlCRH|-_hPy9G$;E zv{+9c>72WG$&m$}%glRkljU?StNCTmf+GvUuZ3Tu92@nZ^MBR3Rp)d&^W=D<{mjBu zg{!XA&Jy)L`FQz!9PjmG<#fEt#OJ@Q?zaDV+ikB_K3ghIn!23x>4jS>8r*f+*^fRd zd4Kyi{^n=OT^A~SbKT#oyoU7)ulv(EQcjHAV>@H}`NQY`m*!Tz@x&85=iZvVV%yJ; zeSg8N*`49=>q~d|jgL&bIWqrn!0u^wc3s$?mDhOiH)iqSth{X>onyAQ8hOUS=ZZJC zZm@6v-m8%r2bv1zWI4@_)~q}=d#>p_ym(3X?*eAWez|}3zy8F$u>Z({fDu@B&4D8e ze1%0Oxj!~?-`Y>)|HRJMWbZ#T707M>mE4&!Zl%jO|N6!A@$S!_Doxer|Ibu3WG<ES zd9$Kn_R{dX%DjQ`R_Px!Y>lUk)!%rp2V3m?fB6W{|J!>c?_d9os?p`8$CTvqt=W-* zvffb9_nV~s>GAGpc}I3=V0g0JQbUh7oPRuB<G}m6-Tq;|TeGw3W5c$AQW>#f%H*$y zf&9O~`9J*t$t8Wyyht*Ye!JJGjfCCtDx=T;lMANt&VT*;KfioWG1Ya;!2L}rWA{6z z9)_xibU6@*oBz5Uuahs5>9RBOR`hpUl+?rJ+JpYyA^D$NxtDA?JX-4$uj~HSauap^ zKmPp-zCS%4zdt>a{2!0?!0{8`<D2{^|C8yw-|BBzIX$>M<#5Wiigh2KaX{|t(?4hW z3tOao*L{AY_xlTl{w7v_>OTT<9U=Twc;DdVkpJ;@1-Cz3{gVG5uKs9$AFTe!fAW7U z<$vt{f9{aZ|Al8w^R^|i`}>6CzJTGoX5PQ3r@|im7iHVux831;LQP)O6MYUq8TtRg zJiyK5s3-D2IrhVNKOV?`@_(%5zgzDAzew)yC%;olDV68{NU1KKl;QRlGj+LJscYBD z{eV34=4a{q0k1AEkLCZarm)oy;QSw7llgi%`A_>yMEfNF$^WsI|8oC-|Hpe8=6>%7 zzn}dbx!>P`;%}=9`{fzdzj}GwqMj+0*Q%Q$x_@9yd#G6V2g-a!@3{{k`H!!O@O7l; zkK{k?GZF2R{3rj%QvUCgyf@8Y#@gN~rF(2|Keo2Fcg>U=KKI|9k^BcIPj0$<ZSM`0 zU)b`O-PauU8gko|BI|sAa7q5-Ya)Cl|7mB*-p;uHPxAf#&H5hPL@fvBwKe>2az39C zY&T1L!Uf?UZ`-8*zVGh#6|I%$|2c0CwqHDXw&bIqZuH+VSE=*o`unZ<2AAYNE+@Oo z!@2+8Nc$PgH?fZc&i^_8A4})|I>*m6g+uy#fTlcHSC<X)`+6yjyg7}buSo8Hxi8YC zr`tUz^v>V=$^VJ%+*r#wuK&|s6VX2<|H=Qcmj7YN|G{}a++oiD@ih^?j<nxF{?k4a z(LTw4@}K;F@AD@|>NfM?_SJBk{HGmVjdpY;kH~-WKN<7?SV{Eved=tSO1=E^=OeFQ z>G<G%98QG8gWDPTPrFM*yCeU}|76Vn3d3cnNVsvJbYJZ<)#Wv&rKL?B>@LH&n+SKW z)XvC%+F>HvA^A`KCu9Em?e(gDPDq~r6Q6S6@BayS{tu2N!m%sm=~#Q8{O7ny#BoFZ zlmE$?|Lcvb@jbtxz8A>%|L`>tzK*otLH^S|6VX1&fAW6}=f5p>|KDIvv|LuNrft;s z12zi>a$k}EIGPAYN1F3P%}4&zZWGaN$$#>HEakucy}vsY^6%?#Dl>i3v!%UqpZ~0^ zoX$w9>(lc;t$fFoDl`na@2|XHZ+QN3v;H1ok<}k~=XZmg|Kn>Sd>ty^uQZ<gr+p@( zeUks=|5(cZM+<bGFO&QK2J9)JZ?-JzQEJ=4x63X?yheTZ?)EM6+#mCzn$ju5>F$;e zeP6($vNGS`>j30Gz9z!gE9Lx9@00(u&qTCO@}K-4OZmV0xYr0~WVG+^yCeJMy-J?* zqg2^0^^`8nu+q;1*x;>F=FHgenbMwsJ-@1I1^JJsiSTr&JRk0O@}Ksai1teUlmBBW z|MmU<<?%lmTl)?<x^35e`r^q$4$tmT_r;R|d)bpIvp+7+{cFpbH?LHl`|Ei?KPS*@ zEU&unKJp(=6XEG_bAG7l$$#2wBHAnYPyUak{Lg=|!pM*>_^p+ES7%MLW53s!;xSGA zT)-!mo2Jftb;dOH^MBKKnC8Wk754lhljr}s@H7#g4wdJ_9Z&w#UK7z?$$#>HEam^; z^Z1bt^Y?#zO@yz*&3$Wn@}KsZi1tbTlmFyD_y4zYhcY>Ghx|{D{V?8-2lAi%PsaQY z*eA*F{M=@|%2bz2O`A52`~UHG0aqf<vxb`^<UjeJh;~W-lmE$>|A*EdUSi6MrPTde z%8+rj%R{Sv?Oa|}l|NjUnI4xD<#LX^jx5M@70QctN4twhyCeU}|76U6f5WS5JA1=6 zUB38q*s0592g3EyviF@*4Gdf5;P<WJ_q=bbd&E&CFI>gdc)0pu+ZE^kw97=aOY)!m zPsaRT-<;Rbzx(msfo6Y$SsQulYL|Hp(flv}uud~SPRGM(@}G7Uk9IZKEApTGPsaSO zXbv>&ef!eeekosz^mbWgpt+*q={ar{Xzs35mu|m3(EMtpN}rYz9Ul(M_`HU&VSD`# z!!mtze5{Nv=RhRlZ@$A(o_kPbx*l;fx;Lc{J}wx>-$eL3R68U8X@7}mf8;;;pN#n* zlJow3yLaynH3yo_zOLSPm+iHYNTAt!&jnfWP;*}MqoJ^&-*5lXcW=yVa2ao0nD&i8 zvs>9FrB8~^_hQ!*v9j;2Xt_+6dChm}=L<KVb`}@ra~**10VdV=0Lg#ypZr%UBCll3 zC)O*z`y~_%H1|LHy$1rpQ1e@nu1-B135OeF!;zZ7WhgkQ&QR)}K(osz^gS^-u+wXt zUH-%C{LPb;ZR&NIvFZD3|9HN2HEqGnh2;n9>sMyq>Igr&d)H9sV~oe+M0jkqGxDGI zmWcMo`G0(KjDEt%>bFhue)rS6w}*ml&0+VMuvO~u;iu*-X$$5_-0v+81z(*j{liH* z=YM=ot_K8PU1FPZa+|Hrr80f_e(4ve>!zmLtoi>?9;!Sj*Z%{}kJxQfQ`G6Xk2u~w z_x1(LUrPK<gug?zGxDGImx%U9{*Nr5$o2TvTU#&`{9ei=TWD)ruutyyYn5%mw$g*S zx%F*9zwz7jO>+M3R_RmIC$$B;mq<Tgb$2jtse0wbDQ!X5(*OFDQr%nq&5wo~-PZh8 z*(S?1Yq}zB!BkiM!E*T>z(z;9D_{)eG2`(#5guFZjQpp)C8E8N|MAT+`UxYe-|nrU z;O^ZUQo1FlBd>*xcb7LmCV%e_x>V82pV9eV`fA5yDf5<kY)Z9l?GEmLEfRV6^n>S* z4X50rRDJHDwQ1@XFZb11?o#4zBHX=FJ0t&Thlyy1<p0R>iCm9wz1bda3+{V#cgXf| z_g1qr^4j~Bb<e#2_@SnM*ZA(OD;)>R%jJ82k2qX`M)#FG=6yU)gvVApBmZe{iD+-+ ze|&R{e!|G=*S52J>%K?Vr*!YJJzU#aYhLwoEBF6NgfCW}Q4TN9$p7Kh7wv0g^+o=V zET72r_|}_y=c%o`ch637?@Zab?wPRC<#&5^X?r;GbZj^_UWVTq7(Y}Qo&JyK>)yJN z`~SVgbB~fWH^~2y<yo@s&tzRM@;|;gMn7R>^?S=>9{GNM*o;@1x^ttws_K3-S&vKB zYhwFP{!i@vpQz)X{2y69k?ZlTw+h2$nBlN<pfs*_nd)+?v}sePCF(fibMSmPe{UiG zX)nWRFC+DZ{Eu&r(N7pz{rc_ejc`*qw82qf)JFQ&>0u)r4qdG>)#bOR$!ny?4fEl1 zJbfnrX<zYdUzT6w|H$%*T#s+PS@}QQGGJHA9da)pD0d7s|3LmvwT=TyyiJ6+AGDp3 z|Fp+Mv`6wkzBxueVPy3?RBl`2r&M|kxh+uonGz{$OyANSdbmAfVUJ6-?%sRd+FqrW zpLwk1#*KP>aiKMzRg(Ysnh0M%Xx<N=j{K*6CZc_k|0ByMay`EFX65wYQlHoR{r<aJ zjvsP7Q2j)8YwOOr^{v}-I{PwR4~6%9WAh%R9?!`5_K6Oqe(_SrL#?e|qp+}y{O9>T zN%ef6!FfM;nEWUI$$y^zGg12)gLB^+j^+Pdw-q1$S;@BwUU;Fk^^WY{eDZ$V{KHv! z4aa}}rH)pmu6<>)t@v=jK5vS1VQXuJ<bNUgPyUnt{N6J%xSuuAhdKX`?|JM*&F>Gp z-B#WYF0K4;Z9U|8{WDeDHtz}8yHsoa{KJPA96$1}KRv(y*6ig|R)0#L_uuTyaR&EP z*vWr>=Rek*=lq}Zf6o6o|NpQ#I#%Z&ocq@BK>j~B<3LtkfeP%Oao}eqt@pbpm-bAl z{K_4#*;<bOtmOXI(5?&n?_82$GcK*#qp$xL7F+XMC4c|N*F^X_(tZc|Py0+n`{ewe z^M846o=UcUf|b+nTZTh(bU(n9FZ$~;<BU6W=Mt}BlP?6`k*$>Pd?XTmKh)HyOIK4? z^ml{$_k`B+$bWoIgs<d3?JU{bnJ34Sb7VoPt8i7}s*$$Wt36+`<q_+X*nX0g)A1_F ze_T#>mviKGWI?7&|NZJ}`^xfY|GNEi{-4-fqaX3X>N{S!Z!MSn$3OC)?}beCeh~Tp z!SaaQPHa8hF<1Xh!c_4p8|J#y<9l{&o9M?s+n=2MAnh{e|MXAdeSDJtiOn_o5g)9+ zk1UWcC76A+@hW%9^0)1Hd{4ZOFP5L2`I_weh5V=8B;xuM`TxQ4h}=$WJ?Z=YFRO`k z#j6~+{?GmYxi3Ic-WP!UC;!R+<ojMa{eZ;u1FXN($E)P;|M;2+Uq^bKf&8a^CZc_E z{-2mUqu(&RdX87_TgxT?@ih^?lK-@`WN&Bu{*UVn$<{xyaynim`H#!V?lRy1<NJRj z?LU6d`8fX{-g#-V<>Yu>pLpfIwOsO_{3rj(*AJSL<8?anpZp(l_y2p>ef+FEA8twh zlmFyD`9EHB{ex~7`A`0n|2X(Ty<ob;^qaKHT>s~|jQ8<I{*(XYKhDJ48J0i1egpYG zy!xVjjjX=NfAXLFC;#ztWPZl0-pK#t+8^=S&!fFu@}K-C|H=Q+c8Jga<GdsP|F}MA zAMvRV@}K-C|H*%RjgPM*tyl6tx%S6M_hYipNB)!l<UjeJ>`qOz^&|hu|3v&gh5RS~ z$$#>HqUCF{Z!h^z{*(XQmv6k@-{(r(HQe9x4|jTg|BtVU@O7m94Dz4$nTYnu`9J6X zod0wFKhm6HzWC%6`5&KprM>=fy^{arKlxAo<KiE?INJ3wkpG`JvLMq{SXfvz+UvvT zCs+HpTKi)l|67m9{iX_675?4TTJDEk&XukY^Zxkmw+#1s{&1(~`v3U!?8Cal{IrWi zTz99PUA~=>@8dh)ue5)Mo9Dxwp8UW3@rO^;4>`zn!O?QyO8LP1<GX(_+&mxd^yEMJ zM!u16qm^%b?&bX<-^X{pUupjiH_wMV{XqVw4IGEHl+ojndT>z>(l4SN&<<Rq*$%Fh z54=CV`v*?>QsUF5X~wJc8cN+L%hzQiCG{}Ec5uoezfm^ZJ+bmJ_5NkCFG<_kvVrw# zkFEEAzGwXpMYpfyV1m}a-TItvd$eA2V(azPd)6y9etEey{TL~&`Zi<hzsD9UhiW%N zjlW+FY;SRbw)>s^5?kNkKzko*zgX-2nre&gN3(Fa>q)+k?|i?~{?)%p;6)82dktt0 z)8uu^;g%jmuClG%V3@zP`A#|7?2S{ho%(%WzAu0NVWp&-&``KX$^$BG{$ZzK_GQ^x zTV?p9<1b}cnSGgCt~u;A%!o?;vmMd#e}3YmjIVj5arN;E8Ncl}7xv5e+KtAA{W9IM zr3Lf#^y?001@&^))Tc(@?`mm@j$d&wHK;%Dt@>>($D`9dXw~N-S+6h4OS_yQuTze0 zdstq9dp6k3x}%;@X^QIm_g|bl<)|=n<&`gUnr1MgJ^DGhtAE_<ROYhZ-xdA57eAlX z+A7=ATmMRjUccZ}?}h#H{zD&Kyh?w5pVJoI{(hG$x<1SGu&hs)^DjTQ!)2IXtMY{; zC-i#wrIg=06zY2Oc+w<iHpm$Diht=LyL8h-ww0$GQkI=^I8|<WZh5r+WW81&m(Pi8 zoAKsJS>B8D=55|1>$CiuGglv%^?30MH*MDIvAjGlx?ble6|}d?bjzOnm+1QRs`?oR zD(vQY<GI*=Fd8qOl=WM&<bj1fGJj;w%-H-b*R;3F@S1}&tom-(+uP;1DY`!+w$Gl| z!~M4I==SvoLKU+1W^tjZ>-*j>-}_~!Y)pHwJ*eBq1G8t(cI)kFPfK&Jl<oVG@egfM z?{z2bpYh)#!^P8Ya<80i)LeAVZ!6tyH}9`nZSAKD*>6R^Dk_xiG0b156vX!Xx7@M) zk(t_ZT*lY@{7<9x)Ad=~zK)RH?7HbMR_&4PUY=hPeZRQnGtu{dWRw@{{c--Iw-xJl z+Lz^y?ziQ>620Dfzv%TYl9#S`-QH(^w$g}n&6?G=O4f7TuD!OEvVLn`xv9G{MZI|T zptPIqb`_WuZU1HRD%S10KYdfQ9lZGY8)h7k&uPDIV|0BlG^Rz@YgvAwd`$FprF`K1 z@!dbL+5sgskjyopb1LegE6<p7S2!G6wZUE%`cFB(Ycq_j4TgSS=fBROpL&9(b~df) zl36RmS?+z&;XiG<UrLwp%_sX!?Qv@APV)zwmAcl^V(#m38G&!~nHToEjPh5`m@^JI zmGiUz9-ZD-SS;g}a&B2J!%lTw{Tg$=ET?7L?&xr4<Nfl!)7Z8xZ2F}Ckh`(ZbV_AP zeimuh-!K2WS8ca@p06`?4xM}H-}<fmzvph5FQZ-7(HDOtW!2OC?X>(S<|lg|y< zUwP%Mxk&Qz&gNFLUAAMf@k3K@_w1|=vsn5cF89MS>@{BhUcZzs!|ndR(e13NsxpVp z|Jg>ataF~^p>2g}=1ESLEjOipWBET{awH>Ynlrn+#<zF96|Jv%Sx?FGm73FZSM>8g zYuj&LBlS~N@l~@!>Z>4SPqg0tkL~ejeW_5L)T>gHpZi~??k7Fi__)09HQe1_i>{}` z@m+I<Z0|J>70dKW&Df@w>ou-xyj!Mo84lZjN<DZD+sD2u?|Y5x$!BFbN`*qZO`X$b zVbS~Mzr_D+|I!q7{j?iv{r7CQ8_)fRY~O6-hN)kD(Y<nm-RbT({r6NF8L~aylK-o# z?w9S3_0yvHU)WKo_jkdDJ<;`Yf9v0)?Z>DuiIur}{gkSA{!?^)X1kv<s~&W!{9okD z_PPwUt3&pa*C;o>FYmjI+aCPBt}kPD)(_1Wr2YAPzUX?tfBt{bn00d`61Zo!@ta*| z%sg37TT`{H?`)&vm9y=q9I{7Uws-P>Z--ND+O%h&e%I)BY&_@uPV{s2@u2%xh53JP z%C3&S$oKJ`?^oKtRy&}i29mi3qWPcGE-ve}3=b;LnB8*zmtR)=j%&%XElydHFH1+j ztIz-2b)L!y+nzz|AKRXT<;&x~{(5~R#^3pzZdo7CJN3HzJ?o|GH8%n4C98E+UQSuc zz3<*WS$`${lJ{>ngWJ)d;rg#d_gmq+mUmTF@_iQKlyMpF`kZKc_YBqE2d$^N7~Q_I zqCf1{_xXtT9d)IA;Qfi+KVbi}|1})61KI)Yfa3rMZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY z2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFuf zZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz z00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY2XFufZ~zBz00(dY X2XFufZ~zBz00(dY2XJ7lIq?4h$F>!r literal 524416 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 zI3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uy zhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}Kd zfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H z4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF z;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-< zAP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny) z1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t) zI3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uy zhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}Kd zfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H z4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF z;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-< zAP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny) z1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t) zI3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uy zhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}Kd zfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H z4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF z;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-< zAP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny) z1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t) zI3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uy zhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}Kd zfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H z4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF z;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-< zAP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny) p1LA-<AP$HF;($0H4u}KdfH)uyhy&t)I3Ny)1LA-<@c$<V{vSA7enbEO diff --git a/img/helpmenu/manageredithelp.dds b/img/helpmenu/manageredithelp.dds index 650b75d80bfd673f85a423784a8536c7ff59c0aa..590f8cc24784798ab927ea9cc844e65be8107a07 100644 GIT binary patch literal 524416 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 z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ zzyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb< z0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q z9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_ szyTb<0UW>q9KZn_zyTb<0UW>q9KZn_zyTb<0UW>q9KZn_7;O&xe~a<L82|tP literal 524416 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 z2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_Bo zaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa z5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@ z0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y z91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU z!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt! zKpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp z2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_Bo zaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa z5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@ z0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y z91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU z!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt! zKpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp z2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_Bo zaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa z5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@ z0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y z91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU z!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt! zKpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp z2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_Bo zaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa z5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@ z0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y z91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU z!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt! zKpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp z2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_Bo zaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa z5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@ u0dYVa5C_BoaX=gp2gCt!KpYSU!~t<Y91sV@0dYVa5C_Boap3<q4*Wme^72;z diff --git a/img/helpmenu/readyjobmenuhelp.dds b/img/helpmenu/readyjobmenuhelp.dds index 4edcf92996fd94fed75bcc01284573996f0983f6..b52191a726cfd7a1a9603f3066f08948ce861146 100644 GIT binary patch literal 524416 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 z2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;A zbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!> zFb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D z0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX z4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK z<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y% zU=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ z0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO( z9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e z%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvj zz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp z1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^Xem zIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<` zm;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6 zfH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG z2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;A zbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!> zFb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D z0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX z4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK z<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y% zU=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ z0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO( z9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e z%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvj zz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp z1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^Xem zIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<` zm;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6 zfH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG z2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;A zbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D0CRvjz#L!> zFb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX4loCp1Iz*D z0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK<^XemIlvrX z4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y%U=A<`m;=lK z<^XemIlvrX4loCp1Iz*D0CRvjz#L!>Fb9|e%mL;AbAUO(9AFMG2bcrQ0p<X6fH}Y% dU=A<`m;=lK<^XemIlvrX4loCp1OGP%{(pkt_dfst literal 524416 zcmeF44_s6A{{Igk;-F>T8|l4eH|}-9Q1DKhwp}ckkaE<r!C`ZvE|ApCZrJMkRm@l- z{=@FAE_AzHZ&3-t2ASPv_gkraD@n}k&m+|D&8z+@U97rlMR-Zs@AW?8I2lYBe-!2H z@u-*Q@6OJ~dA|RB-k;>;v|Aow7$)qxaE2L#f6#x-5GDx!r~d+Z-GZIz@ML|uqQm2^ zYrl4^xkAfvA5_lZb>bakvT6D4H$BUS%nEIVmX2zth6=6Q?cP<jg7$YrR2Qt%a``sK zY>8&w?$XSv71P+EL&F>GmP!M|yvTXf#to~EHAh5*aKju`D{xxLE1T^WPM48xx7aQ8 zK7v+R)*BKMMqKwn!3Qh0+H5vx0d4=FAi`|5S`*_px(qwE{ry$%dSQFGxu&MZc<n}) zsjPZyRr~FG4T*`zep_9zUK<n?WdERGjG19<oIkC2Fd-v$?}PfY%KZ2$yUjK-{#+a` z=Z)L9j*Op)*U0#h@pN4)?;07etmin_g9&%Nv|bkZLBU4)d_nEsU%1ZcmPEX+t8Ory zJ9n<&u43E=jytn&1+SUHedxZ@_g0GYtb^Kc{{vMzK3L}a{!bNew8-#!wfNOy`rJ<z zn=PN><=wBy;=&EGTZ$|Nyte&b?Up^-#2SXFGv{c|o%=B}ENu;o)8uV1%hbQTd6Lw7 z9^p8XS&tp;7Rj7yUW+XF8gPDLd&g?C<Y+!Ulv^`+`dT(PIJk0MwoL7E9f`DC?$k&^ zg3tfXg!3%U9;M#AdGlLgX@jS?;6=L2?Y1qNlcIUH`PrN4^Hk#{?C;eL=0yA;!_q>> zzn)(dc7pv<!Tiy{gr$9{_;W-svl#Dddc$-43opG`YPW={jrbfzmeCq6hCbIqRvJuN zWTE#hf?_jsx~ho3&%+!=7Cm0V`u1wFG~;EmnE2x^P;(2dH=FSLW*9+PGs`dg*H|;1 zFV-B(?-y&9^UHFxJe$_bvnAOsyd+RMuOxf3g1#TgoXv`rI~k@SYqW-*TMbzaS+pH$ zp#2*R4R{XVdpFdz>K+4q-aUrVnnbLBPy3SmTvMiXExTE6%R3rYp<OMXByIoPS*Kmd z)?oj&Y=Qjs!@1TJ%}a8djhnQP&6YnmNy04Du4N;_!^0P{3+Z>ocy%gE*D;ly%CGmQ z(d=lJu4^=ujvuWVt?}q!4SoK>n!)^Xdp(gv=Rc8jB8m3*>TwkvcU*N`MeFaLenM5R zlu1aB<?1tzt9GmCd)f`9?cLWh6}^9%s*FER2da1(e>cAC@eFs<`-(a+??VK5|7|MA z5%=Htl;g%i@0|^WJ#deK<Jxh*)gG8`snFKfmqb<<m@V}8UR_YVf_{I~!T%p#d+oIk z3hE4@czsY%wSvR%&Z`?O58!#Ja7;H#@KQMJmP~zajqp4@RYm-B$c9z#&H5?JX#JqT zh37~}+y^*6PIK&!n<C8l^M4gRtlDKdlWVN`pkOby2TQP>?1RM9n<KLG-M@-HT@_(I zduUdohwHSR=dn>y?aF%o`AJfRL%LCi14y}xsQga}x2I=hWC)zUdCSQ7$ViSmdUE8% z`~MwV_uRpe@$=JloUpw<`T2>z!VA}zb&d-Tz7E&v#v8QPaC1J^D;(bz1ci`(wAsX8 z$G#Qwj?wd<?4o(gg!|%8|G0_nSACqq@xFztAIFS$?8E(LpyB3&M-CsR`$nHHP~Eqp z(l81y-}mjD{~hz#_dIsX7S@RCvJ(e+1zC9Q>hYcJmztf(pD$FcEA?>p;^)6pb12uy zESfV)ZL{ytk4_t{wpxw5$X<vJmEbxr{(eQ4`(SU(g1u1=dn2SeEbTsU!n%moPl}dG zIy=5E_}|>z(jstQQ2*Q)?1Dh~FW3QsHe?a+y3YSSygjhTP{G>?74R!|;SZgZo+rM~ zpLJTYV;N?hcCEG<`>lmN@VZC&{TFH%^4q=K7wmvb&Hv*`xGdNMJpWt9f%jaAOge6Q zttz<2s87}GR^hS1b)WaX_Clxp@6av~xj!(uFMRL5e_vf)j)i>?8Cg<a4Zhp$_R4C3 z|K?~Ge$;00Kg+{^!QU0MdIgJ@m;WDa*1$mnzgFln&M?Cq-xNrB9uBL{Gnrh&s>96( zav6q{csb0`U?P7>;W%BTfgLfdx*@A+8S&DU<*xVaC*imMaZ@6m6T=*?tj0`Q@5;(A zq2tUe$X-!66b?wIde^48cV*7fY?!xeWIR_NIeeFM<V4Q4;eP1t4{zOYfBsRdw{2K_ zJ8hq*|7S$@<U320p+O4A1Dk^Q&(*c}X@5O;Z8Y-N2d+00&%t$oo$Mv9F&w(Dpgw}G z|M$hjHQ(chImVa|<K;j8AbcMkYj^pd;4HFmW!3lmWq)0k8*zuO>LVv{oSWhLN>`pV zTez*IonFtR+J>z848sktt;=eJcJ=yce>hUy4Le^_9fUj8w0khwHEufJxo(^%^JLYE z#-_914s%Gd&z#X`BB}!O(8YPweO@<M$Ub&+CJWiYHm{mnm(6va?XAk5*GhhW)_kEh zbDm{47i*@tosE<8>gMFx>pjc7x;aVfvPYY-ybjxG{UT@!*N{bOBL$DKY#?=Wb%tqB z8@H#8x?7jsT!kS9ezy(Ea~%H1I&DQoMWygu^77wf2dsr1fS*4DJD|VuUj_a*#}oJK z$=`RXLaavpK5#!1+$X=E?3>=>{{jUbhs(-TIf(t*xH8p(4z#6py&IZL+vf`$5OrYM z0m9!^pjrp}9BwdIy`sVp91<cC_)qcwAMpC1V3Bs{(4l1?6fB{*B;xCWPN(>NzL)>q z<>RF~?%IusR))ED^?co<XTE!z{Czyebgjxg2mWK3yLQR*3(ws#u4tH}QronHczj*; zV+GbyUoQ9Iay9uI3dc9dJ6M+uga3c{@PUJun*TRCW6ewP3O9%I@nccDdLLXSiaWbZ z#Ce9%?RS~h<3(|tOuU5k{&la3c(T{Dm-oN-W)c4x#=ll((f*ZL#D9jlh#JVBuQZT7 zz%c6#WCvVK7xRAnV#6XnzP||E$J;b7(h~m}W|4N9mVT$xptS5?W3+TX7HXr}AWo;N zfJQWO+&XO-%fvc#>#&T&rTvZnVVV;vV(Hy!<nPBt+&w)^9c(pb?qiR)a^I5&=so^} z^RO#6gZpGx<S2;$4AZ$LE9m<dxj!(uFMRKvbAQ4N{G9Oj$1CjdHY%n3zeFk(_;0IT zu}BMd8-Dj*Ln91;VU8sh_(521tB89Sb>z49As@Ts@B4rMajSC^@xUpbdncEne{keP z`1>4}k^9Kx+c_>W^izkl__b=g&HwRzZLhq)!dhd@r8<IiT$p`Do#EC`^@#gd7~l>d z{}PyfQ&6GZTwc~L&)!-5U*vyRdGEX5)yw~8aOOh(yI=tL=e}SUc=#U!&XXMw&GNiw z1YO96vC&88&tFUSK*Z$wh1#iP4+LGh_&?9_V}muni9(&tqk}b5<O~y`d>}1MT`!BU z-ks)DF~^o=m^8;#^!F&_|GnB%{~z41S(Z@<-e=<co%7z;E)e;T-^+lnd+^8qI^_8y z@v6|Co{l=Xp!WOyY{nqG$p1$gKd#!7Uq=>xI{Eu~3dEnu{~uQU(PsL)9Of8C{;6Hz z80IL!>+*53XB__2aZT~fc`n;Ge_ffuaf~gl>hPoMU#z%{+!sEN-IAMYJVtrs($dm# zo#&e`p3nAo{HvYR|B3wf<$70_uU`JU5I?w(|2EhIJ>`Ei_)l?(Fep98$sUk4a@;~K z)#pVx`Y8YJ^49%Li8`h~{NuZFO;ic;d@n^!`Nj3tMC)B?Q=+WR=NeJ}r^54ZXlwre zeEnbd`Onw&)$E3!Uz4lX<tP>?s17!8?hAH+$bb6X2!8ND)%IMk;Nx=b`Cl^Q)Y;9W zDzuInObFQn9{z`;UQh=6lj;E&C@p^lr8;lQ{|`gObVQs?3GP>a{q@%?hdFYoZrq6c zI4P%a+`W<VgC2E5X*+kpGHs{zeWkNn&wr)s-N;k8xd)0(=BAd+(s8RzW;Y5Cds|!i zAMUUIU*vyRdGEX5)yscix~>23a`o8%_vOD}2TX+>5Q*o{LQnmEkk=0AdHo;E7TaJ= zt%|WGCcY$}5_RoRNzhC3m*Dp^jG)I=nyBa-Zg`IPzZEayKaO*GxgTi!e>KJPjXd`s z$>@fkFYw-1MgG(GHc+`QeD1FEf5MC(^hRriHf;vgwF&%hfBZVi@8#JsJ^=WaJyC2n zPdey`{dx7QjYh8C{c7>7;`vI3f%0|tuNE)eNcDIEPek>MtGiO}gYWo1k^jD2@9Of^ z%YWdnt^XfD{@-8x-yi=4J76K~fFNmTXz%eqOoQi|DR)kohHHF0Bvl<1<#xCHAaMUB zc>(MIYvOU$ucL0iJvzFT|F-V)pLlKMxu1&wfA{@-U+xRbBL7AHw;x|C@Z977mvEKZ z@iT&gDqAYFA5AAe+^Z81{~sJti97+!?Hw-E;kw=Vku#}}Kz&^BtHm}$8RB2B7C%*N zuc)wjdG=H>{*I2lTKps__iFJ;8YiFmYVm$jELXRxu-q$}?>&F7oDbJIP|x!%eLr!+ z_bKwft9<Wlzbk|Pm8k!7BOmX-4zPXwUti?^JLmt2B$=|Lr0e{TX7N07wIBaKn7IGd z6KSJ0^(8XND`{bxRmYai3sY;NemI<4^OF4Y5g31b?j`xJqpUT#%Hv7Di3*hjw;BH* znEy|FHtNaW53HU~;Cfe8<iE&&|NIy1fcDD!_ZXMUtL}dDB5nS`vWWZ5CT(0e!`yG4 zN%j5BuNKcVQ~dwc;wq{KSVnPU#ySt&EV5YhIqu&p%$Aybx7%)6Vvdl82lL~fF(8E$ z^@6-&+!U`ER~4{+Q&40nx#NyIs2<*=(P-$r-8oM$9T#r5Y%tqUN7;Hlj0fkh$J)vH zV_Ual{5ikBuU7aRkN4?tKi~W3yB~l5yK;ZT{C{U0@9y?1hyPTM7YP3^MgCv#`vr|= zG5!*TxeL+n2ji1%q{Lx=eSfB+zL2*2^8Z?JpW{j*pGb?=gsiH`-G}qixP5OOzyf*G zI))jnDO2oNWwm<y{s?`5x*Gonw~^Q<?o+*B@B90{c7e!$k^h14pXdFth&k)D^XDH_ z-VZ;&J}!dieI@?Bi2JaU^Jntog=;c%H`W=3ZfPvrx1xyZ(ZTs53+>mwu58F0d=Kbi zGlcqy4Q{`^dv$8x6xc0PzX<*p0RP~B_s-M(<2G1uKla)!G7V!qv^^{>MUzn-%xo~H zXe7${F1zJE)GY;Hq%Vw(?4}yKB}L6x^*?OL9<5<GH}2nk8YWme$!_7dhj_1V4eFk1 z5~+_KV`#MV$4MfOVm}&(7v$Y91^ZczbRA4aqn-8-jyr0yTjaR^!58e;@%?&AtpDqy zagDvne~bg{qw#;_{|CzdOPT){_&=3(rcxiSK>U9n{=@HgwY)MXTBDRnCB%PyW`@qo z{lWM<BJK<7zyBYd*MI)M5aag}XO*c6FA?_zJ3!<=eSbn8e4uK3|K3yo=UvZp-!wLR zA^Q66lHPBw)B^k9{{8%O5X%@3Y&BBczecY^eqf!pV%#`zs)FzT=jDzsPl#)|ho{Yv zsnaoTZaw;>%8outT*Tj1u+;x?zUS$9`SSC>oVC_i6VVq|9f9!^)KB)znKSOeP#XVW zM}JyrbTl~L@%2;vBx9|?@%L#)NhKkJrw^V^zt=@`?$q3D$OEsxG62`-d0k{#qscw= z>8D}X56@e7bXuWi(V`l}7g99AArd~`5F2YV6``NqsY9DyYrUS9!D(eI^*5mXf}~*= zxgO7Tiu~`cf8W`0y~lsxrnx`ypRT7({r@G&|2vaPlrmX>{I`upA7T8N8AtQu`WWt& zIZj2vs?QfM1m_>k&(HVrUf{l<f%3mlf$_R}f%}CR$47Qj-~0U{|LHk8P<ikBxxJk4 zkozrsKA%&D!mrmNt;PHM@N1jN4gmJ&(RLbt>U$pl{3pNFY79%eQ!@(V2Z@KoMdA_S zbZ#5?A2w<DaQ|HN$IG_s`vd=}zyB8P;OUw3=A{e#=KIR;+J(NdIU3;qSN=ce7HuNH z4F0oduM`)J`vd!H9No7XtML@^KLm|~y~nT7e0uoMAt&AsU&pNIv^5&o4~zJI_Yw)> z1Vt8;$rQW6JWBo6TW<;b>FIiqg$dqZk*P-y4(rtJ68Ya5$Gf||_xMlw_Ws2G%ai|i zrpjbeY1jFm9fiLaqYQcd!#k^O?<xx9|6B!oTb(UmU(e-_M&CZGb=6}%=YJu_57aE% ztt#aE{}#gU$JgZ|{*#LQAK3kPeEHnh%f8%h&Fh2jn9rx)5qH0NuR-8G_>)g@f9o-Q z#$LlZZ93QJZrvaCfo<)F_Wa*~c!BL%yJfMt1{1)LUmGe78|2|n^BJ-Oh(k7T=vQ~# z(WxKm<z8Fv3+pdG|D6W%s}~`@Vc%N*Lo1iTTW}fn5Ap))NnzxdcYOWA`Xcz{+YisO z4n`glUOGqd`sd=M7sUOA<8w5*Ml0SgMPuLEsJEF@)Q>(Fjq}U1X?%rnK6>BW8oDmp zuPJjq&gaTI8b@HmO3_Y=<BRz?1Im+ze&*L(bjyWPM1e>k>n{C{w+x5xhraeqM< zvL{ldQfWw6`F|(;{#hS=bm&XPyZpHNcjfQMiO28A7swM)&_sQHFzi{<eg5;@&*ixf z10CGw`|*qX7kQ7z^JR-ic)9JV>$?klN4<*@{{98rch!*pk2aGT#QQ2t$XB7=&5uu6 zhk3e~uQIB)kZ#qXE^ssAXB5BZ$Nz;AudK$yTVfCgzvW>Y<~<8l2TP)#+kiOq_Toc` z1FWU~9o66)@&U#p{Cq2tAk?F^<xZf>KK#!L^!VP}KUBR8yxoBL?y4~n0nJM{UrBzu z#D~kBeO_-}B#o!=e|<%kGp+TK5?=kCx5$D%jW|z{CGMj8wLM<se`g%;?)Ki}Kf=iU z%m2TG<Nv{bs@HcdM4cYeZUM~ymn}bhC^vDn;!8Ql=`scWFHn3Thu<@Ku;$lMLx&;{ zcRt@Q#Qg>J7yn0oABoQ6;C&`KhF!pYk^h&GdwuO!Un%Er_1|6L*;Ln8&yU}eV&2l3 zsOQ7igLwZs`1{M|dAaZL^S7|8<+BcL-;$!1sn4COw56y+)h3sX*}}f7P!2z8Addg> z!@D1VUyuBENHF>NJ9q9xoc~VEEF<NmW$GFvrb+)Bz@q(S$S?TkT3Zfvwx7WN(^cOT zP#nIq{l)EFyT2m;yUX{^j_W=C!)@(f{{JP+|1ZS2f0XNAkoR9m{sruS&g%c)RT-_; z(rCp0E2~=)-$j2P_%Y{X>fc0}@^;EbqrRWwV_oMz`S(^M&wY#+=z#ko|M9(CDV)4o zuamg$!lWWI!Fi=r5-f0^mYEvkG8f|fM-HAtoWE^+|31yTiY?`@y_TXz9Uop^T`e~z z8c|<9TJyk%ci$bBHWl?}A$<Lt+r4w=VDR6Hx;C2kToM%2K^(hl9P;v-B|8P@izP+h zL63g30yCJ$#zuPi-*&sOpQr)d7pchqaNaKN{dtXgkN*QX|GyCP0U+%!@y7ix&VRBC z5RNbT#-owj<}B9K6(pkmZ-JcV2Oq6jtw0<NI#`1=Msw%<FC(s_4^W|M76$NNg!>}@ zaigwQu3jmhi|Y64BV|%A_o=S0#yAG?0O~*C%Y9*aE#m#<o8LM_{=dyud5-*l*t@1X z)eE9ZfBOpZ{OhzIAOT15|LW}&=f4yAYld5l`ZLoTlOibY>~L(oNF1A`=Pc^i=y_`e z*P)5jS2)~UgFo0<%r9qaRjlTGpU3+O!p$`P?{t-Y1^NHsW|xWjW&7?YE_aP*bl<6; zgUJ7`-LJrZSG~voftde~CcmG?`_p`Z@Y6fzKkEE3FP}_3CF&nKyX|XM6Lm+u+x^6x zEXA=i?#9vVXbtL-?V~kRchhD5n@%JZs*L({T^G17@*ns0O5x<ydYzQ#4?^61rg<#J ze=~_>4`7@tKaP)hDe%5Rdz^l+vnsUjBA<o%xwQ-AC^rjU%NEG(TQe%|guQG%c61Eo z|6fO-%v=8_&o<&fs#m9Z0PPmm+_oM~@b_bh_weKKe9g_^`Tj@;T<_|0wIlxH<Jh}; zytrRm&WZeQdp~;r_tAU&ABg#X^7|ucyq|U<KYvMQ{7=zj%Rl|$$LcQ?U*dVjFkdR( zLH)hW7OKvYPrUiryjK2KcAfu)s$;pC8NS@Fg*|}B*sNBKD@R-a%jp;F*Q5IX+N2x> z&ZngD1JH~$=pO_P>U<o5IR2l$_p6obUHx2MzrR9zELWdN`2e)(xq$my@b^@cxEA^T zk2Y<sOi>?KaJCIh1^oY^(YHV6wGS4us3XIAPoBWbe}V75T15SETs-fsxw;~Y%d`yf zhTeLvcEo>}^P>NcxC#C41HI28|GRhJx_+N~kN*QP|4;J(mp#6|psW1<Qn5gec}pc; z{tI?MwtU&4kFF#BS5{Vb!T&E)S&c^Y@8RSAjKKBJj+CpEg)>usf+nKhX@av{74&^t z+FYpKvUu@7kJKgweP=LycA!wDpU-hW9dRbrJvQN&BWiOLG6$!l<Crg&F4gZZRNNX? zSorJ#^b2r%`vT5AS*TjRbm`KYoNH8)@AR2J9YMd~2k%W?zCTA1@|{*o%lZeGF7<B* zi2SGLU{^VLweCm$KE?aL&RWO*(AYlDUxE2FF<!4GMZH-rA2UXNVV)o3_MZ`W@2md# z&p$_vMey?zkznYnxO_+ax4{7D`t!K2pZ8bR`9=Qs_4ft-Qv(>2%S6v5-F}y8JznD- z;pR-dg!TS)uL<0D?KSPq8c6wnzTU4sGIA~a{sw&hi}T;c_W{n&Z*IP3PJz6!>8RyP z#TN?rA?_~~n-yqVo;O<a>tFox_UNIV$N!1<RwMfU^5gh@`G1qMOtm;Qb*5ksBsdFI z--m$zC(Bh9eHyr3t_p587~TN?GY^(TCOB(Vo8Dq{&o!-4=`zwYZ#-J40N$4p&qE6f z3;%Lpje_h5f#-A{+MeJ{QQ+sH*JHob)YN;)9(WOU1<tF`Ywx{R*uTAs{1^F;iCn<{ zS()h=*Vneb&%3;q`omebz~A4p{Re%Dn#TRvR#N@{FK?eD_3qd9`1bs#=VwhWaeo#X z0{1nCJL12X{}0^n7rrNv|9#JYsxK4x-V^>0r2Iem{gG(&L;NTEr#=6tU_5f}nL9O_ zC?@lqtx~Irvfc5kU9ZepEzhc{`Fyk{JIdCmXYPdN*T*t<vYX|SV5`-`f19m+{C_v) zK@$gS;KySkfO7c#^u2Y&|3W4C{e`N06f6<v)6zJuX?LOO-c*kJCzihp;kdcZ9Mv8F z;J6sp)BpFj+CqgE`@L}>N3nFNN9jC29l?359QX4w#P=gPZso}wg+<SCnI{Vs4@x+0 zXH%iVf<|m}v7XNR94%ved+v+<|3vP4;~o_6f9vH7<NX51X&k@RNO6BF8uZ?W{QjyP zCfgR|_qpH@aDTL#>S8<RKl~X?j79v=WBjYYlfKrD_%G)FMgI5o@2>mjcbR&N|Cb>D z?+#@CA2Mq$?EeEy+j^(}PoA3iF9``|+Vh{UH%{C(p+GTas6_Jdga;<f$MXt0hU$>M z``r^`{xdZG_V1>=GKH3H?C+<%^uh}-yw854Ve0GaztOy};qbutMzc@D)tsu?r?G1= zKNho3V^ZgxI@?mDHmT{jt}Cmi_b*bjp5w-|<N4z+RP-4#w=Q?4+a`Ras6>N<Z4>UE zK<DwNYfu-Ej#qm-K=l7{+Xj66-ygrJ&TrPC!=G+Zk5Y5>?mJS{vnjo=t51Gc)mVRU zng8QF_5<(#7w&E}A+LX^(e3X3bFq7VS4aF8^Zz3MFZJ)R>+jcl{0CqAZ2Z4}?ytu@ zAPlqK(0=}3x}FR9Pjx>%<v(%1zCKd97W0nN`?lx*YI#j!V$H`BUQ$q=|KkZCPoVt- zosh6?%+aIyncq$MZVIg@{saFnd;|W|{6^nsU<cqu{KxgXz<+gOZf3e&ZRf{3+to#C zve$~Bv|j)^o<DAU(Rlv2ZYuE~_y_;D@t^DGw{Dw&x<9?JEU3tTvA-bw&ivIcP#quK z{KNOb&&TL_)cs*xOhojNE#Uuw!-t<)sQ#<cxM}p?Qq<dTtwta%RIQ3TXtYLDYV#&D zA(FxE?SL4Tu`ZeyZl1={c(JZO|GLL{f9LOr|6=~X@A?1FBe7=6OZxj>#P$Au=e@`O zzRdr3hyQdv7xLc)1E8n;UkCn&hDszC<3G)x@$m%2^Cc3g!2ORWJR4^<est5v6W)xY zaZjD|->$Zn8ubGA=Q<G=P!{e^aDKybF=euyO;G*Pzkhb1NX^L#Lld0R>D*Tj@6W5X zt2u75^(N;x8gAa<gt;dXN4V$v@tICq|E~#|CrQ8k&5h2Y@f>%q;IkuDYA*Bao}w<= z1HSx!b;8K_YZG2Eeg}I1c0jtwf06qa<Gx@I+^5;Bc<uGATWK7>8-0CcYU<x(Wmct* zQ0_G?TEwVsQ!fA7m33S-TWOg6Sg?(qt$f`gS?&V=qknbX&%OMQX3;N5Kb4JPY5r>P zU)$gKFXsRIp8q#GMgG&@UEp?3@A1Dc^Z$YIAASD~i?o=IpsoK8dmu3WyIZ1JJYVpd z3dJ!0$aUJOEb-ev{}=N6->La{f?l7Q`SFBpW0>%=X~g|!FmU<1-{r+&{>)(i{NG3O z|C-3}r#>hG|Bt%u>fL1}$j9%~$X-6f4c}g*KICw4&+ac$7Zx7J`YQE%yN@I9U!=C4 z`I`IeK$WH{=I_YYS7{V~`X|=!)5KnL0{MEo`cD(8xtop_jepRx9QzfGPu#PU>n{Jd z!LIq}rorI?_XWMU|Bu2)yy(7)Ixz3Uo#6ktaq*+Qb^XUxrKMjj(x+@rQYt4;UUkpx z$=@~SeLX4V-K6@)=9<0QJmp{hvi#b;+S!wT@rz%Ks6f9B%I~k!g8TS8i2?uV_tYQx z54S_C{};#qb!+@%;5_%|=lfgVd;IUq{C^<)r#Lt9AJW$U4>bSpZaL3?^85chDSGHS z?K(d0zYg(#ir0JN|7+RU*q661WcmERW&4bUY;{~*+yQvRkDfYp=DR7|#whW861XpD z`}lvA+Bi#}QH6Mav#Z%9@ZYQbEB-I=-dFwee*w)m+$?f`U~*sh-YNcHd?>eu{C<Yn zdXVb;^NvoA7^EFGW04D;{lNJk$>>d$+7XkVm}Nx0pJDP)NrACKtD1c4U};DNxKHz1 z2;3L6Kk{G9|98hP_3%op|M$o3-df*#{D*tpFY^Dy7vBHxdH&xm`2XwRZ%8q(PY3*G zsejK}_9gi-{ijWX#uvzcs%Myz8Sg5j(zyEVGp1@PDk`|C>~~WTo<tnMSMQqgpa1*^ z`Ti40sC%AYn}m2g_7l{u#&~?{FW#y}?UWz?&mjuH@7I|YswnQyp<jSUi^kJDK@9wV zzCOQG-8Lab5fT!@ZJXfP-=DVfzk_iABL8u}2YlS4Lc2v<W2HL&(VBU<9~IiQEcNH} z#`R}U&gHk~O}3*Ub_L@8M^Fj42Y-ip!#th9dtdF3{O?cs|9>8dFgJ##q|C-R@A)5< zl-%STV_6w<!=INHhnqPJ0PcH#Gcaz6{15DY1^)f@9{>AG{$JpKPxJq9|KZ<L+`lA} z#w(pW*V59S|En>6zs5-1r}@SPjUU9<*9{uKKz<AA0H>n9DLgnNXe#>r<M9Um`)V}o z2bU?DtxcK*ho&~E7Ipvw{(H1Mshl6@SFYmi0LH5wkN*?K{dYlo)c>!2UB58CuQL_- zPv5WL2M<*Ba$Xqs*O^kh-)N*fKivLye!pM`^hf^pr~H3{6aDmY|Ne5oWqRiOrAt?y z9AkMp=5LY2e+<{r1<sGY<&((&zP-QH2a9;w-^c%VC;s0X`G3kYDrKP(;y=av+w*^c z+*ng%CH@2R5-<Og<ux_NQ^bFm1s!l-umft7tj1+}f%m>D@_%5*$*)%X0JHxjzzjk^ z|E}p&wwT{PhAp|MPBr`~y|lEn>~e9e!}|Yzl>eW-Kin*Br2Zc0hlIL`$*>1DpubnR zd2bfQUoO{u^Y^|Y|F0bWU-tYz?3l%dMFz5GdX)diLpKK8Z*jLgO8m!kp~3$7zmPS- zf&T;g`Zek1M~qF&mb=~D*@f(ssAnh1gZ<C{I~e2q5)-Gw@25IHU+xRbBL7AHUybpC zk?|wr>2vkAhMUhS`(6FN*#E!Vc7@3QD~JEU;APGK6ZbFVKV8p-<Ns`382=xmjp6J2 zwxGVKzC<bW#wk1Y|G&e0rN|E`CoHhBlHi)$-_JR&I^5!RH|<!6yur$p?GDn}X(f)S z(45^LtRO9Py0T6r<>x;?{MPE)But-^F7V!0FV6p#7T^AVU5NWrodC@jaQWi@sB64h z{aW#Jb<JMuZSD)_>yP~JPx*g;es+IdFY^D&;s52%|NHX4JNbWs|K#__Nu`MU3wg=5 z{I5XYUz@@3%LxlAwV{%Nw<j!E#OgD2oG|~Ne*Z!?<+vkqJGigjIB0a!j_~Yjrr-Jd z+iI&<JhFWG@&h}ZNpFAp>F2lAI#UPl|HH=NJ5MAb{gx33|64F|!<EQ?;9so&hoL}r z*g&-x1YY~oUCxp5T)k4dt7+uK#^A4XN1?i75sss7tmkx@{*LR9{O?cs|NhE(f7d1Q z{|e$i0K7MAZx%h*{A*<v{Bl=iRwcjRMbv=PwNx4^Tl@P7eg6FEV%p!;#{b8PHvT`} zx3ls8YvKQ+JO=fCo^k&E_@BODotEMQ6BevvjhMd_{r_2aLPCPT|2o4$HWDWa&tAw5 zhsI`4WocgV3hjxc&*#n4*$gL=;P`WUvucxWhc4FECQ<*AK=_Z3cSZ7_u6v+zPyE~- zyT>2D|Mq+0dwt}tljkP??}NvN-26H%b8o>8p!MU9Hl0&Gm|e5q-|_vC|A;$^<Nw9+ z{{vwBe{b-Agc8$!C$487N+=0o{#>cuK2|?!(4h5(^szzG!GrzR0ebuY5?{KT|Hrsu zE#~#PAnxz$|1Z)q$BvawSg>9zl|EMR#GHi~hYY_T@c^=W1pY5%cdJS<LEcmr<MZ$U zo60UwFz)>P!EBDAg6DjSg4Wk2wbgQ!XBmpVs2t|`Np96qZM0D3S?*L7t@|kk@yF+M zU!&-HaQsCz`TX`|1;stvj}s8<|LJ!!(Cq+!{BC)9WPC|vWaOhqM^3yI1?lkjZ!djt z_Hxoy*|X=&`*37@Sg0&?<iwHj*qrWrp8m*xar}Sx>k38xe_;9lby-bg6Y*MPczW!E zM&nQG4F|_=KEqfSYo8t!7Iv*95dOE#|AX6j`S<^$S*j1HFkqa&Bn16M1nvv!iT~d< zhw}aPk!j%n+gZk3^#5VMZD{zmBmUF;$z`hTw=#^;SgtrT7Uj;al3Ov49%s_#C{p<R zdkU1U>jIUN{gNV2mZN;Zl?<ir$?{Qh8h<bfO3Q)kAe_(T8YG7ufLAv(ir#-v6!=fa zccM9pWCfmkBL4w;x?cmG_r9Osm)pYf&OeNdk1VO@DDI!0secgMpID|;M&=(K8K0Sv zk%72CT;T_G**yPIH!B<`Xn*9tnEw~~?=Qb``PcW}_&?&7|G2-97w9bhAD5Ph=Vhhg z=~2`#uhQ`Fs7&Z!(;t8L0stKd|J&yOFV6o81M2<iBO@ye6<W&I`Qtyu{Yy%eN+^r4 ze<tz&YjD*c|97hnjy-njqs&hxoQ=ET&xbAVDMrK%l|9PjC?>!j2$EjFeZda!@*f|7 ztIm;gTuRxkWuxTWx!BBytD!o*;o<6JIk)ojN3Jh}=C3Nmey}T+E1oRI_J<2)-*3YD zM#)l+j>}Ipe7^}=FOzX)gXG-&+=f3`ppBs~&NM?YUGRsr%yI8r#4Zr|kKds$$He9C z**5~O{ps`0krV${s^m}yaC_wNTY`6;9C^ELQn(TJK>ULkFyrsI{>XnZ|1a|Y@^ju_ z9HGDQ|9oA5f$WJu^8e)D`p^G&#{X%E`(vEHp@OeB=!pN1;`PvyO07~Rm69Eho}tr$ z|5|!qfBYXZ{`IlxqgJnePjPVU!^4X|T?2hE^FT?CLWT0eYp=a1|8d^`0_T-wyI0-{ z&Zi%K_Wyp^JW8IKe!}^*BZl4c+E**D2j4%iSik;$Q=wu!6Iai{K2Q!XslUD~B+8=I z&s(~2lwALee%`C#zbr=Pc-oO7udICaNIL9*it_TN1CBMSonI%d{C?9K#jmZOH;h7j zfcmL-U<Zi&7x~|7_K!b)`>vmR-^lnl7^ZXQjf~ereYg8A_ecK2oe=Z?V*X#u|Ks_& z7j=Kc%Zt?g33Y)z;D1X?`~1H@{!gX6Kuv{VDl0*so=*7RlD=fGcK!PQeWwoG|KZ2R zM*sZJQJh(}OrHbZkJ6*>Kl<g)LV+LFj(x_+!2cf+cdevN+`qm5e^ti+Q5RryiR1qe zIKc0&*LZrR#UXsT-CN83jsLOW9?TX-)b8Df*8Km_r;0aPP?jZowfNOy`n!3u*lhU} zFT2HVp=ELYKY#vxXX{-i%Io$<|3C8AdmjJqx&Kd}r+xqbj`?3<n5IQKUwNMUe*FJG z&Z*$@{^{`lvy7DYU(44MwCDd+){2I}Q&H!K^gzoR6~*N_M$p36<wDf?<>%*P9DWLI zx8|S@fcAq@9R31LZe8Z}REi=Q{e|4N6x8(@3<lH<jN-TZYI5s(svDs5wQ5cKx`9D* zlgWg7f<gR#7tts}7Pg;PoleIu)A?!r1&YtZ=l^Z(>;IDZx`!)`SK{^duDw#BMLj>B z+k#GGJ<C1S(OG|)S%wCHmn+^X^#4;hq^q;CRs|~@b_>VZ7*Tt6A8xI#wm2L+{~_|f zGoA;!{p#g^SMvYF|4<bCb;kdPmaM0|zwDtU>sXrC*FXPHBw<?8TxY5?iSmA==p)$T zOgfQ7_R$F_*{dhe-=E?jzT^FTmoMJ`=Sm#^C-R@Z|AB5#c=Kz~EZX|d68iT~V}G8^ z{9sOsVSkp)NCr!V^@1)&|9{w-=l2)Z3))}${|~D!vS5DV#PJS1kKG(6YIpBLteM%` z*f;_F|Hm7zJ4F5m%J<IpzcTn=X;^Qd`FOfg|99U12XDRO|J(Zii?s0j;rQ<{tkb^m z!i%My@jo5hkCRFYPNUx+^8Q?V{`39(6Bn}1)J)9d1Ma8NbDrlvE=c5m4xitkxB%Hb z1D$^Z{quYIAH&YlXQm7MChkA*1H;(Cd8$JZ)`zi&mDVbqE>(LtF0_o+pNnIHCGG5k z{=)y$RrVD$j)~@15%d3I{omz^M|J+bR}TM)>w)ke_;>l^{~q}LB`EmYgLpsoYxc+g zxHKW|zn*>PZKILq0mzs?e|~%Z57yKeXX(Shf8twP{<}r~D@6W_e!n;FA@HC2Y*QVr zz<FPtM*e<%3AX|KKWuv#`;CmxGrsfoH1IztPO0?Wzd!O{9RDx!zjF?E=BM}7_N$lw zz_4HcpZG741ch|Q|A&@TXc6|8l_2krxPOK)|DVABh3FHA=Q{lUFy3Dg_`luZOy#7? z$dc+XmecEG=<`eM{>FZG1j`&Z=D?0H4K(DYqqICbdqnLC+Ag`xqeGu|@XO(E9Kiel zv;X!xuDVvm-T&YFhSx$l?$~xmIcegR*Z)`a|3y21^8Sd&S!w<&;=k9=Pi6Cz>7Ty! zS`52M$+?4rQ`vLMx+w)AcJRLLe;?3Eryq_>UHRaHsqEo6Nsv@>CGcMy|1a`i<bPMk z|94#fNAdrF{eKt$zWzUPpR2D|g8SN_V21I>|2p*l0bffVLcKr2{_jxzAH^em`M*{h z#vaSn!|x9xJAe`RUjqK4jTz?Ij$s|U%edjS)7TkzE#bCf+@9jGpeL8_UvE&HuGDRJ zgt2M&q;d~C%2cOM2c0f)3}JI~ujQI{#IRr88qfU~wh#N+pSWAg%2h*$?$b5x4rAYY z?-+M*N4e_GJMZK~{`YQO+CY!XU#ibf^J~;#f^1)Y+rjlwKUAcXzrQL!V|KLmTwH{G z?%eq4Zz=C)R(>19p1HMv2?~l~ADBY()%qUaFa3Wc0Q&Fqzpwnt-Z`(>|F3uMqyOLE zRqX#GuHO;=m7&t0J%&B}IR6g#uf?CMYdrk^dL#%?_Xob`>ILo#x)#qb1`dqZ?{CZh zA;<%eKR-mn`~8fd55upQV8c`m-|ye;L>(X&ncr@#O<Ib72+OT%drwuX!ZR7l`}<6L zEu%O9Q>zlRP=)kFMY*bwU++%~RppeA=gJj@{I~;OU4wZ2?Afy!QaXR2+P>~zjQ^8; zc&Yf>-`@v)wpk3L(R=wlApzxbiBC+)Q<nX|ZQJ6fpNm`g<<6Fmr<{wM{eL$*woN%4 zcfYkHJBA&h#C+WCxG$WiKk{G9|6dOOpZ7OWf5F#baaV}e`{#6D*#TEC|AGGt{Qqg- zy30oXe-Iph|NO7i;<&sL`28|C{^_s-sK39!eL>0Zr+I(8et%p3J5%>*sQ+)3rb<K4 z{Qy*r_iwIJ+tpM@V29H2UM=Fc7paT*^#SO3I&abVqVaUT&h_r!Fd4MpZ*OGk?mb-; zJL^V9cHJx9<ILkJ9%CMF=FjJ=g|HjYt~H~*9U#X4>Hhb3KKJ$Yk^iqpLQUXy4ExHI zL@UEAX5Ui&r!1p1hW)E@yG#PkpGh!bLzwoj%KVk}@$dWO3%tk2@bS0)$bT{ae>wPH znML)N$*4D`zqkIbwMoee#ET<E{`X$||HAw~U+;%RejWUN;+8-DBhPO@y%CM`)4mPB zSD@Yx`8%7y{dM3z{C?w9%J;)D?u`GrQ>rM>AGuF+Sb6N&ymM6=gOUlBPP&La!1KMY z_`gq6G#=NZ^m5#vJ#f#@;g20N#}2*m$)`R#RWyF+ji*kXdcu30$bWjS^ff21*7KA9 zkLi2-*8TZ%y{pUpk^lWE|9_KnjJa|BJs&4HBh0!3k;89-=I>F0J2Xzg-tYPpc0c|v z@}Hhho!b+g`TwIZ-%Dwx%QOn}#so`UCYt}HbRLwJB`(um6ZxroO?$J*zwE32KRwO= z*TL_XVw}G}{@0-%fS$Ef?}y!1O=$J|g*xR6`Hyse2Y$al|Nl*8jd2;`03``Q!NDQ> zG=Eh-b1HXMmF8JYZxAdI?0~z{Ja)pf`$_N5l}@ixgVT>CIQMDt-`@SsPe-KFSFTD3 zPH;-oR)+lYH#a%4UrD6$rlUn_-Mq9kX<CsQ!x`)6I=^98?%BNVjs4i)n!v0)8K1_j zI-aufWRbca^@vRl^LXW?Nt2fEx2qdNxsn9uHyYhd37hlw@6*&zO4BAdi_}L;|8Dr9 zcw_9t>Cwucj#QDopf4JK&(D83^uoA-$bXUl-M4?J?vLU_UFCXL_v?@R?@#^zZ*sa! z1!-ybBsiC>xar0lZ~W;<gc)w<q;NCh<-s9Ao%5ov;?NzRSLA=k_oer~r;GESyz$J; z!<+tg^2HI2TsU}g?{zwEQ6jCE;MJe`?}9zD$FPVW=U<0${!}m25&z$Kqq(_$NgeY2 z%DC5{aQo*8e!rLh#P=FwH2nUvKmF9)-tX_2|NAs~N~5s`@&7#d{gt|Xn%T<Sk8YBE zi@ZRlPR9xSj|2ZB0ovKMRT|^{(EU~FIxG|a%QmdwK0B~aBQG*@Znt#$<jR%YO-{R7 z_Ssx4S7{zho6G%Tr(Mm>Nt^rA0b~UBuE6#p^_Zd++#CDtnwj^`<^I-$^WS`s8?n<o ze$phRW93P^S{d5N{dB~x4m&g-S~NcQlcRXQqVbt1bo{UCqVY-x$9;zN)OX$M_Y3g? zk^dt9yU+g^Hku_aw&yzf{xHm;820oOXlVQGo;bh1_5G3m{VD(d#*uLIV~zB89d6#l zKVQPld**Y<kGo8jxX|9?O7Cz><bUtnNB`gJ#rgmE3Wj;^xtni3(zLUQ{QI3vF&fIN zTnYTg<+>Wce@xehxPKjA@8`?^53?%_)CZ_|iAyU9dE2-Z%T(tta9_}M$XCwNr)LoV z={|Iw|5ch@h%1oapLxs*J79YP+Wudx9zad!@2mJftb_4eaXsyME^xi8D)L|Ce^2<| zF~_H|Y00YxZT?3L+mtX)8WJ3XdH{xL(cLwj`u^!I=o8fO@%@qiV*bA?ap;aYC-T4J z`{JMHf$o2C{_o978gkQ3w=iD*FJ_7Vd$VqV{~yc*>i^eubN-+1#Q%|&$RqwsB|*XM z`OnYuceZ#*odNUtNht1*a=wfT<N>yB-TJb?eX3KkT8+8U@K@-09~l47fb-5IhOxoV zr@p@zYHjOs$6A|2@qah!{>oK!oS;Rm%SGxke!W)<TbDc5LKWrx-I#Bntrm@^x_le_ z`@q!Py1zg5)c@%)-;q%FFQ{1mC)y3|$L)Fj`NK*Kt1*UY{}mUGK7T6Zxj3@N7i0YY zDs<9EKY%zUMCvy#&>#8VpYs2`$BjTa)4lyg{&(-bb^Uw3IRDAdKZZixznzR4Vy~7+ z5}daT$(yIw)98xeZp8mD75@?UHy}SA*<fgB_50iU|7lC$_aor%c*vzKK%rm69P;~h z9Qe=ge<A;27U%-!zthwIH;MR<KENIH|0h4+x8J{axtyOLfcpPaeMLFO|IvB8TBf4o zdpQ2zMx3U4$o8BTc-`A7@}Is(gkT2r{C{5FxAo(vv2VpCTCI;zzaNUrrP91Wc7~Bo z{}-0iKTBo*rIZGR*!>;fANk*(^8a1uLSJ*IYv=DT{HMO<_><igYDlK>(3$?m6UemK zK0BN9x82|R{#yS}oCp5Fe^0)DE|kXYxJ=#4|6eNpliyE%y&wMXJn)c<{QmVNumgzi z=eZC6pLpBq_s{ZjKT!X_-}9fhCq(`aYz|*8JH#KqzXA6#E&y@<bLj4?jErFo%EZJP zBlQ8O=D5exlh4KN-u-Q8Dvbl=_iJlETyFk<QxHz|bdjz7|7+|Pmx<>86Se#IK`ISr z0-yi4vRB9dy{97XuS{{yPFBp~>fgL?IV~SdAHvdns0?>c)hP6Fai=`EDMiK2cYkUk zJAlid?vMAr>-#JJU8WaD=t~*qYtt9E#Mv0c``;NckJryfApGC&;{S_K=NB7mb2VhG z(<1E8`0_t(K^^b+*FRLJefw?WGV1^H)#ooZcf@}^3iJjD|C>esU$g@*Wn97^|0&;J zW34f61Mef@Xf4KkKbRk2CB^-NLV~LIvHyzGW$0uv>>sBD1qHS119Z9h-#Y(q$?2-! z7i)rpgHBh)ZNxmsw$rcmk$TP_u5jS|Hpbd#@6&qy!>aku5k8FG4+Ft2)%$(7dEDxf zXP$Yh>+}Eh4*%DX-+#xie&tjp%N<Ye9Q?j^jiR!CV$RS<Ft571AmZOEFkidNW}R*> zS3DdZZu|F&HF90k;4L<7j$-lszkkS=^Zu6m8~+uKa5MF<ahb@|C(irRz{daewg2Cc zwa7qy`x>$u4CKcP{BJ=0-^r8i`3`U&VSxzb`+2{g_)l?kFaOb(6!VcrV|+5rH`rbN z>-UGTvXtGssgLiHhu>TKm;KY&`ueZ-Z{KkuN%!L)OqdTKH6y*a_#l?i&VTK2njc{9 zvG<yG)Fzc2{l})|IG$Vn#R~NKPs%uW&7$S|sZZb^mK;FeUyfVyNIClb(RKGA{(s*8 zr}K#X7x~{`_>bW-49)MuH4bIclm8VLd3@o-4YbVdjvzZAR3?k?XFmx1k1&Uu5!-N$ zv4oppM#x&1TlzZwT?Buh{QtW)n#`;6^Yh2xI?mz(=>2WDpx)N4o5Ia9hr^+8#F~wG z_4fIF&v$BbxH(QHlMx><4tkdF_TFCpeQ~T=+Su6mYO&c8i}$_M{7+Wo$oGcZY(vyJ ziq{>-3+>tz#Rm=l`RU|}_f(T3BPZ{-l&c=0`KvT*R8oA;)7TXG=pTP<d1_;h0&#Lg z0fhcT8-K~@M-5I^RR4@AKwl#H#-A~c?~KCsfx`d2J*xk6yE~iz5B%4LN+cn^{6C$| z`~8vI5%;Hj{~kl77UNxmFW^7l_ZNLhw0iXWBmPrfyu18ge!!WUlEb0jzcZ<DH^)8g z2-BQ7hKSLQFxGSp$I(1L#l<D{#QU|!=Ie<6Q?VWM1|$`ga@>g8+N2d9(s8v(i?BcS z`=9Z9j=LW706a2-<3#?8yuOsY?(IB*KYq7g|5hB@P%`@{-jC-iANPm0-_Mf=xX~HL zI*>S6zP~Q3CZEgu=K)uiWNF&z+iKy@7}E5YivM<t$xL<9G_I~M^Z$2k3^(KBDv8I$ z|GxfwAA$eCu<W|(hOAt?`uhDYcmBWp9*W~fhJLktl&U(y*8I(jYnF~Vscg(UvcdMR zvY>mn*Kc@`TV~|mH^4758EuNy@2R+YW#sq8IV!DTUS<yR2Kd`Ko2+;_yrulwYf}^( ze>SCVx;YuzTw<jDgz{h}I7s9_RT?r(CG3M%HIQFgX{hAu@%(dty{BH!u-ITS*?!rO zRbfDTaK_93IxY3-Kk!fkU+3TSP~CaIKO}_u01N&<`Te>2Y2ZKa_xF(h&eX$k)HkU% z=`7U7)h3~CxdSb4UGGl|TaPbPdCt>bJ?G6)byF3d<5GHAot}9E(%Z)s#QHz_efKs` zue9^|<9AQjKe=g|R@vAnTX}N2W%}{scdtC@GOeF8=lcZoy~&)DX6S2P3I6|u{O`;0 z|04hG7W#X-(HU!AikJ8M?Vb4l%a$BfqfA!#)Fbad$c?et->dw2Nqlsu^yT++o;>y6 z4?q0X|7^%IF8b=LxH;1_yYBe2@@3=!wq8H+^|dejJ1qhY-|VYK{X(B-$RpmDXHp;H zB|lsIjz{w%`M$(*;EECXf1dl@;y?NQN=)nn?(@eB{I4@ylZJ8tljEU?ITASjGv@5! z$M<{t|E|-PmX;bT&>xWIAtnCvet!@7KLmBlR5v*?eq=oTK3iT!9W&yPBPWvT9ud}G ztxEi-`Ye(EVw{40|GxG~PdV@J_=g<PEco9@{(dh0;D$qKzO54Y#}q&C@}X<>=Yj6u z5&!#g{J+Tm-sOLa0)1|c43ndHXGDzqjB)A4QS$1t!m>|_ubCs;x^-OPU!E$aKC<O6 zB;PB2TpB8Wc8&a4uKq`-DkLhF(`8(f{l$oJCH35zZ6@v8E|YmNn=D^;=Ip;)#}Bj! z@qS+w`QKgsFE+rwc1f}q8893%*q8r5BHoXB043`E44B3*)3^W69>Y|v$M2tpI05pG zz0Chd4-)U|Bab$Xywef!kBsM?BPZ(gSs!$l`z!C`?Fi-0M|q9>n@5Z1&f}MF*tv$+ zS;F~SxBu_Xr}_1d@BD*DJ$49}-g>@FVgKGzk^dt1>F>#x`@*t6eotf3|L@cN@5i7I z5F;Hd^|znD^+NtXP;U3u{vGkZFUS9j{O=w9r?igmTLb>@8WT2Xh$cr~r*Y4pvEs!T z$MFyI#}>S3w7T8(DeGC585%aU@UcOvp+$F3|4KC|YFW$h=Z9$SlZS-_*<&>CjL<JK zxwfdj{4Em_6f`FLKE(qkA1@;QpKWPD9zduEXsaUsyT^Y?_Ij%4kCbFLWSzzYH68I^ z7AKKVpC8l_>)P{wnig&SbknrG%YQjLnfL#9f%nQdiIO<1%ShMlY8pAQOeQOP{^-bw zoGz2=jJNr;U&;E%`S?Lb{Uq2Aw>x&vymr^g$#==#-Fygk#l)DuKKtzLTSvwh!`~+R zB5QH(V&S~cADw&`rVWUr{r}(hzqye#>dBx#@pj0chu?tZJJa)4BtD1ZxaDz=&&?qF zARYhr#4Wn3BL7AHcZdHm?1bbK|6mw?o<H>c5jekrJsek>nGrUPy*Zu<k@&R>`Xm3v z{Qs5E|1U=#8{5pj2cExd|IZ!osYXSeIeJ4?jQXA5=DYUqFW0_v3+Ik&bfykceZTVb z=~0VwqKxjw`Ynn<Q7`P?@*LR<8(u#8;d<?;sQm9gfBP%dJ0s-PCwJXiwng>rw{>+q z|IeNk`QOd@KY#guN%lH}GBi|5{HJ;Wf&WXg8~DDy^$&e*D0oL>r279Ko7~M_{!i61 zHHnE{?)NVLYg5n0(fA4QJ6>7BaXfF&21_43I`a0y)LeM$UE}ND{%d4>B;uCj-&6c@ zGRPbXeg5Rg__vy%IR7&TIj*IAWc&~}$B})Jg?`@iJ{&pmo_pqU^!}IzfY!(V=A2i_ zp7?Gh$KC$k$cdO9fTOs<Qk=J|*VWm6BL7AHcaQ&V$MXyP55qXXEg6~m#jpdU>*rwJ zAk?h{OO?WQLHi^B#r%K&=YJ03_ZZ`*UIU)ToBkGkpL|f%M8`D=?_oJ(`{<WHFA9mu z|M%H{zo$x(H@HlTa^!E1s5yK1^)-qwM*QY~q~*l_mv)tuE#3H@D&K6LQm7aewL_O{ z<f_*w>grPO_xS&Ax9I;@X3@C33+nuYe7~<6i0A(5|0)dd`;pco{s*Cs&zJuW)ce(! zlqf0wkGQ{{_}|>@c6<3hO-uQJzT&@c{vVu;mo<W~6Gz5B0+4sc&ntbq$bXUly}|!f z_D%HvPd~7jU7<V`hWP-rZz-Rictd(B;s6tGorpdFZS95r$p5~~|Hq5<|GiTGm!k-Y zYGz|&bL3x)s9N-|O+(aQ-ty<B=t)KF7b8#)mPMSO+cJ4at?KO&UzHx4mm_~?#I^bL zk>#ipc;ov9=LT(x{9I#+lB<4Cm7SGkT!Zyr9p&a<ll{(!_uc2t`QyH@UX1?-7XPQX zKic;yCE1c}dd~Rr|3|*wKU6yW;RZwIyo?N?|3B6HV?02Ox86_i`vvWt`afstn{k<E z80NjC&*IQ_AN8Fc4b$8nk3JoYpw1-Ea(U8Q{C>W=Ch0K0evPWF7OFhw6SQ2_dY%9^ zNAV_q{*+!;p8ts7iuHe@9ng^-;LGh8_K7JpPR9iQ{+0~xc+B}Ye@VipI6(}Xr*tf1 zn8jMu)vow`1@#9AmC*b^{h$ARng18~KTI6|*Tee%FGeIX6*l7f*>jIBAEJ42#H6G1 zb-y>iGeV1a>L|nmf>EeHN*)qr#P4ww_B&@~7~=fl)8GEr5X~32jN9RG+_Q0w!kBA7 z|KAs{JJ>??{qKwjlJa%_m}kYmiu@0h|LY90P)Se+{6{@tbMud=_mfK27i~ozfa?0a z{GX;xOw7$~@AvmM|9jg1m*xdRzyH4L|1b9c!TlBL(L}wpc8V{zV^H^(m}q5ao*${S zQ5OR{fYN%zeat{|WE%Smr8QFq-eVYVu>bjk`lbJm1VC@q7y6z@<bUtf|0SdDPY3&w zc%6|)`r>u}eQpT9T(;E;MZdpliAQyM>nJ(K3E(wK{?dr=KHPMM@&S{SCH3XlujbT0 z8wJ0<t19xpYy5A>qPRa_@5hhx_vL@Mxxqls@&m~Gzw@?6BlQ2fkpH=vnHeJgX`Voa zDa1HE%Cofh(|dc~AK`rB_<y?Z1Kr+e&wq~}KMmZ^$o%M*Y3vB)y$8bK_p6j>oYAh& zufOB_Bmes{|1a{txA@<-9>1&HPF4((Q-j=O<PXpfnYRyucn$1)BLBO_e~SB~-PHwt zf7|%~_$A2uV?4kC^!YU!b924?U!+CYKXD!E{Hczvule7!+nI{D2|c(2eSJCZAz}W% zFqZoLawTKU)X$eo7v}r(T^81h{{N-*O9wiaFV*Mw$M2r5?~nYy6#0KK{@=a+KTSK( z-|zn2vh9u>1$S-wf#v%PRb2X~PdDu-RB)#<jN2X9Zk*ZhG;N==B$=zm^76$qv0cSQ z+<g5(+OF5>`0digi*K#Qaq0T-5w+Mpd+y&4?pT2Sf%OXheK_3({eaur2O|G3&VPQK zAMf|0zaQ)?uixL6|4olH7)l&UWfSuMn5Hn(%l~Owm;<x?bHBs*zq`Bre{ZWD!lo3~ zbIbQfv&Ywd^Y7XFZ3eDVek1k$Z+vOaoX?IplMsf;xUF`*mSfrEzZ{sR%{-HlA#h*N zuJXT3Rj8uAg@sVu9;VAG`u_uZ9^OmEd*AozDd+ti-yivZHRk^njxm;5=x^qmkLc~? zzV83qv?E6$OIgVg&!4{jd2SZ^_TMo41N3drQB18ZMc?%t#h(r*a{B!_io5papf7u} zVt1L6dl+`W^*8*PyJusvBDzV3KJ7UQ^J2^ZP+hKKXDq<^auma!{6D^LyW;lip~(vE zd_C#{+Hzl57Wwaw|LghwK63c|n71dSJ@?!4|HnsYUf)n@G4lVwYes_s6@L{KU+uws zz?tc~i}?NR`QMZIf0}kUQ)!&u?ETK9TcO~7QUP>n@rk4~=m_-x*F(2EP9!Bl51`Ng zt5AXaf(F8W>RZ@l?hAH;$bXUluor##+}F$g_}$a>{gMA&8UO$2$uVY5cK`C9PDGfw zLnT~-6U*APdphH3pz(j|he-L;xpe+j8M@y&v5W?ZH=#c=r%y}k?EOBX`G)X1T@`Dd zg;!_$3!f{&NuP_}2WnMD(!2;ZTSaAO#|O@TI<GA++`LACX(WG}tT;FpKkp!cv;9>? z{&&oONp_`yi;N7Fqt5?pj2pZN{|z>m!SLflW3r`>Kla$mRQIRTaSh-;eh0aW5cj9Q z!@&H1cem&Nai(sY!c=5teli8|e-P%ADTw<+Pb7WvJ4^!r&U-X|3i1F=RaI3QD7Fh) zn^dKy{mPT<yxP^6Ujo~U&TCogvY?1J@!K!1g{q?QbY4M=#v`sp$8}M+O~C%=LPB`O zasG4-><TVJ?El->9KBM{-&4-}JH9{iACDb-XPjHUKinL2@6yFT9T{WR-?Q|d1m|?i zJ$Q9?T%i1a<3P9>(-TJibR@#O>W1ONoAG{J{iNNPC&^?$VzjgO3zYxD=lZPmb7jKr z*t#_`@_tK$>74uQ+0NcCQ2x72Nkd8zmw4{zz1KbOkV?<SC0(}+6G}Q&%yqWv>MD`{ zzJ0G<Ch{AF`F(Z!F<%B=G@n#o%>Uc<`Ty!@UY|rc_z$<AA^ua}0Dt_aem^?h!QsPK zhR=Bi12C!oe<fbT|5=&o>5H_;|Kk-9{}BM_Zv6j5>c>;){(Um#6O3yj{-4+1(f$79 zci=x>@l#BiAMmQuRB31#szy0pOO@KLZgw@h>`+?o)gm<=U!*SL*9V~EaoXmh@!&sR z-PLUqzEWV`^`LDNwoRb(_*3G)*#H01`_)&9J>ZYuG3;BY_qXQK_&z3KC;I=<JX$u7 zT3y^?+8!k5s6QaWW$C`>>5u&HO8$Q?{O}JP9QV_aaI+TUl`^r6_FbLwI8gq-3jaM4 zH}33-aPwG10v<gXZeEYRU=$a?u(eL#FHrsqpX)Pxu1^kd9N8bSCVC%N7R}Sv+x(BP zL=B;NSB{=c8Y0C@=%aEjZjC%$Z?sY$9{i2??ZbDu>;uvNZ|ncd&6S2oWvE1MmS+?1 zI^+NFhr_d}-w);g`F=l`2arL7zY6f5#{YGT|1B-u&i}XPKlL%GF@Ey9XO-v+9egqV z)92;+4?{rU{_I-2n&Td?d%C&^nw~Cj|JnVt%q<m`FQVXob2GM&ce{7(QvR)IJf1R{ zdHYE@rDwgnr8B#rJ^$Z~W5P#|@U;U({)=^sWFPvoM|`>MUCvWJfmaNBFyZM0hPh^X z42y8p6CXqGsOL=6FdlI6l*GiluuS%)_c+h^f&R$<OOpSuzC`o?1{nYEjPIRo57__D zN*ba!zH-~#qYP6W&M=cF{!5AYOX9Q2k@58$_agN>2}Zu-uS(SIp<utiXpRb8U`pyc zyC1#3y?^=ti1%mFd^yDbAdK_vjQ`<?3vi7}<?xkbvXS;*zx|<xEad%lG#{YRI7=^Z zU(i7O|DNRk+w=c$+%qFiB_2%p<agk0dZ7Hj$yuc-Xjr`Xxuyr^l)sytoZ#H2+3<x# zev?z0Hf8D3q?IR&)c>QZe0=4}t=Y~cZ#?zv{wmEYt7;MxoPW$d{_is%tvp$!G3{Hk zC)4>Yo3-!VJqgZX51qhyGMyip$Et6<QB_+s9_0}kf%E^bak<84Zv7bXfDFVB1v^0G zzsUcd@;|YLVK%Vy<3IRt{~s2!&nhEB;)YMd`2R$_t|b0<CI8>u=lXg7U*tdE|7UO3 zEzBXjymsextLgVbc>x&!(cSxe^&Zy?{D)t;*R+@KzrQzYZx;RC`&YmHfB8DUSa4q^ z4GM`h%gyAkcE<nWhroXheSRMapM&Xra~rbi48bA6m<N#R{k(Di?(iS(e=o-WCnO}| zzI^h#H<dJgAyEF`HOH=|0fQfzi_|lqf8SW7UJre0b&+~z>-yc$SF(%LPeHYh6seb# zaNK7Huzc8~%Mb^7aCOmm7+AF5_<f%J#{UX>$_)NP1LwagE)1VX;J%<2=fBP7{reO- ziQDJbPrh33lds?IT^9U*k3YYdy-gXmb}IS;y{Q~=vy8bC`QMfNe|PyJ^1to)e-{Sh zA3B8izx!*%e}k*t?srDi)YRnFfl8DQ=<a>KdXMWl|B2H*<$o;v{`&ez8F8QN<<9x< zGGIKv^#<hoF~42``u%3<Gs*97FwpVDe~J$Z+^70Kln(?}|96@4{~g8ug*<>Ad4Jk| zkvPA<x`2L?|6f4;h6rn@9vpbw6F;}-d-w9*!+G!W?8#3&G3!_=d)s7&;oQEun4LX2 zF_GKl@&7YW7tr?j{uux7O8&pQd=dHY%l}&#z5XkPvdLwk;k?e%m%<+<{)cy3Z_wTE z;mSSk^76j{^8xB~y5ZozAygW|#F}Hx#D&iIf5XG@`|BMtiu=Ru*EeKI=GeDx-73ug z*Af3=7Ptb5{{yq#&i^}8_h~ZmIo{VelNk8b_Vaq8b=jjP)csdryBtdU#k6W|Qj8ku zDfIvKs2p|wwBHaY?dR39)@6+Q=hwGYiMo(K?kgJahwbeYb$a~zE~*LR1VV5gQaa9` zQeQ!Y8#v#-f(uZ8L*IkQ!GUf6_;S0qmir_Bug3iUsm<Z$S-H8nea*c<^Z$K)Jy+lP zFE9UDb7fYEGE~Zf`xHm$kpGX)n<vTs67we<%aqJH4lT(azS5<YMyzTm=JWkJj^_V4 zgX0DMFEXst28D!#wDJD|p04sgMjOrcB>(SBy|n%Rss9hf1p*lVuRuJ+b*cOR694Ia zMgEKYCp*8lxG$WiKk{G9|94*J?~kkP`TtaL^tf^3o-8(-KgG*#Y3u*{JCC^Dcif+_ z?1%rTC+X_=KjL<v@qg6!Aq`y;3H}=-!3^V<|JiF<YfVjJnP#_Yi$oGqrnys-U%@bs zXMd?$zw78x;{LAE(wBVa|0Dhb|84kh19t-9e^2uNBL9Vc{|m_9Zzekf;egOG@IU`x z#DL3pi}p@idq?1PZ)<<#KYst6j{om2p9Wt1|Ec1w`T6-vi&-;`n-=*maJ;*EdH7F$ zzf39(Va+Tb@AuFDFBL|-$~03olJM||GEJF={Dwl!yNXwg##g|7!;3FGR?rpx1OEZ} z|0w_SSO3=%#)h$U9qrYbiuZ9X-%tJjb5dqn`2PQ)*JSg0#|$(20CC*JX)OBvaNMUS z3rOpKd2mM<i|GHx{VTAnOaHocK7iDRtIM%{?t|qj#QpK|YMF}ct}+$k|9D+~|DWf) zt4zgnURtK2zSlvxt_6w`*mt;2#a28Bh3gf0*p`O_?SAw&_l5KINB)cX|K9WepDKQ; zn4Whc|GUe3-{USV|54wcm4Eb(FfaEn&i{=TncC#C#qQ4@q(0Z!bhb>BqG4<{lN|Yg z=l?uO=Isa6z>hz;F#g}p{|_MlPw{_$`G1PDx97fK4|w>0TWuJdl9D33t#%q)R@Sg_ z`Tj7?#wAN0ZQ8L|yL#ik!3TGoNGiU5_<`*^)*DK9@6K)7QJb{!#EBD4J7U-`{-5%( z;kApkFWK_`_qJN}1^(=_ZBEPsjDCjcm!JP0f4-ITp}UFm^>!<1iLJC$;Cp8}(0C_) z-nRCR!0WE-Y}5l@JGis``y>Ca#{7R*`~P+~t|1)f8~+#i@5}F=F1O|Xq}Kj_!+Jgb zUzt@}dh8bR`=to@cY*)eF8H^TP~TUglu0KRj@IPno@qfHfGWR)=KndalEMxQg#Rx1 zpMk{x{pJ6=!v9MX|98RP?koR)0qQK6Q@cI>yoA@15QaF9MitQAeB5C--k;(fq;2C7 z!gY15A`jd0aG>3f&Nv>!mLxD%X4y2<`v*%;%t>YcsvJC|BoFffPmZ~DaEQO-`y>Cw z{J-e`3-(O^(@VpD)?AmxMN*xg;P3mYQ2!@4+rX1QJtRkW%KAv^=Nq!B#(3QlNjCb^ zaMTBoMvr;9-**0=2K4=_NldI+guZ`YH#h%uVf`PSHxT|4=R4~E+co~@D5wv0ZBmZH zia>ukI7;>MUQJOj$YYhOpaz41S8QKH%g{nqirk~gtvX81V4y^y3QF4*qvWN~94IYc zOljOe%K7~U$t!T46wF&q$F*uB&0~$_kYxFwC}wgnQ>aLm55;y;+E36jRkFMS^C}6; zzPh?~f3ICqrl9*?i1<I(*!b-b4dyH0>W2<A-ie>Lt-VvBoyyYq7GGVbJ&;WEbXREe zCLcf_kV<G|<|(F9`|M<^BznYpEC)&QPmb1JH-m|-v{z`+2N)ws1+`nE5pNqhl>A2O z?{?|cWh%LY-q&QN{zZNL{?gUqW~DMVc35?+*=Rg=tgqkiqfL@*S=_jBr#55$pWIWY z`uhFo{Go94o+?Hp7%$QP@2S7<dwk#WUy{9-@8?JS_tg3Ma$i_J&;M+f$z+<eGBz7- zzhrqKn@!{TiTj<*|I;S^Pkn!Zxo(gDZ;k)=i2u}w+IRe$cR7WxgBO-l6yQEyP&xV0 zDe`2w3;q9-p|n0(K1xn@$0#T*cUKq42g!B1b1%{PxO&fdL;3Ry?{je_-cw%%?{)d^ zKPt*rQE@vskNkgQh@4%mn7<<&b>3x|QUdkfni#%*L*!vw9uBno@wH(Z>vnUDuM*#D z5{;+Q@RysBu5*8#<=JnVA#d_C`dkxjmjs1J3+sLL-vx`<YBB&;$C~N<?Uij^-@7_) zwdZ`7L+`kwp7~j|*?jC+UUOf+|0|p1+2h7JVn5z&w&X4|RP^=x3Fm(b^?u{V$-+hc z`|`V|%YDm#6SxnKUr^^K@ZMKF{I@*BqMu)Vq`q)THudvck}c1szI4QYxRHM6|M9K= zYaRce*~b45JpbQo{C83uy+k?lJ!m>UP!4!cpQoq%=c>VR^zXm5jMuSCH}ZN7=BLJb z$2H$?BIUw=yLtg>$O70CWMBB_w=d7zE_?YuD#~iLzV)7JRFuK;!O(KV7dS^OQ?8K7 zfqU>?u0h?Cy<MD^`XDe&=0M|}_<7sfJ2SJ#X4~8x=c_Za^HNS36PcOjyva=PIrmKS zff?un#LYCnIXThz{J62^A7|X~YX;lLnyHE8OmiHsGoaz)cI|qy_<nOJUQZS;-AG*~ zdrO}>f4(RA`m-mCJ^B=Mt9$-@+K#%UmR8+G>uHp$u%ADr{dR42yGiMKd!i+~x_bOB zT%V_oE>L|H$L+#{<kd}26}zB;9^YI0i}im!<-Txy-|*jLhTmUbA~PZ0Ux~cC|G2+k z2YC3utS~m)p_EFO!SA1?&&Z(uzm$jX4*&J~T;qlO4>bQz{&r8|{}=lI#QmAFNRE`l z#JFAJf3wT4|4;sVe*XNS#QDPAvij|g6#0jb|Dow=#{&7jyYBk$>&t3YRaFUCe?qnG zg`&pm%NEE#w^-%|%72PO;Lp5-_^#s|V}@u(Md_M$R%}6>f;jI~tx?2%_(gNMqMVOk z2%5?|lVW3S^A@pAmCbc<%p$EbDG&AmDb33v@~|xr2ipAr{b@c<t^-BiR(}1(^kE#w zZ;wOYFzkPBZ1!G$8Pqon9XhmZDMr%Jc>>iZi#^BnRG%vTkbnRFs!tZ5Y`s1~`}g&> z-p4q>eh~RD@VlqlyZmoJ|9`hT{|@r|sh^+8Y-%0fCve_Z<=ISWsWH}k9Q9;Y-K0Vr z8&a_9+YiBgKHuNP`Tsid|BYz;d?EjB@V75z{{KS$3x2*=yW;=5nu*uouZ;5jI-Nb3 zwEoS~QuNW0OYneOpjd!Bfy=c(zJSjQbgmuy|0(hXQ83fq-l7;4<<8#`43*EX4rYk+ zl^DOb1$^K2hTA<vQ_j!pTdr8c_wl1X|F{f2>>9Z(@8FnP*f;qC=W*O3^j*d@pHR>I z%YqIx-ie>Lt-VvBU8lwG&D5$~moz-w@%RdK41Mge$Li4E<Lue9>AuI=Efrew|Lv9{ zi`!=7NOwU`SJ^EN<c~kzTx8KNTehrqyRE%$q~khR&z~=V^}=~NR=S?D#~)w+&59z+ zqb)6G?Us)B>+SV-ZM0kJBO@b+Rll;CLjaGoK0G}9qs?|p2J(8X?}z)wU(Y2_d_S?V z+_35@OQl32iQ8CZ(Rr@}_uF${#Qc9xIq&cI-sL~}{q+d=3)~mfzyJTGswVf);d1s% z1##vCdt4sdY;wub_lF4%3F>VAA8#FC+wp%5m<Ol^zwZXr0dTquWD^7H|KCgb|5B>| z1HUnD-&a!<DF4H__!I@o|Ckd{>=%9_DFwWDGbfUg<@I=Eo=`22H}YCY+bgIJfByXW zd>uZWmmh!UQL+>MKYQOF7gd$_f0<Eeh7h-MrLC<<emEeyQ_UJ=i6e<#%WQNQ8i|=0 z6`ZN0X!jvVXA)3UeC*kU)otCH40O0iw9k{ePg_EztlBU3`-Jkeu2A`lu>!YHcZ#{+ z_vhTX4ua&5+IIJm`RDyQ_nv$1xo7S@@6WIEIaFp?(;p!xu=9``fa9;w6ZUWndm4ZX zHNCjc75dGrzd_s-C@<IU-)}lSmM_y{dqqV$kAAd@Ts+Fuzj%I=madQIn-Sk<9KiQ7 z@5?yEhZrpUF4u9Ocs<JNF_uT3P4MmcFdcdRtMDWK2mL=?j_WB7rT=xo_<ygR+QmNe z3~9X@#g7s1l3Xt5jrqQj?vK3XNhh{MRIFTvob=%13=r@4)eqwTQKPC_e^7s7OGih? z$3Lh~s-SqwqzbP+D?2+|9<T2cA)g7|TX;tZeo5#b{jdGUV~GC;<L_^K{JY`*1jk-G z=KWEm_fwpo%=<D9&i`pC`#v~upqa1XS7X8ca(<J36uxtfZKpN_^5CWOf2W!BV&ea~ znX|3L|9H*5NB#fk44U7u4sePH2gcDUN2Z`VzBuIl5k*sYI_3YVN(7HKPl3A3#qptf z(&yv&I6d_{P9LYI>*MsHtQ-oY@<3$mdXJ!eIuE3Ec>cJQ=48?7)GcCsT#x<Jz{}9* z7r`!1a@Hb#22b~y0i^4K7>4_}Gx7{!dZB2YqTC#&w`^4_%2CGUTsic3^5+nee?;c< zi^a>8_lBNZl6!*ufA;=81L8#j`}@fJUon9H&%Qp$|7XvO-!=aePhq~(b#EbjT>)M% zHW~%)#Fn@Fpbup_UPo9!-`|NXNfq>+pV-p9MR=*9VR7a6%m1Gi6aTk@|JPPRy$bn3 zeO-YyzYy|}l<)N)wLa~<{9hr*|NloN(*I5vvj3m`eqZ|k_TBKmy<l$+-1bU4*1Zrf z$h<G(VEq4CL7YC@T$Hehk4sT1Rh3uFJEZm64^GrzeSZk}=G*hX$DG>a!up`;24}DT z%jrPkyTNfR<F#+0|1+LX=a0_7{GiO`k4!PP3a)fu4epx`eP=c766t(8<6Rl!`8diq z<iBwKKQaY0?4QmbNp?Dox>bfF(7PMdcEWJt_py3%2rS|kvHJvZ29N8q7x7_wm&=o} z(r|^|yL7u^CGmWXnj^kvJZ~;1e=5oW1mC~9W5<q*<@v$t5AooT{x9kOXY=<zJO7iN z9N{pqBfL|b1jiP5yjs1gp@H!^Uh3{GYd7Gtv374+OmTw7K~P8av0Ygs$psnNS>KyI zg809+wH5aN#T%erg?Hn5va;r8Tq^(H!`4|EV-qFJ2$XFX%SS`}|6TF<#kTY9_@9Tp z-_kx&WiQ|zO7QhX@W0KCgg}V%chEY&QEP_t*{8quCj|2PoNxcXpxwX!`v1-x;w|I> z4Cw!zjK}tvryH`e@>2)reOWHJIR4K?9$@giDi`{(vFav{_<k$yp9_AcydQ+5XUjZ4 z8200TAL;$LJb>PRTLk+6=^MoJroV)9s|-hzTfy_I42ul)o>yO8WmswO`bBZ2VG&R9 z{iNTc8>b-$0FRkgn#bzB&QToW`6PE6FeRKzu&e~X*J0i|!}BuV_ebzQ=zoa+F;?_` z4aqOxZX5>J|7qXf(<jIBSEYeIvA^%xxVtFR=<zhR2krl7%MX8V{r|+4)ddkMrIL6b zpBwnTyBgnV9dHZ17u{RbHY@hUt1njDD8E5}?B251teZS}GVy;Se#HNoh51dZ!OLjw z^|}H!pP2#(|2r#R0wS&o16Ws(AS1<LAr1oZ)QuC8eo)_1ty;Y*p<x5;?QlR`D*yla zV7Nmy$E?2oXkx*>QCojH>1epaYBJ^w@&Cp0@IR{lrSZSLzzci-s0cgW&k(#=;Qa!? z1@nLFQ#|GO(G`~6!`Aun4hL)JocsSlFTVKr|Ni`syg=yRZpZ<~jI5@?<$!bZe>w62 zgMUL{2j`7j72FD74dk5_hFn7@_&pa$&ktmJd@i5M^zQTXe>~549(I3V#!5b(@w{LL z-}gjv0_2Jw=ox}x6|=h&E_FKn#P_K0C%$Lb8K(1Y_tL6b@Vw7?CkNzB$eU4%=Oue1 zl{uGnb{K!(b&qFe7##`!akUN;PN-qltseIaYc7!gVed~vyq_x{2=jtq930CKF* z(*M65PySz(GY04XtI~!?L`00`Pfg-Dr7CC-=uN_|;OmJ0^YE)Z<NyDD`2QW!o2y2R zI<aL|Wr$L-tfji8n!fk$EiKj1;~N5UfY?%<=m7nL|F;y=`q%p6dWy4$93Wu}*3FN` zPnH`_;QCZNSWETHf~6fUSAB6j-wQuzeep_zBtKj#)&{OWgJx>41@cF1K>iqkg%U4A z|DQWIE9+AEe^S>>hdDd@i%Cb{E4gjkwrj3Cn&{Yd`0)Ef{Qr;Q^NYRj#qs|t;(e^^ zTUC(YcwwX4-G}!F;s4#Ff9KmB9`NH>Bk}-I`ROOi9HjS$XD;C#l;4N;|2cj7^f~wc zC4KhW^FO;Ux5vEOOm_C&=5#}5Q$QXt!aqQAfzbp+J~1T5@etovQC_ilz8)Q>IAdCe zSPn!HyuTuVxdzhjFP#6GU*Epc9H)0W+wXjv-A}_2l0N^o*>0$VVMH(s2F(4w05%HH z^UKT2=MoBb<0#lO_#$I0x5tit*LvKwIV%G^&(=eM?_~^w9O6oi=kFV%pPgmOA2Z=< zT36BSm~b7%*@o&TT&Jrt8siN2+#z^2KHWzSVDS<t6N=?${QWh_G`}?`H?#b|1F=Ty zr2LDJ50uK<e)2)W);#!MXkPrk%4<W<bz$<(VEjLWzbXy%AF+!6TBm$*&YT&BAOF;o zZLu8Zdy{?$-rW7T=^w)X<11wTZ>jzQKbijrV)zB~KYa&5yTPo=)YMdKeevELii4Bo zh3^ix6w^FK^2tQ}$o>HOpFsYMtSpL$yfpuR@_TBB3X0KxZcsaZ2#ji0JFLJV{=Zlr z{zuh6H~*j5LhnOnrq}MZ6W^_7yswK`4Ld*S`>Ubv6E6?W|8Xgx9qU;5^E0>C*ERFQ zZXx-z3;X!IKKe$nuxzGdSx0_0@je`3b%fO3x%7WJ{@wFGlS^O^@Gw7t2T0F(1`Q;y z5bw)a&f)?tkpD%}`@x$<653mjo6{jz^n@Qb$Lm`saL9YM$^i8oeE&AP@7w0RW~%?T zd8NVQ@npVjhF<SIIN6)Ch+m2ey!JdpD-`)XIkh=32LSglKL^lE;}hh2nfLRk|KR<_ ziK}vE&&^MqaCKIO(KzAi30LDdNBmE5wsQH%lnK|B=~|8D2E-96RKw)w3}Rj&-tT<; ze{Q*e)(Mc_lV?aD;(v_)<t`@-#{bg;{Qoxpn>*(ydpxW7AOEzSLnJ?+eCKG+C~SUw zBKY5KzgYdhI}ra*@p_jM|4%$wRW%Cnel69$4?h%0Zx_WnwS<0qP!70I{lA6$2Jjy+ zg#BPvCB;M37kj@q{y*b?VCx|+jsGu}=Lf4l<p2L?<Nr=`Vd3f1jQ6WzBf<9p-e1l1 z{BPucAI1O0rsSV)YdgX>^Y!)hwaq}{oo0TaKDDXI#XE-G5<Yx*d@wGM_}}mU7v+B; z*N{hiFM15qcu};(UTFj0w@y%eiu|Ro!%y&O&T+H2bg6jUd_2JSwTz$NhMk||I+ljp z;`QDgNh-BtrJ3Y|PWv8niOVHm9V7NlayJl?-J%cg2l&5s5dI%~ixn3>llYUXi?cE^ zWS$@S#DuGjOWxIW7l8jOtMlaJ6RwNAdNN2U)G%Sp%CfRD()URo3F!SjK|B7%^8cpf z&+(J_fBF#r55*5)9vF=O7lG%)L$k0Rh4%Y1p20<d@*f}1yf?3?$|LcAMn=#-Ao&3X z^ZyUV|0EA!Jzgxu!5K|nj{M=U1D@``{*3CyCaIL&Tiz+b_gYAPdrmO1yaJqh!dBw( z-qWXFl<oiTmeeEe->3*6C!=itm*oYz4m=Dw;2*;OBCS^d{kzw0NN|`eB1cH;kH2s1 z-U5DsV};gLkY3V%{C_bN|4;E7++gz4r5~5{|24(r&o_>mchp|o`L{2=rZ^XplUE~u z2Hkfc4zB+%UAoja!}f`0{cEo={)d^XH^~2+c!a;F@{U8=xVXs3zWh)8_OHYLj2}<0 zz&<7$H-3D>m9{+aeUc*Fw$hLUzQ59TJO}G*8;JLzo_i@zIqdLR$6@#9x-CY*9M7MI z9iG+$Y~8$FP&=BF!^2k>ciN#ppWffIC+El%?As&m$-(}4jTRv3)vB!*&i_}WAgz*X z#x0LshxsgP<Wt~%W6v7GDx>JumQJ`X(<FMBJYW=Wx^GP6)$Q$#6G9F5jVUWzxv|_( zrtLr=Q}dKEEoXt;MhF8=Gxq;^=2gOT>-oXso03T$YEEYSFAnklh1ta~7rVe<{NJC? z<#ONke=G4TzexZ8VEo@gypA8!!}n#FPHb6QX;ma(J$hZiQaAbcg#uci+r8z=QU%v! zCHWu=->u9~6c;cG6DQSIgh#}-RF@m9F8JB2y*XG8cn+*J>(HLe|3PH-|J3^8as%A| zwe`h`EMD)zF`VkR4ql&~H?~x($^H*Jh7_+z>k)BX=AX7C(mZbopz(ZoV#`db5BcX} zk=+N^N%(_$LKn(gC{7|Isq}pO|5(M)`oHf|4v_f&x^}f=l&)siE4%87U)fw=e}eL4 zWo2ay<o(qFd0=q<2jBS}j^VfD=Qly$haW%@;%VkLf&aMU<OgVKvbOP#omzy^_SpwG zfvp?-C+B~AIpXZ_9C_i;??ub@@Nu?0!&dn1ue4oBakgBz?bDoN9#7M!IkdjdntdGD zlx;NTf#*-x)(OG*e-`(Lbpgnq=Ss~de%uiog1q_2hk}Fw1M?0yCuj3l!0x{(S)m%f z4t@YL<fuAAjQ6XxKwMYU-wzOPSbXbj{i=i$^FCL+9sW|UwqnB7*&fmO6YxJF*#WM( zLNp$kF@8*|uIvS}|I@ll;(zk<ADPme+}qom9pv?I=KW3B2N-D<yhmrmXau2Gd2~h^ z(8#4D9$@lhT|z+rN2c>3KBV{Mavg{M{C{-~^81wC``P;?WBJV4#?1fR5XLWg?3X_Z zw^2M!#@xa9{}+`wkHEYyQ2+R`jN>2fn{KFC^T=oWUROJ^GlPC;ng7X-PkN2a-&EG= zwpMi`o)6nvX18EpJNzvs(&K^PONKhCXN5d0U)LXr|8-TZtt3ZeWfm3%d0mPVJb!uZ zhEx~H1r-I7-0<*4VRyAuzdyDNIPNHjRg*l!)-Akx;+47rq(dycRR8~_>z!xYyIZOQ z`G3yC|FoaO5dVMY{D0Sf?^|ft^Tl6Jw^YAVtW=gfIjeG2!CaHccoF>XfWGH_3Hg2@ z4&V6d8i!+RMEJ7xRk8Z)Ouv7I?U1(4*Z{sCp0ZA(=)?OweP<~Dv+_TSf6KnomJ42P zj~(a8<Duqr<G}YJP2CJJhkHr?=eVXqs@DO&C%=DXL&G<$|BL4lj$@~MdK+RQBc}6A zuHdGF|6xB_I*|XvsJ<7g4w9pg*qGS`6iQWX4)k)|ccmFv*o$?1uooDeqQ?xmLJ`|u zi*<Z<5b=@w^T%X8&I_{<|A*;xwrux5GULAMurJXX%I`BAfgQ5HKhF@O>3|}WXIP*W zF(Cmp5~gXy*6!}X<N@M;>SvyzCYj=xf_{VP6wlZ|{7-g)%a!+ro?DXp&c**L7cIK? zXZ6z!kN@p&PZC!CimSQ}asjsSG!4f8|FdB%k94-EH{CFH^ynX6e{^roa7;j79ZYm& z&AES$%umGYq~{0tAADEG^q5C0p}$vEwYF5hROxapE7?*!({T!caa)QV1r)E;fVj3B z@uTZV9+t1`kDr#H9sEvgdAa%{{Lat!7b+KgH}#l(BN%TW#T_IFZ2jTC0{=fAu>Z^a zpO5_jzr;`eAFex^;CKu_`TxFj^kTXc?Bqwnyt>qD9~KR^O}g~<&X#lj+1k18sM>*W z{&%lyhusBst#^=*`V*(qG&t`MrvJ|*9&aivTkWXQVc*_06^^F-%nZ9@ro&TPhxk63 z=Lf@oIRCSFcf|hzz3}6g8;~F8S4oa?gRv??LHm@@I=XS#w+sMe`<Gb9f$v@U*@&CV z<)LmE+4?_B^5<Xww?ON1S*_E-|Edrrc>jX<|MM{9?J0u7Uu)i!tcuv2i|doW-c(x` zrl-7p92bWD^~jHM6nTfq4p|PlK%t7CSPp+fD$a*u9Y99r<La=K`^LcC!+Fhzr@T^M zCs3R}nq`RY1M^rp4~R1OoW&DD4)|vO|3LnqPUBfLovjzl-<J+~f-@mwFn;LHWvl;Q z?$6(!|Gxl_4{tRZ$qq0Z-_c{x>qh~P=OE87a4`Pgi}O_Pn%|D)T@M<Kzup(dNA&dc zOv1dDh2xaRmgG(FxGgqT$MpC@#^b9CaNJi1|E5Brz`DJN3I!74f&U9Y|MgpnD~bcx zN&LU%(~^8^lZ)*A@TbkC>*#y{8(N_Mx3*U4C?2rbY{`)MpUUV4iw`gw>)}_Rvgyou zxn4{4%hmWC5TDQT2f^;}t&!TJcKYFB%1^}liMWHRfd3!%5IO!&8ZUX=&WD+Z|Ht!1 zj7Ho)@V5QC7&e{ny8h<}>i=hruh@_OjrkP`0sfcj1^NHt`XB5^Z4UAv+pAtLL*7)= zsN0*dPkQZ5`<{F+P1}B;EeCN~h__Oxg7q);KK~roZG(M`_SyaF;Oc^h=48!1Ki{un z-wVb4(R;s2ivJhw1rE~tm%fVie6%j`$u;#3Ws^1YUDyHWyS0P&ss7;npWr~4nlGUL zi<I{V^|1~>V<Xf!RFo%3j{g_WmG=jBaIeb7^8bs-`-A`KO#UCS)4a!wczXXHARP~^ z{}U<hpRNNE@B2LFa<c2&@5MSr<oAlW*HO#t`n1pJ#?xPm;~ZDcqx$1u_vc)N{&3qg z{`9FHaRsga|E$wVypMMLLEg`$_E;Vu(uad~fAR}Z9zgIjenH+pKmUK;)dl@OOb?zG z!;ruK(+wN0C%&)4jR*4oktwL>%Yz;N#&)NZ<N#U+NOpa&F4pA*kR3<)d`P^_u1nK& zcel5HgZ|I<H4f_k^R=!fE7l9n*KDrcC~QJJ06qWZ&VQHt^Y`a}+0QQ{<*mZ>krY6D zFD(E6ZIu7_oBaP|QKS8T<aJ9jK3b`Ez|Oz5rTXpSe5*B;c-!Z<Smb%{<?6a(q{9uV zVEi8uQ33v^_jE1f21);axq4NC06wiNFl85ZybV1`6pfyL->@3;Kr7bKy;B^E#OKHc zQ0?(}QLmv|Xl$I^0zWFn3pQ*B)pH(iQ3K)wICYh?rTR0(5pp5K`}lkze=z^Qy`s3* zjP?KM2jT}%;k(9{tM}x@PMAFTcdxWmWB(EKgZV?UD9$?zKY6?^6rbJawSP2c%ond7 z^x97$vC69`-*@k#$1e1Gx!xK6uUO6E|K$Hg#>a4+>^F~9+>iYV+}_!x693ER$$w4y zzuex%<4SXCs*Ah6Jw~I7J$m$4TVpg!jAciE{a%^&ZX`7D+MB1q+^V|xcBS=m|18&q z(|#DDIQhT#;d@x~M*iYTj5j_sc^ret_}))^5Be>l_<!heKeHFGyiy0S?hpEYx3X{v zc%S9*S>sSCUDnYk(>gQ8`(E)pyzgVY4?2^1U(`4f!27h`kKKo(b$@jJT)gjgdws<F zw9j^5-Uki)CEl0Nk3?O&vF@L~=OFLb67OfIZCKYYiaN&oaOB_P2)C&ef_t3f9$Q0$ z_aNE(!JDw_=kZOMCL{4Z(sp|ZLF+-@2meFuhTcC7d3Cw`T$azr-JZ@*<8hyc!TEnI zkNC)1vip<Xeh$08p5%uhzsq|50_|=15&zTqqf?Gfq35H01}HC?ydS{PDWzH~0ujp% z^R;^Xn&HnEzxwK{!T5XN@lDC1$Afr3pdfe%X}^wOy-m#CPsgeMo07}4SjU{23qJ+f z|4Gi7oq6DZ%==^?Ai3mn=fBJS`OnAyZ)5%cQ{yXUt>RzP8C?xki}<2Dzxd4^$(PFi zZ-d{Gun_5G#P2P`ME)q|J=zycp6_tHnBG_7TeVu8OGxtWV#fcYMm0bm_kgEg2D-u< zU{9D;nSt+(^z>TyF&uB$fX_UDBI9$l;!Qir2Q(k9E||`*$FBl;Q&KtZ&1W{0n?ta{ ziFBPG*Nvz3|EU!PwKz`wsDPd&ir+Oto?+|%{{*=s<@x8UUY6p#E}8%Td~mOQ-n=hV z54`8Ko6T7nSx3G0`{!k6TrA(c4F3#0chVsIe}4ty7Vyijm|fvr>a<KKO|X&uU;h8T zI{0n*U*hT8o0Xc)lb?6~`aRmOZMNdi2h%ib))XA@+S4=<SE|$(`&|Dl^%?KO;7j9F z6}#v0525!{94qlV>Bq8OPl#z;bTAa5-%lg{OPyicsc{u<uRCful>FKfft&EuTUwz` zH+g+og(E3^S;v!QyrVGN#CX3O)B8YuKf%F)m`Ts~pnazAV-h5NAM^#@hrW*+6W_bB zPYxky{oK6o^)cQj5;=?aaXsUGtRo~O9t`q6t&8^-?Z*0gsPP9$zsI(^%6lCv4G{_s zybrs++kN^Thu6$q;j-TYxxi)3HhRsu{M-lOS4X}dYVX{<PxgM=f5&B?roZ{xjsrXD z4{4`H>xu;EEw*SyMEt}c|3e-?|KecxPi=C!DDDqtN7cZ%zcBDV>G?9>%Xk!d|Gjp) zZri&5JrSlqGQ|#m4e>svi!;10^Z$`4^R*qvx<}@L|4F_jy<f!op8hzU`T4PbA49JT z`nr}wzlhiAe<P&-jgaos7rii;QQ7Y_83X)pP4C112M)B&mf`|MQR06zfFH>#LwI?P z!{GeC4|)Gw*867ucsy@YiV-t=ld4)xSu^+py2<-IirrZM=V@41SUyPne^CG5YloOB zdhHTY-ZIK7)UW{=OOs-^t_3e=7FwI^1*;t*(owC2Tmbq}uHEqH1`{q^yJ7JLV*sld zGJ8DY0oHD4Y{gC+Kzs)beHIJDy58P1$H|@mI=9*jRyoAR2EhUT@3wNTnONu3RJ>mt zUy)~sXgz+sVZ&!7v=2|ihVd1s&p#~lf5F2A^qp0l1J!j6>^Q#5^I<~;_Rpp7Oh&S2 zQ2w81Ag@6Ew?BjNAibaRxk~!~+2baU-#1`R5gX^)9E1;HX4-47?=$Y-aGf2)dglLW z*dXzLL@DCV@MC<0=a*6D|JfC@OG)3EP^t>>KkU({H|eOO;QsrcjQtAo2(4edRJ^Wz z4EANE>lbMKCXu5u?QeQVJFSoFiM!Nx|IycdcyMOHs5Lp+6PxjWGksqgH^%oEzdm_< z9K^$<-(y@&cEszYTDwm-^JRLtsj)wAGw-GPa~7uDXmN`F^VCAV<E!4DbMif22jY4B z&ER?PKYo&ZUkvE^#P@ymeE+%m9tH?W&-b3q_u!>Yb0^d5iSJ=HVD^6EX|nSJ8j0_* zpPl^?X4h{`BHpipBmWV{J&wPLMkDdQixZaK3qL>S%F7OiKR<t;k@39;`3Q;kNzNeN zC%Zx}v-3*`It1^h^LUS8-?wQ}5g+`C_C*c81MZOa5bWg~7n?FQ`mPVnMd|!-y+^36 zmGb<HVql*i==<|Ek#UpSi2wcBVsAVj#`5<M<o!_O2lo0%pZ3~)ca<Z)&-Nz#{g4BM zy83G3e{|D_c>O5?zK<hQ*!sI1ir>3&Vx%hQznA^^{gM3i0`mJFnSy!9OY8iU%3km_ z`3a2Rb!PuJZAy;O;9q=J;&U(KbF#lsnZ~1r>Hl_nI^q&Yo{#6@FR(UYpJU_;Mj!&6 zr*TAIhxi}w$K^T>gYo}#tp9KOI{%rmSnvONYmUp#TgDf^G-ASZKI%^8oB3@&nE&Uy z^PF28SKWiSz>K*A*8fvH0NMZd+V|#Q+7V^6W1kY@|IbS74y3s<uH9gFxUgW4(3E}J zy>>%H1(u(?i2rdN&iBW~f#Z~S%bgFcAM=iYdi`-V{052;$P>i>scaq<#8Y-hI*(%0 z%z|=5h(fXQ;R0_?Q&XybE%=|lleHU05dWvj{Qod`pXQJ68W(Ty<`Dmr9pgsqvkLxq zAkAAQjSum^JYL^7QoaEihlo=6jqwYN8}Sn34{fP~#c$58_(`eH=XA>aFQ2FDV!>;K zn6AEa9D#P@{toT_z53g~UC6oQPZ#9zlKl2wr{Q5c#ck*U$UX5e+4GGY<>xgTd(|I2 zH7uq5U|T-%KK6Gn4zA-bgPq%zhjskP*hfl5JkP<7K<H-re}V(F!1R0YmN&@z(DQMe z>G{mg5BomY;9T~7=;;B!em^~5@cW4G$^K7>+cCZ;v+`~;<<)UNHO{tz?E0}x&(|rz z_sCO;=O^BO3iA3rkOQD+6pn+Q-vPTm#ryrLwbep=k8Yil_r=LdCGkJl%bAP)c<^~b z&y6F5oqK9DK5y3{?U-mzq0}7G?gY<yiw?sMfILVy#PP#ZXnrByM>oXa{=R7%&?9(! zz6SKz5ytEYTu2!9|3MaDKOyQjhUpR4f2{C_<j0SXP$ExZx!DT=p2`Obn<y?%EA|+( zj$oa>%WV`%zqho;DzJ|)Px8n>{%3J{i0dO1#p#Ia!+7LlogVl;p_TS4#AT|W-9nCA zn6J(6p!kI{*vEoM_6REXLAHNm(4Uc}-B;+sd8{8mo@M+uL;Q~czFfy)F#ewb{vSE= z87t)h3ca$anB;-K{3S#&7U=x9!ls7{9=P|-*Sw3y@=v6JE|hY-fQ)DOKW8s^n&b8c zQ09MmKhnjOkOQhn4j})$QuR#rqm@f7CgY=(vnsiS1d$>Bo_<*O=mxKy)&V#QX5zYp zMn{2z@w9{ZJhdrx@dgL;BcNSJ69`_upWQ+GgSb)?9A(<iGma}}RV*?rX-$Is0sdEV zkUJv4|5iKM39$aZ*8w{Kx^I+`<N_*xTU=bZhxlJd_KgI5_$2=z9uhb1%*nzH2r2I1 z`^J~6?GDh$N|MhQ|08}3-9SHhvKxDUP|7p@$GYXxgoK2K$bkMI9ACNqzdG*1yxU>r z9UDiIe5tX$6-8)w023OYw>y|0yNGwt{wrg**<nA&Foj&~I6NT7>y<rXOH{ZD3)dvh zqx)o*VZSJs4>;3C`K?ATxtq@~aypxN(r3dp&3p}bUnttdA5M0;n(`AI!(kUtDv0+< ztCRJ7$&b%?A99Dp_r!a|`{Z9Z7vEEy-ynLvkM{GE^?SF^`!VBv(P`X+xOIG2ZHYFI zL3Fkzk)CgQJ)FDO@pca7*L}osuj91eX}QP2^6upm?^C=!?D~e&y}c~X|EsT@h~JlZ zA9_BIe%e<UY|)S-igFF57-#iX;(dr2Sl3srL7Zsnv=p1B(9~N;cK&dSmz$=yX*gBH z#A*D^(c2Fcx(_E$jdmS3j*rvpqvKUdj(8t_{yx0V<OVJj{7&!ro}5tqB76X~=H}$q zj1J`QuSq65eJ%Li*)swA_BAKR!v2CdeP-{Vczu$q2uc4gM|?gh)j^)8_oJhb?EY?l zYwvW!d`-eB3(m)Au$-lxc;D+c8K)a)J-;h8osZG9oh1D|O`D4TP&qt)^7HAiOJF>l z>ELayBXd9O720En-zOedylx_-_aZjP^YJXc@qFdWmG_38Tax<*<NsMRrFj0a{OxyE z;XRajo#OwnPOoZnQRcn-;jkRc{vYK3Jvn%9#l6UzNr-(%nEv12P6;`n0G}P=0cKU= zb1=>#{>QIB()l{r3uje&bBO<E7R)63s3;@xe`=~;KdaI~euR+4kSj=^C;p=ElV|$B zi<?=H&MOpeS!Pwl>2cfe#fT$txwh<ov~onH6$1aGxSsg;?}sxBV$uHM4UbmRy!>e8 zXC-h8rhZn!JIJ2GJ1F0QAN#Zrihp;s<313#jL&Rv5dZg5KjzM*{b0_%|H#srjQ>ZL zMwT{U;X)VUGsyqnzuv!kInj~JTU{Ktq$^ER6T8mxU{~Z*37DTpH*bt;`|_0W)a%M< z(G>CclN6uu<m{=uqehE$E6~5s9<TG23$gF+@Q9VYKU|V%!}dG)pA#IBkv$B9|2rKI zhke{{Nw6JC-keeB+HLqm%lTWcP}^SAJQuI#5*%u*hZBt&h8D;xEN;dUK&dQ<MtJif zkIU!`l(BKHjpTGXzSrzVKAKMCv!}9zjXzU^*Ab?t3}<KrpCew+(x_(k{2n#Rd(Bm9 z;(5gF!GTYFPjU$2_R%jc?_LM&+^*A>E1~BXnv5pW^Xuy+-uEvxO1uw&B*6Q=70}}m zuReR&EmNakuT^lvZn0@Bc7e-<eII#u;&XAnqpj@#>Gu(l$}@I;k^_F^E3{7IcO`QQ zp>`Vh-sd+tiT4{CVDFdocy9njnfGO+{C?r#;SUo3lN=_YB9_+g3)t9~_UR>l5AZ(Y zdz#l6@B2klkoRG)#{-1v=W8psV&4V$<;mVoh<V5fyS_Hx#R=(<3zVu@#3e}adSsUm z35l*{{&`V!*1+CBvZLd6;`yfhwgWY=`>PZj?D#dXYY5EF4}_l~O*8u7Qur}&U%ZC{ zc|VM&`sCLj#QFs$9}Mx}<!%3v_`iwe0VaPfmCuij+`}yX|LLbYv0s+S<oEQyj>o#~ zo}L=y6|K#o_WEK(L^wzM0{iCVS(U)8Ns3vOs|&Dic|IY1ce5%f51m3ct8#oL=Q;L1 zuz~|mvNG~$oZ)}Wx2_KGKTsV>*a`kmJ;VPb2cX;(ls6I_-KX6GkNN8$CZ<`H2@bd; znB1}NEV%>s1p%}Baa^4&hmiaspyLdC0|<G9l?TP1z<F^{oK?Ao$tjUfy;NXSC<KO_ z&=1vtGWYkx()Wsubl=jK3M}{|q<)-@Nn8TXcNN3#E7JM71Nzy&-{&iH34!}?=SKN@ z^?B-XgIs^<$GeJ$Mdu-q{Q9GJYu?&()U<8un^Bu5-gx7w*JGlyp4c~P-`1Gu*ipaz z71dKkju?OL{ttftk@SBHf=jy2EseKZ5W0$S94}y3@tOi>0OfKkE7#+^8}-%{m|zHa z4@l?VD}IFjH-@ZS@Q5RbZufgsMgZ+g1@}5ks7FZWUn;nV(6dHD?hL|CPv_SF!Rx{r z$0H8%6FdUMVetLEK#!M@<m-DO2jFl%#q%M}u8HFL3-P^pptn!J`b3X`^ljmJIlm2y z=aYD!>CwdhK3{t~cs{yq!-fsRZaG9ecbw2}x5Lk0qM5jE-8$&`(Uk~j1OLMguzHQ` z=YI=!f8;f^#_8v4VCPpbJ3mOSFYjZyfHPDN{hRyx6$!<nP^{x~rMA5W`@gm)LZJxN zHzz-caSGFyY1ieugmSXWheS-wgPz}tcn0EosN+t?`}ppIyf6Fn(=_?rZEeK&9*-2K z2YCp7d*tOqe**jtKAx`~{d~J6oo}9!+O%=wkts2ny4p?P`2~3HgrYF?^M)JZRmAUK zo;)d*X(<kX;|9Yp*b7vVVet~ri<STy!}!nu@5?x!<df7P{=dBWe`bNl(5ln5K8!rK zm`_9rg$uxY3RXftc(@>u<`IDq^Ms7*S(S;78Z6+SRT)tVJ^^pb_*se5B?#le?+F~Y z6#NT2=V?n4){z$?KA-d-;`utn=TB%QJwL&r2v6Ed{4YLL7criYQ${Aieop#-qO^~x zF^H5eq{%tHawhX*&#D|zp<kv>9A5!B0}BSxKKWlyzf+uGqxDiGf2bgM5YIzB!u>w~ zCFsmPzLKq9Hjam#0iW9dB>AK7@&0A`I{Ek@i2IYALE}?F{BM1)q^sDJ;rDzF#5B{` z#n5Z-D(+(IApfso<WpV6H2!_ji?aN@bi4}hL*MiL>;FB=?-f5Zve3<Ox794r-1vM^ z(e2HjM73u;_13fdhDGZp);O-INz-iKuzl-=_wRk|TjAe763^uok2|}H-!JA|oXAj+ zP!X=c^W(VNZTy@N30yxfey<qu-^TZfKQCwy1n~TOke8dp_aHCOd-PrbLn9%=02pF@ zt?{LTf5&;w7eFUNkH=VFu%-ZNsc}ug8i%O-0=!SW?h%RSIhVjtz#l<+J?zt2r01g@ z(<6>u;2}5S^$6RI;~XU#^sn9ve#|zC%$`qvevhFV=bNkx!Plox*4M?P%-5`Q3fQ+7 zc70`JJop|wiuL;7eITOzNj~86a`~ID{e4SE&ahibH4uku4{6^5&mUVl4f_AY_~FA5 z=NH|UX>B5XU#+gI-HCWVJbwn+`IkNyKRoEyXYqPW-<Nnll-h^Af4(LrX4tSW{U<k( zyl`a7RLzL+5GBR$VR`?6`28@%`~ApA`ShCM7gF}grzi70`R{3+Kk!vir1-$L1GL_* zCYg5GWxNmh2zonPcOS$w4ZizS;%WFBLr5NgdmjGz7!BngjpsKd?|}o8;`VA^eDTG> zcwasq20KAKegVGs11WC77b>AhNbwnDm!SI&@&Dz`|MI??$j)J)c&&@YPF$zJb%b=i zj9ATNh^(opanL$t%;WtL{!*J8$H_m5cTga#LjnysK1oK6W2OyeKkmLtjU)VZBX}6{ z0hic19(e>{pRF5Fp=SDtjN_?{`Xpaa{%n#Lux<d?jVSHF1Th|bpY5UJyo2@!8DE)H zf#p5^q>2b;Ur54rh!(IXz&=n5{{f<>m>>Q^F)>j8o3Iu0Peetnnd1Mbe=x^k9Rm0t z-~ITChXehWa0~3~V)obm1e@9>LckirfAlzn>8;i#*Uz@TFjDnct5~^pOmrp`!UV@y zUWfNi{(sbDXGdgJH|+8FERwc95l+apG8DPdOUBudzt;7{Q)=7NtZY*P_NnnhkS3J= zXRq03^mx#Y8EM-&8DYOS)A@6ui5=f-7EK<U2O0%;XU<-<gFHNl&!g-5BE^YyVt;zk z;&$&ryq*BV!5-{)PxqtvJwHSE@r{DZW8P!<*ex`an~xdP>V)wtz{3Yl*3{pfa#*{> z>)|#Vsx`6eo<B%-eUArue1KS9XDK(>G}io<mY9@bw@jW~_uqttg+;#Q@YDbFx#yl+ zh<H4F4yg;to}cw){qS4nYmB1ciq%hzUb+3CkNCVk<Cro|pA)^b^-lQ1r$*B{LB#v< zc0q()|K@A=A9HbWdd0PVeE6jI!_5y2)q8zjpKRCfk64eV8HOL_??L(>JH_j()L2&m z{zrfMU^A_&_cbGqKNSKH#qq;GhB$uWdy3PaPJVwA<<mvlJ^=_h#^<9vdu?rP`MbdP zlxHl+_cH&NAwLh+?@t+{@%nz0Li~?y3F;($UGPe{19o~^2SE0I@PEUmS7e?ah{$7z z`;xr?>>m+|{6u7rk@y~n^@*@5bxBC}kFsRY|FR+eAL4&{-(@*qFs#M?+nB%V0+=ya zJ^8rC<}p+JKgAj0J7Muga8n`P=rH78Yo^M!6MBIW#|f3OfqMS4>kEy+bxFbXN!&78 zx2&>FXeGb63i8Dn>>;^=<Pk)%(Q&MsB6J4Ilvh<_OKde7M^vCa7#PSdfcr;`2mj-; zjEIT~@PDe6_#gED0{p5J_ij8J8+)X(fO((J2N7NfRvrwgegg9SezscT{}H$^=pX$# z8^>3^Q><R~rruGYvVHxv-`Q_m0^=!<>)!)ed|T=j*f*Y=d$Q(=dDYr*Ow1bOkLOfj z>jM4pew%(J1l|kE=MZS8fwj+Bvs82+uF9gZg%UrgzRi84_is1Fr&;m7L66MB{m_1* z5$*4!_fKxW-}~otO8wyqH8#dma=o?mey>Bvg8fz@ZqH}F<zWr+yzb-^urJkzb0?n& zpQGSh*+2Z5dJeXALL6VB4PhO|eNU)u9Tv#H_&&I0f#-*Tn6-yVnGj}ni;B+E01S$q zId07R6t9P2#P`*S{b~Eh>3hAMW<00MI{T0Oi`s7dkGl;oY81ow7j@<=<YCsYrFD9s zi$3f0JrvhZdHKx5BOW02gY5XEH^!!vXjJ$>?oNqG5q5a(cc;9lSzl;=mh$g%5WHC& zABYorKGvOCn}(&7P&~eBSPJ!{19^E~)Qp}OI~;y|4fq#+{%S4wZzI|Hi=2M{A#JrL zzk_)GX82X&<D~pOl)o>I_xk<h&*!N>%)XBYmhAgprsrc_ilsl_2g~3=^y|nJW%QT0 zp6u;LJL&(>>lJnNLH=*1d_GRf(?jDzc6_X}Xp;F~%AfB?ygu>0;3f1zuV!(2kQc~b z4>2*Y-mZ2V`SrctI<X8${}UnJlbTS}n;0Jv!t{0MKVgsq=(z%V`>T>3FG3DT(|mAX zwzW)~ra{<MZJCz*187IW#xiY;#%;~Jfu4u#nUs$}z<a>>9{nfm`ZuF7_<WL_gHw=F z5$Q36m*qIfd>zDcvSSr_@ZEC1&-P!HwoBU>y9jH+4^2_{dwV>FtM5rVXvIA6*O@eL z2CswukDpvtAx?<+KR9o}PKfzuxqheiS4oMA<@|DeO^wmmjCtU$Y_A~614!(4W}aJ4 z-vRgscD>9~^~cOW-BRzsdWSK8H|4YA9wX*I%r6n^FmF=66fPv#-r4i02KT8GdyNql zDqGUlR^XNrj$5`e0(ozs4%pR*SHkobqK5uP-`RJIjpAn|68}e3e6||x<NlwmRtL}{ zh^-NjPslDw{iJy3!Jb1U@sd?Fk9dqjz0LI8ea7+Z=N`PB{`GchkS?^POn*qL3RiH; z^<{eEmE|Z$VM3S2S25`L4tAg4c6H`#jd*v)^+%7Hi}H=V4|XLW4rB>8>8J|4`InF- zUDNc?m!cl*S_pKees%CgZBMJ{dXSy(RqQy0`iIv<F6pBB`$s(3MSN?Toh6-DJ$E!= z9?D-o-<62ty~^i~CZc?5#H;&W)Hp2%M~-g3I|b!o4|bKI{l6$5Jh}q$F1^Y-+o}Hk zE3@0FzNO>Aqh9kPGiHQmb={46mS=w36-)hTYCq*Q*MIcMGPFzWE0!|+*@)-qKCO*o zm$t8+(<A!x?`*$kp3~B!TGF*kYpR+&_PMUO6r<_j?NYxj)-2ZlQ?7P;o_UXVEa^(H zWoPfdy&Z2b#^X(?UhmD<wNu%20QGFBXQkuWnWwp|F4(z@t)UOHc1*LMe6KTS(TpWi z9_%7}!xE0h`H_3yjePD@ry2d9{YUV9+iS19vOs@#ib^2}^HYu_BOe|3y;_5>PDMPA zuzw#RXe`M0u<L=}{2ti#Id?bo`Vx&R|G;)yPp^s{*BzTe@%gTt<-9)n*}LM06YrCK zIbhfKt>D4%;Fer|OmxFOEBIV-t@rtG#POAA$UaALe9)80kB|6CMWXD-KcxKx>*qSU zr%~K~d|W*7KFkRw#`lnS2<d%fybq!#q;+^fo|o<XG9G~*FJN8%k>tsfDL>C)#N&zL zVT!Xy{vWajC`0ONAO|EvO%aLrsr_Jnz4_XvRO|244OmYPeoZIdM?J>(f`6%mM&rP^ zyBN)ZJQw^8SZ~*B%x5T?wuZvKZ`rN_&mY11Jda1>d*b;p{nuZke=|b$7&al4r#wRf z_`5kd4}vn}7R~FJe-DG_MX@J)Q}QP_3Et-wB+pEQVWdC54_-!o0>OJ6_qZ&1Z|J$D z|6S&5ve%K_svPTVn(|u?Je2(Jw|cyGdu+<@ZY6p0cej>kt0pTy@fiMdD)L7tNDc@c zcl%F**OTAQWre>Fez=5Il`WTt`#*y85so|UCw^*aX=_`qU!V`cJ|xTa%Tb>LuPjK( z%(SL<<{*wYEG&%bj}?wH9>+d^nalOVQ&eiTn(Ad`x?QBlM8?NIud?k%-f*w|80N3G zZ8i1O?t>OVSgwyvu?sGa^i^!PaU;8bLL=IPo`iTcLTR0IXO0qnIVvY$9{6mv%BECB z#D2Eg0sha+Q)53-#{Xo;b%FnZ*nWfIgb9rd;Z6vkKY&s_j+4(z^+~#B_TV$vD85_b zVfO#eR=-<<W^jKQKPwR$8X8D0$<NHnVExB=hUx|haREI)<epSEufNhzyIg-bnfxTj z3>e3C2M@CP@Z(BI_33}O7^MHJ1^mAtMWu>}ApXZU<0t;NLM~C+su8EGR9<YmJ5F`x z%$Ttzb_sauqZdBO+i|MC;5Jv5BI{_i)`lNu^c4J_UDD+-yr25>)L*}MIJtZF?Ac4e z=Xc&5wQNaOr+NAE<#7j3?J>U_@qXNnQ=usZnHKPWXi87WN2&*pPD^P+9tBZcrT^;} zUx0rfvAsRx-OzTi*6_<;{$f2O7=F{Hm)tu}9Wy+HJjEn$e4?sSwx3$1Upt~A?zgD_ zQNA&MX_wb9bH<E_9j9{nTW`IpZAq7A-twl5rX@$q`Hwz&=&|Qcy<hPEKKw<<gGcuo zt}HEGzT?z!^V;EMx+PsR9S`Ap;@v&5#)wHrhutzdYUKJQU3aJKiyMFYgXsUv)EU!> z$I;Eq9j6j)bLJGhaqv`E$@SM?f5Q&Q7k|W$#^)ExFO(nd>&%&(VVt|9YmeDrD44pW ztJX~UEAcLx-y88B`s>Xp$b4}2uix_+-gx7UDLXJO*Iz#ga+1R_Yu0@c?O2gt@YomY zPgK9C-M>F0YXQCwL0Hy#cS<$wdt>+Dds1QJ8{&P?odtS+fbW6eaVq0D{Q0O4MLi~^ zR2!)Z4=4TKitjfD*y<u4M_k_MyPl)?J&(}aLrDBde*Hy@j8(aOI^<GU9U;jR4ACyx z^JzbiLt5nLQzRl^-&-1(9YmBhF1NiX4sinr&xQSe4CD+hGfp3JEyw-d5~tq@eSBH{ z;C$bl>=rzB7T@PKZ{J0EZ;T3V7v;@?Ut?D?+KV{YbeIqe@o+pkZhb!T?Zu$1s3(0N z_j`--@8CN_-XO&3#j6zY;Qg-<o<#n6#6J|0p3i0Wz@JX-ie8Gp<F+~x*9TN2Zja|f zF%Cu}?C>O)W8a^nQxLZSJs*B~$PY&u&o?%bTtM?D^!I44Bcq4>0%#9%2>J&ke&-Zh zEk~x1|3I;l<P?7b2YH~G?K?n-W?|Q0=s!Z&VWD6%Jzr*P@1|s2kN!1NJ5^4!`^inP zUTq)Z!OL6z3Gz4FcgjcWYAauPrE=CCu_-#8Zq&4t*c5!mU++x*-K{@8=vCisUVMi~ zsIT`J*3KM>p9kLq-rLk%etxv|gO_S!F|QaK!9(U!O+;+FlhzG+gu2>J@brNLZ7tA0 z!2602@CxEmM3M9nSE}1eybk)0VC#$V9#a|9P16F{3$U(Lg?hK(dpkzQ;Wm%$G#`dM zo|;O0q)ALnl-lbOC2VZmg!$-4K0DfhT|yDUQM;H{ke95J*)jX$yCt3GBy3Fd4%Q_! zHKjIrbJF>cklJ2v4)h)bFnlxC1<nfr^bS&At|uC|l;jy`{r|fq?||=ndKhA(f_F>Q zu=h40Uty>DDXd30(@*rD)-Orp19@OxD8|itY(dISE%cGVczS&_UKqFTPV+*<6NM-P z<5?$PcQ)2FpbWVHe&7N4BIOHnQF}1kCY9lTQYe-cFVt6StldAlf$TdWuvZSe4(r2V zc<gf=W#orJ|B;uu;U2_;--t;^?bvK6#e`LXIFUUl^N!GzStxJjVF#{1_4N$fA??TT z+tC00{KVlZ2d;Yv^&SS^08DVKFh`+&qHU-4>r<yrT`^~BN*Rvt!h3bUh3f6p#&2J@ z?qTG4qWTV$vylHyHAi{N?V{MbiBC&e358KTuUfPE;PVqz@b~<P^#|h)!)&LyRC{~3 z)%8dCx!pqD_5|GLW6`po$*C$O@;G|TbU&5DW4;F0b>cliq5vH4%%OPq1lwWl*x8x( zV}`p^p6YFF)y#`YSuZ**^gQF@<0DnJT0R8VInd6#XovFO{|EFt!rQ!pK>XjPz{(j& zZs30-cJDLHp!?zcW4MlYtYFXeF!B&k9>GaRJIxP0W;8zR$mLg}UIOZk25L|b!jO2( zyh~dMJ#;g^i-z88*y&0%2PZ~w(ChhdHRoo$55YLd_oNSaq3;8+UXA?y^u5vdtqNBQ z@Z+Zl2y7#NzIW+TXB^~!_0K+g1MxjxQ)e#X^N`l(z%)Jl`ld!99rgi*?)jozK8(j> z)}})ap!ZHjs(0#xOpfddFoXS+{Qm8u6!7DZiC)%`-?CGS_55{e#ao)G(InpwPq{f- zcjH0C?e78~E_+(?YYSc=`RkWbyu5YxEAdCBEMV*9f_iXsvPU5Me{-@O^FcFs@vauD zi^&79XJVbc##9#`PVsw7_EY~BXsZ;2@jNbLc74z^cr{H!`}L7sU#Ro>Y7m!?VR8oj z@>s{;L~({#pRbA?O@4T2+r5E)GJ8DiBu>f`MDr*3T9bd^RPO@qd<{M;0jQ<?4D*3B zPV-^+?{G2wKRRM!yR(_e0bGd0_aq;XzXIzDr;Ne4TW8ncxehco;QBNTniJ=1(llPL z-${sh#E9qzP0`0R17F_!cey|RS^O`k)ujKQ+;*TX3H(0_0oR{`|4nnV4C8Mt(I`q5 zI~ST~-C8Ta17(;sGiuVXNDtWyFfqhJFVKW56|tyK?<>kIu64KhZavcKD!~6*iBGDv z3U&R-1^VIObHTouFV%SM2Pf|apHp7b&K&6Vcq_r%Jg*kur!0wXIC&cLRkg<JY*p`u zT%p4ExEu3XYHAa=0C7a%r|}Cg@6&x{1iNT_A0E7Gj~RY01T+N^-<#lU+QagZDnlq9 zsu6KF#Q!?vwSTvyqBzg*_iZUwv-MGgP#3uG8taM^96Gf74t6>g8hXM14;QP|tGv{Y z!psa)C;YLHyI4Lo`bx<^+ZT76k;kx!<2FHFtF7O5Kps!h(|jL37cb{qr}0#r<9a%d z?GwP$up`iTp98yL2i^m@ZC|Df<F^y}%Kh*|4^OEA@t><cYX|+@fsUU!kFxZC?l!C0 z|GL}k37}m6464ch9GjAgal!kL+zO;|P{RH~h&WBwZe*W+|Iu(rd-BT<px54w_fAnu za*A3g0?$@!CL<0y(2w5T=1@JgOZ?kwDs<5}31Tna%NI3J>)3lI_XBwl*!{?Evm5oZ zT-2XKS{>dyYLCYA%=4XN+@y9k8&Wwz*kcCY;{B!TEyh@eWcMC$J)X~l^<ng!Xdd#% zZnIA`As&~#2Mp;rm3@$lP#bcr-%sem_z+UR2&o+)c-v`22|Uj1`3BO*`+o17$H(~H zEm$m!=Y6zJ4|;$zupS@d&HVUy4|DN7!A{N%L%tnlNK)J|@ILszmi+nHpEr-<@^AQQ zd_*o!eq}H0hPLQ0H`mvbKfkuqXe51}?CjIX?hkvCWG^?F(viQ2^9YFRKcXeMfb95J zxCLg<_joPP^YzgOu<&iCrdrc0`u&8Bh#TIixjEXJM?9{$mhyHp-pBDH(C=MM))vy2 z$!<wJPjU>y<6YvBWcc$<CgjPd_5B>%PsimFyu_Cy#znoHXV{gD^}zMhk*^QlO!O~I z@4iXEzNbf~c-$RM()SOXJk?9Q@AFwqLB1y*r*(M?D88YCA;!HXd02Fc*ABS^`n^I~ zOLlz0Ex-@Y^8Emn(boL7gF&93uki@4`iZwO3|@)98yY0^`R2pVp9(@J{aqOzHZdRv zpq(<b>jwWTqmiGYhj`wP|1k|n{Td7}cmBKFpFcPs$o~8J(coz=R*!WziCeP-$otE} zMdNYsZ~MCN@GIx6Fx(m5VYzaS$3Xj)5&zSE7O_~@qX^e*x;tg~ErRfI??Ujh-TvWS zyUm-SM=LSU?lvFC_sQnd4t!Z=TeP_S3N{~7elR*ud4FWyqIs?{Fb{6+A$@GOIom?( z6Quc&<~hjCUXlx5Ys+`t4ZRf0^i2y8|BJk7&+mae5g(yaAg&qwCV<CeJW6q|sk`CN zB>TR6zlvfoPpc7nIr5$;l3KSEvwVUow*RnyZU#g2|B?_11EhA0BE!1Z>$ZURGw>a6 zDIxvaRkWpKx48p}QRq3L|C|{IxqpKr;-p-}Po)|s^cCV68qeT3k)ME$UmVi)pjp-* zs0(NDj~B-;w!Ob!{hiuOD+j%Hr?&2OQz7ZM-XbxQ<cA2%|NX~-#@FtY-Y5CJ@>?!u zyf1znK?4Ea{-$xm`|S2GKBxDU-fs`|aY9@Vy?nQU?8Z1s_IakK%e)S~uMe-w-!ay~ zg7}EfS5bY^<IxbI-)}N8UKjj+Lb@LIdV?GF2?12*0nC2S&clyigYS^^ddhQ4d3iXY z*GvBUqV^={%cabpkNsw}sj#21e0<^Is-Qo=lz7=1hk0T6@Ca3$eyYZ0%|AeTeO~J_ z1@XM9X1_B|Ukx5zwo<n1Q~ti~+qZqNQ}dz*+Zq|GfpANRFKU$0Zg+?EaI)fB@<Sa? zrnq9_^BB#@sBtmGub4k9#P>z`e}jB4^S+Fm5Z~uSo*nQoCbAlB8tm*LA-l9STBId1 z)@Vz$H|_BIYqZMfrjEQqinAmC|G9V{<K`vahnnqNKym<n3$$t4Cg6PSTT!a;@#Dbr z8gySw0~&i88|Q03iHZx2R55=4%Ex#gf0IaMs>k$ov4`=wPqbKqJpM`4r1dQ=be`sm z`I=8|g4M7n4X8pM$Y8y}_&-LYK!0(IA^$)ApUZU|2J*k;2k<}+E`)+XdQ#O`E_`4; zd7hE6MvJs$i2E%?UTNeJf_@&0codqyz$0hoF)GtbN7&f!IKB6DT=2e$xTJCe=D~Va zMxaXo2ejkCd|2eAd2x73HvUJC;c)V3_$6wweyL8t!eQcl{DOGo4F9WYYis+qi+(v_ z*EaX`^ysj^p*cwxp{yu&6uUihGgcQrT%3>)9$o>x9nEC~&`A3fqud9{e$P28U^f7- z<2c=4uxx>Rf%FO}W1dGoSF-!MF)zVC!tA%ykN)FyW<2C^8N##v|Ni5X%Jg-Q`Qr}h zJ^EJ~PvUpjebw|npW*j_J)WMEjuRgY{0@oNC7x&35zm9*iRZy<QeM3P&kJZk*5?C! z?_+%L^I39<?*+4w5Z?jf^>UegK8Wc!@5S+Sei=H%{P&P+3GGEj=*QD=p7!5~)AQgJ zTEDk-+kwJESeNesK@pF4*v+HC`_U@ZvL@pF>LgY8PQ>}sy5ARJ*M}Mbd%5CTB?A7* zPOe5fN2c5y-PV%d^rGhGYbib-^)#3l>~y{P!*#mD$-9zYKz|5f-+*49hIRdMVFURd zd3Xeo<=^vT9GbB%&+8SjpAPoH#q(0Wou;M^YmIhTH0cGz`;ap%r9gboTq*b;el;u8 z>p8(kh{uJ!e*x8dFv$1wwdl5&?DzD)&DSm<`@5I;IR-z**AT>l{QPDV%oicS@?d=3 z|M;6x-H5|Y(~OB$DC=s$`%#n+1y9fH{Q>k!_!aIKqw)J28H!@IB?e`BE_88-|1aZl zko1DaMhO$_#NV#M4-nrwUt>AWaVxMNM#SXjAqN~G`8s6a{3G)}<{P}|)DFT{8AifS zICy>eIvFW`kV_@MWJmU?-f8+0%><(t*Fzr&j~#vC?HrjhGHU&L<m;rkgSsH^%W{Ck z{~qM;qVHU8m+~MPja5kw_=}Se?@qkF(I+DRKUQ|5U*K(nt0UV)@eEIaw`o3Z#J95t z*gtOq+_lEtST9NXB_Y`@2<wqAP`=*~%K!Ti;=PC5dcJWWp7-H9B<!!p3qDDor#!TB z{QU~H9%lv0)IQ^RTnFS4o?#;NSuBk2iT}>#c>#KTkneFB`2B2ueXtzIgWeFwhjn>~ zr;y_FDIOno02i&p9}{i-1EEb5t0t^w_JN(?t#Fm59{jKMe)W}DttruPO{wsM-y9tu z7B(FIdf4qLUjH!dQxATBW8_KX?I3+0>IIA6BR|S6@IF1Cj1=z{7Z(#F<=yjQ|GZ7f z$bV#E`FHRjgyaVauE#G!9HATbf8^Ia=Cu>g)4UMm{l5I4pU?PS>=Mu7d-Rj|9<&d> zpAY_(^mmG<W_rCB`o89^D9Sew<oOu*O`Dq1G;c;>|MAG+@%}t7m%-Qg|B1&T$FTOS z-Q6wVe@$&|uQ*k+F{-1Z!x{s-2*p=Xz2lJoWS$4YUL*KoG#_Ih3obtf_XV2p*AMYO z2I6uZ2jY7yf1g|qJGu+sAMrlcdnxWloNzA)3wCvFVTT+3QXAIchJ^*^DP}*jlAQp< z*UILl9gVLC&$Ic;2mb)%XId9P-?y`s@`P|nMoYP2C$3X~f2H}X9C0_+{BEo#dP}QO z<Nk;vFgryf#SuicbazYjPNTix{3p+YXJd0R#YZDgw}JKvASC;kjOEy8BNac7VLGo2 zsjXEz$_+>Z7y<q_Q(OV@eMdt>J;?)z!z<(qurC=5kLSPd`T8|<{_Owh8^!~UZ)v=h zbrK>j?Y#f{H?)WO@?YY5@G|Qs_?8gM-2(c1KySx$Fnc{FtFw7sw%ZHLj!*hO@jL1N zxr6HSvVKoI?_=@!FaubEyxyPh<57?4_dtr<2k)bwjPJpFgkJQE{P$FkAp`<KsL@8* zo*zU-^tP|tI;u4a*xOgtS8K;y>+%*&Jd&)C{PwWtgJzjtkNcTOzxN?NK<4>@Sc5#f zu+t-c|F-L&nN9k=o$U8O%uiT{FC*gh5XZ;j_RwD`ZqH~e1MjD%=C_oBzp*dqvLOG@ zM_hv6KOekmg@0gy_LG}vp9e{=XL>&4d$Q+KzRvmNcSnFmkk5&aU8%(LBf?e6;Bkr9 zkyScsMChj1h_5+>NfSbBU_38iKv^06B*g1silKV=jfs9U>Y4SEZW{wRivABOr$U~H z(L6m8{d*nt*m1djO!ONFPi;z7M!zufsm--@A4k10lH!`s^br4F#^bOM{_-EaDy{po z=3A|?DTlF-D6Q|2<M^=eIDRB2KrRRWLr;g_Z3Wiz=KC8PDDIE)?UDST(mmhK=C1~l z1Ii6%{NOLtbgzGHQ*6r4<X%C6_`pNSPOnQ?f%a5b$0zA6K0lSoensb%b#<@DrXb&n z*IqOYdc$eohc|ej$7JJsX7%yT5;g`9uRkkGxGNdz|9UAucO8!B8H7f<4k*<3P<#(P zkJ^#W_x9eIG_zp0IZ3$@@zmHi4RQkQ3(R#CWl%hK7W|D;{{rpfdt_zY_^ji&ao;kg zLsq7E{l3TPJ7s0)9}Lgajv$|x?<=<lg^jL9GiSFGs7Lyo%n$57ay|L}G6vcaunv;d zlaB}55#;*?j+5SWR(o>0{pwNP5vqq~&g#dR@sQgIwA;w)fsb6Y-y7t_!h@$4!jC@= zd1@$s45x@do>)nbrFL;UsULxUQ@`Z%GWM_6_c-<Itn+evtRJ$zd^S>j-0rOBZ<NdO z^Pl5?q4yf|ZYI7T$iWj@iI=f2O+B?o<1Y7WAWC_0QJ?Jls7L6x(Df)9a~WnZr1veT zzstPdAHnk=kX+L114#P+K)xq^Ugmj0;(6RJpw}~=CteEhJ>z$(FY!I{@qzDQ1~>=b zBOeU}4Hp0I^I0fvpKf$G`R3>hrq|zmtp@Tp@jRLnSv-Df)(40>j?NGjOz%(HZgK8P zo{#ZVsCI$RDPI90p2t#?yh}@Q^W>+;vK8>X5_WObX2$dQUkJ(1KhSTVhIk<t^!`t7 zT7TOF75HC^xH}&q%np|MOrJ;mJdoa-`Pw(5BF9f!PdpDfLL|g-r-Xgu?ZDIS?r!4! zrjEAf$S<$l=oZr8zrgxI=<8!xd>@_P&}Quj=;I-0_&UJz1Ip1Gp|>|##{dyFpcoTP z`n)T3O!U(uk(QzU<EW=ceg->BjOK-?r1-|hm}q76M!YaF(J#PX(B1ZN6x08?kE1@K z<umk1L;Qak^FQg=d4;FJm!+B<d^c29B-OvN0Q@V05UGr1_C<6)Or=o39*=xo`E9W& zyOL2<#Oh<oZjbBAH1>|BCJ*+zR$$>7@_-rKShqb4@<D_qDJd4~h3XK`OlTKdjV*WN zXxlB83Au(jX}H|@^!<jk1mx+xDowBL=`nGCMZN*hG2#MqwA*XyJecofJ0>qc?~v!g zfmp-z|2$fki1rBao&%@TasnXocw&Db%I||^&kD68(N?$7?NK{&c~?haHsvqFb{$gt zFd&@OzI?o~-*L>3XVts#^8J^1-NoHc?l<cf8}GR*40GAIuLz9)d9d|@`ib|2)$2cQ z7cd?hXZ#>dNJy|>b=yr^YzK=t^ILDD_u{JCO0?Jxp361di+odD$OZMod!ByC|DP{V z4*`xB)UNz|crm}RKI-M-d%?E6^!{sF>(z>3u&XV_x-~i;5j*kuv&KpOfAD><<7hvZ z_#JW$L(I_w`8}OSy!;Nv=a|O>DEZ|B{7(9Rkl&}#ci;_{<9IjtnCbBd3}6U>0KCrp z{u1Ax;rX-mde8>rd-OY~&xf*kAXp~8r~JD>@Mv)T9rEr#U^@c+9<&_L?*%{OeLOGt zoa}|EyWrpd@?_7}5+nevt2=(&rm;oWV&Bmkiua3%468vsJP-7JAlj*cAAz2a{Pr#= z?4-|MGrP%E242Vd;cY?QM_isDWYc-FOVWAkhue}$wJ}<9oX^*mYADW<@jm)9UqkW+ z;`hMwE|13tKYmnxhuclOtx&~frjcD83+aKVche3DJ%x1M)l|4{Dv;Kf5Z^<ucQVAd zjX}IaBkn``yZ7_Y!Ot6`B45jg{!aX^s0Z&yLz)p{G{b;CQ9AA}8Ur5x{ZEtBW1`2< zePIW9VI;yTQ;GLgN_BXE*F`FO;15tne}eW9A&Gh|)>KxHQ>)<x_$Z3}74)NahVb$n z2eP|Qj-`A)@Y`>#sqtWb%IYA0WHsXWetJbY|JOTCcaopHVJ7+Ax@R6vcKa;O6IdUZ zJ?H-WgZz(mfUXX!$52CgVbI<*ePQA0)3LBG)Ymt@GA!kgmRnX^4}?8VbS}R2A?^Er z{Nt{29`hXO^#P=Lj1#Jidr=<M+S;1Sw@f8}Tws3NI&dEBujiC%Gu@Prq%4{268*6= z2l=>H(|WI4=Hj(a^_Wkk#f|4$iT7ze!{F_w^9lG6rTz(ZgV*cdAGu7=<;V4pPNn;n z^Cu_8(0%coxNXM8w<ouIaro%WT%HUw@_3&QbNQ>%ATJ@m!^Pd_V(W*`j8AE}@4ove zuZ*IuuAbyXimSjnGEIlC=-FKUMBqIbtRLwZU$0mCe;UrgmxEn*smJMleoNqfuS+P| z(K*DOq{Hro^hs^>-iKhrdA42-d^j9(Koowt{6ew=^m&icdC-}k+JU+z^EivIBY8I+ z$MN3aeNN{g-k?l;?iD0Feq2JyF3))+{oREP>ZXyM9{XiVD2jwwwieLm1wZ3?uaD{T z#K(-+(Qp8zGHw{;^>gt&TX&DVx;`iIJy-?n(ZKWIEkd%-5Wl-|K+^BYpD)Gh?E=zs z1(D(d1aGNUacu+g-4pK%7*AXD&CxE;u{`Xu9F44^h|ep-Iy<+$Moaqa6|mnky*8;D z{15R9WyJ5{c}TCP`G9!7&LkinAMyYu28z2shz&r<Kc9&EpW%Dx`RFcqAM5X6U?3!U zg^=v;q`zZ4y`;}uU9_%lV^qD*&5Z%iQ(hs${3^nYQ7W_>1Kvk_CgOSeUkPDXfW2Pk zZyB+^!0i@DuiuCL44}2M_6S{QmvAfAM=PPvquD<A2JHSE_YwF$ytaPxM^SG?amdT? zQPe|MSdX1fQARHtdFNN``jIX?w-R=O`W|!B8>o+Us;xu3e|hr0{J)oj_vw8if4Eu@ ziopL`&W)W%DDF;?sPh<3%q0Hx7@mN>m^o?Oyd31s@CYy8QIdS(#Q)5p_yA7fb;8ac z?YnDy1j!Mvqw!kk-I?H-X^0;@klDc<O2#_<y50=3C-`6nczLERdDpw|zn{+kY?8ZW zeOn$sH*Mpe*B7MoPo!=9pQt+;=2dI`PgzWBVF#EyckY+S#**aTm!wM9->&1XsXUp- z7wNd_ZNJLnAJB1getl;-;r&sW>HOA7+>dX#k>*K?zazZ{`3QfsCnufPMX18}nCIMs z$4w2N_v4?wJocS?%K4}}o05-zp2t^C3W*%nT#oB7eay|}pVe{u7vs5-b=-yz7Ov!f zqvK+(d4(NEqP1MQ|Dq-B<@}R6?(sRl%%%Gqe)*?#LRBQzL1P{#J+2Qfg+GD%mml@% z&OCnJovv5CNAe&Cgp3$AA|3VUe_-wGZ;MK&_V+Emua^IVj?3H>%I^0+4?Iv#{kZ;` z8(4Y$fv4F0Z~Nhmc~mcM^0jO{s{ZyDx-Zg>vHRBrkot4w%yb@Rnb|8b&SSSnZm8ww zq>cTz`}UWkeA9+6=hOe-{$SX!mHf}r+$UqcOeg-=r>3U!Rg>U8Y+6a<(xb%l;eQwv zAI30Vhx?+xRe$@fKDkII7<WvXla`Y#Mn)pPyYw7+G){AmWT3rC9P$zcpEsRvbQ+E0 z(62J=7j@zeo3_WaZo*;h)#EU08E-{9`}h05P7m;ZIkopeXe8^$zG(~bzD$C?w1SP- zx+fo!kJs`ClDGq*C*!I9rfvJu@jr7<zwr?Jf9CDDEtlUmiJN#E5a+WFm9lotKYoCX z_ng~5%%$;sD^<_hJ#4qK_n_urTP}a<HtxUv^rt*}FZMsunn(Y~zQ}Ftx(`NdOXq)+ z#N|H|b$Cj$PO-?Boqo$}N!;J^zZjnKKoa-q!ra?+N3_PDjQ$B5x7ZuDviEjH?6vGZ zt(!_2;yGA<GN%1}8lQxGn;{Ck$9mz_##Zn*^2XVT*Y!d}eGl<D#2zQ(@g2y6&))Yy zUVLAXbiAv#hn>fFDe?NNehHCBS0Ab;{hW~Gf>0p6hXaxHd7q@uBXKs<>(B7KS3C@! zhZq4~Kg{@C;(4#c_h_HW<ZlS_JpA!4_gB#Olevuo>HUxw{IJu@di=*xw651i_H98t zRSJH$x9h@dv{T`qze%7n<sqI5zjwWY<EEnC{yevbjwAbZY$@#b9@qg0DSnXgJ9t|{ zvg6A<-$%c9QJg;bp6Ub9ZA%)E#zW$LFL-~d#s?l}dOQf7&<pthyw2(ozayN&Pe}4d zkmteMg79kJGUOre81Ok+;R&(a!!nTH`}6%^<)=q}fQ7&x-}LlI!D#}|TdyFyL$E!J zi`!j9{Eyk*B%!)q=7Ax+Jmmln*%=cAM8QFCK5!C#_nPE-(wnjV|3?3-!lC<LzN5DG zg%`>VbMMKTl<6)<-ajQa_;~C7tL};0v}w}{*a?gXkS{mv(vrL@%M;~>IL0$HKV|#8 zOz(giB-t79!cXI0o=W)x)A_lRqGlt%mi~!Jt_LX3R@J0yv7)ZwR-0!3f!5ZBTMrTc z&poVtAW60THSRaMU0TJY{l;{Ck#6C%RoMUIK+@zte~jykbnAh+`akGy|8OIYH%#S_ zP^i`%&$n((Qq|^o%wK_L%FSEvA-O#s*FF01r&q*-7d}7!H`Kp%(!x!lS5D)P>qGDy zap0@%k!mha|A(ZEnaj|xKj;=VuLAZaz468}T%WAlzhfEr`hafU1U2vvx<6f;fa`9V zMB_lujeK{~{Cq|aBRy{Jq_ni1SH^)~7dpN|{S$W}zd7zp_38Wrx|mZtaGxUGeLw#_ z@`Xm}HfNm1{hrkQ@2_u0{bb#5e|;11cHL87k43%T=zjfYH_CrVs`{^uC_k&a6W_hm zULEOudZ?)u#vfQm9LHZdmG)&w$MfbRU%<;#<MjDh`S3(qx_-=aB%jQk6lbCTVQ$*6 z3>pWC|EKZ)K9z%_5eNSJ;php#eUtu}z8BB^tZvjV-CU7Quid@HZ_3f0NWA8n*E~D5 zQAyoxw{yF+MM?Ml+wW2T0o{?Ge+`73g}`m8e*yn8^3=p(+(|CE68#wkc_oftJ5~B0 zUY<(hna=-->HqY+ktctT@{_u4p=z{u;*M2eBT)X0?xDZjh4(-@9*_4S4taN|yl`4M z&fl*4-4Dj2ew6OE1Kq%VlWsHlfDh=F{~V})K=;w#P#^97`hP!W<87Tv|8tSfx_B*) zC+pg_M{@D{1G@kE?RYMF%Be}MOO7Wzd*@Wmgv8^<+kt=GSciT;tBY#sK)cDh_9b4f zWXiU2|L@O#5YV5b1q;h@AG3~M+<@~ZZ#$Ju_xp`*)Gyq~Q}ckXI_65WFF(fvx}T%p z^n6@cD6R|DkM($qLh*kZElVZ-{(YfT7X3TuJoM6lzP{8%WpCHdjD!>iOGxryC=kO5 zen*}jx1_g&m)O2=@F#r5cwO)_JsvcN`iG}rxvKFn#pA<XOgxX{On=99brR1Dtc=gv zNoAz%>BIA~eqWR9f<7(rypQoc{EH@Lzem3bL4(YWpMWyy@i1cm(=_98BP#zK?J|CE zYDy)(Pf{f&PK8|{T)j#l%t8lknm3~Ey6c7;7|)}>^EEGw{QvFUUu;|FdB<^@oLq~V zYoS9nIVc=4iX@AMYH&dsC~Xr<&X(5eg%MOyW+bN4Xo#WScozXYVKEuinLF=dH*;GA zokOXXK^<0RFQO=L6H#`oT?`1?iz+e-BNn9DQz>jww<^^Xlicrl505Fi&9Ge#{_^I< zIXr(~9)6zl{yFanWsa8zG+*O@Y5I!l3!;-2-?KUqOSM?47Uws|`*PXhy~4YT^VSd0 z;=SdIi}N}@m+H7{alNHlFCU~9-^(X!aro}|ZSi=a8jpJ`kyu*O4eg)4e6apfbKILQ zsD8g$*;9<fUeJ1(BViZc<7aJtQTc1B9k1g3j^o#^e|Wj^)kgKh3SDk|VaK^*R!yDv zX58bv{onmNDyJVfpniWnV``<}zhCwFk5zQz+S>D{kIksw_s3tl+4XaMYH{7KFZj;W zpZmH!&tm<U%7Vwr{k><;PP+7&qiRR5>sehre0aP13tC>c9XhE0#li`D{#XCQ)Q=DL zB!2qS_v_kv{y^ookbK&j{>MIzD?T?D`um5kTKs>;o)Zd%ykec!-?L}WqvqKi@AN-( z)4BXwFkkl{J-u^hANkG^J?9r%y`4Ss)t;!H8_sGRxOMgCKh{&%^Pd_AwyozIUk=6F z6zAQv;-l8nNPFh&n#Q}YZg1M=>$L<ao3BbU_x=&<&t}saq%~jtUfbK=V}7-1yib)| zT$*-iTaS(RQ{7KryY6>F@rM*YxA!cqttoEkJ$I@Pw)N$3u-&3<w|CI<-1ee6KRb@< zc0<v8!A;w9S6VM)`&Fv*+_e8Tz3<zOqphc){!dRSpQF*we&g=^{;lalwckUR!CdWp zw14$ms~wk)Z_M-RR8RlrTT@f^9+z>&8%u|j+H>Fc>$6?^Pb!}*Eh%3uRlen_sq*vT zFRQ-wz;7K#&%@R0<5yo&HB`iw*YD!;yLbG8`<=9%l{U?H`J(ezHWw`)l=|0&;?!OH z^Yc}|^PTh3@|N#+ruDm|)sNez!|EU4=da(N-OJ_mJHHq^e&U0VuWDSoNqKrz<;m1P zr!(8tUp3PD=pVW5<VvN@+jG~0?MDH}-al{c-1dK1c<$rU)kI5d&eQ#)*6*B5D*oE{ z;cf0b^x_yxhbl>zP88-`dgN$wQ#`N5^|W3<@5gc5kCofq``@geoR;6(9`EA1^2<;C z`g;AH{PMj1w|;yt4!YF+p0kSo_Pfq@t>}JYR;k@j)KZn_^+H06^TXaJez{)#8phN= zPwnba7w>gFo>IK;EmaB@*Y#XmHQrZTs&K40ulf3%Qu$qp+5?n&#eW-6`MzHr-7Ws_ z+c!RLaX+t$g%+hcO~vUeO|Rd7{~n9;>HeQxe=s)MIMEP|J{Swf))tBz;oT#RpU*6$ zt$p755!T|qmEXOKuKixe?|+{?>gx6Pe(;J*L-Dh}t6U$|eL;)L@2WoV%kf8(o67OS zipyEW|IwyUeCidYnvPCddwg2s^j>ayLB|&kUsiv7HLr%OUjFeD$NF77A6_ZBa(Xnc z?Tt2NH1PUT^J24ZbILB&`7Sp1??}(AUbDEqrWd6st~dT-Ih$4M#10K-ylb&p=b2tu z)P8n+VSjG?a^py&{Vyyn7cIw#|8||-tNCzR@xOo2;?7v2v-QH8*6&`|N6w{#gGWC1 z<khaQ2E@re{qWcAd67T<#=m?`&G&u3`m-N)t*Tt0@_!;amQ=rJ_1jU0+uD7?c2}OW z@`F|@ZkKZ!$FpK_ymv+MO5=P#a3h)eYR`-MU+2c3vhql!lC(6f{<Ic1qHq1+XXg`F zW1g3huP?^Z!^NW(|J&Pl?%Xa9?RPWJbpPw>C8IiSy>FwZ(mQvb)jxDj<Z{;UfBvlX z<KNz+?tOi=^@8>PzofUicc?&QabV?TE62I>F8^Q8=P9jUFSjkV_VxcwI-})2eLr=2 z9wf6~r?|EDo1b5|-&f<YDxT?mF~?3Sjvb7>{>BrJe){@;{<rIR?%Q%a?s}LHj?d2f z<NxtE?*1Kh9cBFS;KR{|Ty6i_Zrsano>oEX(&p(<)Vo-l4z_!%`;026x2s+(lwLk| zzT;r5O*bU&zElJ7TDs~LZs@x0*mLf~5C65h?y=`JV6FR|w(Kw1-ocoLlR17q@$HVU zbavP{IEjVjjg;b{wF77Dy)^X=yV|sVmzOK~aa(cHwx=+Y)c;EL<&`y;Dqi2}RvgZF zWovK0r1Gk3cNc2b&rWgQrAk~p*L`By;<x@bNtYIFe4bid*W~Yr>p`4Xe9zvtxbFJh z1*wbc`GPCQ>vp1Y-o96yxBhs_Z$Hg=YZmWym`O{^x)0wH=bODtF3x9*Wox(B{n0Ti z?=K%+D_LBRXgm-X*Bd(D=+X3e{w>A#@NWHHxd&rcnnT&_ptZYy;lfY$wWwX-yHjtz zrO|<Rn-gIzC)Wmp=_ge$pw}MOyYjrkpr!w=3V)09>Hcf~VC%Q$ysX9bpICWc)6W*w zUr%v3vh$1QY<^MqHJ@Hi*Yo%%elWGvJldpT&_YYic0QI4H`;pvEN-v<Wcr%L{ayN4 z>Z$$aa??m-f3a9xQa!#M-(vGpQ&Hm{+jRT2Vs=sM+kMMo^J0@dAGFx~!jA91{mwfU z&+{LB<i~YYSS%{uYxwK-#U?#}P%L^D|J6O<%@HkU#}TCWYTV<z{rf&WpOnyh@7hmm z+)j<_nQnbdJ#2E5$<uldOF22;spt9<I~%NC-^K~G_jzl%>mN_G${%ZYYt#Gr`qJsN z-3O>!^BcAJf3wT#b8pI}bpJ8c-yKx_!dIsBzjx2eYC3lO_|TA*m$r?JkI$-pv3ISM zcaEu`WR6-ry*XIvyY12&kL(ZC$={v&Bm4b}EH0@ZaVjMjZ+7M1om2hwt?tRro%Qbh zoB~hdi+Ahwz|ZOX?&46&EuUYTbE*FJ){fbw>Ss;g>MFSSA}=eYIhz(=&Qa_CW9Jdt zp>nzAi(|LEm#n64+Sc2;z1&*$dwJ@d*7kl!+O1{++n$xv{N;4qTb3*M<+s71RMO7d z>K**^ziT<SeZ>oZf8B~3b8Z}!BhNfz+f%VURoiZ`oGTA1AKdY8-EXkGDv<5_tTM#T z-{1bNu6$)}E?DnYcS}sew1xB>cPyOy=&vo+^W5*=>^>N4)j;fSy_BCH`MEj&d=+Rm zZ*N-nlP(`zyt95|eY#%@@-;X<?SI9^L$xQ{`^+_8Csw;2{NvO6&-MFNzt;~FY{xBE z^v|QH-_1?C^K$v4?Pz@s;AF?8!*i+5Q_E?--lTi8d%)gzF>^}oy9)pH7QgkoRn9AZ zo2$C}iDk2IH`)GvSt*rl`_p%R_u#rp_F2BH9)4Ew|5QguB&X-!^}K-k^PlfH)sa~q z8ob%9ad7ncC#~PtS5dsaS!-7>ZVb72o&Sv=w>Qdtv(<9An}0Ipo|iA^`g7AdkKlX7 z>#k7qZ7r`fy-;+i-Ye-+#cic}KE9H2sm9&3`4<)c##}t#aQ*G=e_{RYbsypS+p8a> zi|<k8gT?!>t{0t8LjSi-<LBu<Kp(#x?=AajPW|&V-u*^7Wz!p}Wbc5cEv^kDE;k;( zcI~#6+v~%Th6h!?xAh;4U2eMn(qQqRdaxf(TlxJ;Q%Ucouy|fs%cUREblYbeA`hv) z|D7MKuLtqImX0)Oc>ewtJtuGPJ+9T?yK-`WL~;HzJFUNbP|tpMI@)wx4;V&tAE4)) z>TAm_HS2a$_3)#O>BYezi}Skd!?m~{#DA6Fm2XQ;{f*aF7ZxmT@7nqPnx%FfSvoV5 zN?AL8{rJkaEX`~rOINJF-oE+NQqyo_`-Q?kT3pxb!d#sH{<Ui-MpXV+_B4#BUf<qJ zct!Dk`)77Wu4p;SH=9;}iUt4saLJ|1>-LH$t9DS)i}YU12aW2Tc;bNv9<cJU&cFZk z)3>^hheE$Ot@`tAx}Jh~o>lx$D*mgTAR(461pWTo^!%UrRh&Jac>Ua-J$img?;Y{H zmk!PP@xNNGu;+Qp*PlK9ZMmuPz{^)Pee%Ymt$|DC)R5YzE!A7k<dWj8J~ka!9J6#? zf!<PU&)odI{x?Z`p7_QaXCG<T`#RL$y7bhc+3v7cJlUq>xUJ=Fy^P9BmR{0*zNK2P z<WkkAR;P8}G23-<IvITL_G|ykS<MfQN5g*l-z#q0^4VazdYoE5<+i5)<x;I*O@r<F z`wix+{?fjW<|eB1)y}GEuQ0i0sm{YqXS}NS<vxGAxsv-{@h{k}zh1Ci#m`$>|BU+i zy6qLK$D#P@rmM^8y)pBfr|o<<PnQM0o7NA#dA_H>5KO!KHn-kD#|ahOZgvgmJp^$* z-|h1K&T?+~>UzVTpO4?0n^>{=>IUX7cU19F(|7VQ_<e$WuKu37zwysMSWnkMbv^(5 zF5dY*nD6Iru$}7fce_%}*OlkD<cn9R&M#JvEAQufQ8#R}dTxE{*Vhl*^J(f|`d>fP z{q3mwdCcm$^F3c|+vSd<dYqy6{Oeb*C345Hq5DkxKj`(AGxmQ7@5@cN?WEJ+w0eb6 z#Y6q-yW93RL}rx_cHLPV)_w|GrdN-jusE$@aVIRz=)Ey+xpXy->pHagm&(>2@7mWj zp8acyz0-R`W198`2mQFN|F4zTb($`&t6spxan*;rdV00Dlr5gC8=i~n(<=9#(zG2{ zEgeXN_FhuCJ(#a)-4|GVxB3Ugb+`P0?sG1BUfjibo4@J#I%@~8_W0&YHr~yZrnlbu zwTu6DJ%(cs?#{>K@$hc-`<t2RxB7sc_1hHx?Kyh2zjs75e$RmeEqXq0Tm9dTTRDEU ze`YnjTlMZ<{#}iCV{!1Cs&{YNIQMvai{kgLFI>3LqBw8&J2tKNeh*rEc|;9yu3bHQ zsqEU%i^WQ{9o@C34<&n5?pL{fVq(JPujqGPvhui&f3ZpT2dh6jY~}UvnT;b$%_?uZ z?-ie;h3_pX9$P-!a{a}P4U5AKt)GNl{0_f2XK{Y$M(Zw1U;M@OUkBy%ARSd4fBX;k z++U09uQnb?`|-V$T+djX@0;2ezM}2xae;Jgx!Qd|Dr3vP+Nj4AX#dx5q3hzFz5h|1 z)_bi#^S+h;PkgX8*{bq??<bkiY0Ymvn@rh!^~2tF?CH~o=1TVb+L*m>?S!=tj89BV zwyONE{>XiLZ*by)2Oiz;;=kw3y`*t=to-jh(*JW-@t{9+#xwWy7*5=#TXlWf_j~2c z7T40=>oz}~w)t{u+C0>8csaQ%2eVe*8B6H)ch1s<A*<IP>+0;B(EGskMlkP}uh_KO z`CU0Dtb&{^7gzg&r6IMW`l&fAH&xzu-@D^9M^|jWT{<lLzLvV<)O4n3$J3?u_mWHf z`H8q!cH6(RzS`^ceIgt$RLfhJ{N)vIZN92u{r!Y;HecsA?bc6c{QgnqubUq=_q9Kb zQ|eNG{jP8{SDl~s=f?~F@#@7aZaEE@ZfU&yjKBZkkG7sqaQtbvADvIZj!XNwbAGmb zLiMXDe|y#KhTU>H-s*ChPquEinop1Kt#0>De!25h9CO=K|1Ot**6w<#x_vue`+iFr zj&7a4`+LP19k=c)L+PV_8lQ99Y4GzuQ{3>6Uk{+T`O6zgrHQaAaBTX6M>_TXciV4z z*-!obrTdS#<wCiMHJd-PP_*Mp?0R=z`T3;kr!wA{wcDpO-i*@c&&G8<KiN_5dD`x4 z36<wHUY)Mf8z1VpbUiPA%Z@|W;au-*x85&Pnb&eHvH2qlX^pxO^Kvg|$GR4q%99O> z_kYpx>TpK?m$Af(o;w>;YH`rUv9W$$f1~NSwMx<AwyJ|t)}Fq$Rw=mp_UP1{OVd_9 ze=U(29#(oPu|wnNx%vA4Sh_IO=i<3;gIsF;18ea+h~q&z(6Lt)Ko-yS^Z0Q+NM)$S zbF1fYaoxM%%JJ53&f>X#FZI9E^Yi*0EcH||Vd?V5(LU>6r}A*lPdCagRlkdaD$lQ9 zTkW@aABsFta;YxN@NSil2ZyFxVyeH7ghRD{cVUbFDJ#E6-uufxxB7YwBd$0*(wJPg z=if#eORc-6BC*A$v2<4PeYENK9%|cPE5DC6sy)V)|D!5D{C2s0$;Qi3J^Xu3dOl!i zQRVOkz2C^{;fuO2UQ}EU>H9^+-;C-PE~_0rn;*{waoghi`ttI!#ociI&Q^=h3k$^| z#p_0m|KDKg)Q>K_WAoFxp&!+9Z>rzb=Jm60ue<w&&#MEJEvMtDSgQTHRL>8%RNbsy zI;`KvEmx?f)$cW1+t+&caNJWC+|zMCIDd=hU;C>g*Z<_Pn_ZKoQpx)N>F=1U({z72 zt^R4ViEZ_Dbv6!3zECK<r22oWmp`rbO1(p>_dk`W%;ns)x}B??;H5*AVsY`ML*bbE z|2->ZV|(Y<N;YmssG)PpoJ#2h0hacrDjUu<9n6^Cw`JF(&3{?_g2uY!j<=C?UMZeg zy7%0?oEng;8z<d-m1AwW%A@z)XXVd{Qc3l+8vjET&-VTA7n1J%U7_ZJ?_tRM#O7zR zw%ykfUbrD=(_y*dwilnCcWG$YEoX5iIIdc1{-!tl<-E0Os^!edYCC7V{6TfPy5DI2 zyv|Eb`^z=I{h|N-vh7#fi#ANy@|pGZqi(&h-yhokr~Y=bCtZGoE7ko?-|*wXivRuX z=aO!@_{65;@$=JdKjans?P$D|&BqxW@22y#{GYo~$@|O2-FoRDf1;K@!FKI@?Ry=6 zddAPc{jHP!ddoxZc+UjKyEz|SJ{7j)hwcyDdeb&utK~;d*Nc|RR<u7&+wtkRYWY#k zXWie}^`-WmYcIwkyDRnk;xEP&hl|TayWS$Z+NW$gsz2?u^RjyM&F7VE&XzCO{)Pvi zar@J_6YhJ<f92Cm#!vT`k0jmvXs65H)f3OU-zhvXIcM{ATb4R+<IbI!Upr|1%%ZLx zV0Y(yGNt3t__mR8x4xHhaaOlIeyaORxBjqKI<M_qoL4_N`+dTruedbpf4_Xv%G(3_ zKYZ7JuKi*@@22-=Z`*P!spmJv?SaIOtUW)kIGw5Vx#dD<N;bVWuJXK6b=OciUGJw+ z+_3bF?&EB}im!fLSAGA0O&6;1T-TvZD}4CzJhSeei`UQ6=i<BS)hvBm^-`8vyUUYG z?f+a$t$$s5LGA3Z(B5g4?=_t+exm7)GpeVs`P#ortsS5xw%B|?9px;}m&zwH7Qc(B z`Moy%L}!EA;Z<(mNNKr9ET{XaAdW98Zcbmg@FR=I>UZzrvRyZeDu?fR{q=L!9v?~{ zIAQU>zCP#q<#umlsoBQQ&19{9J@R+?3pTCCAu28{`te<f;_0i6X}w=qakWvc1S&@> z?(0)a_1wM2F|~LfiA27=C4TFGEUwqJ-*?}r;{D9bkF~bO572gPe$)34_m67-+FXOa z-|@~n>;13#_4*;DJM?=MM;g0#JpOoFyW+R?<I9ce{zkKHyQ=q(4%&L5(Cyw)*A5V> zuP#>^(ela;?PsJhCx}M1orTrgA8XgdL1j4kz;$5%W3B_P1Fi%92RMKOIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8< zfCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y z12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*K zIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KIDi8<fCD&y12}*KI1mc` EH_FOlrT_o{ diff --git a/img/helpmenu/startjobmenuhelp.dds b/img/helpmenu/startjobmenuhelp.dds index c3e8ed21786f016f6ea6191ae2156a7a0145db7b..65c9a21c43c2c628f289a488a4dd037eaf854fc0 100644 GIT binary patch literal 524416 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%0Dhrnxzv<~R1pAv3gzuwN4T<4cDXtL1r<Zcb8v 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 z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej z=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhrfDX_BIzR{L03DzMbbt=f0Xjej p=l~s{19X56&;dF?2j~DDpaXP(4$uKQKnLgm9iRhr;H&7se*uaOc>DkW literal 524416 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 +=2!8OCWk3%&JEU)d_&owJ8Q@d!dPA1O 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 NIKTl8aNy;3;7`$if^z@> diff --git a/img/helpmenu/vineworkgen.dds b/img/helpmenu/vineworkgen.dds index 2b915399585264865d779c27c91bd3d1061a4993..e829aefe8a927c76b9456ff39dd62c94318406da 100644 GIT binary patch literal 524416 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 z0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo z8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}Z zzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv z02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t z2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PU zZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u z00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv z0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo z8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}Z zzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv z02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t z2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PU zZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u z00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv z0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo z8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}Z zzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv z02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t z2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PU zZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u z00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv0dN2u00+PUZ~z<t2fzVv02}}ZzyWXo8~_Kv U0dN2u00+PUZ~z>*H8}A90atvq)Bpeg literal 524416 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 zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB zf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pBf&+pB Qf&+pBf&+pB3vuB82d_ezK>z>% From ee333db6652dc8521f630fcfc224e782567c2a8f Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Fri, 13 Dec 2024 19:36:00 +0100 Subject: [PATCH 106/158] Update VehicleConfigurations.xml --- config/VehicleConfigurations.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index 388b1cea2..d36fd84b0 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -505,7 +505,6 @@ You can define the following custom settings: baleCollectorOffset = "1.6" /> <Vehicle name="pw10012.xml" - noReverse = "true" implementWheelAlwaysOnGround = "true" tightTurnOffsetDistanceInTurns = "1" workingWidth = "5.8" From 727cdbfdc98caf52bca622fffb1e06b7cb0c771a Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 14 Dec 2024 09:47:22 +0100 Subject: [PATCH 107/158] WIP using new overlay manager for sprites --- Courseplay.lua | 3 + img/iconSprite.xml | 79 +++++++++ img/ui_courseplay.dds | Bin 524416 -> 524416 bytes img/ui_courseplay.xml | 57 ++++++ scripts/gui/CpGuiUtil.lua | 50 +++--- scripts/gui/CpInGameMenu.lua | 21 +-- scripts/gui/hud/CpBaseHud.lua | 173 ++++++------------- scripts/gui/hud/CpCombineUnloaderHudPage.lua | 6 +- scripts/gui/hud/CpFieldworkHudPage.lua | 21 ++- scripts/gui/hud/CpHudInfoTexts.lua | 12 +- scripts/gui/pages/CpCourseManagerFrame.lua | 6 +- 11 files changed, 250 insertions(+), 178 deletions(-) create mode 100644 img/iconSprite.xml create mode 100644 img/ui_courseplay.xml diff --git a/Courseplay.lua b/Courseplay.lua index 2032ddd58..2e4af0576 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -17,6 +17,9 @@ function Courseplay:init() createFolder(self.baseDir) --- Base cp folder self.cpFilePath = self.baseDir.."courseplay.xml" + + g_overlayManager:addTextureConfigFile(Utils.getFilename("img/iconSprite.xml", self.BASE_DIRECTORY), "cpIconSprite") + g_overlayManager:addTextureConfigFile(Utils.getFilename("img/ui_courseplay.xml", self.BASE_DIRECTORY), "cpUi") end function Courseplay:registerXmlSchema() diff --git a/img/iconSprite.xml b/img/iconSprite.xml new file mode 100644 index 000000000..af1a244ff --- /dev/null +++ b/img/iconSprite.xml @@ -0,0 +1,79 @@ +<texture> + <meta> + <filename>iconSprite.dds</filename> + <size width="256" height="512" /> + </meta> + <slices> + <slice id="white_harvesterExclamationmark" uvs="4px 4px 32px 32px"/> + <slice id="white_playStopPauseRecord" uvs="40px 4px 32px 32px"/> + <slice id="white_globe" uvs="76px 4px 32px 32px"/> + <slice id="white_sliders" uvs="112px 4px 32px 32px"/> + <slice id="white_harvesterMagnifyingGlass" uvs="148px 4px 32px 32px"/> + <slice id="white_cruiseControl" uvs="184px 4px 32px 32px"/> + <slice id="white_cogwheel" uvs="220px 4px 32px 32px"/> + <slice id="white_vehicle" uvs="4px 40px 32px 32px"/> + <slice id="white_fieldCourse" uvs="40px 40px 32px 32px"/> + <slice id="white_shovel" uvs="76px 40px 32px 32px"/> + <slice id="white_refillCourse" uvs="112px 40px 32px 32px"/> + <slice id="white_refillCourseCombine" uvs="148px 40px 32px 32px"/> + <slice id="white_refillCourseCombinePlate" uvs="184px 40px 32px 32px"/> + <slice id="white_refillCourseSpreader" uvs="220px 40px 32px 32px"/> + <slice id="white_driveToCourse" uvs="4px 76px 32px 32px"/> + <slice id="white_fieldworkCourse" uvs="40px 76px 32px 32px"/> + <slice id="white_bale" uvs="76px 76px 32px 32px"/> + <slice id="white_refillCourseTank" uvs="112px 76px 32px 32px"/> + <slice id="refillCourse" uvs="184px 76px 32px 32px"/> + <slice id="refillCourseCombine" uvs="220px 76px 32px 32px"/> + <slice id="refillCourseCombinePlate" uvs="4px 112px 32px 32px"/> + <slice id="refillCourseSpreader" uvs="40px 112px 32px 32px"/> + <slice id="driveToCourse" uvs="76px 112px 32px 32px"/> + <slice id="fieldworkCourse" uvs="112px 112px 32px 32px"/> + <slice id="bale" uvs="148px 112px 32px 32px"/> + <slice id="refillCourseTank" uvs="184px 112px 32px 32px"/> + <slice id="shovel" uvs="220px 112px 32px 32px"/> + <slice id="waypoint" uvs="4px 148px 32px 32px"/> + <slice id="parking" uvs="40px 148px 32px 32px"/> + <slice id="triangleCross" uvs="76px 148px 32px 32px"/> + <slice id="infoCircle" uvs="112px 148px 32px 32px"/> + <slice id="eye" uvs="148px 148px 32px 32px"/> + <slice id="copy" uvs="184px 148px 32px 32px"/> + <slice id="save" uvs="220px 148px 32px 32px"/> + <slice id="leftArrow" uvs="4px 184px 32px 32px"/> + <slice id="rightArrow" uvs="40px 184px 32px 32px"/> + <slice id="upArrow" uvs="76px 184px 32px 32px"/> + <slice id="downArrow" uvs="112px 184px 32px 32px"/> + <slice id="crossCircle" uvs="148px 184px 32px 32px"/> + <slice id="trashcan" uvs="184px 184px 32px 32px"/> + <slice id="favoriteFolder" uvs="220px 184px 32px 32px"/> + <slice id="folder" uvs="4px 220px 32px 32px"/> + <slice id="plusFolder" uvs="40px 220px 32px 32px"/> + <slice id="upFolder" uvs="76px 220px 32px 32px"/> + <slice id="downFolder" uvs="112px 220px 32px 32px"/> + <slice id="plusCircle" uvs="148px 220px 32px 32px"/> + <slice id="minusCircle" uvs="184px 220px 32px 32px"/> + <slice id="refresh" uvs="220px 220px 32px 32px"/> + <slice id="magnifyingGlass" uvs="4px 256px 32px 32px"/> + <slice id="clear" uvs="40px 256px 32px 32px"/> + <slice id="calculator" uvs="76px 256px 32px 32px"/> + <slice id="rightArrowCourse" uvs="112px 256px 64px 32px"/> + <slice id="leftArrowCourse" uvs="184px 256px 64px 32px"/> + <slice id="rightTurn" uvs="4px 292px 32px 32px"/> + <slice id="leftTurn" uvs="40px 292px 32px 32px"/> + <slice id="loadingShovel" uvs="76px 292px 32px 32px"/> + <slice id="unloadingShovel" uvs="112px 292px 32px 32px"/> + <slice id="preUnloadShovel" uvs="148px 292px 32px 32px"/> + <slice id="transportShovel" uvs="184px 292px 32px 32px"/> + <slice id="play" uvs="220px 292px 32px 32px"/> + <slice id="uturn" uvs="4px 328px 32px 32px"/> + <slice id="pause" uvs="40px 328px 32px 32px"/> + <slice id="stop" uvs="76px 328px 32px 32px"/> + <slice id="reverse" uvs="112px 328px 32px 32px"/> + <slice id="trashcanDownArrow" uvs="148px 328px 32px 32px"/> + <slice id="exit" uvs="184px 328px 32px 32px"/> + <slice id="waypointBroken" uvs="220px 328px 32px 32px"/> + <slice id="circle" uvs="4px 367px 26px 26px"/> + <slice id="silo" uvs="220px 364px 26px 26px"/> + <slice id="unload" uvs="4px 400px 26px 26px"/> + <slice id="white_silo" uvs="220px 400px 26px 26px"/> + </slices> +</texture> \ No newline at end of file diff --git a/img/ui_courseplay.dds b/img/ui_courseplay.dds index 4060a254b7231672dcf1310a4a8f65a4c5e009f3..6d880a561adf61ced60db6ce0ba4e9be390b9c25 100644 GIT binary patch delta 730 zcmb7=O=uHQ6or{LbDN(=I|M5>sp(K_97@1Yl$7{GgjTAS5<(Zc5XFs9C}<JmW*|j0 z5C&x&$OWyoD2-6GZ4CCJMCqaiapO)z(j=&KArz$)E5V>M6G^*uaR<)(?m6$gNsLR0 zacO>%QAdb*8F@lX`tNTxWoP>5wwIdv6T@%$ZvBbZp^v`Url$hl+^TnuY3h*v&G$@G z)43jLgyCSHa1HYraOQk_Zt}VsjiB1e$v9DD1uYf1?v|5onKOnN!;@PU{Ok5YEmJFQ z#LE<EhhO$Baxb)U{FQPI4G_Z_KWw+ZsZCqExafz0h9Yqv1UH=+?1D}j*$)|Lq+#$V zgei6qW?F=evQk{=hus_>FDoP+hgBON|FcZu5G=Q_Vw}F8g)@$Zm7*8noRi!6c>XfD z8mH+4M&6nNIU8Rtr3`hF)^5O<-At!!qKBiqx1qbqkfpB%9Y8S+!vamdA}ujSpVCk@ znNl%W?^bq!-aP=1&GfAsOx|P|&2qzJ{M}xYDi!mY9(2z_%!<_$(Lp_M%tec{&{y~4 zk3(=8&))*!-^F2kxBwH3Z@zP)6-Spq(NZ#12|QMps@1cxu2(JvF#iY~bpJIZtvicW zTWcfKaU_J|J)w)#0(`Uyrmj1_zz!5MTsyi>aaSq)9-MWr*>_jHjb)xnvcvd$nro)H g5AeL6fYKD-PftrgmiAe1PUcs@m?p}v*p}M<0xu~oVgLXD delta 1238 zcmY*XZAcVR6m>rCy5^)JE2M5EW-1~jVIf<XL5Y13v9wJ7h>`vjfnXB-lbMJTM6GSW z2Vox+sfd)D<tvHwM_7au1k*xLNI^toi8K|m({$gPaoa9D?w)(jx%a-eW1KBB&X$c| z7uV}qSuTbe^Rne<*RRuWB7Bz0b=9L|KJC~Z*3xxyqbblF?~kw7{#V+ThjqVB@>&Di z61^j0y{4&2#yL2iWzEkz2E#T6ulv|>Y&&7Rl(u-ZW!ny;S#Y_ZRgBEKg*a}^)vHO% zU)s|reZ7Xk^bQ8gs*hPEVa$oLg%LMG;f%dvOsMNBznkqiE(#@?bs}wh17R}Q5(p}& z;5s%qZah^o{D?y#Cie$(ou<WXGBXmWNc8>`%n!v=ax%$Q52c+ke37{Enna#UHRf1p zj@kZbwb@f!I<ZiCS5!0{;Pqpw5MP4a9}iTTU)J4|ZJXahS($3#bxp%MJyBnFLafkq z?@^psj6bFxvv}^(b)Ips8#kh4Y6z5xbsH{7VN2CG<NA(Yw!<;Lwo5v;OB+dFxlm;) zRhKGM?9LWjk!qIBF|NWhZo-<ME3ED=tQPQycqCd_Em!^sK68eg6nbe>7=8yc;6^AC zBxDE@Q8HQydBIudHrJAwN@z(VT2KXb|D2?*0?O&h%`gP%ZltaeA~d=cM)PHym73fG zYXyl-Eo+8_Z2qg@AiT>LQ!f2}6k2oAR(j|-Xsd*Q;qg<Tq^kq<*l(!|CeZt*2hl#d zbRIf`towAr*dov?7ojGA@XuFdTS&nG9G0gs;zlQZI{?d!bLWw3$mEXE7uTR9oB2}6 zRcCThivLc8aqc|3WC<y}15w`8Y??V>*6vd1%pKV6xyj;oI6~U`LH5T+$jwO@6mxSg z?=2$jPoayP+$U_Bd<Y@F$}(Rwtv5URYsuqVkVCINfo?vOE!5z3+=;<I0@^nV$!wWn z&+!pLq+m!06J@_}hDKh&D$mOnUXx*7yddo$soO#>z550pcpb=%VR1Jddk+seZp3e< Qd+HP53DBue*|Wue0rb**rvLx| diff --git a/img/ui_courseplay.xml b/img/ui_courseplay.xml new file mode 100644 index 000000000..b05e8b242 --- /dev/null +++ b/img/ui_courseplay.xml @@ -0,0 +1,57 @@ +<texture> + <meta> + <filename>ui_courseplay.dds</filename> + <size width="1024" height="1024" /> + </meta> + <slices> + <slice id="harvesterExclamationmark" uvs="0px 0px 128px 128px"/> + <slice id="navigation" uvs="128px 0px 128px 128px"/> + <slice id="navigationPath" uvs="256px 0px 128px 128px"/> + <slice id="slider" uvs="384px 0px 128px 128px"/> + <slice id="harvesterSearch" uvs="512px 0px 128px 128px"/> + <slice id="cruiseControl" uvs="640px 0px 128px 128px"/> + <slice id="cogwheel" uvs="768px 0px 128px 128px"/> + <slice id="vehicleCogwheel" uvs="896px 0px 128px 128px"/> + <!-- <slice id="todo" uvs="0px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="128px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="256px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="384px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="512px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="640px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="768px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="896px 128px 128px 128px"/> --> + <!-- <slice id="todo" uvs="0px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="128px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="256px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="384px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="512px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="640px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="768px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="896px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="0px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="128px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="256px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="384px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="512px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="640px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="768px 384px 128px 128px"/> --> + <!-- <slice id="todo" uvs="896px 384px 128px 128px"/> --> + <slice id="plus" uvs="0px 512px 128px 128px"/> + <slice id="minus" uvs="128px 512px 128px 128px"/> + <slice id="exit" uvs="256px 512px 128px 128px"/> + <slice id="leftArrow" uvs="384px 512px 128px 128px"/> + <slice id="rightArrow" uvs="512px 512px 128px 128px"/> + <slice id="upArrow" uvs="640px 512px 128px 128px"/> + <slice id="downArrow" uvs="768px 512px 128px 128px"/> + <slice id="trashcan" uvs="896px 512px 128px 128px"/> + <slice id="trailer" uvs="0px 640px 128px 128px"/> + <slice id="copy" uvs="128px 640px 128px 128px"/> + <slice id="paste" uvs="256px 640px 128px 128px"/> + <!-- <slice id="todo" uvs="384px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="512px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="640px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="768px 256px 128px 128px"/> --> + <!-- <slice id="todo" uvs="896px 256px 128px 128px"/> --> + <slice id="skip" uvs="0px 768px 128px 128px"/> + </slices> +</texture> \ No newline at end of file diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua index f11860ba0..936f4ae88 100644 --- a/scripts/gui/CpGuiUtil.lua +++ b/scripts/gui/CpGuiUtil.lua @@ -300,20 +300,30 @@ end ---@param iconData table filename, uvs ---@param color table r, g, b, alpha ---@param alignment table vertical, horizontal alignments ----@param sliceId string|nil slice id -function CpGuiUtil.createOverlay(size, iconData, color, alignment, sliceId) +---@return table +function CpGuiUtil.createOverlay(size, iconData, color, alignment) local filename, uvs = unpack(iconData) local overlay = Overlay.new(filename, 0, 0, unpack(size)) - if sliceId then - overlay:setSliceId(sliceId) - else - overlay:setUVs(uvs) - end + overlay:setUVs(uvs) overlay:setColor(unpack(color)) overlay:setAlignment(unpack(alignment)) return overlay end +--- Creates a new overlay with the overlay manager +---@param sliceId string slice id +---@param size table x, y +---@param color table r, g, b, alpha +---@param alignment table vertical, horizontal alignments +---@return table +function CpGuiUtil.createOverlayFromSlice(sliceId, size, color, alignment) + local overlay = g_overlayManager:createOverlay(sliceId, 0, 0, unpack(size)) + overlay:setColor(unpack(color)) + overlay:setAlignment(unpack(alignment)) + return overlay +end + + --- Enable/disable camera rotation when a vehicle is selected. We want to disable camera rotation per mouse --- when we enable the mouse cursor so it can be used click controls on a GUI ---@param vehicle table @@ -349,26 +359,26 @@ end ---@param hMargin number ---@param line number function CpGuiUtil.addCopyAndPasteButtons(layout, baseHud, vehicle, lines, wMargin, hMargin, line) - local imageFilename = Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY) - local imageFilename2 = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local leftX, leftY = unpack(lines[line].left) local rightX, rightY = unpack(lines[line].right) local btnYOffset = hMargin/3 local width, height = getNormalizedScreenValues(22, 22) - local copyOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(CpBaseHud.uvs.copySymbol))}, - CpBaseHud.OFF_COLOR, - CpBaseHud.alignments.bottomRight) - - local pasteOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(CpBaseHud.uvs.pasteSymbol))}, + local copyOverlay = CpGuiUtil.createOverlayFromSlice( + "cpUi.copy", + {width, height}, CpBaseHud.OFF_COLOR, CpBaseHud.alignments.bottomRight) - - local clearCourseOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename2, GuiUtils.getUVs(unpack(CpBaseHud.uvs.clearCourseSymbol))}, + + local pasteOverlay = CpGuiUtil.createOverlayFromSlice( + "cpUi.paste", + {width, height}, + CpBaseHud.OFF_COLOR, + CpBaseHud.alignments.bottomRight) + + local clearCourseOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.clear", + {width, height}, CpBaseHud.OFF_COLOR, CpBaseHud.alignments.bottomRight) diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 9e15d73b7..b68f2a22c 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -128,51 +128,42 @@ function CpInGameMenu:setupMenuPages() function () return true end, - {768, 0, 128, 128} + "cpUi.cogwheel" }, { self.pageVehicleSettings, function () return self.currentVehicle ~= nil end, - {896, 0, 128, 128} + "cpUi.vehicleCogwheel" }, { self.pageCourseGenerator, function () return true end, - {128, 0, 128, 128} + "cpUi.navigation" }, { self.pageCourseManager, function () return true end, - {256, 0, 128, 128} + "cpUi.navigationPath" }, { self.pageHelpLine, function () return true end, - nil, "gui.icon_options_help2" } } - for i, pageDef in ipairs(orderedDefaultPages) do - local page, predicate, iconUVs, sliceId = unpack(pageDef) - + local page, predicate, sliceId = unpack(pageDef) if page ~= nil then self:registerPage(page, i, predicate) - local normalizedUVs = nil - local path = nil - if iconUVs then - normalizedUVs = GuiUtils.getUVs(iconUVs) - path = Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY) - end - self:addPageTab(page, path, normalizedUVs, sliceId) + self:addPageTab(page, nil, nil, sliceId) end end end diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 67ce2d1a5..f3729960f 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -35,75 +35,6 @@ CpBaseHud.defaultFontSize = 16 CpBaseHud.numLines = 8 -CpBaseHud.uvs = { - plusSymbol = { - {0, 512, 128, 128} - }, - minusSymbol = { - {128, 512, 128, 128} - }, - leftArrowSymbol = { - {384, 512, 128, 128} - }, - rightArrowSymbol = { - {512, 512, 128, 128} - }, - pasteSymbol = { - {255, 639, 128, 128} - }, - copySymbol = { - {127, 637, 128, 128} - }, - driveNowSymbol = { - {0, 768, 128, 128} - }, - exitSymbol = { - {148, 184, 32, 32}, {256, 512} - }, - circleSymbol = { - {0, 366, 28, 28}, {256, 512} - }, - pauseSymbol = { - {40, 328, 32, 32}, {256, 512} - }, - clearCourseSymbol = { - {40, 256, 32, 32}, {256, 512} - }, - eye = { - {148, 150, 32, 32}, {256, 512} - }, - refresh = { - {220, 222, 32, 32}, {256, 512} - }, - cpIcon = { - {80, 26, 144, 144}, {256, 256} - }, - shovelSymbol = { - {128, 128, 128, 128} - }, - bunkerSymbol = { - {256, 128, 128, 128} - }, - fieldWorkSymbol = { - {7*128, 128, 128, 128} - }, - streetLoadAndUnloadSymbol = { - {0, 3*128, 128, 128} - }, - unloaderSymbol = { - {128, 3*128, 128, 128} - }, - streetDriveToSymbol = { - {5*128, 3*128, 128, 128} - }, - baleFinderSymbol = { - {7*128, 3*128, 128, 128} - }, - playSymbol = { - {224, 294, 32, 32}, {256, 512} - } - -} --- Vertical + horizontal overlay alignment CpBaseHud.alignments = { bottomLeft = {Overlay.ALIGN_VERTICAL_BOTTOM, Overlay.ALIGN_HORIZONTAL_LEFT}, @@ -216,7 +147,7 @@ function CpBaseHud:init(vehicle) local cpIconWidth, height = getNormalizedScreenValues(22, 22) local cpIconOverlay = CpGuiUtil.createOverlay({cpIconWidth, height}, {Utils.getFilename("img/courseplayIconHud.dds", Courseplay.BASE_DIRECTORY), - GuiUtils.getUVs(unpack(self.uvs.cpIcon))}, + GuiUtils.getUVs({80, 26, 144, 144}, {256, 256})}, self.BASE_COLOR, self.alignments.bottomLeft) self.cpIcon = CpHudButtonElement.new(cpIconOverlay, self.baseHud) @@ -253,12 +184,12 @@ function CpBaseHud:init(vehicle) -------------------------------------- --- Exit button local exitWidth, height = getNormalizedScreenValues(18, 18) - local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local exitBtnOverlay = CpGuiUtil.createOverlay({exitWidth, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.exitSymbol))}, - self.WHITE_COLOR, - self.alignments.bottomRight) - + local exitBtnOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.crossCircle", + {exitWidth, height}, + self.WHITE_COLOR, + self.alignments.bottomRight) + self.exitBtn = CpHudButtonElement.new(exitBtnOverlay, self.baseHud) local x, y = CpBaseHud.x + self.width -exitWidth/3 , CpBaseHud.y + self.height - headerHeight + self.hMargin/12 self.exitBtn:setPosition(x, y) @@ -266,11 +197,11 @@ function CpBaseHud:init(vehicle) vehicle:closeCpHud() end) local width, height = getNormalizedScreenValues(20, 20) - local helpMenuOverlay = CpGuiUtil.createOverlay({width, height}, - {"dataS/menu/gui.png", {nil}}, - self.WHITE_COLOR, - self.alignments.bottomRight, - "gui.icon_options_help2") + local helpMenuOverlay = CpGuiUtil.createOverlayFromSlice( + "gui.icon_options_help2", + {width, height}, + self.WHITE_COLOR, + self.alignments.bottomRight) self.helpMenuBtn = CpHudButtonElement.new(helpMenuOverlay, self.baseHud) local x, y = CpBaseHud.x + self.width -width/3 - exitWidth - self.wMargin/2, CpBaseHud.y + self.height - headerHeight + self.hMargin/12 @@ -283,10 +214,12 @@ function CpBaseHud:init(vehicle) --- Create start/stop button local onOffBtnWidth, height = getNormalizedScreenValues(20, 20) - local onOffIndicatorOverlay = CpGuiUtil.createOverlay({onOffBtnWidth, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.playSymbol))}, - self.OFF_COLOR, - self.alignments.bottomRight) + local onOffIndicatorOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.play", + {onOffBtnWidth, height}, + self.OFF_COLOR, + self.alignments.bottomRight) + self.onOffButton = CpHudButtonElement.new(onOffIndicatorOverlay, self.baseHud) local x, y = unpack(self.lines[8].right) self.onOffButton:setPosition(x, y - self.hMargin/8) @@ -296,11 +229,11 @@ function CpBaseHud:init(vehicle) --- Create start/stop field boarder record button local recordingBtnWidth, height = getNormalizedScreenValues(18, 18) - local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local circleOverlay = CpGuiUtil.createOverlay({recordingBtnWidth, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.circleSymbol))}, - self.OFF_COLOR, - self.alignments.bottomRight) + local circleOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.circle", + {recordingBtnWidth, height}, + self.OFF_COLOR, + self.alignments.bottomRight) self.startStopRecordingBtn = CpHudButtonElement.new(circleOverlay, self.baseHud) local x, y = unpack(self.lines[8].right) x = x - onOffBtnWidth - self.wMargin/2 @@ -314,11 +247,12 @@ function CpBaseHud:init(vehicle) end) --- Create start/stop field boarder record button - local circleOverlay = CpGuiUtil.createOverlay({recordingBtnWidth, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.pauseSymbol))}, - self.OFF_COLOR, - self.alignments.bottomRight) - self.pauseRecordingBtn = CpHudButtonElement.new(circleOverlay, self.baseHud) + local pauseOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.pause", + {recordingBtnWidth, height}, + self.OFF_COLOR, + self.alignments.bottomRight) + self.pauseRecordingBtn = CpHudButtonElement.new(pauseOverlay, self.baseHud) local x, y = unpack(self.lines[8].right) self.pauseRecordingBtn:setPosition(x, y) self.pauseRecordingBtn:setCallback("onClickPrimary", self.vehicle, function (vehicle) @@ -329,11 +263,11 @@ function CpBaseHud:init(vehicle) --- Clear course button. local width, height = getNormalizedScreenValues(18, 18) - local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local clearCourseOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.clearCourseSymbol))}, - self.OFF_COLOR, - self.alignments.bottomRight) + local clearCourseOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.clear", + {width, height}, + self.OFF_COLOR, + self.alignments.bottomRight) self.clearCourseBtn = CpHudButtonElement.new(clearCourseOverlay, self.baseHud) local x, y = unpack(self.lines[8].right) x = x - 2*width - self.wMargin/2 - self.wMargin/4 @@ -346,11 +280,11 @@ function CpBaseHud:init(vehicle) --- Goal button. local width, height = getNormalizedScreenValues(28, 28) - local goalOverlay = CpGuiUtil.createOverlay({width, height}, - {"dataS/menu/gui.png", nil}, - self.OFF_COLOR, - self.alignments.bottomRight, - "gui.aiParameterPosition") + local goalOverlay = CpGuiUtil.createOverlayFromSlice( + "gui.aiParameterPosition", + {width, height}, + self.OFF_COLOR, + self.alignments.bottomRight) self.goalBtn = CpHudButtonElement.new(goalOverlay, self.baseHud) local x, y = unpack(self.lines[7].right) @@ -391,14 +325,15 @@ function CpBaseHud:addLeftLineTextButton(parent, line, textSize, callbackFunc, c return element end -function CpBaseHud:addLeftLineTextButtonWithIcon(parent, line, textSize, callbackFunc, callbackClass, iconWidth, iconHeight, iconFilePath) +function CpBaseHud:addLeftLineTextButtonWithIcon(parent, line, textSize, callbackFunc, callbackClass, iconWidth, iconHeight, iconSliceId) local x, y = unpack(self.lines[line].left) local width, height = getNormalizedScreenValues(iconWidth, iconHeight) local element = CpTextHudElement.new(parent, x + width + self.wMargin/4, y, textSize) element:setCallback("onClickPrimary", callbackClass, callbackFunc) - local overlay = CpGuiUtil.createOverlay({width, height}, - {Utils.getFilename(iconFilePath, Courseplay.BASE_DIRECTORY), GuiUtils.getUVs({0,0,0,0})}, + local overlay = CpGuiUtil.createOverlayFromSlice( + iconSliceId, + {width, height}, self.OFF_COLOR, self.alignments.bottomLeft) @@ -448,18 +383,18 @@ function CpBaseHud:addLineTextButton(parent, line, textSize, setting) end function CpBaseHud:addLineTextButtonWithIncrementalButtons(parent, line, textSize, setting) - local imageFilename = Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY) - local width, height = getNormalizedScreenValues(16, 16) - local incrementalOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.plusSymbol))}, - self.OFF_COLOR, - self.alignments.bottomRight) - - local decrementalOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(self.uvs.minusSymbol))}, - self.OFF_COLOR, - self.alignments.bottomLeft) + local incrementalOverlay = CpGuiUtil.createOverlayFromSlice( + "cpUi.plus", + {width, height}, + CpBaseHud.OFF_COLOR, + CpBaseHud.alignments.bottomRight) + + local decrementalOverlay = CpGuiUtil.createOverlayFromSlice( + "cpUi.minus", + {width, height}, + CpBaseHud.OFF_COLOR, + CpBaseHud.alignments.bottomLeft) local x, y = unpack(self.lines[line].left) local dx, dy = unpack(self.lines[line].right) diff --git a/scripts/gui/hud/CpCombineUnloaderHudPage.lua b/scripts/gui/hud/CpCombineUnloaderHudPage.lua index 405a17638..c8fd465b0 100644 --- a/scripts/gui/hud/CpCombineUnloaderHudPage.lua +++ b/scripts/gui/hud/CpCombineUnloaderHudPage.lua @@ -36,9 +36,9 @@ function CpCombineUnloaderHudPageElement:setupElements(baseHud, vehicle, lines, --- Drive now button local width, height = getNormalizedScreenValues(22, 22) local driveNowBtnWidth, height = getNormalizedScreenValues(26, 30) - local imageFilename = Utils.getFilename('img/ui_courseplay.dds', g_Courseplay.BASE_DIRECTORY) - local driveNowOverlay = CpGuiUtil.createOverlay({driveNowBtnWidth, height}, - {imageFilename, GuiUtils.getUVs(unpack(CpBaseHud.uvs.driveNowSymbol))}, + local driveNowOverlay = CpGuiUtil.createOverlayFromSlice( + "cpUi.skip", + {driveNowBtnWidth, height}, CpBaseHud.OFF_COLOR, CpBaseHud.alignments.bottomRight) diff --git a/scripts/gui/hud/CpFieldworkHudPage.lua b/scripts/gui/hud/CpFieldworkHudPage.lua index 2145f9427..82ad4cb37 100644 --- a/scripts/gui/hud/CpFieldworkHudPage.lua +++ b/scripts/gui/hud/CpFieldworkHudPage.lua @@ -17,14 +17,13 @@ function CpFieldWorkHudPageElement:setupElements(baseHud, vehicle, lines, wMargi self.timeRemainingText = CpTextHudElement.new(self, x - 2 * baseHud.wMargin, y, CpBaseHud.defaultFontSize, RenderText.ALIGN_RIGHT) - --- Toggle waypoint visibility. local width, height = getNormalizedScreenValues(20, 20) - local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local courseVisibilityOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(CpBaseHud.uvs.eye))}, - CpBaseHud.OFF_COLOR, - CpBaseHud.alignments.bottomRight) + local courseVisibilityOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.eye", + {width, height}, + CpBaseHud.OFF_COLOR, + CpBaseHud.alignments.bottomRight) self.courseVisibilityBtn = CpHudButtonElement.new(courseVisibilityOverlay, self) local x, y = unpack(lines[8].right) y = y - hMargin/8 @@ -34,11 +33,11 @@ function CpFieldWorkHudPageElement:setupElements(baseHud, vehicle, lines, wMargi vehicle:getCpSettings().showCourse:setNextItem() end) local width, height = getNormalizedScreenValues(18, 18) - local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) - local reverseCourseOverlay = CpGuiUtil.createOverlay({width, height}, - {imageFilename, GuiUtils.getUVs(unpack(CpBaseHud.uvs.refresh))}, - CpBaseHud.OFF_COLOR, - CpBaseHud.alignments.bottomRight) + local reverseCourseOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.refresh", + {width, height}, + CpBaseHud.OFF_COLOR, + CpBaseHud.alignments.bottomRight) self.reverseCourseBtn = CpHudButtonElement.new(reverseCourseOverlay, self) local reverseCourseBtnX = courseVisibilityBtnX - 2*width - wMargin/2 - wMargin/8 self.reverseCourseBtn:setPosition(reverseCourseBtnX, y + hMargin/32) diff --git a/scripts/gui/hud/CpHudInfoTexts.lua b/scripts/gui/hud/CpHudInfoTexts.lua index 952689cfe..b4db1d187 100644 --- a/scripts/gui/hud/CpHudInfoTexts.lua +++ b/scripts/gui/hud/CpHudInfoTexts.lua @@ -81,7 +81,6 @@ function CpHudInfoTexts:init() leftTopText:setTextDetails("Courseplay") local rightTopText = CpTextHudElement.new(self.baseHud, CpHudInfoTexts.x + self.width - self.wMargin, CpHudInfoTexts.y - headerHeight + self.hMargin/16, self.titleFontSize, RenderText.ALIGN_RIGHT) rightTopText:setTextDetails(g_Courseplay.currentVersion) - local imageFilename = Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY) local width, height = getNormalizedScreenValues(20, 20) @@ -92,11 +91,12 @@ function CpHudInfoTexts:init() for i=1, self.maxLines do y = y - self.lineHeight - local vehicleOverlay = Overlay.new(imageFilename, 0, 0, width, height) - vehicleOverlay:setAlignment(Overlay.ALIGN_VERTICAL_BOTTOM, Overlay.ALIGN_HORIZONTAL_LEFT) - vehicleOverlay:setUVs(GuiUtils.getUVs(unpack(self.uvs.vehicleIcon),{256,512})) - vehicleOverlay:setColor(unpack(self.OFF_COLOR)) - + local vehicleOverlay = CpGuiUtil.createOverlayFromSlice( + "cpIconSprite.white_vehicle", + {width, height}, + CpBaseHud.OFF_COLOR, + CpBaseHud.alignments.bottomLeft) + local vehicleBtn = CpHudButtonElement.new(vehicleOverlay, self.baseHud) vehicleBtn:setPosition(x, y) diff --git a/scripts/gui/pages/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua index 3a93dc858..32f704edb 100644 --- a/scripts/gui/pages/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -300,15 +300,13 @@ end function CpCourseManagerFrame.setFolderIcon(element) element.iconImageSize = {32, 32} - element:setImageFilename(Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY)) - element:setImageUVs(nil, unpack(GuiUtils.getUVs({0, 220, 32, 32}, {256, 512}))) + element:setImageSlice(nil, "cpIconSprite.folder") element:setImageColor(nil, 0, 0, 0, 0.5) end function CpCourseManagerFrame.setCourseIcon(element) element.iconImageSize = {32, 32} - element:setImageFilename(Utils.getFilename('img/iconSprite.dds', g_Courseplay.BASE_DIRECTORY)) - element:setImageUVs(nil, unpack(GuiUtils.getUVs({40, 76, 32, 32}, {256, 512}))) + element:setImageSlice(nil, "cpIconSprite.white_fieldworkCourse") element:setImageColor(nil, 0, 0, 0, 0.5) end From 174f6e708932725a6224c6082f8f19d2c74c05a3 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 14 Dec 2024 11:37:47 -0500 Subject: [PATCH 108/158] feat: tight turn offset improvements --- scripts/Course.lua | 21 +++++++++++----- scripts/Waypoint.lua | 8 +++++++ scripts/ai/turns/AITurn.lua | 2 +- scripts/ai/turns/TurnManeuver.lua | 40 +++++++++++++++++++++++++++++-- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/scripts/Course.lua b/scripts/Course.lua index 75f395a0e..e921fc4a3 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -273,9 +273,12 @@ function Course:enrichWaypointData(startIx) 'Course with %d waypoints created/updated, %.1f meters, %d turns', #self.waypoints, self.length, self.totalTurns) end +function Course:getDeltaAngle(ix) + return CpMathUtil.getDeltaAngle(self.waypoints[ix].yRot, self.waypoints[ix - 1].yRot) +end + function Course:calculateSignedRadius(ix) - local deltaAngle = CpMathUtil.getDeltaAngle(self.waypoints[ix].yRot, self.waypoints[ix - 1].yRot) - return CpMathUtil.divide(self:getDistanceToNextWaypoint(ix), (2 * math.sin(deltaAngle / 2))) + return CpMathUtil.divide(self:getDistanceToNextWaypoint(ix), (2 * math.sin(self:getDeltaAngle(ix) / 2))) end function Course:calculateRadius(ix) @@ -493,8 +496,12 @@ function Course:getTurnControls(ix) return self.waypoints[ix].turnControls end -function Course:useTightTurnOffset(ix) - return self.waypoints[ix].useTightTurnOffset +function Course:setUseTightTurnOffset(ix) + return self.waypoints[ix]:setUseTightTurnOffset() +end + +function Course:getUseTightTurnOffset(ix) + return self.waypoints[ix]:getUseTightTurnOffset() end --- Returns the position of the waypoint at ix with the current offset applied. @@ -1230,7 +1237,9 @@ function Course:draw() Utils.renderTextAtWorldPosition(x, y + 3.2, z, tostring(i), getCorrectTextSize(0.012), 0, color) if i < self:getNumberOfWaypoints() then local nx, ny, nz = self:getWaypointPosition(i + 1) - DebugUtil.drawDebugLine(x, y + 3, z, nx, ny + 3, nz, 0, 0, 100) + -- cyan, darker blue with tight turn offset + color = self:getUseTightTurnOffset(i) and {0, 0, 0.5} or {0, 0.5, 0.5} + DebugUtil.drawDebugLine(x, y + 3, z, nx, ny + 3, nz, table.unpack(color)) end end end @@ -1275,7 +1284,7 @@ end function Course:setUseTightTurnOffsetForLastWaypoints(d) self:executeFunctionForLastWaypoints(d, function(wp) - wp.useTightTurnOffset = true + wp:setUseTightTurnOffset() end) end diff --git a/scripts/Waypoint.lua b/scripts/Waypoint.lua index a96b1ff8c..e08b386f3 100644 --- a/scripts/Waypoint.lua +++ b/scripts/Waypoint.lua @@ -285,6 +285,14 @@ function Waypoint:setOnConnectingPath(onConnectingPath) self.attributes:setOnConnectingPath(onConnectingPath) end +function Waypoint:setUseTightTurnOffset() + self.useTightTurnOffset = true +end + +function Waypoint:getUseTightTurnOffset() + return self.useTightTurnOffset +end + function Waypoint:copyRowData(other) self.attributes.rowNumber = other.attributes.rowNumber self.attributes.leftSideWorked = other.attributes.leftSideWorked diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index e08c9d7cd..fd50e5d8d 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -649,7 +649,7 @@ end function CourseTurn:onWaypointChange(ix) AITurn.onWaypointChange(self, ix) if self.turnCourse then - if self.forceTightTurnOffset or (self.enableTightTurnOffset and self.turnCourse:useTightTurnOffset(ix)) then + if self.forceTightTurnOffset or (self.enableTightTurnOffset and self.turnCourse:getUseTightTurnOffset(ix)) then -- adjust the course a bit to the outside in a curve to keep a towed implement on the course self.tightTurnOffset = AIUtil.calculateTightTurnOffsetForTurnManeuver(self.vehicle, self.steeringLength, self.turnCourse, self.turnCourse:getCurrentWaypointIx(), self.tightTurnOffset) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index bdf58d785..8a4216204 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -333,6 +333,42 @@ function TurnManeuver:applyTightTurnOffset(length) end end +-- Apply tight turn offset to an analytically generated 180 turn section. The goal is to align a towed +-- implement properly with the next row +function TurnManeuver:applyTightTurnOffsetToAnalyticPath(course) + if self.tightTurnOffsetEnabled then + local totalDeltaAngle = 0 + local totalDistance = 0 + local previousDeltaAngle = course:getDeltaAngle(course:getNumberOfWaypoints()) + for i = course:getNumberOfWaypoints(), 2, -1 do + course:setUseTightTurnOffset(i) + local deltaAngle = course:getDeltaAngle(i) + totalDeltaAngle = totalDeltaAngle + deltaAngle + -- check for configured distance + totalDistance = totalDistance + course:getDistanceToNextWaypoint(i - 1) + if totalDistance > g_vehicleConfigurations:getRecursively(self.vehicle, 'tightTurnOffsetDistanceInTurns') then + self:debug('Total distance %.1f > configured, stop applying tight turn offset', totalDistance) + break + end + -- Check for direction change: this is to have offset only at the foot of an omega turn and not + -- around the body, only when the foot is significantly narrower than the body. + -- This is for the case when the turn diameter is significantly bigger than the working width + if math.abs(totalDeltaAngle) > math.pi / 6 and + math.abs(deltaAngle) > 0.01 and math.sign(deltaAngle) ~= math.sign(previousDeltaAngle) then + self:debug('Curve direction change at %d (total delta angle %.1f, stop applying tight turn offset', + i, math.deg(totalDeltaAngle)) + break + end + -- in all other cases, apply to half circle + if math.abs(totalDeltaAngle) > math.pi / 2 then + self:debug('Total direction change more than 90, stop applying tight turn offset') + break + end + previousDeltaAngle = deltaAngle + end + end +end + ---@class AnalyticTurnManeuver : TurnManeuver AnalyticTurnManeuver = CpObject(TurnManeuver) function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, turningRadius, workWidth, steeringLength, distanceToFieldEdge) @@ -358,11 +394,11 @@ function AnalyticTurnManeuver:init(vehicle, turnContext, vehicleDirectionNode, t dBack = dBack < 2 and 2 or dBack self:debug('Not enough space on field, regenerating course back %.1f meters', dBack) self.course = self:findAnalyticPath(vehicleDirectionNode, 0, -dBack, turnEndNode, self.turnEndXOffset, endZOffset + dBack, self.turningRadius) - self:applyTightTurnOffset() + self:applyTightTurnOffsetToAnalyticPath(self.course) local ixBeforeEndingTurnSection = self.course:getNumberOfWaypoints() self.course, endingTurnLength = self:adjustCourseToFitField(self.course, dBack, ixBeforeEndingTurnSection) else - self:applyTightTurnOffset() + self:applyTightTurnOffsetToAnalyticPath(self.course) endingTurnLength = self.turnContext:appendEndingTurnCourse(self.course, steeringLength) self:applyTightTurnOffset(endingTurnLength) end From 10ad0fa5fc0d04ee2dd6bf15f171839c01000285 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 14 Dec 2024 12:09:54 -0500 Subject: [PATCH 109/158] fix: stupid nil error --- scripts/ai/turns/TurnManeuver.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 8a4216204..1f28cb6d5 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -346,8 +346,9 @@ function TurnManeuver:applyTightTurnOffsetToAnalyticPath(course) totalDeltaAngle = totalDeltaAngle + deltaAngle -- check for configured distance totalDistance = totalDistance + course:getDistanceToNextWaypoint(i - 1) - if totalDistance > g_vehicleConfigurations:getRecursively(self.vehicle, 'tightTurnOffsetDistanceInTurns') then - self:debug('Total distance %.1f > configured, stop applying tight turn offset', totalDistance) + if totalDistance > + (g_vehicleConfigurations:getRecursively(self.vehicle, 'tightTurnOffsetDistanceInTurns') or math.huge) then + selff:debug('Total distance %.1f > configured, stop applying tight turn offset', totalDistance) break end -- Check for direction change: this is to have offset only at the foot of an omega turn and not From 8233f93281a4f555ddc011338a8c79cfb76a723c Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 14 Dec 2024 12:23:15 -0500 Subject: [PATCH 110/158] fix: another stupid typo --- scripts/ai/turns/TurnManeuver.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/turns/TurnManeuver.lua b/scripts/ai/turns/TurnManeuver.lua index 1f28cb6d5..d249e66a0 100644 --- a/scripts/ai/turns/TurnManeuver.lua +++ b/scripts/ai/turns/TurnManeuver.lua @@ -348,7 +348,7 @@ function TurnManeuver:applyTightTurnOffsetToAnalyticPath(course) totalDistance = totalDistance + course:getDistanceToNextWaypoint(i - 1) if totalDistance > (g_vehicleConfigurations:getRecursively(self.vehicle, 'tightTurnOffsetDistanceInTurns') or math.huge) then - selff:debug('Total distance %.1f > configured, stop applying tight turn offset', totalDistance) + self:debug('Total distance %.1f > configured, stop applying tight turn offset', totalDistance) break end -- Check for direction change: this is to have offset only at the foot of an omega turn and not From fe372c57d39d60cba6ca1b2ec6e5d34d064fef69 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 14 Dec 2024 18:47:53 +0100 Subject: [PATCH 111/158] Tool configs adjusted --- config/VehicleConfigurations.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index d36fd84b0..712a81262 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -246,6 +246,10 @@ You can define the following custom settings: raiseLate = "true" lowerEarly = "true" /> + <Vehicle name="grizzlyX4.xml" + turnRadius = "8" + /> + <!--\vehicles\andersonGroup--> <Vehicle name="rbm2000.xml" toolOffsetX = "-2.7" @@ -508,7 +512,7 @@ You can define the following custom settings: implementWheelAlwaysOnGround = "true" tightTurnOffsetDistanceInTurns = "1" workingWidth = "5.8" - turnRadius = "7.8" + turnRadius = "9" raiseLate = "true" lowerEarly = "true" /> From f63a47115592cfd104fd8537485e1528de05fbde Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 15 Dec 2024 09:47:23 +0100 Subject: [PATCH 112/158] Added collision flag for rice field pumps --- scripts/CpUtil.lua | 5 +++++ scripts/ai/ProximitySensor.lua | 4 ++-- scripts/ai/controllers/ShovelController.lua | 3 ++- scripts/dev/DevHelper.lua | 2 +- scripts/pathfinder/PathfinderUtil.lua | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/CpUtil.lua b/scripts/CpUtil.lua index f2e46fb79..78d8d7515 100644 --- a/scripts/CpUtil.lua +++ b/scripts/CpUtil.lua @@ -476,4 +476,9 @@ end ---@return Vehicle the currently selected/controlled vehicle, formerly known as g_currentMission.controlledVehicle function CpUtil.getCurrentVehicle() return g_currentMission.playerSystem:getLocalPlayer():getCurrentVehicle() +end + +function CpUtil.getDefaultCollisionFlags() + return CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + + CollisionFlag.VEHICLE + CollisionFlag.BUILDING + CollisionFlag.PLACEMENT_BLOCKING end \ No newline at end of file diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index c97e3a617..79d825c8b 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -114,7 +114,7 @@ function ProximitySensor:update() self.objectId = nil self.hitTerrain = false if self.enabled then - local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.BUILDING + local raycastMask = CpUtil.getDefaultCollisionFlags() raycastClosest(x, y1 + self.height, z, nx, ny, nz, self.range, 'raycastCallback', self, raycastMask) if CpDebug:isChannelActive(CpDebug.DBG_TRAFFIC, self.vehicle) then DebugUtil.drawDebugLine(x, y1 + self.height, z, x + 5 * nx, y1 + self.height + 5 * ny, z + 5 * nz, 0, 1, 0) @@ -460,7 +460,7 @@ function VerticalProximitySensor:update() self.objectId = nil self.hitTerrain = false if self.enabled then - local raycastMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.BUILDING + local raycastMask = CpUtil.getDefaultCollisionFlags() -- straight up from 10 cm above the ground to height raycastClosest(x, y + self.minHeightAboveGround, z, 0, 1, 0, self.height - self.minHeightAboveGround, diff --git a/scripts/ai/controllers/ShovelController.lua b/scripts/ai/controllers/ShovelController.lua index 2883b7c96..7c40c3e6d 100644 --- a/scripts/ai/controllers/ShovelController.lua +++ b/scripts/ai/controllers/ShovelController.lua @@ -11,7 +11,8 @@ ShovelController.POSITIONS = { ShovelController.MAX_TRIGGER_HEIGHT = 8 ShovelController.MIN_TRIGGER_HEIGHT = 1 ShovelController.TRIGGER_HEIGHT_RAYCAST_COLLISION_MASK = - CollisionFlag.DEFAULT + CollisionFlag.STATIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.DEFAULT + CollisionFlag.STATIC_OBJECT + CollisionFlag.VEHICLE + + CollisionFlag.BUILDING + CollisionFlag.PLACEMENT_BLOCKING function ShovelController:init(vehicle, implement, isConsoleCommand) ImplementController.init(self, vehicle, implement) diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index 0bcdb4895..d64cf03e2 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -83,7 +83,7 @@ function DevHelper:update() self.data.isOnFieldArea, self.data.onFieldArea, self.data.totalOnFieldArea = CpFieldUtil.isOnFieldArea(self.data.x, self.data.z) self.data.nx, self.data.ny, self.data.nz = getTerrainNormalAtWorldPos(g_currentMission.terrainRootNode, self.data.x, y, self.data.z) - local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + CollisionFlag.BUILDING + local collisionMask = CpUtil.getDefaultCollisionFlags() + CollisionFlag.TERRAIN_DELTA self.data.collidingShapes = '' overlapBox(self.data.x, self.data.y + 0.2, self.data.z, 0, self.yRot, 0, 1.6, 1, 8, "overlapBoxCallback", self, collisionMask, true, true, true) diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index 164552fb2..a5bd2352d 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -343,7 +343,7 @@ function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, self.collidingShapes = 0 self.collidingShapesText = 'unknown' - local collisionMask = CollisionFlag.DEFAULT + CollisionFlag.TREE + CollisionFlag.DYNAMIC_OBJECT + CollisionFlag.VEHICLE + CollisionFlag.TERRAIN_DELTA + CollisionFlag.BUILDING + local collisionMask = CpUtil.getDefaultCollisionFlags() + CollisionFlag.TERRAIN_DELTA overlapBox(x, y + 0.2, z, xRot, yRot, zRot, width, 1, length, 'overlapBoxCallback', self, collisionMask, true, true, true) From abb91346cd15dea6d3c8d39f4e0703bc04983eaa Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 15 Dec 2024 10:35:20 +0100 Subject: [PATCH 113/158] Integrated info hud view into the ai map --- config/gui/pages/CourseGeneratorFrame.xml | 71 +++++++++++++++++--- scripts/ai/InfoTextsManager.lua | 2 + scripts/gui/CpInGameMenu.lua | 9 ++- scripts/gui/pages/CpCourseGeneratorFrame.lua | 54 ++++++++++++--- scripts/specializations/CpInfoTexts.lua | 1 + 5 files changed, 116 insertions(+), 21 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 84e9f2214..b9f8f0505 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -90,7 +90,7 @@ <Bitmap profile="fs25_aiCreateJobParameterInvalid" name="invalid" /> </Button> <Button profile="fs25_aiCreateJobParameterButton" - id="createPositionRotationTemplate" text="172a" + id="createPositionRotationTemplate" onClick="onClickPositionRotationParameter"> <ThreePartBitmap profile="fs25_aiCreateJobParameterBg" /> <Bitmap profile="fs25_aiCreateJobParameterIcon" /> @@ -141,14 +141,25 @@ </Bitmap> </GuiElement> <GuiElement profile="cpMenuContainer"> - <GuiElement profile="fs25_menuHeaderPanel"> - <!-- <RoundCorner profile="fs25_mapMoneyBoxBg" id="shopMoneyBoxBg" /> - <BoxLayout profile="fs25_shopMoneyBox" id="shopMoneyBox"> - <Text profile="fs25_shopBalance" text="$l10n_ui_balance:" /> - <Text profile="fs25_shopMoney" id="currentBalanceText" /> - </BoxLayout> --> - <Text profile="fs25_ingameMenuAIStatusText" id="statusMessage" /> + <GuiElement id="infoTextHud"> + <GuiElement profile="cpInfoTextHeaderPanel"> + <Bitmap profile="cpInfoTextHeaderBg"/> + <Text profile="cpInfoTextHeaderTitle" text="$l10n_CP_help_title"/> + </GuiElement> + <SmoothList profile="cpInfoTextList" id="infoTextList" onClick="onClickList"> + <ListItem profile="cpInfoTextListElement"> + <Bitmap profile="cpInfoTextListElementBg" /> + <Text profile="cpInfoTextListElementText" name="text" /> + <Button profile="cpInfoTextListElementIcon"/> + </ListItem> + </SmoothList> </GuiElement> + <!-- <RoundCorner profile="fs25_mapMoneyBoxBg" id="shopMoneyBoxBg" /> + <BoxLayout profile="fs25_shopMoneyBox" id="shopMoneyBox"> + <Text profile="fs25_shopBalance" text="$l10n_ui_balance:" /> + <Text profile="fs25_shopMoney" id="currentBalanceText" /> + </BoxLayout> --> + <Text profile="fs25_ingameMenuAIStatusText" id="statusMessage" /> </GuiElement> <GuiElement profile="fs25_mapContextBoxContainer" newLayer="true"> <GuiElement profile="fs25_mapContextBox" id="contextBox"> @@ -256,10 +267,48 @@ <width value="100%"/> <height value="80px" /> </Profile> - <Profile name="cpMenuContainer" extends="fs25_menuContainer"> - <position value="0px 0px"/> - <absoluteSizeOffset value="10px 10px" /> + <Profile name="cpMenuContainer" extends="fs25_menuContainer" with="anchorTopRight"> + <position value="20px 0px"/> <!-- <position value="0px 92px" /> --> </Profile> + <Profile name="cpInfoTextHeaderPanel" extends="emptyPanel" with="anchorTopRight"> + + </Profile> + <Profile name="cpInfoTextHeaderBg" extends="baseReference" with="anchorTopRight"> + <size value="250px 20px"/> + <imageSliceId value="gui.button_middle" /> + <imageColor value="$preset_fs25_colorMainHighlight"/> + </Profile> + <Profile name="cpInfoTextHeaderTitle" extends="fs25_textDefault" with="anchorTopRight"> + <textOffset value="10px 0px" /> + </Profile> + <Profile name="cpInfoTextList" extends="emptyPanel" with="anchorTopRight"> + <size value="250px 300px"/> + <position value="0 -20px" /> + <listItemSpacing value="3px" /> + <!-- <position value="0px -200px" /> --> + </Profile> + <Profile name="cpInfoTextListElement" extends="emptyPanel" with="anchorTopStretchingX"> + <height value="26px" /> + </Profile> + <Profile name="cpInfoTextListElementText" extends="fs25_textDefault" with="anchorTopStretchingX"> + <width value="100%" /> + <textSize value="12px" /> + <textOffset value="30px -2px" /> + <textSelectedColor value="$preset_fs25_colorMainHighlight" /> + <textFocusedColor value="$preset_fs25_colorMainHighlight" /> + <textHighlightedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="cpInfoTextListElementIcon" extends="emptyPanel" with="anchorTopLeft"> + <size value="26px 26px"/> + <absoluteSizeOffset value="3px 3px"/> + <iconSliceId value="cpIconSprite.white_vehicle" /> + <iconSize value="20px 20px" /> + <iconColor value="$preset_fs25_colorMainHighlight" /> + </Profile> + <Profile name="cpInfoTextListElementBg" extends="baseReference" with="anchorTopStretchingX"> + <imageSliceId value="gui.animalScreen_left" /> + <!-- <imageSelectedColor value="$preset_colorBlack80" /> --> + </Profile> </GUIProfiles> </GUI> diff --git a/scripts/ai/InfoTextsManager.lua b/scripts/ai/InfoTextsManager.lua index e0863601a..459924175 100644 --- a/scripts/ai/InfoTextsManager.lua +++ b/scripts/ai/InfoTextsManager.lua @@ -38,6 +38,7 @@ function CpInfoTextElement:init(name, text, id, hasFinished, event, aiMessageCla if aiMessageClass then self.aiMessageClass = CpUtil.getClassObject(aiMessageClass) end + MessageType.CP_INFO_TEXT_CHANGED = nextMessageTypeId() end function CpInfoTextElement:__tostring() @@ -204,6 +205,7 @@ function InfoTextManager:changeNumActiveTexts() if InfoTextManager.numActiveTexts > 10 then InfoTextManager.numActiveTexts = -1 end + g_messageCenter:publishDelayed(MessageType.CP_INFO_TEXT_CHANGED) end g_infoTextManager = InfoTextManager() \ No newline at end of file diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index b68f2a22c..e07addae7 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -105,8 +105,13 @@ end -- Lines 279-324 function CpInGameMenu:initializePages() - self.clickBackCallback = self:makeSelfCallback(self.onButtonBack) - + self.clickBackCallback = function () + if self.currentPage.onClickBack then + --- Force closes the page + self.currentPage:onClickBack(true) + end + self:exitMenu() + end self.pageCourseGenerator:setInGameMap( g_inGameMenu.baseIngameMap, g_currentMission.hud) diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 53fd5ac84..1eba3d0f0 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -62,6 +62,7 @@ function CpCourseGeneratorFrame.new(target, custom_mt) self.subCategoryTabs = {} self.contextActions = {} self.contextActionMapping = {} + self.activeInfoTexts = {} self.hasFullScreenMap = true self.dataTables = {} self.hotspotModeActive = false @@ -295,6 +296,9 @@ function CpCourseGeneratorFrame:onFrameOpen() -- InGameMenuMapUtil.hideContextBox(self.contextBox) end, self) + g_messageCenter:subscribe(MessageType.CP_INFO_TEXT_CHANGED, function (menu) + self.infoTextList:reloadData() + end, self) -- g_messageCenter:subscribe(MessageType.AI_TASK_SKIPPED, self.onAITaskSkipped, self) local hotspotValue = g_gameSettings:getValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER) for i, hotspotCategory in pairs(self.hotspotFilterCategories[1]) do @@ -320,6 +324,12 @@ function CpCourseGeneratorFrame:onFrameOpen() end self.activeWorkerList:reloadData() self.filterList:reloadData() + if g_Courseplay.globalSettings.infoTextHudActive:getValue() > 0 then + self.infoTextList:reloadData() + self.infoTextHud:setVisible(true) + else + self.infoTextHud:setVisible(false) + end self.ingameMapBase:restoreDefaultFilter() self:generateJobTypes() self.mode = self.AI_MODE_OVERVIEW @@ -377,6 +387,8 @@ function CpCourseGeneratorFrame:onFrameClose() g_messageCenter:unsubscribe(MessageType.AI_JOB_STARTED, self) g_messageCenter:unsubscribe(MessageType.AI_JOB_STOPPED, self) g_messageCenter:unsubscribe(MessageType.AI_JOB_REMOVED, self) + g_messageCenter:unsubscribe(MessageType.AI_VEHICLE_STATE_CHANGE, self) + g_messageCenter:unsubscribe(MessageType.CP_INFO_TEXT_CHANGED, self) g_messageCenter:unsubscribe(AIJobStartRequestEvent, self) self.jobTypeInstances = {} g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot) @@ -397,23 +409,28 @@ function CpCourseGeneratorFrame:onFrameClose() CpCourseGeneratorFrame:superClass().onFrameClose(self) end -function CpCourseGeneratorFrame:onClickBack() - if self.startJobPending then - return +function CpCourseGeneratorFrame:onClickBack(force) + if self.startJobPending and not force then + return false end if self.mode == self.AI_MODE_CREATE then if self:getIsPicking() then self:executePickingCallback(false) self:updateContextActions() - return false + if not force then + return false + end end self.mode = self.AI_MODE_OVERVIEW self:setJobMenuVisible(false) - self:setMapSelectionItem(self.currentHotspot) - return false + if not force then + self:setMapSelectionItem(self.currentHotspot) + return false + end + self:setMapSelectionItem(nil) elseif self.currentHotspot then self:setMapSelectionItem(nil) - return false + return not force end return true end @@ -956,6 +973,9 @@ function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) return 0 end return #self.contextActionMapping + elseif list == self.infoTextList then + self.activeInfoTexts = g_infoTextManager:getActiveInfoTexts() + return #self.activeInfoTexts end return 0 end @@ -1001,6 +1021,18 @@ function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, inde local buttonInfo = self.contextActions[self.contextActionMapping[index]] cell:getAttribute("text"):setText(buttonInfo.text) cell.onClickCallback = buttonInfo.callback + elseif list == self.infoTextList then + cell:getAttribute("text"):setText(self.activeInfoTexts[index].text) + cell.onClickCallback = function () + if self:getIsPicking() then + return + end + if self.mode == self.AI_MODE_CREATE then + self.mode = self.AI_MODE_OVERVIEW + self:setJobMenuVisible(false) + end + self:setAIVehicle(self.activeInfoTexts[index].vehicle) + end end end @@ -1023,6 +1055,8 @@ function CpCourseGeneratorFrame:onClickList(list, section, index, listElement) listElement.onClickCallback(self) elseif list == self.createJobButtonList then listElement.onClickCallback(self) + elseif list == self.infoTextList then + listElement.onClickCallback(self) end end @@ -1115,6 +1149,10 @@ function CpCourseGeneratorFrame:generateJobTypes() end function CpCourseGeneratorFrame:addStatusMessage(message) + if g_Courseplay.globalSettings.infoTextHudActive:getValue() > 0 then + --- Status Meldung werden als info text angezeigt .. + return + end table.insert(self.statusMessages, { removeTime = g_time + 5000, text = message @@ -1139,7 +1177,7 @@ function CpCourseGeneratorFrame:initializeContextActions() local vehicle = self.currentHotspot:getVehicle() if vehicle then if vehicle.getIsEnterableFromMenu ~= nil and vehicle:getIsEnterableFromMenu() then - self.onClickBackCallback(nil, nil, true) + self.onClickBackCallback() g_localPlayer:requestToEnterVehicle(vehicle) end end diff --git a/scripts/specializations/CpInfoTexts.lua b/scripts/specializations/CpInfoTexts.lua index 2acc25d17..0a7b7b31d 100644 --- a/scripts/specializations/CpInfoTexts.lua +++ b/scripts/specializations/CpInfoTexts.lua @@ -100,6 +100,7 @@ function CpInfoTexts:raiseDirtyFlag() if self.isServer then local spec = self.spec_cpInfoTexts self:raiseDirtyFlags(spec.dirtyFlag) + g_messageCenter:publishDelayed(MessageType.CP_INFO_TEXT_CHANGED) end end From d323f0e50c5b84ca1b6e465f578887caf0e14621 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 15 Dec 2024 11:47:02 +0100 Subject: [PATCH 114/158] Disabled course generator with no field work job creation and bug fixes --- config/gui/pages/CourseGeneratorFrame.xml | 5 +- scripts/gui/CpInGameMenu.lua | 3 ++ scripts/gui/pages/CpCourseGeneratorFrame.lua | 55 +++++++++++--------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index b9f8f0505..8275fdfdc 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -150,7 +150,7 @@ <ListItem profile="cpInfoTextListElement"> <Bitmap profile="cpInfoTextListElementBg" /> <Text profile="cpInfoTextListElementText" name="text" /> - <Button profile="cpInfoTextListElementIcon"/> + <Button profile="cpInfoTextListElementIcon" name="icon"/> </ListItem> </SmoothList> </GuiElement> @@ -304,7 +304,8 @@ <absoluteSizeOffset value="3px 3px"/> <iconSliceId value="cpIconSprite.white_vehicle" /> <iconSize value="20px 20px" /> - <iconColor value="$preset_fs25_colorMainHighlight" /> + <iconColor value="$preset_fs25_colorGrey" /> + <iconSelectedColor value="$preset_fs25_colorMainHighlight" /> </Profile> <Profile name="cpInfoTextListElementBg" extends="baseReference" with="anchorTopStretchingX"> <imageSliceId value="gui.animalScreen_left" /> diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index e07addae7..d0d235841 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -324,7 +324,10 @@ end function CpInGameMenu:onCurrentVehicleChanged() if self:getIsOpen() then + local prevPage = self.pagingElement:getPageElementByIndex(self.currentPageId) self:updatePages() + local index = self.pagingElement:getPageMappingIndexByElement(prevPage) + self.pagingTabList:setSelectedIndex(index, true, 0) end end diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 1eba3d0f0..051d9ae1c 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -187,23 +187,9 @@ function CpCourseGeneratorFrame:initialize(menu) self.currentHotspot = nil g_messageCenter:subscribe(MessageType.GUI_CP_INGAME_CURRENT_VEHICLE_CHANGED, function(self, vehicle) - if vehicle then - self.subCategoryPaging:setDisabled(false) - for ix=1, self.NUM_CATEGORIES do - if ix ~= self.CATEGRORIES.IN_GAME_MAP then - self.subCategoryTabs[ix]:setDisabled(false) - end - end - self:updateSettings(vehicle) - else - self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) - self.subCategoryPaging:setDisabled(true) - for ix=1, self.NUM_CATEGORIES do - if ix ~= self.CATEGRORIES.IN_GAME_MAP then - self.subCategoryTabs[ix]:setDisabled(true) - end - end - end + local _, pageTitle = CpCourseGeneratorSettings.getSettingSetup() + local title = string.format(g_i18n:getText(pageTitle), vehicle and vehicle:getName() or "--") + self.categoryHeaderText:setText(title) end, self) end @@ -246,9 +232,7 @@ end function CpCourseGeneratorFrame:updateSettings(vehicle) local settings = vehicle:getCourseGeneratorSettings() - local settingsBySubTitle, pageTitle = CpCourseGeneratorSettings.getSettingSetup() - local title = string.format(g_i18n:getText(pageTitle), vehicle:getName()) - self.categoryHeaderText:setText(title) + local settingsBySubTitle = CpCourseGeneratorSettings.getSettingSetup() local layout = self.subCategoryPages[self.CATEGRORIES.BASIC_SETTINGS]:getDescendantByName("layout") for i = #layout.elements, 1, -1 do @@ -343,14 +327,12 @@ function CpCourseGeneratorFrame:onFrameOpen() self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) CpCourseGeneratorFrame:superClass().onFrameOpen(self) - + self:updateCourseGenerator(false) local vehicle = self.cpMenu:getCurrentVehicle() if vehicle and self.currentHotspot == nil then - self.subCategoryPaging:setDisabled(false) self:setAIVehicle(vehicle) elseif vehicle == nil then self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) - self.subCategoryPaging:setDisabled(true) self:setMapSelectionItem(nil) end @@ -430,7 +412,7 @@ function CpCourseGeneratorFrame:onClickBack(force) self:setMapSelectionItem(nil) elseif self.currentHotspot then self:setMapSelectionItem(nil) - return not force + return force end return true end @@ -439,6 +421,26 @@ function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) CpSettingsUtil.updateGuiElementsBoundToSettings(guiElement.parent.parent, self.cpMenu:getCurrentVehicle()) end +function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) + if visible then + self.subCategoryPaging:setDisabled(false) + for ix=1, self.NUM_CATEGORIES do + if ix ~= self.CATEGRORIES.IN_GAME_MAP then + self.subCategoryTabs[ix]:setDisabled(false) + end + end + self:updateSettings(vehicle) + else + self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) + self.subCategoryPaging:setDisabled(true) + for ix=1, self.NUM_CATEGORIES do + if ix ~= self.CATEGRORIES.IN_GAME_MAP then + self.subCategoryTabs[ix]:setDisabled(true) + end + end + end +end + function CpCourseGeneratorFrame:updateSubCategoryPages(state) for i, _ in ipairs(self.subCategoryPages) do self.subCategoryPages[i]:setVisible(false) @@ -1023,6 +1025,9 @@ function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, inde cell.onClickCallback = buttonInfo.callback elseif list == self.infoTextList then cell:getAttribute("text"):setText(self.activeInfoTexts[index].text) + cell:getAttribute("icon").getIsSelected = function () + return self.currentHotspot and self.currentHotspot:getVehicle() == self.activeInfoTexts[index].vehicle + end cell.onClickCallback = function () if self:getIsPicking() then return @@ -1365,6 +1370,7 @@ function CpCourseGeneratorFrame:setJobMenuVisible(isVisible) if not isVisible then self.currentJob = nil self.currentJobVehicle = nil + self:updateCourseGenerator(false) FocusManager:setFocus(self.mapOverviewSelector) self.mapOverviewSelector:setState(self.MAP_SELECTOR_ACTIVE_JOBS, true) else @@ -1421,6 +1427,7 @@ function CpCourseGeneratorFrame:setActiveJobTypeSelection(jobTypeIndex) self:validateParameters() self.jobMenuLayout:invalidateLayout() FocusManager:setFocus(self.jobTypeElement) + self:updateCourseGenerator(self.currentJob:isa(CpAIJobFieldWork), self.currentJobVehicle) end self:updateContextActions() end From 238b1d7de703780973ac9de3e15fc2b42e35bb85 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 15 Dec 2024 07:11:00 -0500 Subject: [PATCH 115/158] fix: CpFieldUtil.detectFieldBoundary() everywhere Use CpFieldUtil.detectFieldBoundary() to detect rice fields too. --- scripts/dev/DevHelper.lua | 5 +---- scripts/field/CpFieldUtil.lua | 6 +++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua index d64cf03e2..2f9d8ec67 100644 --- a/scripts/dev/DevHelper.lua +++ b/scripts/dev/DevHelper.lua @@ -153,11 +153,8 @@ function DevHelper:keyEvent(unicode, sym, modifier, isDown) elseif bitAND(modifier, Input.MOD_LCTRL) ~= 0 and isDown and sym == Input.KEY_space then -- restore vehicle position DevHelper.restoreVehiclePosition(CpUtil.getCurrentVehicle()) - elseif bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_c then - self:debug('Finding contour of current field') - local valid, points = g_fieldScanner:findContour(self.data.x, self.data.z) elseif bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_g then - local valid, points = g_fieldScanner:findContour(self.data.x, self.data.z) + local points = CpFieldUtil.detectFieldBoundary(self.data.x, self.data.z, true) self:debug('Generate course') local vehicle = CpUtil.getCurrentVehicle() diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index 317fb48c2..fa175ae5d 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -167,15 +167,15 @@ function CpFieldUtil.getFieldPolygonAtWorldPosition(x, z) local fieldNum = CpFieldUtil.getFieldIdAtWorldPosition(x, z) CpUtil.info('Scanning field %d on %s, prefer custom fields %s', fieldNum, g_currentMission.missionInfo.mapTitle, g_Courseplay.globalSettings.preferCustomFields:getValue()) - local mapField, mapFieldPolygon = g_fieldScanner:findContour(x, z) + local mapFieldPolygon = CpFieldUtil.detectFieldBoundary(x, z, true) - if customField and (not mapField or g_Courseplay.globalSettings.preferCustomFields:getValue()) then + if customField and (not mapFieldPolygon or g_Courseplay.globalSettings.preferCustomFields:getValue()) then -- use a custom field if there is one under us and either there's no regular map field or, there is, -- but the user prefers custom fields CpUtil.info('Custom field found: %s', customField:getName()) fieldPolygon = customField:getVertices() isCustomField = true - elseif mapField then + elseif mapFieldPolygon then fieldPolygon = mapFieldPolygon end return fieldPolygon, isCustomField From 2e3fae738a2fb060e648cb1787e25afcc66fe32d Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 15 Dec 2024 08:35:33 -0500 Subject: [PATCH 116/158] fix: never tested alignment failure case --- scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 5 +++-- scripts/field/CpFieldUtil.lua | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index c4ec2224f..324825591 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -580,8 +580,9 @@ function AIDriveStrategyFieldWorkCourse:startAlignmentTurn(fieldWorkCourse, star self:startCourse(self.workStarter:getCourse(), 1) else self:debug('Could not create alignment course to first up/down row waypoint, continue without it') - self:startWaitingForLower() - self:lowerImplements() + self:startCourse(fieldWorkCourse, startIx) + self.state = self.states.INITIAL + self:prepareForFieldWork() end end diff --git a/scripts/field/CpFieldUtil.lua b/scripts/field/CpFieldUtil.lua index fa175ae5d..1a96cbd08 100644 --- a/scripts/field/CpFieldUtil.lua +++ b/scripts/field/CpFieldUtil.lua @@ -198,6 +198,7 @@ function CpFieldUtil.detectFieldBoundary(x, z, detect, useGiantsDetector) local _, _, _, riceField = PlaceableRiceField.getRiceFieldAtPosition(x, y, z) if riceField then -- rice fields are somewhat special, so always use the Giants method + CpUtil.info('Rice field found') return CpFieldUtil.getRiceFieldPolygon(riceField) else local valid, points = g_fieldScanner:findContour(x, z) From 6f2bb80415a3ee236a6bce8da7c9800c17b066a0 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 15 Dec 2024 16:11:06 +0100 Subject: [PATCH 117/158] Disabled custom field hotspots --- scripts/field/CustomField.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/field/CustomField.lua b/scripts/field/CustomField.lua index 468aafcfa..82bbd2a61 100644 --- a/scripts/field/CustomField.lua +++ b/scripts/field/CustomField.lua @@ -81,7 +81,8 @@ function CustomField:draw(map) if not self.fieldHotspot then -- add hotspot when draw first called. Can't create in the constructor as on game load -- when the custom fields are loaded there's no player yet - self:addHotspot() + -- TODO_25 + -- self:addHotspot() end self.fieldPlot:draw(map) end From d726cd8fe911e196543cbc98a21cec3a7095e265 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 16 Dec 2024 02:29:00 -0500 Subject: [PATCH 118/158] feat: better headland turn Back up less with implements on front in headland turns. --- scripts/ai/turns/AITurn.lua | 76 ++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index fd50e5d8d..72a5ac20d 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -253,10 +253,41 @@ function AITurn:getDriveData(dt) return gx, gz, moveForwards, maxSpeed end --- default for 180 turns: we need to raise the implement (when finishing a row) when we reach the --- workEndNode. function AITurn:getRaiseImplementNode() - return self.turnContext.workEndNode + if not self.raiseImplementNode then + self.raiseImplementNode, self.lowerImplementNode = self:setRaiseLowerNodes() + end + return self.raiseImplementNode +end + +function AITurn:getLowerImplementNode() + if not self.lowerImplementNode then + self.raiseImplementNode, self.lowerImplementNode = self:setRaiseLowerNodes() + end + return self.lowerImplementNode +end + +---@return number, number the node where the implements should be raised when finishing work, the node where the +--- implements should be lowered when starting work +function AITurn:setRaiseLowerNodes() + if self.turnContext:isHeadlandCorner() then + -- in headland corners, we want to stay on the field as much as possible to avoid hitting obstacles around the field. + local _, backMarkerDistance = self.driveStrategy:getFrontAndBackMarkers() + if backMarkerDistance < 0 then + -- implement on the back of the vehicle, so before the corner, we don't work all the way to the field edge, + -- stop a work width before it, then make the turn, back up until the implement reaches the field edge, + -- lower, and continue on the new headland direction + return self.turnContext.workEndNode, self.turnContext.workStartNode + else + -- implement on the front of the vehicle, so we can work all the way to the field edge, then make the turn + -- and back up only until the implement reaches the already worked part, work width from the field edge + return self.turnContext.lateWorkEndNode, self.turnContext.lateWorkStartNode + end + else + -- default for 180 turns: we need to raise the implement (when finishing a row) when we reach the + -- workEndNode. + return self.turnContext.workEndNode, self.turnContext.workStartNode + end end function AITurn:finishRow(dt) @@ -286,7 +317,7 @@ function AITurn:resumeFieldworkAfterTurn(ix) -- just in case, raise this event so plows are rotated to the working position. Should really never end up -- here though, as the course should be long enough for the normal turn end processing to be triggered. self.driveStrategy:raiseControllerEvent(AIDriveStrategyCourse.onTurnEndProgressEvent, - self.turnContext.workStartNode, self.ppc:isReversing(), true, self.turnContext:isLeftTurn()) + self:getLowerImplementNode(), self.ppc:isReversing(), true, self.turnContext:isLeftTurn()) if self.proximityController then self.proximityController:unregisterBlockingObjectListener() @@ -434,12 +465,6 @@ function CombineHeadlandTurn:onWaypointPassed(ix, course) -- pass the last waypoint which causes the turn to end and return to field work end --- in a combine headland turn we want to raise the header after it reached the field edge (or headland edge on an inner --- headland. -function CombineHeadlandTurn:getRaiseImplementNode() - return self.turnContext.lateWorkEndNode -end - function CombineHeadlandTurn:turn(dt) local gx, gz, moveForwards, maxSpeed = AITurn.turn(self) local dx, _, dz = self.turnContext:getLocalPositionFromTurnEnd(self.vehicle:getAIDirectionNode()) @@ -613,9 +638,9 @@ end ---@return boolean true if it is ok the continue driving, false when the vehicle should stop function CourseTurn:endTurn(dt) -- keep driving on the turn course until we need to lower our implements - local shouldLower, dz = self.driveStrategy:shouldLowerImplements(self.turnContext.workStartNode, self.ppc:isReversing()) + local shouldLower, dz = self.driveStrategy:shouldLowerImplements(self:getLowerImplementNode(), self.ppc:isReversing()) self.driveStrategy:raiseControllerEvent(AIDriveStrategyCourse.onTurnEndProgressEvent, - self.turnContext.workStartNode, self.ppc:isReversing(), shouldLower, self.turnContext:isLeftTurn()) + self:getLowerImplementNode(), self.ppc:isReversing(), shouldLower, self.turnContext:isLeftTurn()) if shouldLower then if not self.implementsLowered then -- have not started lowering implements yet @@ -849,22 +874,6 @@ function RecoveryTurn:onBlocked() end end ---- Combines (in general, when harvesting) in headland corners we want to work the corner first, then back up and then ---- turn so we harvest any area before we drive over it ----@class CombineCourseTurn : CourseTurn -CombineCourseTurn = CpObject(CourseTurn) - ----@param turnContext TurnContext -function CombineCourseTurn:init(vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, workWidth, name) - CourseTurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, workWidth, name or 'CombineCourseTurn') -end - --- in a combine headland turn we want to raise the header after it reached the field edge (or headland edge on an inner --- headland. -function CombineCourseTurn:getRaiseImplementNode() - return self.turnContext.lateWorkEndNode -end - --[[ Headland turn for combines on the outermost headland: 1. drive forward to the field edge or the headland path edge @@ -873,8 +882,8 @@ end corner while reversing 4. forward to the turn start to continue on headland ]] ----@class CombinePocketHeadlandTurn : CombineCourseTurn -CombinePocketHeadlandTurn = CpObject(CombineCourseTurn) +---@class CombinePocketHeadlandTurn : CourseTurn +CombinePocketHeadlandTurn = CpObject(CourseTurn) ---@param driveStrategy AIDriveStrategyCombineCourse ---@param turnContext TurnContext @@ -1036,11 +1045,10 @@ function StartRowOnly:getDriveData() self.state = self.states.APPROACHING_ROW self:debug('Approaching row') self.driveStrategy:raiseControllerEvent(AIDriveStrategyCourse.onTurnEndProgressEvent, - self.turnContext.workStartNode, self.ppc:isReversing(), true, not self.turnContext:isNextTurnLeft()) + self:getLowerImplementNode(), self.ppc:isReversing(), true, not self.turnContext:isNextTurnLeft()) end elseif self.state == self.states.APPROACHING_ROW then - local shouldLower, _ = self.driveStrategy:shouldLowerImplements(self.turnContext.workStartNode, - self.ppc:isReversing()) + local shouldLower, _ = self.driveStrategy:shouldLowerImplements(self:getLowerImplementNode(), self.ppc:isReversing()) if shouldLower then -- have not started lowering implements yet self:debug('Lowering implements') @@ -1053,7 +1061,7 @@ function StartRowOnly:getDriveData() end return nil, nil, nil, self:getForwardSpeed() elseif self.state == self.states.IMPLEMENTS_LOWERING then - local _, dz = self.driveStrategy:shouldLowerImplements(self.turnContext.workStartNode, self.ppc:isReversing()) + local _, dz = self.driveStrategy:shouldLowerImplements(self:getLowerImplementNode(), self.ppc:isReversing()) -- implements already lowering, making sure we check if they are lowered, the faster we go, the earlier, -- for those people who set insanely high turn speeds... local implementCheckDistance = math.max(1, 0.1 * self.vehicle:getLastSpeed()) From 68714793dcfdaf5f2e02e3f3f09d430fc9955201 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Mon, 16 Dec 2024 15:52:05 +0100 Subject: [PATCH 119/158] Added file system load check and custom field gui improvements --- Courseplay.lua | 5 +- config/MasterTranslations.xml | 4 ++ modDesc.xml | 3 +- scripts/courseManager/FileSystem.lua | 11 +++- scripts/editor/CourseEditor.lua | 37 +++++++----- scripts/field/CustomField.lua | 53 ++++++++++------- scripts/field/CustomFieldManager.lua | 52 ++++++++++------- scripts/gui/CpInGameMenu.lua | 34 ++++++++++- ...pAIHotspots.lua => CustomFieldHotspot.lua} | 53 +++++++++-------- scripts/gui/pages/CpCourseGeneratorFrame.lua | 57 +++++++++++++++++-- scripts/gui/pages/CpCourseManagerFrame.lua | 16 +++++- scripts/gui/pages/CpGlobalSettingsFrame.lua | 12 ++++ scripts/gui/pages/CpHelpFrame.lua | 12 ++++ scripts/gui/pages/CpVehicleSettingsFrame.lua | 12 ++++ scripts/specializations/CpCourseManager.lua | 17 +++--- scripts/test/CourseManagerTest.lua | 2 +- 16 files changed, 278 insertions(+), 102 deletions(-) rename scripts/gui/{CpAIHotspots.lua => CustomFieldHotspot.lua} (55%) diff --git a/Courseplay.lua b/Courseplay.lua index 2e4af0576..7e220d65e 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -28,6 +28,7 @@ function Courseplay:registerXmlSchema() self.globalSettings:registerXmlSchema(self.xmlSchema, self.xmlKey) CpBaseHud.registerXmlSchema(self.xmlSchema, self.xmlKey) CpHudInfoTexts.registerXmlSchema(self.xmlSchema, self.xmlKey) + CpInGameMenu.registerXmlSchema(self.xmlSchema, self.xmlKey) end --- Loads data not tied to a savegame. @@ -36,6 +37,7 @@ function Courseplay:loadUserSettings() if xmlFile then self:showUserInformation(xmlFile, self.baseXmlKey) self.globalSettings:loadFromXMLFile(xmlFile, self.xmlKey) + g_cpInGameMenu:loadFromXMLFile(xmlFile, self.xmlKey) CpBaseHud.loadFromXmlFile(xmlFile, self.xmlKey) CpHudInfoTexts.loadFromXmlFile(xmlFile, self.xmlKey) xmlFile:save() @@ -55,6 +57,7 @@ function Courseplay:saveUserSettings() if self.currentVersion then xmlFile:setValue(self.baseXmlKey.."#lastVersion", self.currentVersion) end + g_cpInGameMenu:saveToXMLFile(xmlFile, g_Courseplay.xmlKey) xmlFile:save() xmlFile:delete() end @@ -94,7 +97,6 @@ end function Courseplay:loadMap(filename) self.globalSettings = CpGlobalSettings() self:registerXmlSchema() - self:loadUserSettings() --- Savegame infos here CpUtil.info("Map loaded: %s, Savegame name: %s(%d)", g_currentMission.missionInfo.mapId, @@ -102,6 +104,7 @@ function Courseplay:loadMap(filename) g_currentMission.missionInfo.savegameIndex) self:load() self:setupGui() + self:loadUserSettings() if g_currentMission.missionInfo.savegameDirectory ~= nil then local saveGamePath = g_currentMission.missionInfo.savegameDirectory .."/" local filePath = saveGamePath .. "Courseplay.xml" diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index 85d44e071..4d4b4719a 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -1778,6 +1778,10 @@ The course is saved automatically on closing of the editor and overrides the sel </Translation> </Category> <Category name="Custom field manager"> + <Translation name="CP_customFieldManager_hotspotName"> + <Text language="de"><![CDATA[Randkurs]]></Text> + <Text language="en"><![CDATA[Custom field]]></Text> + </Translation> <Translation name="CP_customFieldManager_confirm_save"> <Text language="de"><![CDATA[Möchtest du diesen benutzerdefinierten Randkurs als %s speichern?]]></Text> <Text language="en"><![CDATA[Do you want to save the recorded course as custom field %s?]]></Text> diff --git a/modDesc.xml b/modDesc.xml index 70a4d6fa3..5bbdc64e8 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -489,8 +489,7 @@ Changelog 7.1.0.0: <sourceFile filename="scripts/gui/HeapPlot.lua"/> <sourceFile filename="scripts/gui/UnloadingTriggerPlot.lua"/> <sourceFile filename="scripts/gui/CpStatus.lua"/> - <!-- <sourceFile filename="scripts/gui/CpAIHotspots.lua"/> - <sourceFile filename="scripts/gui/CpAIFrameExtended.lua"/> --> + <sourceFile filename="scripts/gui/CustomFieldHotspot.lua"/> <sourceFile filename="scripts/gui/CourseDisplay.lua"/> <!-- <sourceFile filename="scripts/gui/CpGamePadHudScreen.lua"/> --> <sourceFile filename="scripts/gui/elements/CpBinaryOptionElement.lua"/> diff --git a/scripts/courseManager/FileSystem.lua b/scripts/courseManager/FileSystem.lua index 2609757ca..732bfe775 100644 --- a/scripts/courseManager/FileSystem.lua +++ b/scripts/courseManager/FileSystem.lua @@ -123,13 +123,14 @@ end function File:load(xmlSchema, xmlBaseKey, lambda, class,...) local xmlFile = XMLFile.load("tempXmlFile", self.fullPath, xmlSchema) if xmlFile ~= nil then + local retValue = false if class then - lambda(class, xmlFile, xmlBaseKey, ..., self.name) + retValue = lambda(class, xmlFile, xmlBaseKey, ..., self.name) else - lambda(xmlFile, xmlBaseKey, ..., self.name) + retValue = lambda(xmlFile, xmlBaseKey, ..., self.name) end xmlFile:delete() - return true + return retValue end CpUtil.debugFormat(CpDebug.DBG_COURSES, "couldn't load xml file: %s", self.fullPath) return false @@ -635,6 +636,10 @@ function DirectoryView:getEntryByName(name) end end +function DirectoryView:hasEntryWithName(name) + return self:getEntryByName(name) ~= nil +end + --- File system to handle multiple files/directions. ---@class FileSystem FileSystem = CpObject() diff --git a/scripts/editor/CourseEditor.lua b/scripts/editor/CourseEditor.lua index 91de51b45..bf252fa11 100644 --- a/scripts/editor/CourseEditor.lua +++ b/scripts/editor/CourseEditor.lua @@ -28,20 +28,28 @@ end --- Loads the course, might be a good idea to consolidate this with the loading of CpCourseManager. function CourseEditor:loadCourse() local function load(self, xmlFile, baseKey, noEventSend, name) + local course = nil xmlFile:iterate(baseKey, function (i, key) CpUtil.debugVehicle(CpDebug.DBG_COURSES, self, "Loading assigned course: %s", key) - local course = Course.createFromXml(nil, xmlFile, key) + course = Course.createFromXml(nil, xmlFile, key) course:setName(name) + end) + if course then self.courseWrapper = EditorCourseWrapper(course) - end) + return true + end + return false end - self.file:load(CpCourseManager.xmlSchema, CpCourseManager.xmlKeyFileManager, - load, self, false) - self.courseDisplay:setCourse(self.courseWrapper) - local course = self.courseWrapper:getCourse() - if course and course:getMultiTools() > 1 then - self.needsMultiToolDialog = true + if self.file:load(CpCourseManager.xmlSchema, CpCourseManager.xmlKeyFileManager, + load, self, false) then + self.courseDisplay:setCourse(self.courseWrapper) + local course = self.courseWrapper:getCourse() + if course and course:getMultiTools() > 1 then + self.needsMultiToolDialog = true + end + return true end + return false end --- Saves the course, might be a good idea to consolidate this with the saving of CpCourseManager. @@ -96,12 +104,15 @@ function CourseEditor:activate(file) return end if file then - self.isActive = true - self.file = file - self:loadCourse() - g_gui:showGui("ShopMenu") - g_shopMenu:onButtonConstruction() + if self:loadCourse() then + self.isActive = true + self.file = file + g_gui:showGui("ShopMenu") + g_shopMenu:onButtonConstruction() + return true + end end + return false end function CourseEditor:activateCustomField(file, field) diff --git a/scripts/field/CustomField.lua b/scripts/field/CustomField.lua index 82bbd2a61..fa68e66a8 100644 --- a/scripts/field/CustomField.lua +++ b/scripts/field/CustomField.lua @@ -25,16 +25,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. ---@class CustomField CustomField = CpObject() - -CustomField.xmlSchema = XMLSchema.new("customField") -CustomField.xmlSchema:register(XMLValueType.STRING ,"customField#name", "Name") -- for backwards compatibility -CustomField.xmlSchema:register(XMLValueType.STRING ,"customField.vertices", "Vertices of the field polygon") - CustomField.rootXmlKey = "customField" - -function CustomField:init() - -end +CustomField.vertexKey = "vertex" +CustomField.xmlSchema = XMLSchema.new(CustomField.rootXmlKey) +CustomField.xmlSchema:register(XMLValueType.VECTOR_2, + string.format("%s.%s(?)", CustomField.rootXmlKey, CustomField.vertexKey), + "Vertices of the field polygon") function CustomField:setup(name, vertices) @@ -81,8 +77,7 @@ function CustomField:draw(map) if not self.fieldHotspot then -- add hotspot when draw first called. Can't create in the constructor as on game load -- when the custom fields are loaded there's no player yet - -- TODO_25 - -- self:addHotspot() + self:addHotspot() end self.fieldPlot:draw(map) end @@ -128,15 +123,26 @@ function CustomField:findFieldCenter() self.posZ = z / #self.vertices end +function CustomField:getCenter() + return self.posX, self.posZ +end -function CustomField:saveToXml(xmlFile, baseKey,name) - xmlFile:setValue(baseKey .. '#name', name) - xmlFile:setValue(baseKey .. '.vertices', self:serializeVertices()) +function CustomField:saveToXml(xmlFile, baseKey, name) + for i=1, #self.vertices do + xmlFile:setValue(string.format('%s.%s(%d)', baseKey, self.vertexKey, i-1), self.vertices[i].x, self.vertices[i].z ) + end end -function CustomField:loadFromXml(xmlFile,baseKey) - local vertices = CustomField.deserializeVertices(xmlFile:getValue(baseKey .. '.vertices')) - self:setup(nil,vertices) +function CustomField:loadFromXml(xmlFile, baseKey) + local vertices = {} + xmlFile:iterate(baseKey .. "." .. self.vertexKey, function (_, key) + local x, z = xmlFile:getValue(key) + table.insert(vertices, {x = x, z = z}) + end) + if #vertices > 0 then + self:setup(nil, vertices) + return true + end end function CustomField:writeStream(streamId, connection) @@ -169,12 +175,17 @@ function CustomField.deserializeVertices(serializedVertices) return vertices end +--- Trys to load a custom field +---@param file File +---@return CustomField|nil function CustomField.createFromXmlFile(file) local field = CustomField() - file:load(CustomField.xmlSchema,CustomField.rootXmlKey, - CustomField.loadFromXml,field) - field:setName(file:getName()) - return field + if file:load(CustomField.xmlSchema, CustomField.rootXmlKey, + CustomField.loadFromXml, field) then + field:setName(file:getName()) + return field + end + return nil end function CustomField.createFromStream(streamId, connection) diff --git a/scripts/field/CustomFieldManager.lua b/scripts/field/CustomFieldManager.lua index 32f097dd6..0f9a90f04 100644 --- a/scripts/field/CustomFieldManager.lua +++ b/scripts/field/CustomFieldManager.lua @@ -40,34 +40,37 @@ function CustomFieldManager:load() self.fileSystem:refresh() local entries = self.rootDir:getEntries(false, true) for i, entry in pairs(entries) do - table.insert(self.fields, CustomField.createFromXmlFile(entry)) + local field = CustomField.createFromXmlFile(entry) + if field == nil then + CpUtil.info("Failed to load custom field: %s", tostring(entry)) + else + table.insert(self.fields, CustomField.createFromXmlFile(entry)) + end end --- Adds reference to the custom fields, for extern mod support. g_fieldManager.cpCustomFields = self.fields end --- New fields are created with a prefix and the next available number. +---@return number function CustomFieldManager:getNewFieldNumber() local numbers = {} for i, entry in pairs(self.fields) do local name = entry:getName() if name:startsWith("CP-") then local n = entry:getFieldNumber() - if n then - table.insert(numbers, entry) - end + numbers[n] = true end end - table.sort(numbers, function (a, b) return a:getFieldNumber() < b:getFieldNumber() end) - for i, entry in pairs(numbers) do - if i ~= entry:getFieldNumber() then - -- the i. entry is not i, so we can use i as a new number (entries is sorted) - return i - end + local ix = 1 + while self.currentView:hasEntryWithName(self.namePrefix..tostring(ix)) or numbers[ix] do + ix = ix + 1 end - return #numbers + 1 + return ix end +--- Creates a new custom field from a given vertices table. +---@param waypoints table function CustomFieldManager:addField(waypoints) if #waypoints < 10 then CpUtil.info('Recorded course has less than 10 waypoints, ignoring.') @@ -82,7 +85,8 @@ function CustomFieldManager:addField(waypoints) nil, nil, nil, nil, nil, nil, newField) end - +--- Tries to delete a given custom field. +---@param fieldToDelete CustomField function CustomFieldManager:deleteField(fieldToDelete) YesNoDialog.show( CustomFieldManager.onClickDeleteDialog, self, @@ -90,7 +94,9 @@ function CustomFieldManager:deleteField(fieldToDelete) nil, nil, nil, nil, nil, nil, fieldToDelete) end -function CustomFieldManager:renameField(field, hotspot) +--- Tries renames a given custom field +---@param field CustomField +function CustomFieldManager:renameField(field) TextInputDialog.show( CustomFieldManager.onClickRenameDialog, self, field:getName() or "", @@ -98,7 +104,9 @@ function CustomFieldManager:renameField(field, hotspot) nil, 30, g_i18n:getText("button_ok"), field) end -function CustomFieldManager:editField(fieldToEdit, hotspot) +--- Tries to edit a given custom field, with the course editor. +---@param fieldToEdit CustomField +function CustomFieldManager:editField(fieldToEdit) for i, field in pairs(self.fields) do if field == fieldToEdit then local file = self.currentView:getEntryByName(fieldToEdit:getName()) @@ -109,13 +117,17 @@ function CustomFieldManager:editField(fieldToEdit, hotspot) end end +--- Saves the given custom field +---@param file File +---@param field CustomField +---@param forceReload boolean|nil function CustomFieldManager:saveField(file, field, forceReload) file:save(CustomField.rootXmlKey, - CustomField.xmlSchema, - CustomField.rootXmlKey, - CustomField.saveToXml, - field, - field:getName()) + CustomField.xmlSchema, + CustomField.rootXmlKey, + CustomField.saveToXml, + field, + field:getName()) if forceReload then self:delete() self:load() @@ -133,6 +145,8 @@ function CustomFieldManager:onClickSaveDialog(clickOk, field) fieldValid = true table.insert(self.fields, field) self.fileSystem:refresh() + else + CpUtil.info("Failed to create custom Field: %s", field:getName()) end end if not fieldValid then diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index d0d235841..6087f4d4a 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -1,4 +1,6 @@ -CpInGameMenu = {} +CpInGameMenu = { + BASE_XML_KEY = "InGameMenu" +} local CpInGameMenu_mt = Class(CpInGameMenu, TabbedMenu) function CpInGameMenu.new(target, customMt, messageCenter, l10n, inputManager, courseStorage) local self = CpInGameMenu:superClass().new(target, customMt or CpInGameMenu_mt, messageCenter, l10n, inputManager) @@ -103,7 +105,33 @@ function CpInGameMenu.setupGui(courseStorage) "CpInGameMenu", g_cpInGameMenu) end --- Lines 279-324 +function CpInGameMenu.registerXmlSchema(xmlSchema, xmlKey) + xmlKey = xmlKey .. CpInGameMenu.BASE_XML_KEY .. "." + CpCourseGeneratorFrame.registerXmlSchema(xmlSchema, xmlKey) + CpGlobalSettingsFrame.registerXmlSchema(xmlSchema, xmlKey) + CpVehicleSettingsFrame.registerXmlSchema(xmlSchema, xmlKey) + CpCourseManagerFrame.registerXmlSchema(xmlSchema, xmlKey) + CpHelpFrame.registerXmlSchema(xmlSchema, xmlKey) +end + +function CpInGameMenu:loadFromXMLFile(xmlFile, baseKey) + baseKey = baseKey .. CpInGameMenu.BASE_XML_KEY .. "." + self.pageCourseGenerator:loadFromXMLFile(xmlFile, baseKey) + self.pageGlobalSettings:loadFromXMLFile(xmlFile, baseKey) + self.pageVehicleSettings:loadFromXMLFile(xmlFile, baseKey) + self.pageCourseManager:loadFromXMLFile(xmlFile, baseKey) + self.pageHelpLine:loadFromXMLFile(xmlFile, baseKey) +end + +function CpInGameMenu:saveToXMLFile(xmlFile, baseKey) + baseKey = baseKey .. CpInGameMenu.BASE_XML_KEY .. "." + self.pageCourseGenerator:saveToXMLFile(xmlFile, baseKey) + self.pageGlobalSettings:saveToXMLFile(xmlFile, baseKey) + self.pageVehicleSettings:saveToXMLFile(xmlFile, baseKey) + self.pageCourseManager:saveToXMLFile(xmlFile, baseKey) + self.pageHelpLine:saveToXMLFile(xmlFile, baseKey) +end + function CpInGameMenu:initializePages() self.clickBackCallback = function () if self.currentPage.onClickBack then @@ -115,13 +143,13 @@ function CpInGameMenu:initializePages() self.pageCourseGenerator:setInGameMap( g_inGameMenu.baseIngameMap, g_currentMission.hud) + self.pageCourseManager:setCourseStorage(self.courseStorage) self.pageHelpLine:initialize(self) self.pageGlobalSettings:initialize(self) self.pageVehicleSettings:initialize(self) self.pageCourseGenerator:initialize(self) self.pageCourseManager:initialize(self) - self.pageCourseManager:setCourseStorage(self.courseStorage) end -- Lines 327-362 diff --git a/scripts/gui/CpAIHotspots.lua b/scripts/gui/CustomFieldHotspot.lua similarity index 55% rename from scripts/gui/CpAIHotspots.lua rename to scripts/gui/CustomFieldHotspot.lua index 572b66051..2ae8133d1 100644 --- a/scripts/gui/CpAIHotspots.lua +++ b/scripts/gui/CustomFieldHotspot.lua @@ -1,21 +1,42 @@ --- Custom field hotspot that can be clicked for deleting of the field course. CustomFieldHotspot = {} CustomFieldHotspot.CATEGORY = 200 -local CustomFieldHotspot_mt = Class(CustomFieldHotspot, FieldHotspot) +CustomFieldHotspot.SLICE_ID = "gui.ingameMap_other" +CustomFieldHotspot.NAME = "CP_customFieldManager_hotspotName" +CustomFieldHotspot.COLOR = {0.61049, 0.56471, 0.00303, 1} +local CustomFieldHotspot_mt = Class(CustomFieldHotspot, FarmlandHotspot) function CustomFieldHotspot.new(customMt) - local self = FieldHotspot.new(customMt or CustomFieldHotspot_mt) - + local self = FarmlandHotspot.new(customMt or CustomFieldHotspot_mt) + self.lastName = "" return self end +function CustomFieldHotspot:render(x, y, rotation, small) + local name = self:getName() + if name ~= self.lastName then + setTextBold(true) + local width = getTextWidth(self.textSize * 1/(self.scale * 0.75), self.field:getName()) + setTextBold(false) + self.icon:setDimension(width + self.width) + end + self.lastName = name + CustomFieldHotspot:superClass().render(self, x, y, rotation, small) +end + +function CustomFieldHotspot:setScale(scale) + self.scale = scale +end + function CustomFieldHotspot:getCategory() return CustomFieldHotspot.CATEGORY end +---@param field CustomField function CustomFieldHotspot:setField(field) - CustomFieldHotspot:superClass().setField(self, field) - + self.field = field + local worldX, worldZ = field:getCenter() + self:setWorldPosition(worldX, worldZ) self.clickArea = MapHotspot.getClickArea( { 0, @@ -30,6 +51,10 @@ function CustomFieldHotspot:setField(field) ) end +function CustomFieldHotspot:getName() + return self.field:getName() +end + function CustomFieldHotspot:onClickDelete() CpUtil.debugFormat(CpDebug.DBG_HUD,"Delete custom field %s.", self.name) g_customFieldManager:deleteField(self.field) @@ -50,21 +75,3 @@ function CustomFieldHotspot:getAreaText() return g_i18n:formatArea(self.field:getAreaInSqMeters()/10000 , 2) end ---- Enables field hotspot area content box. -FieldHotspot.clickArea = MapHotspot.getClickArea( - { - 0, - 0, - 1, - 1 - }, - { - 1,1, - }, - 0 -) - -FieldHotspot.getAreaText = function (self) - --- Is already in ha. - return g_i18n:formatArea(self.field.fieldArea , 2) -end \ No newline at end of file diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 051d9ae1c..4b932b3dc 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -44,6 +44,8 @@ CpCourseGeneratorFrame = { MAP_SELECTOR_HOTSPOT = 1, MAP_SELECTOR_CREATE_JOB = 2, MAP_SELECTOR_ACTIVE_JOBS = 3, + CP_MAP_HOTSPOT_OFFSET = 200, + BASE_XML_KEY = "CourseGenerator" -- POSITION_UVS = GuiUtils.getUVs({ -- 760, @@ -94,6 +96,7 @@ function CpCourseGeneratorFrame.new(target, custom_mt) g_i18n:getText("button_createJob"), g_i18n:getText("ui_activeAIJobs")} + self.cpHotspotSettingValue = 1 return self end @@ -113,6 +116,22 @@ function CpCourseGeneratorFrame.createFromExistingGui(gui, guiName) return newGui end +function CpCourseGeneratorFrame.registerXmlSchema(xmlSchema, xmlKey) + xmlKey = xmlKey .. CpCourseGeneratorFrame.BASE_XML_KEY + xmlSchema:register(XMLValueType.INT, xmlKey .. "#mapHotspotValue", "Selected hotspots") +end + + +function CpCourseGeneratorFrame:loadFromXMLFile(xmlFile, baseKey) + self.cpHotspotSettingValue = xmlFile:getValue( + string.format("%s%s#mapHotspotValue", baseKey, self.BASE_XML_KEY), 0xFF) +end + +function CpCourseGeneratorFrame:saveToXMLFile(xmlFile, baseKey) + xmlFile:setValue(string.format("%s%s#mapHotspotValue", + baseKey, self.BASE_XML_KEY), self.cpHotspotSettingValue) +end + function CpCourseGeneratorFrame:setInGameMap(ingameMap, hud) self.ingameMapBase = ingameMap self.ingameMap:setIngameMap(ingameMap) @@ -173,7 +192,14 @@ function CpCourseGeneratorFrame:initialize(menu) self.mapOverviewSelector:setTexts(self.mapSelectorTexts) self.mapOverviewSelector:setState(1, true) - self.hotspotFilterCategories = InGameMenuMapFrame.HOTSPOT_FILTER_CATEGORIES + self.hotspotFilterCategories = table.clone(InGameMenuMapFrame.HOTSPOT_FILTER_CATEGORIES) + table.insert(self.hotspotFilterCategories[2], { + ["id"] = CustomFieldHotspot.CATEGORY, + ["sliceId"] = CustomFieldHotspot.SLICE_ID, + ["name"] = CustomFieldHotspot.NAME, + ["color"] = {CustomFieldHotspot.COLOR}}) + self.CUSTOM_FIELD_HOTSPOTS = #self.hotspotFilterCategories[2] + self.hotspotStateFilter = {} for i, data in ipairs(self.hotspotFilterCategories) do self.hotspotStateFilter[i] = {} @@ -287,11 +313,17 @@ function CpCourseGeneratorFrame:onFrameOpen() local hotspotValue = g_gameSettings:getValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER) for i, hotspotCategory in pairs(self.hotspotFilterCategories[1]) do local isBitSet = Utils.isBitSet(hotspotValue, hotspotCategory.id) + if hotspotCategory.id >= self.CP_MAP_HOTSPOT_OFFSET then + isBitSet = Utils.isBitSet(self.cpHotspotSettingValue, hotspotCategory.id - self.CP_MAP_HOTSPOT_OFFSET) + end self.hotspotStateFilter[1][i] = isBitSet self.ingameMapBase:setDefaultFilterValue(hotspotCategory.id, isBitSet) end for i, hotspotCategory in pairs(self.hotspotFilterCategories[2]) do local isBitSet = Utils.isBitSet(hotspotValue, hotspotCategory.id) + if hotspotCategory.id >= self.CP_MAP_HOTSPOT_OFFSET then + isBitSet = Utils.isBitSet(self.cpHotspotSettingValue, hotspotCategory.id - self.CP_MAP_HOTSPOT_OFFSET) + end self.hotspotStateFilter[2][i] = isBitSet self.ingameMapBase:setDefaultFilterValue(hotspotCategory.id, isBitSet) end @@ -335,19 +367,30 @@ function CpCourseGeneratorFrame:onFrameOpen() self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) self:setMapSelectionItem(nil) end - + g_customFieldManager:refresh() end function CpCourseGeneratorFrame:saveHotspotFilter() local hotspotValue = 0 + self.cpHotspotSettingValue = 0 for i, state in pairs(self.hotspotStateFilter[1]) do if state then - hotspotValue = Utils.setBit(hotspotValue, i) + local id = self.hotspotFilterCategories[1][i].id + if id >= self.CP_MAP_HOTSPOT_OFFSET then + self.cpHotspotSettingValue = Utils.setBit(self.cpHotspotSettingValue, id - self.CP_MAP_HOTSPOT_OFFSET) + else + hotspotValue = Utils.setBit(hotspotValue, id) + end end end for i, state in pairs(self.hotspotStateFilter[2]) do if state then - hotspotValue = Utils.setBit(hotspotValue, i + #self.hotspotStateFilter[1]) + local id = self.hotspotFilterCategories[2][i].id + if id >= self.CP_MAP_HOTSPOT_OFFSET then + self.cpHotspotSettingValue = Utils.setBit(self.cpHotspotSettingValue, id - self.CP_MAP_HOTSPOT_OFFSET) + else + hotspotValue = Utils.setBit(hotspotValue, id) + end end end g_gameSettings:setValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER, hotspotValue, true) @@ -543,8 +586,10 @@ function CpCourseGeneratorFrame:onDrawPostIngameMap(element, ingameMap) elseif vehicle and vehicle.drawCpCoursePlot then vehicle:drawCpCoursePlot(ingameMap) end - -- show the custom fields on the AI map - g_customFieldManager:draw(ingameMap) + if self.hotspotStateFilter[2][self.CUSTOM_FIELD_HOTSPOTS] then + -- show the custom fields on the AI map + g_customFieldManager:draw(ingameMap) + end -- show the selected field on the AI screen map when creating a job if self.currentJob and self.currentJob.draw then self.currentJob:draw(ingameMap, self.mode == self.MODE_OVERVIEW) diff --git a/scripts/gui/pages/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua index 32f704edb..419a98f20 100644 --- a/scripts/gui/pages/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -83,6 +83,18 @@ function CpCourseManagerFrame.setupGui() "CpCourseManagerFrame", courseManagerFrame, true) end +function CpCourseManagerFrame.registerXmlSchema(xmlSchema, xmlKey) + +end + +function CpCourseManagerFrame:loadFromXMLFile(xmlFile, baseKey) + +end + +function CpCourseManagerFrame:saveToXMLFile(xmlFile, baseKey) + +end + function CpCourseManagerFrame:setCourseStorage(courseStorage) self.courseStorage = courseStorage end @@ -133,7 +145,9 @@ function CpCourseManagerFrame:initialize(menu) if viewEntry then if not viewEntry:isDirectory() then local vehicle = CpUtil.getCurrentVehicle() - vehicle:appendLoadedCpCourse(viewEntry:getEntity()) + if not vehicle:appendLoadedCpCourse(viewEntry:getEntity()) then + --TODO_25 Error message missing! + end else self.showInfoDialog( self.translations.targetIsNoCourse, viewEntry) diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua index 485cbe8a7..de3bdc2cc 100644 --- a/scripts/gui/pages/CpGlobalSettingsFrame.lua +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -40,6 +40,18 @@ function CpGlobalSettingsFrame.createFromExistingGui(gui, guiName) return newGui end +function CpGlobalSettingsFrame.registerXmlSchema(xmlSchema, xmlKey) + +end + +function CpGlobalSettingsFrame:loadFromXMLFile(xmlFile, baseKey) + +end + +function CpGlobalSettingsFrame:saveToXMLFile(xmlFile, baseKey) + +end + function CpGlobalSettingsFrame:initialize(menu) self.booleanPrefab:unlinkElement() diff --git a/scripts/gui/pages/CpHelpFrame.lua b/scripts/gui/pages/CpHelpFrame.lua index deace94b8..a1372a98f 100644 --- a/scripts/gui/pages/CpHelpFrame.lua +++ b/scripts/gui/pages/CpHelpFrame.lua @@ -27,6 +27,18 @@ function CpHelpFrame.createFromExistingGui(gui, guiName) return newGui end +function CpHelpFrame.registerXmlSchema(xmlSchema, xmlKey) + +end + +function CpHelpFrame:loadFromXMLFile(xmlFile, baseKey) + +end + +function CpHelpFrame:saveToXMLFile(xmlFile, baseKey) + +end + function CpHelpFrame:initialize(menu) self.helpLineDotTemplate:unlinkElement() FocusManager:removeElement(self.helpLineDotTemplate) diff --git a/scripts/gui/pages/CpVehicleSettingsFrame.lua b/scripts/gui/pages/CpVehicleSettingsFrame.lua index c17f9d458..70b927bd6 100644 --- a/scripts/gui/pages/CpVehicleSettingsFrame.lua +++ b/scripts/gui/pages/CpVehicleSettingsFrame.lua @@ -38,6 +38,18 @@ function CpVehicleSettingsFrame.createFromExistingGui(gui, guiName) return newGui end +function CpVehicleSettingsFrame.registerXmlSchema(xmlSchema, xmlKey) + +end + +function CpVehicleSettingsFrame:loadFromXMLFile(xmlFile, baseKey) + +end + +function CpVehicleSettingsFrame:saveToXMLFile(xmlFile, baseKey) + +end + function CpVehicleSettingsFrame:initialize(menu) self.cpMenu = menu self.booleanPrefab:unlinkElement() diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua index 35ea92200..2dabee792 100644 --- a/scripts/specializations/CpCourseManager.lua +++ b/scripts/specializations/CpCourseManager.lua @@ -88,7 +88,7 @@ function CpCourseManager.registerFunctions(vehicleType) SpecializationUtil.registerFunction(vehicleType, 'hasCpCourse', CpCourseManager.hasCourse) SpecializationUtil.registerFunction(vehicleType, 'cpCopyCourse', CpCourseManager.cpCopyCourse) - SpecializationUtil.registerFunction(vehicleType, 'appendLoadedCpCourse', CpCourseManager.appendLoadedCourse) + SpecializationUtil.registerFunction(vehicleType, 'appendLoadedCpCourse', CpCourseManager.appendLoadedCpCourse) SpecializationUtil.registerFunction(vehicleType, 'saveCpCourses', CpCourseManager.saveCourses) SpecializationUtil.registerFunction(vehicleType, 'resetCpCourses', CpCourseManager.resetCourses) SpecializationUtil.registerFunction(vehicleType, 'resetCpCoursesFromGui', CpCourseManager.resetCpCoursesFromGui) @@ -148,7 +148,7 @@ function CpCourseManager:loadAssignedCourses(xmlFile, baseKey, noEventSend, name CpUtil.debugVehicle(CpDebug.DBG_COURSES,self,"Loading assigned course: %s",key) local course = Course.createFromXml(self,xmlFile,key) course:setVehicle(self) - table.insert(courses,course) + table.insert(courses, course) end) if courses ~= nil and next(courses) then spec.courses = courses @@ -385,11 +385,14 @@ function CpCourseManager.getCourseName(course) return name end -function CpCourseManager:appendLoadedCourse(file) +--- Trys to load a course from the file system +---@param file File +---@return boolean success +function CpCourseManager:appendLoadedCpCourse(file) --- For now clear the previous courses. CpCourseManager.resetCourses(self) - file:load(CpCourseManager.xmlSchema, CpCourseManager.xmlKeyFileManager, - CpCourseManager.loadAssignedCourses, self, false) + return file:load(CpCourseManager.xmlSchema, CpCourseManager.xmlKeyFileManager, + CpCourseManager.loadAssignedCourses, self, false) end function CpCourseManager:saveCourses(file,text) @@ -430,10 +433,6 @@ function CpCourseManager:cpReverseCurrentCourse(noEventSend) end end -function CpCourseManager:appendCourse(course) - -end - function CpCourseManager:rememberCpLastWaypointIx(ix) local spec = self.spec_cpCourseManager spec.rememberedWpIx = ix diff --git a/scripts/test/CourseManagerTest.lua b/scripts/test/CourseManagerTest.lua index 1330e89b4..6380f2745 100644 --- a/scripts/test/CourseManagerTest.lua +++ b/scripts/test/CourseManagerTest.lua @@ -25,7 +25,7 @@ assert(file.fullPath == coursesDir .. "/" .. "testFile") file:save("xmlRootName",{},"xmlBaseKey",function () end,{},...) -file:load({},"xmlBaseKey",function () end,{},...) +file:load({},"xmlBaseKey",function () return true end,{},...) file:delete() assert(not fileExists(file:getFullPath())) From cd552a07072635aa33e96bbe0c30fbeed1ae2730 Mon Sep 17 00:00:00 2001 From: schwiti6190 <schwiti6190@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:52:25 +0000 Subject: [PATCH 120/158] Updated translations --- translations/translation_br.xml | 1 + translations/translation_cs.xml | 1 + translations/translation_ct.xml | 1 + translations/translation_cz.xml | 1 + translations/translation_da.xml | 1 + translations/translation_de.xml | 1 + translations/translation_ea.xml | 1 + translations/translation_en.xml | 1 + translations/translation_es.xml | 1 + translations/translation_fc.xml | 1 + translations/translation_fi.xml | 1 + translations/translation_fr.xml | 1 + translations/translation_hu.xml | 1 + translations/translation_it.xml | 1 + translations/translation_jp.xml | 1 + translations/translation_kr.xml | 1 + translations/translation_nl.xml | 1 + translations/translation_no.xml | 1 + translations/translation_pl.xml | 1 + translations/translation_pt.xml | 1 + translations/translation_ro.xml | 1 + translations/translation_ru.xml | 1 + translations/translation_sv.xml | 1 + translations/translation_tr.xml | 1 + 24 files changed, 24 insertions(+) diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 0f9e17cc1..7588be1e7 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -504,6 +504,7 @@ A rota é salva automaticamente ao fechar o editor e substituir a rota seleciona <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Deseja salvar a rota gravado como campo customizado %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Deseja excluir o campo customizado %s?"/> <text name="CP_customFieldManager_delete" text="Excluir campo customizado"/> diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index 916209ea3..201799ef4 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -504,6 +504,7 @@ <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="是否要将录制的线路范围保存为自定义田地 %s?"/> <text name="CP_customFieldManager_confirm_delete" text="要删除自定义田地吗 %s?"/> <text name="CP_customFieldManager_delete" text="删除自定义田地"/> diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml index 906a1091f..9daef4b71 100644 --- a/translations/translation_ct.xml +++ b/translations/translation_ct.xml @@ -504,6 +504,7 @@ <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="是否要將錄製的任務儲存為自訂田地 %s?"/> <text name="CP_customFieldManager_confirm_delete" text="要刪除自訂田地嗎 %s?"/> <text name="CP_customFieldManager_delete" text="刪除自訂田地"/> diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index 91cc4ae3d..c157eca8f 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -504,6 +504,7 @@ Trasa se automaticky uloží při zavření editoru a přepíše vybranou trasu. <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Chcete uložit zaznamenanou trasu jako vlastní pole %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Chcete odstranit vlastní pole %s?"/> <text name="CP_customFieldManager_delete" text="Vymazat vlastní pole"/> diff --git a/translations/translation_da.xml b/translations/translation_da.xml index b1cc6a486..e62eae623 100644 --- a/translations/translation_da.xml +++ b/translations/translation_da.xml @@ -504,6 +504,7 @@ Ruten gemmes automatisk ved lukning af editoren og tilsidesætter den valgte rut <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Ønsker du at gemme den optegnede rute som en brugerdefineret mark %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Ønsker du at slette den brugerdefinerede mark %s?"/> <text name="CP_customFieldManager_delete" text="Slet brugerdefineret mark"/> diff --git a/translations/translation_de.xml b/translations/translation_de.xml index c418a938a..6ce031caf 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -504,6 +504,7 @@ Der Kurs wird beim Schließen automatisch gespeichert und überschrieben. <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Randkurs"/> <text name="CP_customFieldManager_confirm_save" text="Möchtest du diesen benutzerdefinierten Randkurs als %s speichern?"/> <text name="CP_customFieldManager_confirm_delete" text="Möchtest du den Randkurs %s löschen?"/> <text name="CP_customFieldManager_delete" text="Randkurs löschen"/> diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml index a8fdcfffb..2d0c19dc4 100644 --- a/translations/translation_ea.xml +++ b/translations/translation_ea.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 8f58ed551..272c09736 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_es.xml b/translations/translation_es.xml index 5f753ae87..835afce2d 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -504,6 +504,7 @@ El curso se guarda automáticamente al cerrar el editor y anula el curso selecci <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="¿Quieres guardar la ruta como campo personalizado %s?"/> <text name="CP_customFieldManager_confirm_delete" text="¿Quieres eliminar el campo personalizado %s?"/> <text name="CP_customFieldManager_delete" text="Borrar Campo Personalizado"/> diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml index ef69c963d..0ff4776ad 100644 --- a/translations/translation_fc.xml +++ b/translations/translation_fc.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml index 315ba94df..e14525c56 100644 --- a/translations/translation_fi.xml +++ b/translations/translation_fi.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index f6022ed44..37c663597 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -504,6 +504,7 @@ La course est automatiquement sauvegardée à la fermeture de l'éditeur et remp <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Souhaitez-vous sauvegarder la course enregistrée comme contour de champ personnalisé %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Souhaitez-vous supprimer le champ personnalisé %s?"/> <text name="CP_customFieldManager_delete" text="Supprimer le champ personnalisé"/> diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index 6cebcc376..d192c4190 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -504,6 +504,7 @@ Az útvonal automatikusan mentésre kerül a szerkesztő bezárásakor és felü <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="El akarod menteni az útvonalat szántóföldként %s néven?"/> <text name="CP_customFieldManager_confirm_delete" text="Törlöd a %s szántóföldet?"/> <text name="CP_customFieldManager_delete" text="Szántóföld törlés"/> diff --git a/translations/translation_it.xml b/translations/translation_it.xml index ad3098e72..343d500f8 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -504,6 +504,7 @@ Il percorso viene salvato automaticamente alla chiusura dell'editor e sovrascriv <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Vuoi salvare il percorso registrato come campo personalizzato %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Vuoi eliminare il campo personalizzato %s?"/> <text name="CP_customFieldManager_delete" text="Cancella campo personalizzato"/> diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index d2a256fdb..c492eadae 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="カスタムコース「%s」を保存しますか?"/> <text name="CP_customFieldManager_confirm_delete" text="カスタムフィールド「%s」を消去しますか?"/> <text name="CP_customFieldManager_delete" text="カスタムフィールドを消去する"/> diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml index 8133e94ed..377292516 100644 --- a/translations/translation_kr.xml +++ b/translations/translation_kr.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index 7f2d1b681..458cb085a 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Wil je de geregistreerde koers opslaan als aangepast veld %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Wilt u het aangepaste veld %s verwijderen?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_no.xml b/translations/translation_no.xml index e367c51d2..683fdc313 100644 --- a/translations/translation_no.xml +++ b/translations/translation_no.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index d84ef34c3..e4a8809a4 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -504,6 +504,7 @@ Kurs jest automatycznie zapisywany po zamknięciu edytora i nadpisuje wybrany ku <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Czy chcesz zapisać nagrany kurs jako nowe pole %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Czy chcesz usunąć nowe pole %s?"/> <text name="CP_customFieldManager_delete" text="Usuń nowe pole"/> diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index 4a8ccfca1..b67f5b068 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -504,6 +504,7 @@ A rota é guardada automaticamente ao fechar o editor e sobrepõe-se a qualquer <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Queres guardar a rota gravada como um campo personalizado %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Queres apagar o campo personalizado %s?"/> <text name="CP_customFieldManager_delete" text="Apagar o campo personalizado"/> diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml index 10c072570..1745d0621 100644 --- a/translations/translation_ro.xml +++ b/translations/translation_ro.xml @@ -504,6 +504,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Do you want to save the recorded course as custom field %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Do you want to delete the custom field %s?"/> <text name="CP_customFieldManager_delete" text="Delete custom field"/> diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index c5cbc7a90..57cce5540 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -504,6 +504,7 @@ <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Вы хотите сохранить записанный курс как пользовательское поле %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Вы хотите удалить пользовательское поле %s?"/> <text name="CP_customFieldManager_delete" text="Удалить пользовательское поле"/> diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml index 84ac7f3e5..e2a52b0a8 100644 --- a/translations/translation_sv.xml +++ b/translations/translation_sv.xml @@ -504,6 +504,7 @@ Banan sparas automatiskt vid stängning av editorn och åsidosätter den valda b <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Vill du spara den inspelade kursen som anpassat fält %s?"/> <text name="CP_customFieldManager_confirm_delete" text="Vill du radera det anpassade fältet %s?"/> <text name="CP_customFieldManager_delete" text="Ta bort anpassat fält"/> diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml index 9448c38e2..b848090da 100644 --- a/translations/translation_tr.xml +++ b/translations/translation_tr.xml @@ -504,6 +504,7 @@ Editör kapatıldığında kurs otomatik olarak kaydedilir ve seçilen kursu ge <!----> <!--Custom field manager--> <!----> + <text name="CP_customFieldManager_hotspotName" text="Custom field"/> <text name="CP_customFieldManager_confirm_save" text="Kaydedilen kursu %s özel alanı olarak kaydetmek ister misiniz?"/> <text name="CP_customFieldManager_confirm_delete" text="%S özel alanını silmek istiyor musunuz?"/> <text name="CP_customFieldManager_delete" text="Özel alanı sil"/> From 91a17693b392ffb90186cc087b2a3c579ea4f1d6 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Mon, 16 Dec 2024 14:31:26 -0500 Subject: [PATCH 121/158] fix: forgotten rename --- scripts/ai/turns/AITurn.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua index 72a5ac20d..5bde28f72 100644 --- a/scripts/ai/turns/AITurn.lua +++ b/scripts/ai/turns/AITurn.lua @@ -888,7 +888,7 @@ CombinePocketHeadlandTurn = CpObject(CourseTurn) ---@param driveStrategy AIDriveStrategyCombineCourse ---@param turnContext TurnContext function CombinePocketHeadlandTurn:init(vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, workWidth) - CombineCourseTurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, + CourseTurn.init(self, vehicle, driveStrategy, ppc, proximityController, turnContext, fieldWorkCourse, workWidth, 'CombinePocketHeadlandTurn') end From c36fd53e26cae7d09fe605d13f507a53dd31df7a Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Tue, 17 Dec 2024 20:11:24 +0100 Subject: [PATCH 122/158] Added missing doc --- scripts/gui/pages/CpCourseGeneratorFrame.lua | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 4b932b3dc..81a981006 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -96,6 +96,7 @@ function CpCourseGeneratorFrame.new(target, custom_mt) g_i18n:getText("button_createJob"), g_i18n:getText("ui_activeAIJobs")} + --- Bitmask for the visible cp hotspots. self.cpHotspotSettingValue = 1 return self end @@ -123,6 +124,8 @@ end function CpCourseGeneratorFrame:loadFromXMLFile(xmlFile, baseKey) + --- We save the hotspot setting in the Courseplay User settings, + --- as we don't want to pollute the giants hotspot setting. self.cpHotspotSettingValue = xmlFile:getValue( string.format("%s%s#mapHotspotValue", baseKey, self.BASE_XML_KEY), 0xFF) end @@ -311,9 +314,13 @@ function CpCourseGeneratorFrame:onFrameOpen() end, self) -- g_messageCenter:subscribe(MessageType.AI_TASK_SKIPPED, self.onAITaskSkipped, self) local hotspotValue = g_gameSettings:getValue(GameSettings.SETTING.INGAME_MAP_HOTSPOT_FILTER) + ------------------------------------------ + --- Loads the hotspot selection + ------------------------------------------ for i, hotspotCategory in pairs(self.hotspotFilterCategories[1]) do local isBitSet = Utils.isBitSet(hotspotValue, hotspotCategory.id) if hotspotCategory.id >= self.CP_MAP_HOTSPOT_OFFSET then + --- Every hotspot selection with a greater ID than X will be saved in the courseplay user settings. isBitSet = Utils.isBitSet(self.cpHotspotSettingValue, hotspotCategory.id - self.CP_MAP_HOTSPOT_OFFSET) end self.hotspotStateFilter[1][i] = isBitSet @@ -322,6 +329,7 @@ function CpCourseGeneratorFrame:onFrameOpen() for i, hotspotCategory in pairs(self.hotspotFilterCategories[2]) do local isBitSet = Utils.isBitSet(hotspotValue, hotspotCategory.id) if hotspotCategory.id >= self.CP_MAP_HOTSPOT_OFFSET then + --- Every hotspot selection with a greater ID than X will be saved in the courseplay user settings. isBitSet = Utils.isBitSet(self.cpHotspotSettingValue, hotspotCategory.id - self.CP_MAP_HOTSPOT_OFFSET) end self.hotspotStateFilter[2][i] = isBitSet From 01ca04d8c194df579fa88a80490fe5d18e4a4ef7 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Wed, 18 Dec 2024 09:47:06 +0100 Subject: [PATCH 123/158] modDesc --- modDesc.xml | 234 ++-------------------------------------------------- 1 file changed, 9 insertions(+), 225 deletions(-) diff --git a/modDesc.xml b/modDesc.xml index 5bbdc64e8..a9044c7a7 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -13,7 +13,7 @@ <description> <en> -<![CDATA[Courseplay FS22 +<![CDATA[Courseplay FS25 Courseplay takes the AI worker to the next level. Sow, harvest and plow smarter: - use headlands - work on irregularly shaped fields, not just rectangles @@ -31,126 +31,18 @@ Courseplay takes the AI worker to the next level. Sow, harvest and plow smarter: - work in bunker silo to push and compact For a link to our latest release, Help page and GitHub page where you can find more information, help, and report issues please visit <a href="https://courseplay.dev/">Courseplay Website</a> -or go directly to our <a href="https://github.com/Courseplay/Courseplay_FS22">GitHub page</a>. +or go directly to our <a href="https://github.com/Courseplay/Courseplay_FS25">GitHub page</a>. Many thanks to our translators and to our community for reporting bugs, giving us feedback and great ideas. -Changelog 7.5.0.1 Hotfix: -- Fixes a rare pathfinder bug. -- Fixes a collision solving bug. - -Changelog 7.5.0.0: -- New course generator 2.0 with new features and updated help menu. -- A multitool course now contains 2-5 single course for each lane depending on the amount of vehicles you selected. -- Added food mixer wagon, fertilizer/lime/manure sprayer and Göweil LT Master as valid target for shovel mode. -- Added chopper unloader support known from FS19 back in. (Second unloader not supported.) -- Added line preview when drawing a custom field on the map and can hold left shift to draw straight lines. -- HUD reworked for a future project. -- Waypoints no more cast a shadow, this might increase performance a bit, when waypoints are active. -- Shovel position is slightly tilted down and finer hight adjustment (0.05 steps). This should help with some silage silos are not getting empty. -- The course on the AI Menu Map will now have arrows that show the direction of the lines. -- Multitool courses generated with previous releases won't work, you must regenerate them with this release. -- Although non-multitool courses saved with previous releases will work, we recommend to regenerate them for best result. -- Added option for waiting on the field for manuel refilling tools and refill fuel. -- More bugfixes. -- Increased desc Version to 80 (Game Version 1.14.0.0) - -WARNING: while this version is backwards compatible with older savegames, it'll write the courses to the savegame in a new format. -Older versions WILL NOT BE ABLE TO READ the games saved with this version! If you install this version, and decide to revert to an old one, YOU MUST BACK UP YOUR OLD SAVEGAME before! - -For a complete changelog please visit our <a href="https://github.com/Courseplay/Courseplay_FS22/releases">Release page</a> on GitHub. - -Changelog 7.4.0.0: -- Enable forestry mulchers and lawnmowers for fieldwork -- Added option to show the info panel only, when at least one message is active. -- Plow handling improvement in turns, now less headlands are needed. -- Improvement for reverse driving with offset tools. -- Added shovel mode driver, please read ingame help menu for how it works. -- Changed description for combine unload on first headland to be more clear. -- Added leveler height offset to the ground setting. -- Added fruit type check for mission and season. -- Combine unloading improvements. -- Can now use a truck and a trailer to unload a combine. -- More bugfixes. -- Added Info texts for vegetable harvester with pallets. -- Increased desc Version to 79 (Patch 1.13.1.0) - -Changelog 7.3.0.0: -- Bale collector/wrapper now works the same way as the combine unloader in terms of markers: -The field position is now saved into the vehicle and will not be reset to the vehicle position, when started by the hud! -The position has to be set everytime in the AI menu! -This fixes problems with AD restarting and also has the advantage that you don't need another AD Target on the field to start the driver. - -- Added Loader Driver for vehicles like Ropa Maus to pick up heaps of for example sugarbeet and to empty bunker silos: -The loader can always work together with a combine unloader (switch the "Combine" on the HUD to "Loader", the rest is the same) when picking up heaps on a field. -Using an unloader when emptying a bunker silo, can be tricky due the silo walls, try at your own risk :) -The Loader will unfold in a default position when it reaches the heap. -If the default position is not working for you, unfold it and move the conveyor to a left or right position and a height you want before you start, it will maintain that position. - -- Added unload on field option for combine unloader. Use the target icon to get into the AI Menu: -At the bottom there is a new option "Unload on Field" that have to be activated and a target set on or within 20m of the field where you unload on. -The direction is in which direction the heap will grow. - -- Added new feature, where the combine automatically can pickup the header from an attached trailer: -A new setting must be activated in the vehicle settings page under combine settings for this to work -and the combine must be towing a header (either with its own wheels or on a trailer). -The combine will then automatically pick up the header when the player starts the harvester near a field or sends the harvester to a field with Giants or Autodrive helper. -Remember, the combine needs enough space to park the trailer, pick the header up, reverse and then drive to the fieldwork course. - -- Added a proper offset setting to the HUD for bale collect/wrap mode. -- Added global setting for fruit destruction of helpers. (off, Courseplay only, all helpers). -- Added a countdown for bales to collect/wrap. Note that new bales produced on the same field will not update the counter immediately for performance reasons. -- Added pause button for recording field boundary. -- Help menu updated. -- Removed most default debugs (kept some info prints for now) -- Increased modDesc Version to 76 (Game Version 1.11.0.0 or higher) -- New website with an exact copy of the in-game help <a href="https://courseplay.dev/">Courseplay Website</a> - -Changelog 7.2.0.5: -- Goeweil pack bug fix for the stationary baler with the Courseplay hud. -- Gui fix for non valid Courseplay vehicles and disables the hud, while sitting in an attached vehicle (e.g. mobile Wood Crane). -- Disables a few hotspots, while drawing a field border. -- Disables shield raising while driving into the silo for now. -- Add option for Fieldwork to send the driver back to the first waypoint after the work is finished. -- Bug fix for mods with invalid capacities. -- Added a global setting to enable/disable harvesting while it's raining. -- Bug fixes and improvements for plows and balers. -- Some more small fixes and improvements. - -Changelog 7.2.0.0: -- added bunker silo mode and combine unloader mode. -- the remaining time for fieldwork is now displayed on the HUD. -- support for <a href="https://farming-simulator.com/mod.php?lang=en&country=us&mod_id=237080&title=fs2022">Universal Autoload</a>. -- option to show the current course on the mini map. -- user setting to change between feet and meters. -- simple collision avoidance for unloaders to not crash into the combine. -- option (keybinding) for course editor to delete all waypoints until the end. -- new course visibility option (blue eye icon) to show everything -between the leading 20 and last 5 passed waypoints. -- help menu sections for unloading combines and compacting/pushing in a bunker silo. -- countless fixes and improvements. - -Changelog 7.1.0.3: -- Updated mod description -- Added missing translation files, so every language supported by the game can be edited now. -- Added new course editor for fieldwork courses and custom field borders (more information in the help menu). -- Improved the interface for the auto load mod: FS22_aPalletAutoLoader. -- Arcusin Multipack F14 now unloads the compacted bales on the field. -- Some more fixes and enhancements. -- Vermeer DLC compatibility. - -Changelog 7.1.0.0: -- initial ModHub Release 7.1.0.0 -- Added vine fieldwork -- The hud is easier to use and a separate gamepad hud was added. -- Ingame help menu extended with Courseplay topics -- Complete new course manager integrated into the ingame menu. -- Works now great with AutoDrive, let Courseplay take care of the field work and use AutoDrive to transport your harvest. +Changelog 8.0.0.0: +- Initial FS25 convert. ]]> + </en> <de> -<![CDATA[Courseplay FS22 +<![CDATA[Courseplay FS25 Courseplay hebt den Helfer auf ein ganz neues Level. Säen, ernten und pflügen Sie intelligenter: - Nutze Vorgewende - Arbeite auf nicht rechteckigen Feldern @@ -172,120 +64,12 @@ AutoDrive erledigt das Abtanken eines Ladewagens oder Auffüllen einer Sämaschi Für mehr Informationen, Hilfe oder Fehlermeldungen schaue hier vorbei: <a href="https://github.com/Courseplay/Courseplay_FS22">GitHub</a>. Ein Link zu der neuesten Version, der Hilfe Seite und Github, wo du mehr Informationen und Hilfe findest, sowie Probleme und Bugs melden kannst, findest du auf unserer <a href="https://courseplay.dev/">Courseplay Webseite</a> -oder du gehst direkt auf unsere <a href="https://github.com/Courseplay/Courseplay_FS22">GitHub Webseite</a>. +oder du gehst direkt auf unsere <a href="https://github.com/Courseplay/Courseplay_FS25">GitHub Webseite</a>. Vielen Dank an unsere Community für die zahlreichen Übersetzungen, Bug Reports, Feedback und Vorschläge zur Verbesserung. -Changelog 7.5.0.1 Hotfix: -- Bugfix für einen selten auftretenden Pathfinder Bug. -- Bugfix für einen Bug bei der Kollisionslösung. - -Changelog 7.5.0.0: -- Neuer Kurs Generator 2.0 mit neuen Features und aktualisiertem Hilfemenü. -- Multitool Kurse beinhalten jetzt 2-5 einzelne Kurse für jede Bahn, abhänging von der Anzahl Fahrzeuge die gewählt wurden. -- Futtermischer, Dünger-, Kalk-, und Miststreuer, sowie Göweil LT Master als Ziel für den Schaufelmodus hinzugefügt. -- Häcksler können wieder abgetankt werden, wie man es aus FS19 kennt. (Das vorzeitige Rufen von einem 2ten Abfahrer ist nicht verfügbar). -- Linienvorschau beim Zeichnen eines eigenen Feldes und mit gedrückter linker Shift-Taste lassen sich gerade Linien ziehen. -- HUD umgebaut für ein zukünftiges Projekt. -- Wegpunkte erzeugen keine Schatten mehr, was die performance etwas verbessern sollte, wenn alle Wegpunkte angezeigt werden. -- Schaufelposition ist jetzt leicht nach unten gekippt und kann feiner eingestellt werden. (0.05er Schritte). Sollte dabei helfen, einige Silagesilos besser zu entleeren. -- Kurse auf der KI Karte haben jetzt kleine Pfeile, um die Richtung der Bahnen anzuzeigen. -- Multitool Kurse, welche mit der vorherigen Version generiert wurden, werden nicht mehr funktionieren. Sie müssen neu generiert werden. -- Auch wenn nicht-multitool Kurse mit dieser Version geladen werden können, empfehlen wir die Kurse neu zu generieren. -- Option zum Warten auf dem Feld für das Nachfüllen von Geräten und Treibstoff hinzugefügt. -- Mehrere bugfixes. -- Desc Version auf 80 erhöht (Spiel Version 1.14.0.0) - -WARNUNG: Auch wenn diese Version abwärtskompatibel mit älteren Speilständen ist, wird diese Version die Kurse in einem neuen Format in das savegame schreiben. -Ältere Courseplay Versionen werden NICHT IN DER LAGE SEIN, Spielstände von der aktuellen Version zu laden! Wenn du diese Version installierst und dich dazu entscheidest auf eine ältere Version zu wechseln, SICHERE VORHER DEINEN SPIELTAND! - -Für eine komplette Changelog, schaut bitte auf unsere <a href="https://github.com/Courseplay/Courseplay_FS22/releases">Release Seite</a> auf GitHub vorbei. - -Changelog 7.4.0.0: -- Forstmulcher und Handmäher können jetzt für die Feldarbeit verwendet werden. -- Option hinzugefügt, welches das Info Panel nur einblendet, wenn eine Nachricht vorhanden ist. -- Verbesserung für Pflüge beim Wenden, es werden jetzt weniger Vorgewende benötigt. -- Verbesserung für das Rückwärtsfahren mit Tools, die einen Versatz haben. -- Schaufel Modus hinzugefügt. Bitte lest die Ingame Hilfe durch, wie dieser Modus funktioniert. -- Beschreibung für Drescher im ersten Vorgewende abtanken geändert, um die Funktion besser zu beschreiben. -- Schildhöhe beim Siloschieben ist im HUD jetzt einstellbar. -- Beim Ansäen wird jetzt die Fruchtsorte überprüft, ob die Frucht in der aktuellen Jahreszeit angesät werden kann und ob sie dem Auftrag entspricht. -- Verbesserungen beim Drescher abtanken Modus. -- Es kann jetzt ein LKW und ein Trailer in Kombination verwendet werden, um Drescher ab zu tanken. -- Mehrere bugfixes. -- Infotext für Gemüseernter mit Paletten hinzugefügt. -- Desc Version auf 79 erhöht (Patch 1.13.1.0) - -Changelog 7.3.0.0: -- Die Feldauswahl im Ballen sammeln und wickeln Modus funktioniert jetzt genauso wie beim Drescher abtanken. -Für jedes neue Feld muss diese Position im Menu jetzt neu gesetzt werden und wird somit nicht mehr durch das Öffnen über das Hud automatisch neu gesetzt! -Das erleichtert die Nutzung mit Autodrive, da somit der Autodrive Fahrer nicht mehr zwingend auf dem Feld halten muss. -- Das Abtanken von Drescher wurde erweitert, um einen Modus zum Abtanken von einem Lader, wie z.B. die Ropa Maus 5. -Durch eine zusätzliche Einstellung im Hud kann zwischen Drescher abtanken und Lader, also z.B. den Ropa, unterschieden werden. -- Zusätzlich wurde dem Drescher abtanken Modus eine Option hinzugefügt zum direktem Abladen auf einem Feld. -Dafür muss diese Funktion ausgewählt werden und auf der Karte ein neuer Marker gesetzt werden. -Der Marker muss innerhalb des ausgewählten Feldes sein oder im Umkreis von maximal 20m um das Feld. -- Zum Laden des Haufens wurde ein neuer Modus hinzugefügt. -Dieser ermöglicht z.B. das Aufladen eines Zuckerüben Haufens mit einem Ropa Maus 5. -Dieser kann wie oben erwähnt mit einem Abfahrer abgetankt werden und dann über den Giants Abfahrer oder mit Autodrive zum entladen geschickt werden. -Ebenfalls funktioniert das Laden eines Bunker Silos mit dem z.B. dem Ropa NawaRo. -Hier ist jedoch das automatische Abladen etwas schwierig und ist nicht immer möglich. -Als Tipp falls das Rohr um positioniert werden muss, kann dieses nach manuellem Aufklappen vor dem Starten des Helfer gemacht werden. -- Als weitere Neuerung wurde das automatische Aufnehmen eines Scheidwerks von einem Schneidwerkswagen hinzugefügt. -Dafür muss der Wagen mit dem Schneidwerk oder ein Schneidwerk mit Rädern, welches keinen extra Wagen benötigt, am Drescher hinten angekuppelt sein. -Nachdem Starten wird der Anhänger abgekuppelt und der Drescher versucht das Schneidwerk anzukuppeln und anschließend ganz normal seine Arbeit zu verrichten. -Wichtig ist hier, dass der Drescher natürlich etwas Platz zum Rangieren benötigt. -Diese Option kann in den Fahrzeug Einstellungen aktiviert werden. -Wenn der Drescher mit Anhänger und Schneidwerk von Giants oder Autodrive zum Feld geschickt wird, dann wird die Funktion natürlich auch benutzt, solange die Einstellung aktiviert ist. - -- Fehler bei der Einstellung des Versatzes im Ballen sammeln/wickeln Modus behoben. -- Eine globale Einstellung für die Fruchtzerstörung von Helfern hinzugefügt. (aus, nur Courseplay Helfer, alle Helfer) -- Einen Zähler für den Ballen sammeln/wickeln Modus hinzugefügt. - Aus Performancegründen wird der Zähler nicht immer sofort aktualisiert. -- Eine Pausefunktion für das Aufzeichnen von Feldrändern hinzugefügt. -- Hilfe Menü Einträge wurden aktualisiert. -- Die meisten debugs wurden standartmäßig deaktiviert (einige Infos werden vorerst aktiv gelassen) -- ModDesc Version auf 76 (Spielversion 1.11.0.0 oder höher) -- Neue Webseite mit einer exakten Kopie der Hilfe aus dem Spiel: <a href="https://courseplay.dev/">Courseplay Webseite</a> - -Changelog 7.2.0.5: -- Göweil Pack Fehler gefixt, wenn das Courseplay HUD im Fahrzeug aktiv ist. -- Fehler im Gui gefixt für Fahrzeuge und Geräte, die nicht mit Courseplay gesteuert werden können (z.B. mobiler Holzkran) -- Hotspots auf der Karte werden ausgeblendet, wenn man einen Feldrandkurs zeichnet. -- Das anheben des Schildes im Bunker Modus vorerst deaktiviert. -- Option hinzugefügt, der Feldhelfer zurück zum Startpunkt fahren lässt. -- Kleine Verbesserung für Modfahrzeuge, die eine eingebaute Kapazität besitzen. -- Globale Option hinzugefügt, ob Drescher bei Regen anhalten sollen oder weiter arbeiten sollen. -- Fehler gefixt und Verbesserungen für Pflüge und Ballenpressen. -- Weitere kleine Bug Fixes und Verbesserungen. - -Changelog 7.2.0.0: -- Bunker Silo Modus und Drescher Abfahrer Modus hinzugefügt -- Verbleibende Zeit für Feldarbeit wird jetzt im Hud angezeigt -- Unterstützung für <a href="https://farming-simulator.com/mod.php?lang=en&country=us&mod_id=237080&title=fs2022">Universal Autoload</a> hinzugefügt. -- Option um den Kurs auf der minimap an zu zeigen hinzugefügt. -- Benutzer Einstellung um zwischen Meter und Fuss zu wechseln hinzugefügt. -- Einfache Kolisions Vermeidung für Abfahrer hinzugefügt um nicht im den Drescher zu fahren. -- Option (Tastenbelegung) für den Kurseditor um alle Wegpunkte bis zum Ende zu entfernen. -- Neue Kursanzeige (Augen im HUD blau) um die letzten 5 und die nächsten 20 Wegpunkte an zu zeigen. -- Hilfemenü um Drescher Abfahrer und Silo Bunker Fahrer erweitert. -- Weitere diverse Fehler behoben und Verbesserungen hinzugefügt. - -Changelog 7.1.0.3: -- Fehlende Übersetzungen hinzugefügt, jetzt können alle Übersetzungen, die vom Spiel unterstützt werden angepasst werden. -- Neuen Kurseditor für Feldarbeitskurse und eigene Feldrandkurse hinzugefügt (mehr Informationen dazu im Ingame Hilfe Menü) -- Interface zum Mod FS22_aPalletAutoLoader verbessert. -- Arcusin Multipack F14 entlädt die gepackten Ballen jetzt auf dem Feld. -- Weitere bug fixes und Verbesserungen. -- Vermeer DLC Kompatibilität. - -Changelog 7.1.0.0: -- Initiale ModHub Version 7.1.0.0 -- Reben Helfer hinzugefügt -- Das HUD ist einfacher zu benutzen und ein separates Gamepad HUD wurde hinzugefügt. -- Ingame Hilfemenü wurde durch Courseplay Einträge erweitert. -- Komplett neuer Kurse Manager in das Ingame Menu integriert. -- Funktioniert jetzt gut mit AutoDrive zusammen, Courseplay übernimmt die Arbeit auf dem Feld und AutoDrive transportiert deine Ernte. +Changelog 8.0.0.0: +- Initiale FS25 Konvertierung. ]]> </de> From a91ff9cc99a1e403c4d44f49c41a5eed73b4c200 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Wed, 18 Dec 2024 10:27:23 +0100 Subject: [PATCH 124/158] Help Menu changes Also replaced some "FS22" with "FS25" --- .github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml | 2 +- .github/ISSUE_TEMPLATE/BUG_REPORT_EN.yml | 2 +- .github/ISSUE_TEMPLATE/QUESTION_DE.yml | 2 +- .github/ISSUE_TEMPLATE/QUESTION_EN.yml | 2 +- .github/ISSUE_TEMPLATE/REQUEST_DE.yml | 2 +- .github/ISSUE_TEMPLATE/REQUEST_EN.yml | 2 +- Courseplay.lua | 2 +- config/MasterTranslations.xml | 55 ++++++++++--------- config/VehicleConfigurations.xml | 2 +- modDesc.xml | 2 +- scripts/Course.lua | 2 +- scripts/ai/AIDriveStrategyAttachHeader.lua | 2 +- scripts/ai/AIDriveStrategyCourse.lua | 2 +- .../AIDriveStrategyDriveToFieldWorkStart.lua | 2 +- scripts/ai/AIDriveStrategyFieldWorkCourse.lua | 2 +- .../ai/AIDriveStrategyShovelSiloLoader.lua | 2 +- scripts/ai/AIDriveStrategyUnloadCombine.lua | 2 +- .../ai/AIDriveStrategyVineFieldWorkCourse.lua | 2 +- scripts/ai/CollisionAvoidanceController.lua | 2 +- scripts/ai/PathfinderController.lua | 2 +- scripts/ai/ProximitySensor.lua | 2 +- scripts/ai/SelfUnloadHelper.lua | 2 +- .../genetic/BlockSequencer.lua | 2 +- scripts/courseGenerator/genetic/Genetic.lua | 2 +- scripts/gui/FieldPlot.lua | 2 +- scripts/pathfinder/PathfinderConstraints.lua | 2 +- scripts/pathfinder/PathfinderContext.lua | 2 +- scripts/pathfinder/PathfinderUtil.lua | 2 +- 28 files changed, 57 insertions(+), 52 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml index c2f343720..57b111bda 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Danke, dass du dir Zeit nimmst, diesen Bugreport auszufüllen. - Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS22/wiki) + Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS25/wiki) durch, bevor du ein neues Issue erstellst! ' diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT_EN.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT_EN.yml index fe0d8eecb..608cacf36 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT_EN.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT_EN.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Thanks for taking the time to fill out this bug report! - Please make sure to read our [Wiki Page](https://github.com/Courseplay/Courseplay_FS22/wiki) + Please make sure to read our [Wiki Page](https://github.com/Courseplay/Courseplay_FS25/wiki) before creating a new issue! ' diff --git a/.github/ISSUE_TEMPLATE/QUESTION_DE.yml b/.github/ISSUE_TEMPLATE/QUESTION_DE.yml index 981c5b612..bc948bf63 100644 --- a/.github/ISSUE_TEMPLATE/QUESTION_DE.yml +++ b/.github/ISSUE_TEMPLATE/QUESTION_DE.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Bitte benutze diese Vorlage, um deine Frage zu stellen! - Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS22/wiki) + Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS25/wiki) durch, bevor du ein neues Issue erstellst! Unsere Hilfe aus dem Spiel, gibt es auch unter [Courseplay.dev]!(https://courseplay.github.io/Courseplay_FS22.github.io/), diff --git a/.github/ISSUE_TEMPLATE/QUESTION_EN.yml b/.github/ISSUE_TEMPLATE/QUESTION_EN.yml index cb706c5f5..09e5de68b 100644 --- a/.github/ISSUE_TEMPLATE/QUESTION_EN.yml +++ b/.github/ISSUE_TEMPLATE/QUESTION_EN.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Please use this template to ask your question! - Please make sure to read our [Wiki Page](https://github.com/Courseplay/Courseplay_FS22/wiki) + Please make sure to read our [Wiki Page](https://github.com/Courseplay/Courseplay_FS25/wiki) before creating a new issue! Our help pages from the game can also be found at [Courseplay.dev]!(https://courseplay.github.io/Courseplay_FS22.github.io/), diff --git a/.github/ISSUE_TEMPLATE/REQUEST_DE.yml b/.github/ISSUE_TEMPLATE/REQUEST_DE.yml index a9f9cce41..f08719485 100644 --- a/.github/ISSUE_TEMPLATE/REQUEST_DE.yml +++ b/.github/ISSUE_TEMPLATE/REQUEST_DE.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Danke, dass du dir Zeit nimmst, diesen Report auszufüllen. - Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS22/wiki) + Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS25/wiki) durch, bevor du ein neues Issue erstellst! ' diff --git a/.github/ISSUE_TEMPLATE/REQUEST_EN.yml b/.github/ISSUE_TEMPLATE/REQUEST_EN.yml index b337c0011..246735bd2 100644 --- a/.github/ISSUE_TEMPLATE/REQUEST_EN.yml +++ b/.github/ISSUE_TEMPLATE/REQUEST_EN.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Thanks for taking the time to fill out this report! - Please make sure to read our [Wiki Page](https://github.com/Courseplay/Courseplay_FS22/wiki) + Please make sure to read our [Wiki Page](https://github.com/Courseplay/Courseplay_FS25/wiki) before creating a new issue! ' diff --git a/Courseplay.lua b/Courseplay.lua index 7e220d65e..6c9ddfecc 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -115,7 +115,7 @@ function Courseplay:loadMap(filename) end --- Ugly hack to get access to the global AutoDrive table, as this global is dependent on the auto drive folder name. - self.autoDrive = FS22_AutoDrive and FS22_AutoDrive.AutoDrive + self.autoDrive = FS25_AutoDrive and FS25_AutoDrive.AutoDrive CpUtil.info("Auto drive found: %s", tostring(self.autoDrive~=nil)) g_courseEditor:load() diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index 4d4b4719a..bb1d9f1e9 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -36,8 +36,8 @@ <Text language="en"><![CDATA[Copy course:]]></Text> </Translation> <Translation name="CP_infoText"> - <Text language="de"><![CDATA[Courseplay(%s): Für Hilfe, Fehler oder Patch Notes, bitte hier vorbei schauen: https://github.com/Courseplay/Courseplay_FS22.]]></Text> - <Text language="en"><![CDATA[Courseplay(%s): For help, bug reports or patch notes, please visit: https://github.com/Courseplay/Courseplay_FS22.]]></Text> + <Text language="de"><![CDATA[Courseplay(%s): Für Hilfe, Fehler oder Patch Notes, bitte hier vorbei schauen: https://github.com/Courseplay/Courseplay_FS25.]]></Text> + <Text language="en"><![CDATA[Courseplay(%s): For help, bug reports or patch notes, please visit: https://github.com/Courseplay/Courseplay_FS25.]]></Text> </Translation> <Translation name="CP_error_no_course"> <Text language="de"><![CDATA[Erstelle einen Kurs vor dem Starten des Jobs.]]></Text> @@ -870,8 +870,8 @@ </Category> <Category name="Course generator settings"> <Translation name="CP_vehicle_courseGeneratorSetting_title"> - <Text language="de"><![CDATA[Kursgenerator-Einstellungen von (%s)]]></Text> - <Text language="en"><![CDATA[Course generator settings of (%s)]]></Text> + <Text language="de"><![CDATA[Helfer Einstellungen von (%s)]]></Text> + <Text language="en"><![CDATA[Helper settings of (%s)]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_basic"> <Text language="de"><![CDATA[Grundeinstellungen]]></Text> @@ -1866,16 +1866,17 @@ The course is saved automatically on closing of the editor and overrides the sel <Text language="de"><![CDATA[ Mit Courseplay ist es möglich, Feldkurse mit zusätzlichen Funktionen zu generieren, beispielsweise Vorgewende. -Ebenso ermöglicht es die Nutzung von Ballenpressen und Sammelwagen mit dem Helfer auf demselben Kurs, welcher zuvor von einem Mähwerk oder Drescher bearbeitet wurde. +Ebenso ermöglicht es die Nutzung von Ballenpressen und Sammelwagen mit dem Helfer auf dem selben Kurs, welcher zuvor von einem Mähwerk oder Drescher bearbeitet wurde. Eine weitere Funktion ist das Ballenwickeln oder -sammeln auf einem Feld. Ganz neu in Courseplay ist die Bearbeitung von Reben. Feldkurse können auch als Multitoolkurs gebaut werden, dadurch ist es möglich bis zu 5 Arbeitsgeräte im Fahrzeugverband auf dem gleichen Feld arbeiten zu lassen. Für Drescher gibt es die Möglichkeit des automatischen Abtankens in einen Anhänger nahe des Feldes. Eigene Feldränder können mit einem Fahrzeug aufgezeichnet oder auf der Menükarte gezeichnet werden und anschließend für die Generierung eines Feldkurses benutzt werden. Außerdem hat CP ein Interface für AutoDrive, dadurch ist es möglich Sämaschinen auffüllen zu lassen, Ladewagen zu entleeren und vieles mehr. -Ab Version 7.2.1.0 ist es auch möglich, mit dem Drescher-Abfahrer die geladene Frucht als Haufen am Feldrand abzulegen. +Drescher-Abfahrer sind in der Lage, die geladene Frucht als Haufen am Feldrand abzulegen. +Im Farming Simulator 25 besitzt Courseplay sein eigenes Menü, welches ihr über verschiedene Buttons im HUD erreicht. -Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS22 . +Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS25 . Dort könnt ihr Fragen stellen, Fehlerberichte einreichen oder Verbesserungen vorschlagen. Ein ganz großes Danke geht an unsere Übersetzer und an die Community für die zahlreichen Fehlermeldungen, Feedbacks und Ideen. @@ -1893,9 +1894,10 @@ Fieldwork courses can be setup in multitool mode, which allows the use of up to It's also possible to have the combine unload in a trailer on/near the field automatically. Custom field borders can be assigned for courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. Lastly cp has a interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. -With Version 7.2.1.0 or higher it is also possible to use the combine unloader to create a heap near the field. +Combine unloader are able to to create a heap near the field with their loaded fruit. +In Farming Simulator 25, courseplay got an own menu, wich you can access over different buttons on the HUD. -For more information please visit our github: https://github.com/Courseplay/Courseplay_FS22 . +For more information please visit our github: https://github.com/Courseplay/Courseplay_FS25 . There you can get help or report any issue you've experienced. Finally, we thank every translator and our community for reporting bugs, giving use feedback and new ideas. @@ -1911,43 +1913,46 @@ That way, we try to help Users to get easier into Courseplay without being overw </Translation> <Translation name="CP_help_page_startJobMenuBase_text"> <Text language="de"><![CDATA[ -Durch Auswählen eines Fahrzeugs im Helfermenü werden die Fahrzeugeinstellungen und der Kursmanager im Menü eingeblendet. -Der aktuell geladene Kurs wird auf der Karte angezeigt. -Die globalen Einstellungen sind dauerhaft sichtbar. +Die Einstellungen für jeden Helfer finden jetzt im Menü "Helfer Einstellungen" statt. +Das Menü funktioniert Grundlegend wie das alte Helfer Menü. +Wird ein Fahrzeug auf der Map gewählt, könnt ihr einen Job erstellen, in abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. +Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur Verfügbar, wenn ihr ein gültiges Fahrzeug gewählt habt und einen Job erstellt habt. Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldarbeit auf der Helferkarte ausgewählt sein. -Um einen Kurs zu generieren, musst du einen Job erstellen und dort "CP: Feldarbeit", "CP: Ballen sammeln/wickeln", "CP: Lader" oder "CP: Bunkersilo" auswählen, ähnlich wie beim Giants-Helfer. +Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. ]]></Text> <Text language="en"><![CDATA[ -First you need to select a vehicle in the AI menu for generating a fieldwork course or gaining access to the vehicle settings and the course manager. -The currently loaded course will be displayed on the map. -The global settings are always visible. -For generating a course and starting the courseplay jobs, you will need to create the job CP: Fieldwork, CP: wrap/collect bales, CP: Loader or CP: Bunker silo similar to the Giants helper. +Helper settings are now found in our own new "Helper settings" menu. +It baiscally works like the old AI Menu. +Select a vehicle in the AI menu for creating a job, based on the attached tools or selected vehicle. +Beside the Map and the job settings, you will find the settings for the course generator and vine generator at the top. Those are only accessibale with a valid tool when you are creating a job. +For generating a course and starting the courseplay jobs, you will need to create the job CP: Fieldwork with a valid tool or vehicle. +Tip: To gain a quicker access, you can click on "no course" on the HUD. You will be in the menu with a created job for the entered vehicle. ]]></Text> </Translation> <Translation name="CP_help_page_startJobMenuFunctions_text"> <Text language="de"><![CDATA[ Um deinen ersten Job zu starten, musst du ein Fahrzeug mit einem für die Arbeit gültigen Arbeitsgerät auswählen. -Klicke dann auf "Job erstellen" und wähle "CP: Feldarbeit" für Feldarbeitsgeräte, -oder "CP: Ballen sammeln/wickeln" für Ballenwickler oder -sammler aus. +Klicke dann auf "Job erstellen" und wähle dann einen der verfügbaren CP Job für dein gewähltes Fahrzeug oder Gerät aus. ]]></Text> <Text language="en"><![CDATA[ To start off with your first CP job, you would have to select a vehicle and a possible valid implement which is supported for the job. -Then, by clicking on create job, you can select CP: Fieldwork for fieldwork tools or CP: wrap/collect -with a bale wrapper or bale collector attached. +Then, by clicking on create job, you can select a CP job for your selected tool or vehicle. ]]></Text> </Translation> <Translation name="CP_help_page_readyJobMenuBase_text"> <Text language="de"><![CDATA[ -Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswählen, auf dem du einen Kurs generieren, oder Ballen wickeln oder sammeln willst. +Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswählen, auf dem du einen Kurs generieren, oder Ballen wickeln oder sammeln möchtest. Die Position bestimmt ungefähr die Startposition deines Kurses. Wenn du möchtest, dass der Giants-Helfer dein Fahrzeug zum Feld bringt, dann muss zusätzlich noch die Zielposition nahe des Startpunktes gesetzt werden. -Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen willst. Mehr dazu unter Multitool. +Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen möchtest. Mehr dazu unter Multitool. +Tip: Stehst du bereits auf einem Feld wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. ]]></Text> <Text language="en"><![CDATA[ With a CP job selected, you would need to place the field position on a field for generating the course or using the bale finder on it. The position also roughly controls the starting point of your course. If you want to use the Giants helper to drive to the field, then you also need to set the target position close to the starting point of the course. The lane offset setting is only used if you want to have multiple helpers working on the same field. For this please checkout the separate help menu page below. +Tip: If you stand already on the field when you click on "no course" to enter the menu, the markers are already placed. ]]></Text> </Translation> <Translation name="CP_help_page_readyJobMenuFunctions_text"> @@ -2206,7 +2211,7 @@ Der Kursmanager erlaubt es dir, Kurse zu speichern und später wieder zu laden. Das ist z.B. wichtig, wenn mehrere Fahrzeuge auf einem Kurs arbeiten sollten (Multitool). Außerdem kannst du so z.B. die Kurse von einem Drescher später für eine Ballenpresse oder einen Sammelwagen zum Stroh aufsammeln verwenden. -Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2022\modSettings\FS22_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). +Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). Der Kursmanager funktioniert anders als du es im LS19 gewohnt bist. Kurse werden immer in Ordnern gespeichert, welche auf der linken Seite angezeigt werden. Das bedeutet, du brauchst mindestens einen Ordner, um dort Kurse abspeichern zu können. Ordner können ganz leicht durch den Button unten am Bildschirmrand erstellt werden. @@ -2216,7 +2221,7 @@ The course manager allows you to save courses and enables you to load the saved This is really important, when you want to have multiple workers driving the same course for convoy (multi tools). This feature also allows you to pickup the swath left behind by a combine or a windrower with a forage wagon or a baler. -The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS22_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). +The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). The course manager works differently from what you are used to in FS19. Courses are saved into folders, which will be displayed on the left. This means you would need at least one folder to start saving courses into. Folders can simply be created by the button at the bottom of the screen and entering a name. diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index 712a81262..2d1e7c037 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -106,7 +106,7 @@ You can define the following custom settings: - modName: Name of the .zip file (without '.zip') In case a Mod has the same .xml filename for the vehicle/implement, as a default giants vehicle/implement, - add the name of the .zip file to prevent conflicts. For example: "FS22_exampleMod". + add the name of the .zip file to prevent conflicts. For example: "FS25_exampleMod". Note: Only 2 of the same .xml files can be added. - ignoreBaleCollisionForward: boolean diff --git a/modDesc.xml b/modDesc.xml index 5bbdc64e8..a5cff6164 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -13,7 +13,7 @@ <description> <en> -<![CDATA[Courseplay FS22 +<![CDATA[Courseplay FS25 Courseplay takes the AI worker to the next level. Sow, harvest and plow smarter: - use headlands - work on irregularly shaped fields, not just rectangles diff --git a/scripts/Course.lua b/scripts/Course.lua index e921fc4a3..94c512515 100644 --- a/scripts/Course.lua +++ b/scripts/Course.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2018-2022 Peter Va9ko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyAttachHeader.lua b/scripts/ai/AIDriveStrategyAttachHeader.lua index 587ba82b9..6fa398c98 100644 --- a/scripts/ai/AIDriveStrategyAttachHeader.lua +++ b/scripts/ai/AIDriveStrategyAttachHeader.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2023 Courseplay Dev Team This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyCourse.lua b/scripts/ai/AIDriveStrategyCourse.lua index aaa2fd98b..e7eb5f8ce 100644 --- a/scripts/ai/AIDriveStrategyCourse.lua +++ b/scripts/ai/AIDriveStrategyCourse.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2021 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua b/scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua index bdb796e55..85485e15f 100644 --- a/scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua +++ b/scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2022 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua index 7c7f97440..b4aee58d2 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyFieldWorkCourse.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2021 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyShovelSiloLoader.lua b/scripts/ai/AIDriveStrategyShovelSiloLoader.lua index 1ebcd4e29..094c4b0fd 100644 --- a/scripts/ai/AIDriveStrategyShovelSiloLoader.lua +++ b/scripts/ai/AIDriveStrategyShovelSiloLoader.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2023 Courseplay Dev Team This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyUnloadCombine.lua b/scripts/ai/AIDriveStrategyUnloadCombine.lua index 25b58a0ef..4334aabe6 100644 --- a/scripts/ai/AIDriveStrategyUnloadCombine.lua +++ b/scripts/ai/AIDriveStrategyUnloadCombine.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2022 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua b/scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua index 2ffe72711..93594a9fc 100644 --- a/scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua +++ b/scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2021 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/CollisionAvoidanceController.lua b/scripts/ai/CollisionAvoidanceController.lua index 097704273..88884a6ed 100644 --- a/scripts/ai/CollisionAvoidanceController.lua +++ b/scripts/ai/CollisionAvoidanceController.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2022 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/PathfinderController.lua b/scripts/ai/PathfinderController.lua index 897f9c961..2cf13685a 100644 --- a/scripts/ai/PathfinderController.lua +++ b/scripts/ai/PathfinderController.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2023 Courseplay Dev Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/scripts/ai/ProximitySensor.lua b/scripts/ai/ProximitySensor.lua index 79d825c8b..136d8f795 100644 --- a/scripts/ai/ProximitySensor.lua +++ b/scripts/ai/ProximitySensor.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2020-2022 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/ai/SelfUnloadHelper.lua b/scripts/ai/SelfUnloadHelper.lua index 9c822dc4d..5c0fca004 100644 --- a/scripts/ai/SelfUnloadHelper.lua +++ b/scripts/ai/SelfUnloadHelper.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2022 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/courseGenerator/genetic/BlockSequencer.lua b/scripts/courseGenerator/genetic/BlockSequencer.lua index 7d0a7dcd4..eca585dd8 100644 --- a/scripts/courseGenerator/genetic/BlockSequencer.lua +++ b/scripts/courseGenerator/genetic/BlockSequencer.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2018-2023 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/courseGenerator/genetic/Genetic.lua b/scripts/courseGenerator/genetic/Genetic.lua index e48a0a3f8..ee2b7832f 100644 --- a/scripts/courseGenerator/genetic/Genetic.lua +++ b/scripts/courseGenerator/genetic/Genetic.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2018-2023 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/gui/FieldPlot.lua b/scripts/gui/FieldPlot.lua index 50a84af4e..f3f3fe6ef 100644 --- a/scripts/gui/FieldPlot.lua +++ b/scripts/gui/FieldPlot.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2022 Peter Vaiko This program is free software: you can redistribute it and/or modify diff --git a/scripts/pathfinder/PathfinderConstraints.lua b/scripts/pathfinder/PathfinderConstraints.lua index c73c6f39f..d789fc5d9 100644 --- a/scripts/pathfinder/PathfinderConstraints.lua +++ b/scripts/pathfinder/PathfinderConstraints.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2024 Courseplay Dev Team This program is free software: you can redistribute it and/or modify diff --git a/scripts/pathfinder/PathfinderContext.lua b/scripts/pathfinder/PathfinderContext.lua index 5a8731ee1..e2b9faad9 100644 --- a/scripts/pathfinder/PathfinderContext.lua +++ b/scripts/pathfinder/PathfinderContext.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2023 Courseplay Dev Team This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua index 1c1d2abfd..2751011d2 100644 --- a/scripts/pathfinder/PathfinderUtil.lua +++ b/scripts/pathfinder/PathfinderUtil.lua @@ -1,5 +1,5 @@ --[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22) +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) Copyright (C) 2024 Courseplay Dev Team This program is free software: you can redistribute it and/or modify From 27f7fc338a91b7c6e268d903dd42591142ebc35a Mon Sep 17 00:00:00 2001 From: Tensuko <Tensuko@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:27:42 +0000 Subject: [PATCH 125/158] Updated translations --- translations/translation_de.xml | 30 ++++++++++++++++-------------- translations/translation_en.xml | 25 ++++++++++++++----------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 6ce031caf..ca44f620f 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -14,7 +14,7 @@ <text name="CP_unit_meter" text="m"/> <text name="CP_unit_foot" text="ft"/> <text name="CP_copy" text="Kurs kopieren:"/> - <text name="CP_infoText" text="Courseplay(%s): Für Hilfe, Fehler oder Patch Notes, bitte hier vorbei schauen: https://github.com/Courseplay/Courseplay_FS22."/> + <text name="CP_infoText" text="Courseplay(%s): Für Hilfe, Fehler oder Patch Notes, bitte hier vorbei schauen: https://github.com/Courseplay/Courseplay_FS25."/> <text name="CP_error_no_course" text="Erstelle einen Kurs vor dem Starten des Jobs."/> <text name="CP_error_could_not_generate_course" text="Konnte keinen Kurs generieren. Das Logfile könnte weitere Informationen enthalten."/> <text name="CP_error_not_on_field" text="Ziel ist nicht auf einem Feld."/> @@ -270,7 +270,7 @@ <!----> <!--Course generator settings--> <!----> - <text name="CP_vehicle_courseGeneratorSetting_title" text="Kursgenerator-Einstellungen von (%s)"/> + <text name="CP_vehicle_courseGeneratorSetting_title" text="Helfer Einstellungen von (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Grundeinstellungen"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Reben Einstellungen"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Feldarbeit Einstellungen"/> @@ -533,16 +533,17 @@ Der Kurs wird beim Schließen automatisch gespeichert und überschrieben. <text name="CP_help_page_GeneralBase_text" text=" Mit Courseplay ist es möglich, Feldkurse mit zusätzlichen Funktionen zu generieren, beispielsweise Vorgewende. -Ebenso ermöglicht es die Nutzung von Ballenpressen und Sammelwagen mit dem Helfer auf demselben Kurs, welcher zuvor von einem Mähwerk oder Drescher bearbeitet wurde. +Ebenso ermöglicht es die Nutzung von Ballenpressen und Sammelwagen mit dem Helfer auf dem selben Kurs, welcher zuvor von einem Mähwerk oder Drescher bearbeitet wurde. Eine weitere Funktion ist das Ballenwickeln oder -sammeln auf einem Feld. Ganz neu in Courseplay ist die Bearbeitung von Reben. Feldkurse können auch als Multitoolkurs gebaut werden, dadurch ist es möglich bis zu 5 Arbeitsgeräte im Fahrzeugverband auf dem gleichen Feld arbeiten zu lassen. Für Drescher gibt es die Möglichkeit des automatischen Abtankens in einen Anhänger nahe des Feldes. Eigene Feldränder können mit einem Fahrzeug aufgezeichnet oder auf der Menükarte gezeichnet werden und anschließend für die Generierung eines Feldkurses benutzt werden. Außerdem hat CP ein Interface für AutoDrive, dadurch ist es möglich Sämaschinen auffüllen zu lassen, Ladewagen zu entleeren und vieles mehr. -Ab Version 7.2.1.0 ist es auch möglich, mit dem Drescher-Abfahrer die geladene Frucht als Haufen am Feldrand abzulegen. +Drescher-Abfahrer sind in der Lage, die geladene Frucht als Haufen am Feldrand abzulegen. +Im Farming Simulator 25 besitzt Courseplay sein eigenes Menü, welches ihr über verschiedene Buttons im HUD erreicht. -Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS22 . +Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS25 . Dort könnt ihr Fragen stellen, Fehlerberichte einreichen oder Verbesserungen vorschlagen. Ein ganz großes Danke geht an unsere Übersetzer und an die Community für die zahlreichen Fehlermeldungen, Feedbacks und Ideen. @@ -553,22 +554,23 @@ Mit dieser Funktion versuchen wir neuen Courseplay-Nutzern den Einstieg zu erlei "/> <text name="CP_help_page_extendedJobMenu_title" text="Erweitertes Helfermenü"/> <text name="CP_help_page_startJobMenuBase_text" text=" -Durch Auswählen eines Fahrzeugs im Helfermenü werden die Fahrzeugeinstellungen und der Kursmanager im Menü eingeblendet. -Der aktuell geladene Kurs wird auf der Karte angezeigt. -Die globalen Einstellungen sind dauerhaft sichtbar. +Die Einstellungen für jeden Helfer finden jetzt im Menü "Helfer Einstellungen" statt. +Das Menü funktioniert Grundlegend wie das alte Helfer Menü. +Wird ein Fahrzeug auf der Map gewählt, könnt ihr einen Job erstellen, in abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. +Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur Verfügbar, wenn ihr ein gültiges Fahrzeug gewählt habt und einen Job erstellt habt. Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldarbeit auf der Helferkarte ausgewählt sein. -Um einen Kurs zu generieren, musst du einen Job erstellen und dort "CP: Feldarbeit", "CP: Ballen sammeln/wickeln", "CP: Lader" oder "CP: Bunkersilo" auswählen, ähnlich wie beim Giants-Helfer. +Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. "/> <text name="CP_help_page_startJobMenuFunctions_text" text=" Um deinen ersten Job zu starten, musst du ein Fahrzeug mit einem für die Arbeit gültigen Arbeitsgerät auswählen. -Klicke dann auf "Job erstellen" und wähle "CP: Feldarbeit" für Feldarbeitsgeräte, -oder "CP: Ballen sammeln/wickeln" für Ballenwickler oder -sammler aus. +Klicke dann auf "Job erstellen" und wähle dann einen der verfügbaren CP Job für dein gewähltes Fahrzeug oder Gerät aus. "/> <text name="CP_help_page_readyJobMenuBase_text" text=" -Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswählen, auf dem du einen Kurs generieren, oder Ballen wickeln oder sammeln willst. +Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswählen, auf dem du einen Kurs generieren, oder Ballen wickeln oder sammeln möchtest. Die Position bestimmt ungefähr die Startposition deines Kurses. Wenn du möchtest, dass der Giants-Helfer dein Fahrzeug zum Feld bringt, dann muss zusätzlich noch die Zielposition nahe des Startpunktes gesetzt werden. -Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen willst. Mehr dazu unter Multitool. +Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen möchtest. Mehr dazu unter Multitool. +Tip: Stehst du bereits auf einem Feld wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. "/> <text name="CP_help_page_readyJobMenuFunctions_text" text=" Wenn die Feldposition richtig auf einem Feld platziert wurde, wird der Feldrand weiß auf der Karte markiert. @@ -675,7 +677,7 @@ Der Kursmanager erlaubt es dir, Kurse zu speichern und später wieder zu laden. Das ist z.B. wichtig, wenn mehrere Fahrzeuge auf einem Kurs arbeiten sollten (Multitool). Außerdem kannst du so z.B. die Kurse von einem Drescher später für eine Ballenpresse oder einen Sammelwagen zum Stroh aufsammeln verwenden. -Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2022\modSettings\FS22_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). +Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). Der Kursmanager funktioniert anders als du es im LS19 gewohnt bist. Kurse werden immer in Ordnern gespeichert, welche auf der linken Seite angezeigt werden. Das bedeutet, du brauchst mindestens einen Ordner, um dort Kurse abspeichern zu können. Ordner können ganz leicht durch den Button unten am Bildschirmrand erstellt werden. diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 272c09736..ad0f7e4d1 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -14,7 +14,7 @@ <text name="CP_unit_meter" text="m"/> <text name="CP_unit_foot" text="ft"/> <text name="CP_copy" text="Copy course:"/> - <text name="CP_infoText" text="Courseplay(%s): For help, bug reports or patch notes, please visit: https://github.com/Courseplay/Courseplay_FS22."/> + <text name="CP_infoText" text="Courseplay(%s): For help, bug reports or patch notes, please visit: https://github.com/Courseplay/Courseplay_FS25."/> <text name="CP_error_no_course" text="Generate a course before starting the job."/> <text name="CP_error_could_not_generate_course" text="Could not generate course. The log may have more information."/> <text name="CP_error_not_on_field" text="Target is not on a field."/> @@ -270,7 +270,7 @@ <!----> <!--Course generator settings--> <!----> - <text name="CP_vehicle_courseGeneratorSetting_title" text="Course generator settings of (%s)"/> + <text name="CP_vehicle_courseGeneratorSetting_title" text="Helper settings of (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Basic settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Vine settings"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Fieldwork settings"/> @@ -539,9 +539,10 @@ Fieldwork courses can be setup in multitool mode, which allows the use of up to It's also possible to have the combine unload in a trailer on/near the field automatically. Custom field borders can be assigned for courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. Lastly cp has a interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. -With Version 7.2.1.0 or higher it is also possible to use the combine unloader to create a heap near the field. +Combine unloader are able to to create a heap near the field with their loaded fruit. +In Farming Simulator 25, courseplay got an own menu, wich you can access over different buttons on the HUD. -For more information please visit our github: https://github.com/Courseplay/Courseplay_FS22 . +For more information please visit our github: https://github.com/Courseplay/Courseplay_FS25 . There you can get help or report any issue you've experienced. Finally, we thank every translator and our community for reporting bugs, giving use feedback and new ideas. @@ -552,21 +553,23 @@ That way, we try to help Users to get easier into Courseplay without being overw "/> <text name="CP_help_page_extendedJobMenu_title" text="Extended AI Menu"/> <text name="CP_help_page_startJobMenuBase_text" text=" -First you need to select a vehicle in the AI menu for generating a fieldwork course or gaining access to the vehicle settings and the course manager. -The currently loaded course will be displayed on the map. -The global settings are always visible. -For generating a course and starting the courseplay jobs, you will need to create the job CP: Fieldwork, CP: wrap/collect bales, CP: Loader or CP: Bunker silo similar to the Giants helper. +Helper settings are now found in our own new "Helper settings" menu. +It baiscally works like the old AI Menu. +Select a vehicle in the AI menu for creating a job, based on the attached tools or selected vehicle. +Beside the Map and the job settings, you will find the settings for the course generator and vine generator at the top. Those are only accessibale with a valid tool when you are creating a job. +For generating a course and starting the courseplay jobs, you will need to create the job CP: Fieldwork with a valid tool or vehicle. +Tip: To gain a quicker access, you can click on "no course" on the HUD. You will be in the menu with a created job for the entered vehicle. "/> <text name="CP_help_page_startJobMenuFunctions_text" text=" To start off with your first CP job, you would have to select a vehicle and a possible valid implement which is supported for the job. -Then, by clicking on create job, you can select CP: Fieldwork for fieldwork tools or CP: wrap/collect -with a bale wrapper or bale collector attached. +Then, by clicking on create job, you can select a CP job for your selected tool or vehicle. "/> <text name="CP_help_page_readyJobMenuBase_text" text=" With a CP job selected, you would need to place the field position on a field for generating the course or using the bale finder on it. The position also roughly controls the starting point of your course. If you want to use the Giants helper to drive to the field, then you also need to set the target position close to the starting point of the course. The lane offset setting is only used if you want to have multiple helpers working on the same field. For this please checkout the separate help menu page below. +Tip: If you stand already on the field when you click on "no course" to enter the menu, the markers are already placed. "/> <text name="CP_help_page_readyJobMenuFunctions_text" text=" When the field position is placed correctly on a field, you will see the field border drawn on the map. @@ -681,7 +684,7 @@ The course manager allows you to save courses and enables you to load the saved This is really important, when you want to have multiple workers driving the same course for convoy (multi tools). This feature also allows you to pickup the swath left behind by a combine or a windrower with a forage wagon or a baler. -The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS22_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). +The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). The course manager works differently from what you are used to in FS19. Courses are saved into folders, which will be displayed on the left. This means you would need at least one folder to start saving courses into. Folders can simply be created by the button at the bottom of the screen and entering a name. From a5cf2a964df1a15c6d5f5e217a5412e2afed1dbf Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Wed, 18 Dec 2024 14:35:10 +0100 Subject: [PATCH 126/158] Update BUG_REPORT_DE.yml --- .github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml b/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml index 57b111bda..7df73ba97 100644 --- a/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT_DE.yml @@ -9,7 +9,7 @@ body: attributes: value: 'Danke, dass du dir Zeit nimmst, diesen Bugreport auszufüllen. - Lese dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS25/wiki) + Lies dir bitte zuerst unsere [Wiki Seite](https://github.com/Courseplay/Courseplay_FS25/wiki) durch, bevor du ein neues Issue erstellst! ' From 5e3e20a8ed49840e039d4b600f845dc447cedac3 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Wed, 18 Dec 2024 09:25:07 -0500 Subject: [PATCH 127/158] doc: English typos fixed --- config/MasterTranslations.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index bb1d9f1e9..a053badf2 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -1892,13 +1892,13 @@ Another big feature is the collecting or wrapping of bales on the field. New with this iteration of CP is the vine fieldwork. Fieldwork courses can be setup in multitool mode, which allows the use of up to 5 driver working in a convoy on the same field. It's also possible to have the combine unload in a trailer on/near the field automatically. -Custom field borders can be assigned for courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. +Custom field borders can be assigned for Courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. Lastly cp has a interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. -Combine unloader are able to to create a heap near the field with their loaded fruit. -In Farming Simulator 25, courseplay got an own menu, wich you can access over different buttons on the HUD. +Combine unloader are able to create a heap near the field with their loaded fruit. +In Farming Simulator 25, Courseplay got its own menu wich you can access over different buttons on the HUD. -For more information please visit our github: https://github.com/Courseplay/Courseplay_FS25 . -There you can get help or report any issue you've experienced. +For more information please visit our GitHub: https://github.com/Courseplay/Courseplay_FS25 . +There you can get help or report any issues you've experienced. Finally, we thank every translator and our community for reporting bugs, giving use feedback and new ideas. What is the expert Mode: @@ -1921,12 +1921,12 @@ Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldar Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. ]]></Text> <Text language="en"><![CDATA[ -Helper settings are now found in our own new "Helper settings" menu. -It baiscally works like the old AI Menu. +Helper settings are now under our own new "Helper settings" menu. +It works much like the old AI Menu. Select a vehicle in the AI menu for creating a job, based on the attached tools or selected vehicle. -Beside the Map and the job settings, you will find the settings for the course generator and vine generator at the top. Those are only accessibale with a valid tool when you are creating a job. -For generating a course and starting the courseplay jobs, you will need to create the job CP: Fieldwork with a valid tool or vehicle. -Tip: To gain a quicker access, you can click on "no course" on the HUD. You will be in the menu with a created job for the entered vehicle. +Beside the map and the job settings, you will find the settings for the course generator and vine course generator at the top. Those are only accessible with a valid tool when you are creating a job. +For generating a course and starting the Courseplay jobs, you will need to create the job CP: Fieldwork with a valid tool or vehicle. +Tip: To gain quicker access, you can click on "no course" on the HUD. This will bring you to the menu with a job already created for the vehicle. ]]></Text> </Translation> <Translation name="CP_help_page_startJobMenuFunctions_text"> @@ -1945,13 +1945,13 @@ Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswä Die Position bestimmt ungefähr die Startposition deines Kurses. Wenn du möchtest, dass der Giants-Helfer dein Fahrzeug zum Feld bringt, dann muss zusätzlich noch die Zielposition nahe des Startpunktes gesetzt werden. Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen möchtest. Mehr dazu unter Multitool. -Tip: Stehst du bereits auf einem Feld wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. +Tip: Stehst du bereits auf einem Feld, wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. ]]></Text> <Text language="en"><![CDATA[ With a CP job selected, you would need to place the field position on a field for generating the course or using the bale finder on it. The position also roughly controls the starting point of your course. If you want to use the Giants helper to drive to the field, then you also need to set the target position close to the starting point of the course. -The lane offset setting is only used if you want to have multiple helpers working on the same field. For this please checkout the separate help menu page below. +The lane offset setting is only used if you want to have multiple helpers working on the same field. For this please check out the separate help menu page below. Tip: If you stand already on the field when you click on "no course" to enter the menu, the markers are already placed. ]]></Text> </Translation> @@ -2219,7 +2219,7 @@ Ordner können ganz leicht durch den Button unten am Bildschirmrand erstellt wer <Text language="en"><![CDATA[ The course manager allows you to save courses and enables you to load the saved course later again. This is really important, when you want to have multiple workers driving the same course for convoy (multi tools). -This feature also allows you to pickup the swath left behind by a combine or a windrower with a forage wagon or a baler. +This feature also allows you to pick up the swath left behind by a combine or a windrower with a forage wagon or a baler. The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). The course manager works differently from what you are used to in FS19. From 68345baca34341135a22872b41d1327ffad68ac1 Mon Sep 17 00:00:00 2001 From: pvaiko <pvaiko@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:25:32 +0000 Subject: [PATCH 128/158] Updated translations --- translations/translation_de.xml | 2 +- translations/translation_en.xml | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/translations/translation_de.xml b/translations/translation_de.xml index ca44f620f..8916fd28b 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -570,7 +570,7 @@ Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswä Die Position bestimmt ungefähr die Startposition deines Kurses. Wenn du möchtest, dass der Giants-Helfer dein Fahrzeug zum Feld bringt, dann muss zusätzlich noch die Zielposition nahe des Startpunktes gesetzt werden. Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen möchtest. Mehr dazu unter Multitool. -Tip: Stehst du bereits auf einem Feld wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. +Tip: Stehst du bereits auf einem Feld, wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. "/> <text name="CP_help_page_readyJobMenuFunctions_text" text=" Wenn die Feldposition richtig auf einem Feld platziert wurde, wird der Feldrand weiß auf der Karte markiert. diff --git a/translations/translation_en.xml b/translations/translation_en.xml index ad0f7e4d1..a26da4851 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -537,13 +537,13 @@ Another big feature is the collecting or wrapping of bales on the field. New with this iteration of CP is the vine fieldwork. Fieldwork courses can be setup in multitool mode, which allows the use of up to 5 driver working in a convoy on the same field. It's also possible to have the combine unload in a trailer on/near the field automatically. -Custom field borders can be assigned for courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. +Custom field borders can be assigned for Courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. Lastly cp has a interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. -Combine unloader are able to to create a heap near the field with their loaded fruit. -In Farming Simulator 25, courseplay got an own menu, wich you can access over different buttons on the HUD. +Combine unloader are able to create a heap near the field with their loaded fruit. +In Farming Simulator 25, Courseplay got its own menu wich you can access over different buttons on the HUD. -For more information please visit our github: https://github.com/Courseplay/Courseplay_FS25 . -There you can get help or report any issue you've experienced. +For more information please visit our GitHub: https://github.com/Courseplay/Courseplay_FS25 . +There you can get help or report any issues you've experienced. Finally, we thank every translator and our community for reporting bugs, giving use feedback and new ideas. What is the expert Mode: @@ -553,12 +553,12 @@ That way, we try to help Users to get easier into Courseplay without being overw "/> <text name="CP_help_page_extendedJobMenu_title" text="Extended AI Menu"/> <text name="CP_help_page_startJobMenuBase_text" text=" -Helper settings are now found in our own new "Helper settings" menu. -It baiscally works like the old AI Menu. +Helper settings are now under our own new "Helper settings" menu. +It works much like the old AI Menu. Select a vehicle in the AI menu for creating a job, based on the attached tools or selected vehicle. -Beside the Map and the job settings, you will find the settings for the course generator and vine generator at the top. Those are only accessibale with a valid tool when you are creating a job. -For generating a course and starting the courseplay jobs, you will need to create the job CP: Fieldwork with a valid tool or vehicle. -Tip: To gain a quicker access, you can click on "no course" on the HUD. You will be in the menu with a created job for the entered vehicle. +Beside the map and the job settings, you will find the settings for the course generator and vine course generator at the top. Those are only accessible with a valid tool when you are creating a job. +For generating a course and starting the Courseplay jobs, you will need to create the job CP: Fieldwork with a valid tool or vehicle. +Tip: To gain quicker access, you can click on "no course" on the HUD. This will bring you to the menu with a job already created for the vehicle. "/> <text name="CP_help_page_startJobMenuFunctions_text" text=" To start off with your first CP job, you would have to select a vehicle and a possible valid implement which is supported for the job. @@ -568,7 +568,7 @@ Then, by clicking on create job, you can select a CP job for your selected tool With a CP job selected, you would need to place the field position on a field for generating the course or using the bale finder on it. The position also roughly controls the starting point of your course. If you want to use the Giants helper to drive to the field, then you also need to set the target position close to the starting point of the course. -The lane offset setting is only used if you want to have multiple helpers working on the same field. For this please checkout the separate help menu page below. +The lane offset setting is only used if you want to have multiple helpers working on the same field. For this please check out the separate help menu page below. Tip: If you stand already on the field when you click on "no course" to enter the menu, the markers are already placed. "/> <text name="CP_help_page_readyJobMenuFunctions_text" text=" @@ -682,7 +682,7 @@ IMPORTANT NOTE: This is an experimental setting which may not work for all field <text name="CP_help_page_courseManagerBase_text" text=" The course manager allows you to save courses and enables you to load the saved course later again. This is really important, when you want to have multiple workers driving the same course for convoy (multi tools). -This feature also allows you to pickup the swath left behind by a combine or a windrower with a forage wagon or a baler. +This feature also allows you to pick up the swath left behind by a combine or a windrower with a forage wagon or a baler. The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). The course manager works differently from what you are used to in FS19. From 9f3a470603f46e03cc8d2998b97ee8f98124f444 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 18 Dec 2024 15:58:07 +0100 Subject: [PATCH 129/158] Might fix auto restart --- scripts/specializations/CpAIFieldWorker.lua | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/specializations/CpAIFieldWorker.lua b/scripts/specializations/CpAIFieldWorker.lua index 131499828..96e7cdf58 100644 --- a/scripts/specializations/CpAIFieldWorker.lua +++ b/scripts/specializations/CpAIFieldWorker.lua @@ -307,4 +307,15 @@ local function onUpdate(vehicle, superFunc, ...) CpUtil.try(superFunc, vehicle, ...) end -AIFieldWorker.onUpdate = Utils.overwrittenFunction(AIFieldWorker.onUpdate, onUpdate) \ No newline at end of file +AIFieldWorker.onUpdate = Utils.overwrittenFunction(AIFieldWorker.onUpdate, onUpdate) + +--- Ugly hack to avoid restarting the giants helper after loading a savegame, +--- if cp was active prior to saving the game ... +local function saveToXMLFile(vehicle, superFunc, xmlFile, key, ...) + superFunc(vehicle, xmlFile, key, ...) + if vehicle.getIsCpActive and vehicle:getIsCpActive() then + xmlFile:setValue(key .. "#isActive", false) + end +end + +AIFieldWorker.saveToXMLFile = Utils.overwrittenFunction(AIFieldWorker.saveToXMLFile, saveToXMLFile) \ No newline at end of file From 610daaf09a1bd6ee00562d52a570a97190c1fc2c Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 18 Dec 2024 18:52:39 +0100 Subject: [PATCH 130/158] Fixed info text hud position saving --- scripts/gui/hud/CpHudInfoTexts.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/gui/hud/CpHudInfoTexts.lua b/scripts/gui/hud/CpHudInfoTexts.lua index b4db1d187..5df49fb24 100644 --- a/scripts/gui/hud/CpHudInfoTexts.lua +++ b/scripts/gui/hud/CpHudInfoTexts.lua @@ -226,9 +226,7 @@ function CpHudInfoTexts.loadFromXmlFile(xmlFile,baseKey) local posX = xmlFile:getValue(baseKey..CpHudInfoTexts.xmlKey.."#posX") local posY = xmlFile:getValue(baseKey..CpHudInfoTexts.xmlKey.."#posY") if posX ~= nil and posY ~= nil then - CpHudInfoTexts.savedPositions = { - posX, posY - } + g_Courseplay.infoTextsHud:moveToPosition(nil, posX, posY) end end From 76e4924a359e4b6e7c7c6e801d2872b92e8c1639 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 18 Dec 2024 19:32:24 +0100 Subject: [PATCH 131/158] Fixes hud movement save bug --- Courseplay.lua | 10 ++-- scripts/gui/hud/CpBaseHud.lua | 10 ++-- scripts/gui/hud/CpHudInfoTexts.lua | 57 ++++++++++----------- scripts/gui/pages/CpGlobalSettingsFrame.lua | 1 + 4 files changed, 37 insertions(+), 41 deletions(-) diff --git a/Courseplay.lua b/Courseplay.lua index 7e220d65e..78d34e144 100644 --- a/Courseplay.lua +++ b/Courseplay.lua @@ -39,7 +39,7 @@ function Courseplay:loadUserSettings() self.globalSettings:loadFromXMLFile(xmlFile, self.xmlKey) g_cpInGameMenu:loadFromXMLFile(xmlFile, self.xmlKey) CpBaseHud.loadFromXmlFile(xmlFile, self.xmlKey) - CpHudInfoTexts.loadFromXmlFile(xmlFile, self.xmlKey) + self.infoTextsHud:loadFromXmlFile(xmlFile, self.xmlKey) xmlFile:save() xmlFile:delete() else @@ -53,11 +53,11 @@ function Courseplay:saveUserSettings() if xmlFile then self.globalSettings:saveUserSettingsToXmlFile(xmlFile, self.xmlKey) CpBaseHud.saveToXmlFile(xmlFile, self.xmlKey) - CpHudInfoTexts.saveToXmlFile(xmlFile, self.xmlKey) + self.infoTextsHud:saveToXmlFile(xmlFile, self.xmlKey) if self.currentVersion then - xmlFile:setValue(self.baseXmlKey.."#lastVersion", self.currentVersion) + xmlFile:setValue(self.baseXmlKey .. "#lastVersion", self.currentVersion) end - g_cpInGameMenu:saveToXMLFile(xmlFile, g_Courseplay.xmlKey) + g_cpInGameMenu:saveToXMLFile(xmlFile, self.xmlKey) xmlFile:save() xmlFile:delete() end @@ -130,8 +130,6 @@ end function Courseplay:setupGui() CpInGameMenu.setupGui(self.courseStorage) - - self.infoTextsHud = CpHudInfoTexts() -- TODO_25 diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index f3729960f..8d0f69407 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -109,7 +109,10 @@ function CpBaseHud:init(vehicle) self.baseHud = CpHudMoveableElement.new(background) self.baseHud:setPosition(CpBaseHud.x, CpBaseHud.y) self.baseHud:setDimension(self.width, self.height) - self.baseHud:setCallback("onMove", self, self.moveToPosition) + self.baseHud:setCallback("onMove", self, function (self, _, x, y) + self.x = x + self.y = y + end) self.fieldworkLayout = self:addHudPage(CpFieldWorkHudPageElement, vehicle) @@ -438,9 +441,8 @@ function CpBaseHud:addLineTextButtonWithIncrementalButtons(parent, line, textSiz end -function CpBaseHud:moveToPosition(element, x, y) - CpBaseHud.x = x - CpBaseHud.y = y +function CpBaseHud:moveToPosition(x, y) + self.baseHud:moveTo(x, y) end function CpBaseHud:openClose(open) diff --git a/scripts/gui/hud/CpHudInfoTexts.lua b/scripts/gui/hud/CpHudInfoTexts.lua index 5df49fb24..c31dd4b50 100644 --- a/scripts/gui/hud/CpHudInfoTexts.lua +++ b/scripts/gui/hud/CpHudInfoTexts.lua @@ -34,22 +34,15 @@ CpHudInfoTexts.OFF_COLOR = {0.2, 0.2, 0.2, 0.9} CpHudInfoTexts.SELECTED_COLOR = {0, 0.6, 0, 0.9} CpHudInfoTexts.xmlKey = "HudInfoTexts" -function CpHudInfoTexts.registerXmlSchema(xmlSchema,baseKey) - xmlSchema:register(XMLValueType.FLOAT,baseKey..CpHudInfoTexts.xmlKey.."#posX","Hud position x.") - xmlSchema:register(XMLValueType.FLOAT,baseKey..CpHudInfoTexts.xmlKey.."#posY","Hud position y.") +function CpHudInfoTexts.registerXmlSchema(xmlSchema, baseKey) + xmlSchema:register(XMLValueType.FLOAT, baseKey .. CpHudInfoTexts.xmlKey .. "#posX", "Hud position x") + xmlSchema:register(XMLValueType.FLOAT, baseKey .. CpHudInfoTexts.xmlKey .. "#posY", "Hud position y") end function CpHudInfoTexts:init() self.uiScale = g_gameSettings:getValue("uiScale") - - - if CpHudInfoTexts.savedPositions then - CpHudInfoTexts.x, CpHudInfoTexts.y = unpack(CpHudInfoTexts.savedPositions) - CpHudInfoTexts.savedPositions = nil - end - if CpHudInfoTexts.x == nil or CpHudInfoTexts.y == nil then - CpHudInfoTexts.x, CpHudInfoTexts.y = getNormalizedScreenValues(self.basePosition.x, self.basePosition.y) - end + + self.x, self.y = getNormalizedScreenValues(self.basePosition.x, self.basePosition.y) self.width, self.height = getNormalizedScreenValues(self.baseSize.x, self.baseSize.y) @@ -63,9 +56,12 @@ function CpHudInfoTexts:init() background:setAlignment(Overlay.ALIGN_VERTICAL_TOP, Overlay.ALIGN_HORIZONTAL_LEFT) --- Base hud element. self.baseHud = CpHudMoveableElement.new(background) - self.baseHud:setPosition(CpHudInfoTexts.x, CpHudInfoTexts.y) + self.baseHud:setPosition(self.x, self.y) self.baseHud:setDimension(self.width, self.height) - self.baseHud:setCallback("onMove", self, self.moveToPosition) + self.baseHud:setCallback("onMove", self, function(self, _, x, y) + self.x = x + self.y = y + end) local headerHeight = self.hMargin/2 local headerBackground = Overlay.new(g_baseUIFilename, 0, 0, self.width, headerHeight) @@ -74,19 +70,19 @@ function CpHudInfoTexts:init() headerBackground:setAlignment(Overlay.ALIGN_VERTICAL_TOP, Overlay.ALIGN_HORIZONTAL_LEFT) local topElement = CpHudElement.new(headerBackground, self.baseHud) - topElement:setPosition(CpHudInfoTexts.x, CpHudInfoTexts.y) + topElement:setPosition(self.x, self.y) topElement:setDimension(self.width, headerHeight) - local leftTopText = CpTextHudElement.new(self.baseHud, CpHudInfoTexts.x + self.wMargin, CpHudInfoTexts.y - headerHeight + self.hMargin/16, self.titleFontSize) + local leftTopText = CpTextHudElement.new(self.baseHud, self.x + self.wMargin, self.y - headerHeight + self.hMargin/16, self.titleFontSize) leftTopText:setTextDetails("Courseplay") - local rightTopText = CpTextHudElement.new(self.baseHud, CpHudInfoTexts.x + self.width - self.wMargin, CpHudInfoTexts.y - headerHeight + self.hMargin/16, self.titleFontSize, RenderText.ALIGN_RIGHT) + local rightTopText = CpTextHudElement.new(self.baseHud, self.x + self.width - self.wMargin, self.y - headerHeight + self.hMargin/16, self.titleFontSize, RenderText.ALIGN_RIGHT) rightTopText:setTextDetails(g_Courseplay.currentVersion) local width, height = getNormalizedScreenValues(20, 20) - local x = CpHudInfoTexts.x + self.wMargin + local x = self.x + self.wMargin local dx = x + self.wMargin + width - local y = CpHudInfoTexts.y - self.hMargin - headerHeight + self.lineHeight + local y = self.y - self.hMargin - headerHeight + self.lineHeight self.infoTextsElements = {} for i=1, self.maxLines do y = y - self.lineHeight @@ -118,9 +114,8 @@ function CpHudInfoTexts:mouseEvent(posX, posY, isDown, isUp, button) end end -function CpHudInfoTexts:moveToPosition(element, x, y) - CpHudInfoTexts.x = x - CpHudInfoTexts.y = y +function CpHudInfoTexts:moveToPosition(x, y) + self.baseHud:moveTo(x, y) end function CpHudInfoTexts:draw() @@ -210,23 +205,23 @@ function CpHudInfoTexts:delete() end function CpHudInfoTexts:debug(str,...) - CpUtil.debugFormat(CpDebug.DBG_HUD,"Info text hud "..str,...) + CpUtil.debugFormat(CpDebug.DBG_HUD, "Info text hud " .. str, ...) end --- Saves hud position. -function CpHudInfoTexts.saveToXmlFile(xmlFile,baseKey) - if CpHudInfoTexts.x ~= nil and CpHudInfoTexts.y ~= nil then - xmlFile:setValue(baseKey..CpHudInfoTexts.xmlKey.."#posX",CpHudInfoTexts.x) - xmlFile:setValue(baseKey..CpHudInfoTexts.xmlKey.."#posY",CpHudInfoTexts.y) +function CpHudInfoTexts:saveToXmlFile(xmlFile, baseKey) + if self.x ~= nil and self.y ~= nil then + xmlFile:setValue(baseKey .. self.xmlKey .. "#posX", self.x) + xmlFile:setValue(baseKey .. self.xmlKey .. "#posY", self.y) end end --- Loads hud position. -function CpHudInfoTexts.loadFromXmlFile(xmlFile,baseKey) - local posX = xmlFile:getValue(baseKey..CpHudInfoTexts.xmlKey.."#posX") - local posY = xmlFile:getValue(baseKey..CpHudInfoTexts.xmlKey.."#posY") +function CpHudInfoTexts:loadFromXmlFile(xmlFile, baseKey) + local posX = xmlFile:getValue(baseKey .. self.xmlKey .. "#posX") + local posY = xmlFile:getValue(baseKey .. self.xmlKey .. "#posY") if posX ~= nil and posY ~= nil then - g_Courseplay.infoTextsHud:moveToPosition(nil, posX, posY) + self:moveToPosition(posX, posY) end end diff --git a/scripts/gui/pages/CpGlobalSettingsFrame.lua b/scripts/gui/pages/CpGlobalSettingsFrame.lua index de3bdc2cc..198d013d7 100644 --- a/scripts/gui/pages/CpGlobalSettingsFrame.lua +++ b/scripts/gui/pages/CpGlobalSettingsFrame.lua @@ -116,5 +116,6 @@ function CpGlobalSettingsFrame:updateSubCategoryPages(state) self.subCategoryPages[state]:setVisible(true) self.subCategoryTabs[state]:setSelected(true) local layout = self.subCategoryPages[state]:getDescendantByName("layout") + CpSettingsUtil.updateGuiElementsBoundToSettings(layout) self.settingsSlider:setDataElement(layout) end From 6741c3d7f180dbe16f8ccd0cba53faf4ca351726 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Wed, 18 Dec 2024 19:43:13 +0100 Subject: [PATCH 132/158] Stupid mistake .. --- scripts/gui/hud/CpBaseHud.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua index 8d0f69407..507b199c9 100644 --- a/scripts/gui/hud/CpBaseHud.lua +++ b/scripts/gui/hud/CpBaseHud.lua @@ -110,8 +110,8 @@ function CpBaseHud:init(vehicle) self.baseHud:setPosition(CpBaseHud.x, CpBaseHud.y) self.baseHud:setDimension(self.width, self.height) self.baseHud:setCallback("onMove", self, function (self, _, x, y) - self.x = x - self.y = y + CpBaseHud.x = x + CpBaseHud.y = y end) self.fieldworkLayout = self:addHudPage(CpFieldWorkHudPageElement, vehicle) From 813787d260fced1555d63468773465b01531e817 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Thu, 19 Dec 2024 11:37:09 +0100 Subject: [PATCH 133/158] PR changes --- config/MasterTranslations.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index a053badf2..4b9be8dce 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -1868,7 +1868,7 @@ The course is saved automatically on closing of the editor and overrides the sel Mit Courseplay ist es möglich, Feldkurse mit zusätzlichen Funktionen zu generieren, beispielsweise Vorgewende. Ebenso ermöglicht es die Nutzung von Ballenpressen und Sammelwagen mit dem Helfer auf dem selben Kurs, welcher zuvor von einem Mähwerk oder Drescher bearbeitet wurde. Eine weitere Funktion ist das Ballenwickeln oder -sammeln auf einem Feld. -Ganz neu in Courseplay ist die Bearbeitung von Reben. +Courseplay ist auch in der Lage, auf Reisfeldern oder Reben zu arbeiten. Feldkurse können auch als Multitoolkurs gebaut werden, dadurch ist es möglich bis zu 5 Arbeitsgeräte im Fahrzeugverband auf dem gleichen Feld arbeiten zu lassen. Für Drescher gibt es die Möglichkeit des automatischen Abtankens in einen Anhänger nahe des Feldes. Eigene Feldränder können mit einem Fahrzeug aufgezeichnet oder auf der Menükarte gezeichnet werden und anschließend für die Generierung eines Feldkurses benutzt werden. @@ -1889,7 +1889,7 @@ Mit dieser Funktion versuchen wir neuen Courseplay-Nutzern den Einstieg zu erlei Courseplay allows you to generate field courses with additional features, for example: headlands. It also enables the usage of balers and forage wagons, that can be send on the same course, as a mower or harvester from before. Another big feature is the collecting or wrapping of bales on the field. -New with this iteration of CP is the vine fieldwork. +Courseplay is also able to work on rice fields and vines. Fieldwork courses can be setup in multitool mode, which allows the use of up to 5 driver working in a convoy on the same field. It's also possible to have the combine unload in a trailer on/near the field automatically. Custom field borders can be assigned for Courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. @@ -1914,9 +1914,9 @@ That way, we try to help Users to get easier into Courseplay without being overw <Translation name="CP_help_page_startJobMenuBase_text"> <Text language="de"><![CDATA[ Die Einstellungen für jeden Helfer finden jetzt im Menü "Helfer Einstellungen" statt. -Das Menü funktioniert Grundlegend wie das alte Helfer Menü. -Wird ein Fahrzeug auf der Map gewählt, könnt ihr einen Job erstellen, in abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. -Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur Verfügbar, wenn ihr ein gültiges Fahrzeug gewählt habt und einen Job erstellt habt. +Das Menü funktioniert grundlegend wie das alte Helfer Menü. +Wird ein Fahrzeug auf der Map ausgewählt, könnt ihr einen Job erstellen, in Abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. +Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur verfügbar, wenn ihr ein gültiges Fahrzeug ausgewählt habt und einen Job erstellt habt. Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldarbeit auf der Helferkarte ausgewählt sein. Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. ]]></Text> @@ -1945,7 +1945,7 @@ Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswä Die Position bestimmt ungefähr die Startposition deines Kurses. Wenn du möchtest, dass der Giants-Helfer dein Fahrzeug zum Feld bringt, dann muss zusätzlich noch die Zielposition nahe des Startpunktes gesetzt werden. Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen möchtest. Mehr dazu unter Multitool. -Tip: Stehst du bereits auf einem Feld, wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. +Tip: Stehst du bereits auf einem Feld, wenn du über "kein Kurs" in dieses Menü gelangst, sind die Marker bereits auf dem Feld platziert. ]]></Text> <Text language="en"><![CDATA[ With a CP job selected, you would need to place the field position on a field for generating the course or using the bale finder on it. @@ -2211,7 +2211,7 @@ Der Kursmanager erlaubt es dir, Kurse zu speichern und später wieder zu laden. Das ist z.B. wichtig, wenn mehrere Fahrzeuge auf einem Kurs arbeiten sollten (Multitool). Außerdem kannst du so z.B. die Kurse von einem Drescher später für eine Ballenpresse oder einen Sammelwagen zum Stroh aufsammeln verwenden. -Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). +Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2025\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). Der Kursmanager funktioniert anders als du es im LS19 gewohnt bist. Kurse werden immer in Ordnern gespeichert, welche auf der linken Seite angezeigt werden. Das bedeutet, du brauchst mindestens einen Ordner, um dort Kurse abspeichern zu können. Ordner können ganz leicht durch den Button unten am Bildschirmrand erstellt werden. @@ -2221,7 +2221,7 @@ The course manager allows you to save courses and enables you to load the saved This is really important, when you want to have multiple workers driving the same course for convoy (multi tools). This feature also allows you to pick up the swath left behind by a combine or a windrower with a forage wagon or a baler. -The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). +The save location for the course files is: ..\My Games\FarmingSimulator2025\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). The course manager works differently from what you are used to in FS19. Courses are saved into folders, which will be displayed on the left. This means you would need at least one folder to start saving courses into. Folders can simply be created by the button at the bottom of the screen and entering a name. From 8997837b3d0ada0799d37200dfa5565da87eca13 Mon Sep 17 00:00:00 2001 From: Tensuko <Tensuko@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:37:29 +0000 Subject: [PATCH 134/158] Updated translations --- translations/translation_de.xml | 12 ++++++------ translations/translation_en.xml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 8916fd28b..049d210c4 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -535,7 +535,7 @@ Der Kurs wird beim Schließen automatisch gespeichert und überschrieben. Mit Courseplay ist es möglich, Feldkurse mit zusätzlichen Funktionen zu generieren, beispielsweise Vorgewende. Ebenso ermöglicht es die Nutzung von Ballenpressen und Sammelwagen mit dem Helfer auf dem selben Kurs, welcher zuvor von einem Mähwerk oder Drescher bearbeitet wurde. Eine weitere Funktion ist das Ballenwickeln oder -sammeln auf einem Feld. -Ganz neu in Courseplay ist die Bearbeitung von Reben. +Courseplay ist auch in der Lage, auf Reisfeldern oder Reben zu arbeiten. Feldkurse können auch als Multitoolkurs gebaut werden, dadurch ist es möglich bis zu 5 Arbeitsgeräte im Fahrzeugverband auf dem gleichen Feld arbeiten zu lassen. Für Drescher gibt es die Möglichkeit des automatischen Abtankens in einen Anhänger nahe des Feldes. Eigene Feldränder können mit einem Fahrzeug aufgezeichnet oder auf der Menükarte gezeichnet werden und anschließend für die Generierung eines Feldkurses benutzt werden. @@ -555,9 +555,9 @@ Mit dieser Funktion versuchen wir neuen Courseplay-Nutzern den Einstieg zu erlei <text name="CP_help_page_extendedJobMenu_title" text="Erweitertes Helfermenü"/> <text name="CP_help_page_startJobMenuBase_text" text=" Die Einstellungen für jeden Helfer finden jetzt im Menü "Helfer Einstellungen" statt. -Das Menü funktioniert Grundlegend wie das alte Helfer Menü. -Wird ein Fahrzeug auf der Map gewählt, könnt ihr einen Job erstellen, in abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. -Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur Verfügbar, wenn ihr ein gültiges Fahrzeug gewählt habt und einen Job erstellt habt. +Das Menü funktioniert grundlegend wie das alte Helfer Menü. +Wird ein Fahrzeug auf der Map ausgewählt, könnt ihr einen Job erstellen, in Abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. +Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur verfügbar, wenn ihr ein gültiges Fahrzeug ausgewählt habt und einen Job erstellt habt. Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldarbeit auf der Helferkarte ausgewählt sein. Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. "/> @@ -570,7 +570,7 @@ Hast du einen CP-Job ausgewählt, musst du mit der Feldposition dein Feld auswä Die Position bestimmt ungefähr die Startposition deines Kurses. Wenn du möchtest, dass der Giants-Helfer dein Fahrzeug zum Feld bringt, dann muss zusätzlich noch die Zielposition nahe des Startpunktes gesetzt werden. Die Einstellung für die Bahn wird nur benutzt, wenn du mehrere Fahrzeuge auf einem Kurs fahren lassen möchtest. Mehr dazu unter Multitool. -Tip: Stehst du bereits auf einem Feld, wenn du über "kein Kurs" in diese Menü gelangst, sind die Marker bereits auf dem Feld platziert. +Tip: Stehst du bereits auf einem Feld, wenn du über "kein Kurs" in dieses Menü gelangst, sind die Marker bereits auf dem Feld platziert. "/> <text name="CP_help_page_readyJobMenuFunctions_text" text=" Wenn die Feldposition richtig auf einem Feld platziert wurde, wird der Feldrand weiß auf der Karte markiert. @@ -677,7 +677,7 @@ Der Kursmanager erlaubt es dir, Kurse zu speichern und später wieder zu laden. Das ist z.B. wichtig, wenn mehrere Fahrzeuge auf einem Kurs arbeiten sollten (Multitool). Außerdem kannst du so z.B. die Kurse von einem Drescher später für eine Ballenpresse oder einen Sammelwagen zum Stroh aufsammeln verwenden. -Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). +Der Speicherort für die Kurse ist: ..\My Games\FarmingSimulator2025\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (oder für Vanilla z.B. MapUS). Der Kursmanager funktioniert anders als du es im LS19 gewohnt bist. Kurse werden immer in Ordnern gespeichert, welche auf der linken Seite angezeigt werden. Das bedeutet, du brauchst mindestens einen Ordner, um dort Kurse abspeichern zu können. Ordner können ganz leicht durch den Button unten am Bildschirmrand erstellt werden. diff --git a/translations/translation_en.xml b/translations/translation_en.xml index a26da4851..e5df5e5ca 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -534,7 +534,7 @@ The course is saved automatically on closing of the editor and overrides the sel Courseplay allows you to generate field courses with additional features, for example: headlands. It also enables the usage of balers and forage wagons, that can be send on the same course, as a mower or harvester from before. Another big feature is the collecting or wrapping of bales on the field. -New with this iteration of CP is the vine fieldwork. +Courseplay is also able to work on rice fields and vines. Fieldwork courses can be setup in multitool mode, which allows the use of up to 5 driver working in a convoy on the same field. It's also possible to have the combine unload in a trailer on/near the field automatically. Custom field borders can be assigned for Courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. @@ -684,7 +684,7 @@ The course manager allows you to save courses and enables you to load the saved This is really important, when you want to have multiple workers driving the same course for convoy (multi tools). This feature also allows you to pick up the swath left behind by a combine or a windrower with a forage wagon or a baler. -The save location for the course files is: ..\My Games\FarmingSimulator2022\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). +The save location for the course files is: ..\My Games\FarmingSimulator2025\modSettings\FS25_Courseplay\Courses\Mapname.SampleModMap (or for vanilla e.g. MapUS). The course manager works differently from what you are used to in FS19. Courses are saved into folders, which will be displayed on the left. This means you would need at least one folder to start saving courses into. Folders can simply be created by the button at the bottom of the screen and entering a name. From 7a4a1231546d7246e674d975dfa2f4a2ae3bcec4 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 20 Dec 2024 21:35:20 +0100 Subject: [PATCH 135/158] Adds nil check to get current vehicle function --- scripts/CpUtil.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/CpUtil.lua b/scripts/CpUtil.lua index 78d8d7515..2dc678578 100644 --- a/scripts/CpUtil.lua +++ b/scripts/CpUtil.lua @@ -473,9 +473,13 @@ function CpUtil.getAllRootVegetables() return rootVegetables end ----@return Vehicle the currently selected/controlled vehicle, formerly known as g_currentMission.controlledVehicle +---@return table|nil the currently selected/controlled vehicle, formerly known as g_currentMission.controlledVehicle function CpUtil.getCurrentVehicle() - return g_currentMission.playerSystem:getLocalPlayer():getCurrentVehicle() + if g_localPlayer == nil then + CpUtil.error("Failed to get current vehicle, as the local player is nil!") + return nil + end + return g_localPlayer:getCurrentVehicle() end function CpUtil.getDefaultCollisionFlags() From 420f7056d3ecb7ff7edcdcabaee81654925d9be2 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Fri, 20 Dec 2024 22:31:02 +0100 Subject: [PATCH 136/158] Fixed hud keybind and removed controller gui setting for now --- config/GlobalSettingsSetup.xml | 2 +- scripts/specializations/CpHud.lua | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/config/GlobalSettingsSetup.xml b/config/GlobalSettingsSetup.xml index 07d35d351..a2fb610d3 100644 --- a/config/GlobalSettingsSetup.xml +++ b/config/GlobalSettingsSetup.xml @@ -50,7 +50,7 @@ <SettingSubTitle prefix="true" title="userSettings"> <!-- When enabled, then a few settings are invisible and return their default values. --> <Setting classType="AIParameterBooleanSetting" name="expertModeActive" isUserSetting="true" defaultBool="false"/> - <Setting classType="AIParameterBooleanSetting" name="controllerHudSelected" isUserSetting="true" defaultBool="false" onChangeCallback="onHudSelectionChanged"/> + <!-- <Setting classType="AIParameterBooleanSetting" name="controllerHudSelected" isUserSetting="true" defaultBool="false" onChangeCallback="onHudSelectionChanged"/> --> <Setting classType="AIParameterBooleanSetting" name="showsAllActiveCourses" isUserSetting="true" defaultBool="false" isExpertModeOnly="true"/> <Setting classType="AIParameterBooleanSetting" name="drawOntoTheHudMap" isUserSetting="true" defaultBool="true"/> <Setting classType="AIParameterBooleanSetting" name="showActionEventHelp" isUserSetting="true" defaultBool="false" onChangeCallback="onActionEventTextVisibilityChanged"/> diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index 49689b634..b00d0dd94 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -105,9 +105,9 @@ function CpHud:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSe local spec = self.spec_cpHud self:clearActionEventsTable(spec.actionEvents) - if g_Courseplay.globalSettings.controllerHudSelected:getValue() then - return - end + -- if g_Courseplay.globalSettings.controllerHudSelected:getValue() then + -- return + -- end if self.isActiveForInputIgnoreSelectionIgnoreAI then --- Toggle mouse cursor action event @@ -117,10 +117,21 @@ function CpHud:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSe --- callbackState, customIconName, ignoreCollisions, reportAnyDeviceCollision) if self:getCpSettings().openHudWithMouse:getValue() then local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.CP_TOGGLE_MOUSE, self, - CpHud.actionEventMouse, false, true, false, true,nil,nil,true) + CpHud.actionEventMouse, false, true, false, true, nil, nil, true) g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL) g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) - g_inputBinding:setActionEventTextVisibility(actionEventId, g_Courseplay.globalSettings.showActionEventHelp:getValue()) + g_inputBinding:setActionEventTextVisibility(actionEventId, + g_Courseplay.globalSettings.showActionEventHelp:getValue()) + else + if self.isActiveForInputIgnoreSelectionIgnoreAI then + local _, actionEventId = self:addActionEvent(spec.actionEvents, + InputAction.CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY, self, + CpHud.actionEventMouse, false, true, false, true, nil) + g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH) + g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) + g_inputBinding:setActionEventTextVisibility(actionEventId, + g_Courseplay.globalSettings.showActionEventHelp:getValue()) + end end end end From 8838f94f5c6022c6741ee89ece1dbb2a10dc3350 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 21 Dec 2024 11:45:29 +0100 Subject: [PATCH 137/158] Update modDesc.xml --- modDesc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modDesc.xml b/modDesc.xml index a9044c7a7..74bf52da3 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -36,7 +36,7 @@ or go directly to our <a href="https://github.com/Courseplay/Courseplay_FS25">Gi Many thanks to our translators and to our community for reporting bugs, giving us feedback and great ideas. Changelog 8.0.0.0: -- Initial FS25 convert. +- Initial FS25 migration ]]> From 951357df062518a7e71e2fb2659a40f8c7812315 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 21 Dec 2024 11:50:23 +0100 Subject: [PATCH 138/158] Update modDesc.xml --- modDesc.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/modDesc.xml b/modDesc.xml index 74bf52da3..09a14fae7 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -61,8 +61,6 @@ Courseplay hebt den Helfer auf ein ganz neues Level. Säen, ernten und pflügen AutoDrive erledigt das Abtanken eines Ladewagens oder Auffüllen einer Sämaschine. - Verändere die generierten Kurse mit dem Kurseditor. -Für mehr Informationen, Hilfe oder Fehlermeldungen schaue hier vorbei: <a href="https://github.com/Courseplay/Courseplay_FS22">GitHub</a>. - Ein Link zu der neuesten Version, der Hilfe Seite und Github, wo du mehr Informationen und Hilfe findest, sowie Probleme und Bugs melden kannst, findest du auf unserer <a href="https://courseplay.dev/">Courseplay Webseite</a> oder du gehst direkt auf unsere <a href="https://github.com/Courseplay/Courseplay_FS25">GitHub Webseite</a>. From 598a3bb61e4a2abda1bf0e27c9bbacb2551350b8 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 21 Dec 2024 12:34:56 +0100 Subject: [PATCH 139/158] Hud open/close keybind always enabled. --- scripts/specializations/CpHud.lua | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index b00d0dd94..0c1d1736e 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -122,16 +122,15 @@ function CpHud:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSe g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) g_inputBinding:setActionEventTextVisibility(actionEventId, g_Courseplay.globalSettings.showActionEventHelp:getValue()) - else - if self.isActiveForInputIgnoreSelectionIgnoreAI then - local _, actionEventId = self:addActionEvent(spec.actionEvents, - InputAction.CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY, self, - CpHud.actionEventMouse, false, true, false, true, nil) - g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH) - g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) - g_inputBinding:setActionEventTextVisibility(actionEventId, - g_Courseplay.globalSettings.showActionEventHelp:getValue()) - end + end + if self.isActiveForInputIgnoreSelectionIgnoreAI then + local _, actionEventId = self:addActionEvent(spec.actionEvents, + InputAction.CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY, self, + CpHud.actionEventMouse, false, true, false, true, nil) + g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH) + g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) + g_inputBinding:setActionEventTextVisibility(actionEventId, + g_Courseplay.globalSettings.showActionEventHelp:getValue()) end end end From 638e6634a70439823f21c3d8faecbbd8eb12734c Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 21 Dec 2024 13:14:29 +0100 Subject: [PATCH 140/158] Fixes master translation syntax.. --- config/MasterTranslations.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index 4b9be8dce..f7146e516 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -3118,7 +3118,7 @@ TPS extension </Translation> <Translation name="input_CP_GENERATE_COURSE"> <Text language="de"><![CDATA[CP: Kurs generieren]]></Text> - <Text language="en"><![CDATA[CP: Generate course]></Text> + <Text language="en"><![CDATA[CP: Generate course]]></Text> </Translation> <Translation name="input_CP_CHANGE_SELECTED_JOB"> <Text language="de"><![CDATA[CP: Aktuell ausgewählten Job ändern]]></Text> From 9e08ed896e9cddb27c4a2a244380d57483d1a9ea Mon Sep 17 00:00:00 2001 From: schwiti6190 <schwiti6190@users.noreply.github.com> Date: Sat, 21 Dec 2024 12:14:47 +0000 Subject: [PATCH 141/158] Updated translations --- translations/translation_br.xml | 1 + translations/translation_cs.xml | 1 + translations/translation_ct.xml | 1 + translations/translation_cz.xml | 1 + translations/translation_da.xml | 1 + translations/translation_de.xml | 1 + translations/translation_ea.xml | 1 + translations/translation_en.xml | 3 ++- translations/translation_es.xml | 1 + translations/translation_fc.xml | 1 + translations/translation_fi.xml | 1 + translations/translation_fr.xml | 1 + translations/translation_hu.xml | 1 + translations/translation_it.xml | 1 + translations/translation_jp.xml | 1 + translations/translation_kr.xml | 1 + translations/translation_nl.xml | 1 + translations/translation_no.xml | 1 + translations/translation_pl.xml | 1 + translations/translation_pt.xml | 1 + translations/translation_ro.xml | 1 + translations/translation_ru.xml | 1 + translations/translation_sv.xml | 1 + translations/translation_tr.xml | 1 + 24 files changed, 25 insertions(+), 1 deletion(-) diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 7588be1e7..58cadf562 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -1046,6 +1046,7 @@ Agora sua seleção deve ser semelhante à imagem. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir mini GUI"/> <text name="input_CP_START_STOP" text="CP: Dirigir/stop motorista"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Mudar ponto inicial"/> <text name="input_CP_CLEAR_COURSE" text="CP: Limpa a rota atual"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Ecibir a rota atual do veículo"/> diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index 201799ef4..d929b5e19 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -1015,6 +1015,7 @@ hud还显示助手工作时堆或思洛存储器的剩余填充水平。 <text name="input_CP_START_STOP" text="CP: 启动/停止司机 "/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: 更改起始点"/> <text name="input_CP_CLEAR_COURSE" text="CP: 清除当前加载的任务路线"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="显示车辆的当前路线"/> diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml index 9daef4b71..2358f9104 100644 --- a/translations/translation_ct.xml +++ b/translations/translation_ct.xml @@ -1015,6 +1015,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: 打開迷你HUB"/> <text name="input_CP_START_STOP" text="CP: 啟動/停止司機"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: 改變起始點"/> <text name="input_CP_CLEAR_COURSE" text="CP: 清除目前載入的任務"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="顯示車輛的目前路線"/> diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index c157eca8f..90076f3ae 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -1013,6 +1013,7 @@ Nyní by váš výběr měl vypadat podobně jako na obrázku. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Otevřt/zavřít nastavení vozidla"/> <text name="input_CP_START_STOP" text="CP: Spustit mini GUI"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Změnit startovní bod"/> <text name="input_CP_CLEAR_COURSE" text="CP: Vymazat aktuálně nahranou trasu"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Zobrazit aktuální trasu vozidla"/> diff --git a/translations/translation_da.xml b/translations/translation_da.xml index e62eae623..faf572a7d 100644 --- a/translations/translation_da.xml +++ b/translations/translation_da.xml @@ -1026,6 +1026,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Åbne mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop CP hjælper"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Skift start punkt"/> <text name="input_CP_CLEAR_COURSE" text="CP: Fjern nuværende aktive rute"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Vis den aktive rute for køretøjet"/> diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 049d210c4..5246702a2 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -1048,6 +1048,7 @@ Das Kreuz sollte jetzt, wie im Bild dargestellt, gelb sein. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Mini-GUI öffnen"/> <text name="input_CP_START_STOP" text="CP: CP-Helfer starten/stoppen"/> <text name="input_CP_GENERATE_COURSE" text="CP: Kurs generieren"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Aktuell ausgewählten Job ändern"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Startpunkt ändern"/> <text name="input_CP_CLEAR_COURSE" text="CP: Aktuell geladenen Kurs löschen"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Kurs Sichtbarkeit ändern"/> diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml index 2d0c19dc4..3b3fa4882 100644 --- a/translations/translation_ea.xml +++ b/translations/translation_ea.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_en.xml b/translations/translation_en.xml index e5df5e5ca..5dfe6aabb 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -1063,7 +1063,8 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Show/hide debug channel menu"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Generate course"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Change starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clear currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Toggle course visibility"/> diff --git a/translations/translation_es.xml b/translations/translation_es.xml index 835afce2d..b72ce9745 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -1024,6 +1024,7 @@ Ahora su selección debería verse similar a la imagen. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir/Cerrar GUI del vehículo."/> <text name="input_CP_START_STOP" text="CP: Iniciar/Detener conductor."/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Cambia el punto de inicio."/> <text name="input_CP_CLEAR_COURSE" text="CP: Borra el curso cargado actualmente"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Muestra el curso actual del vehículo"/> diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml index 0ff4776ad..a3d9b8c3d 100644 --- a/translations/translation_fc.xml +++ b/translations/translation_fc.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml index e14525c56..8ef5bab07 100644 --- a/translations/translation_fi.xml +++ b/translations/translation_fi.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index 37c663597..24b59e1de 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -1008,6 +1008,7 @@ Votre sélection devrait ressembler à l'illustration ci-contre. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Affiche mini ATH"/> <text name="input_CP_START_STOP" text="CP: Ouvrier Courseplay"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Change le point de départ"/> <text name="input_CP_CLEAR_COURSE" text="CP: Efface la course actuellement sélectionnée"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Affiche la course actuelle du véhicule"/> diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index d192c4190..3819f9428 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -1032,6 +1032,7 @@ A kijelölésnek hasonlónak kell lennie, mint a képen. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Nyissa meg a mini GUI-t"/> <text name="input_CP_START_STOP" text="CP: Segítő indítás/leállítás"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Kiindulópont módosítása"/> <text name="input_CP_CLEAR_COURSE" text="CP: Törli az aktuálisan betöltött útvonalat"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mutassa a jármű aktuális irányát"/> diff --git a/translations/translation_it.xml b/translations/translation_it.xml index 343d500f8..720387d2e 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -1028,6 +1028,7 @@ Ora la tua selezione dovrebbe essere simile all'immagine. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Apri mini GUI"/> <text name="input_CP_START_STOP" text="CP: Avvia/Ferma autista"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Cambia punto di partenza"/> <text name="input_CP_CLEAR_COURSE" text="CP: Cancella il percorso attualmente caricato"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mostra il percorso attuale del veicolo"/> diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index c492eadae..92623744e 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -1022,6 +1022,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="コースプレイ:ミニGUIを開く"/> <text name="input_CP_START_STOP" text="コースプレイ:開始/停止"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml index 377292516..99ba0e532 100644 --- a/translations/translation_kr.xml +++ b/translations/translation_kr.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index 458cb085a..582dba2d9 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -1022,6 +1022,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: mini GUI openen"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_no.xml b/translations/translation_no.xml index 683fdc313..d1575167e 100644 --- a/translations/translation_no.xml +++ b/translations/translation_no.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index e4a8809a4..5a5933d1b 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -992,6 +992,7 @@ Twój wybór powinien wyglądać podobnie do tego zdjęcia. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Otwórz mini GUI"/> <text name="input_CP_START_STOP" text="CP: Uruchom/Zatrzymaj kierowcę"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Zmień punkt startowy"/> <text name="input_CP_CLEAR_COURSE" text="CP: Wyczyść aktualnie załadowany kurs"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Pokaż aktualny kurs pojazdu"/> diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index b67f5b068..b2488cbf6 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -1017,6 +1017,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir mini GUI"/> <text name="input_CP_START_STOP" text="CP: Iniciar/parar Condutor"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Altera Ponto de Ínicio"/> <text name="input_CP_CLEAR_COURSE" text="CP: Limpa actual rotas"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mostra as rotas actuais do veículo"/> diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml index 1745d0621..29398c8ce 100644 --- a/translations/translation_ro.xml +++ b/translations/translation_ro.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index 57cce5540..e8c85ad0b 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -1032,6 +1032,7 @@ HUD также показывает оставшийся уровень запо <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Открыть mini GUI"/> <text name="input_CP_START_STOP" text="CP: Отправить/Остановить водителя"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Изменить точку отправки"/> <text name="input_CP_CLEAR_COURSE" text="CP: Очистить текущий загруженный курс"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Показать текущий курс на технике"/> diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml index e2a52b0a8..c40cbaed5 100644 --- a/translations/translation_sv.xml +++ b/translations/translation_sv.xml @@ -1020,6 +1020,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Öppna mini GUI"/> <text name="input_CP_START_STOP" text="CP: Starta/Stoppa förare"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Ändrar startpunkt"/> <text name="input_CP_CLEAR_COURSE" text="CP: Tabort den aktuella inlästa kursen"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Visa fordonets aktuella kurs"/> diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml index b848090da..00ff2feea 100644 --- a/translations/translation_tr.xml +++ b/translations/translation_tr.xml @@ -1020,6 +1020,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> From 51100acb96cda1608eaa00966b62060dcfe552e7 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 21 Dec 2024 13:43:23 +0100 Subject: [PATCH 142/158] small changes There is a tool with 54m in the Basegame so increase to max workWidth to 55m Sharpen corners to default true --- config/CourseGeneratorSettingsSetup.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/CourseGeneratorSettingsSetup.xml b/config/CourseGeneratorSettingsSetup.xml index 613e229f3..cf5d63fdd 100644 --- a/config/CourseGeneratorSettingsSetup.xml +++ b/config/CourseGeneratorSettingsSetup.xml @@ -9,7 +9,7 @@ <Settings prefixText="CP_vehicle_courseGeneratorSetting_"> <!--Basic settings--> <SettingSubTitle title="basic"> - <Setting classType="AIParameterSettingList" name="workWidth" min="0.5" max="50" incremental="0.1" unit="2" setDefault="setAutomaticWorkWidthAndOffset" onChangeCallback="cpShowWorkWidth" isVisible="isWorkWidthSettingVisible"/> + <Setting classType="AIParameterSettingList" name="workWidth" min="0.5" max="55" incremental="0.1" unit="2" setDefault="setAutomaticWorkWidthAndOffset" onChangeCallback="cpShowWorkWidth" isVisible="isWorkWidthSettingVisible"/> <Setting classType="AIParameterSettingList" name="multiTools" min="1" max="5" default="1" isExpertModeOnly="true"/> <Setting classType="AIParameterBooleanSetting" name="useSameTurnWidth" isVisible="hasMoreThenOneVehicle" isExpertModeOnly="true"/> <Setting classType="AIParameterSettingList" name="numberOfHeadlands" min="0" max="40"/> @@ -24,7 +24,7 @@ <!-- Only active with one or more headlands --> <!--Headland settings--> <SettingSubTitle title="headland" isVisible="hasHeadlandsSelected"> - <Setting classType="AIParameterBooleanSetting" name="sharpenCorners" defaultBool="false"/> + <Setting classType="AIParameterBooleanSetting" name="sharpenCorners" defaultBool="true"/> <Setting classType="AIParameterSettingList" name="headlandsWithRoundCorners" min="0" max="50" default="1"/> <Setting classType="AIParameterSettingList" name="turningRadius" min="5" max="12" setDefault="setDefaultTurningRadius"/> <Setting classType="AIParameterBooleanSetting" name="headlandClockwise" defaultBool="true"> From 2bdcc840e32456534998270bd33da40cad1fd627 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 21 Dec 2024 14:43:45 +0100 Subject: [PATCH 143/158] Update modDesc.xml --- modDesc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modDesc.xml b/modDesc.xml index 09a14fae7..2f83008ee 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -43,7 +43,7 @@ Changelog 8.0.0.0: </en> <de> <![CDATA[Courseplay FS25 -Courseplay hebt den Helfer auf ein ganz neues Level. Säen, ernten und pflügen Sie intelligenter: +Courseplay hebt den Helfer auf ein ganz neues Level. Säe, ernte und pflüge intelligenter: - Nutze Vorgewende - Arbeite auf nicht rechteckigen Feldern - Arbeite um Feldinseln (z.B. Strommasten) umher From db03138250b926bcbfb9773820cc04b6abf7dfcc Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 21 Dec 2024 15:27:07 +0100 Subject: [PATCH 144/158] Some more adjustments --- config/gui/CourseGeneratorSettingsFrame.xml | 50 ------ config/gui/VehicleSettingsFrame.xml | 34 ---- config/gui/pages/CourseGeneratorFrame.xml | 10 +- scripts/gui/CpInGameMenu.lua | 175 +------------------ scripts/gui/pages/CpCourseGeneratorFrame.lua | 35 ++++ scripts/gui/pages/CpCourseManagerFrame.lua | 18 +- scripts/specializations/CpHud.lua | 21 ++- 7 files changed, 67 insertions(+), 276 deletions(-) delete mode 100644 config/gui/CourseGeneratorSettingsFrame.xml delete mode 100644 config/gui/VehicleSettingsFrame.xml diff --git a/config/gui/CourseGeneratorSettingsFrame.xml b/config/gui/CourseGeneratorSettingsFrame.xml deleted file mode 100644 index 859e51974..000000000 --- a/config/gui/CourseGeneratorSettingsFrame.xml +++ /dev/null @@ -1,50 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no" ?> -<CourseGeneratorLayout> - - <GuiElement type="cpOptionToggle" profile="ingameMenuAIParameterOption" id="createCpMultiOptionTemplate" onClick="onClickMultiTextOptionParameter" onClickCenter="onClickMultiTextOptionCenterParameter"> - <GuiElement type="button" profile="multiTextOptionLeft"/> - <GuiElement type="button" profile="multiTextOptionRight" /> - <GuiElement type="button" profile="cpMultiTextOptionText" /> - <GuiElement type="bitmap" profile="multiTextOptionBg" /> - <GuiElement type="bitmap" profile="ingameMenuAIParameterInvalid" name="invalid" /> - </GuiElement> - - <GuiElement type="button" profile="ingameMenuAIParameterButton" id="createCpTextTemplate" onClick="onClickMultiTextOptionCenterParameter"> - <GuiElement type="bitmap" profile="ingameMenuAIParameterVehicleIcon" name="icon"/> - <GuiElement type="text" profile="ingameMenuAIParameterTextTitle" name="title" text="My JD" /> - <GuiElement type="bitmap" profile="ingameMenuAIParameterInvalid" name="invalid" /> - </GuiElement> - - - <GuiElement type="text" profile="settingsMenuSubtitle" id="subTitlePrefab"/> - <GuiElement type="cpOptionToggle" profile="multiTextOptionSettings" id="multiTextOptionPrefab" size="1200px 48px" onClick="onClickCpMultiTextOption" namedComponents="true"> - <GuiElement type="button" profile="multiTextOptionSettingsLeft" name="left"/> - <GuiElement type="button" profile="multiTextOptionSettingsRight" name="right"/> - <GuiElement type="button" profile="cpMultiTextOptionSettingsText" name="text"/> - <GuiElement type="text" profile="multiTextOptionSettingsTitle" name="label"/> - <GuiElement type="text" profile="cpMultiTextOptionSettingsTooltip" name="tooltip"/> - <GuiElement type="bitmap" profile="multiTextOptionSettingsBg" name="gradient"/> - </GuiElement> - - <GuiElement type="empty" id="courseGeneratorFrame"> - <GuiElement type="text" profile="cpDrawingCustomFieldHeader" id="drawingCustomFieldHeader"/> - <GuiElement type="text" profile="cpDrawingCustomFieldSubHeader" id="drawingCustomFieldSubHeader"/> - <GuiElement type="bitmap" profile="cpCourseGeneratorBackground" position="30px -86px" id="courseGeneratorLayout"> - <GuiElement type="empty" position="0px -64px"> - <GuiElement type="empty" profile="cpIngameMenuSettingsBox" position="10px -10px"> - <GuiElement type="empty" profile="ingameMenuFrameHeaderPanel" position="0 70px"> - <GuiElement type="text" profile="ingameMenuFrameHeaderText" id="courseGeneratorHeader"/> - </GuiElement> - <GuiElement type="bitmap" profile="topScrollClipper" name="topClipper"/> - <GuiElement type="bitmap" profile="bottomScrollClipper" name="bottomClipper"/> - <!-- Course generator layout --> - <GuiElement type="scrollingLayout" profile="cpIngameMenuSettingsLayout" id="courseGeneratorLayoutElements" topClipperElementName="topClipper" bottomClipperElementName="bottomClipper"/> - - <!-- <GuiElement type="threePartBitmap" profile="verticalListSliderRightDocked"> - <GuiElement type="slider" profile="verticalListSliderBar" dataElementId="courseGeneratorLayoutElements" handleFocus="false" /> - </GuiElement> --> - </GuiElement> - </GuiElement> - </GuiElement> - </GuiElement> -</CourseGeneratorLayout> \ No newline at end of file diff --git a/config/gui/VehicleSettingsFrame.xml b/config/gui/VehicleSettingsFrame.xml deleted file mode 100644 index 9f0b3e4eb..000000000 --- a/config/gui/VehicleSettingsFrame.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="no" ?> -<GUI name="cpVehicleSettings"> - <GuiElement type="empty" profile="uiInGameMenuFrame"> - <GuiElement type="empty" profile="ingameMenuFrameHeaderPanel"> - <GuiElement type="bitmap" profile="menuHeaderIcon" size="64px 64px"/> - <GuiElement type="text" profile="ingameMenuFrameHeaderText" id="header"/> - </GuiElement> - - <GuiElement type="text" profile="settingsMenuSubtitle" id="subTitlePrefab"/> - - <GuiElement type="cpOptionToggle" profile="multiTextOptionSettings" id="multiTextOptionPrefab" onClick="onClickCpMultiTextOption" namedComponents="true"> - <GuiElement type="button" profile="multiTextOptionSettingsLeft" name="left"/> - <GuiElement type="button" profile="multiTextOptionSettingsRight" name="right"/> - <GuiElement type="button" profile="cpMultiTextOptionSettingsText" name="text"/> - <GuiElement type="text" profile="multiTextOptionSettingsTitle" name="label"/> - <GuiElement type="text" profile="multiTextOptionSettingsTooltip" name="tooltip"/> - <GuiElement type="bitmap" profile="multiTextOptionSettingsBg" name="gradient"/> - </GuiElement> - - <GuiElement type="empty" profile="ingameMenuSettingsBox" id="settingsContainer"> - <GuiElement type="bitmap" profile="topScrollClipper" name="topClipper" /> - <GuiElement type="bitmap" profile="bottomScrollClipper" name="bottomClipper" /> - - <GuiElement type="scrollingLayout" profile="ingameMenuSettingsLayout" id="boxLayout" topClipperElementName="topClipper" bottomClipperElementName="bottomClipper"> - - - </GuiElement> - </GuiElement> - - <GuiElement type="threePartBitmap" profile="verticalListSliderRightDocked"> - <GuiElement type="slider" profile="verticalListSliderBar" dataElementId="boxLayout" handleFocus="false" /> - </GuiElement> - </GuiElement> -</GUI> diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 8275fdfdc..4ee9669c1 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -98,11 +98,11 @@ </Button> </BoxLayout> <SmoothList profile="fs25_mapContextButtonList" id="createJobButtonList" - onClick="onClickList" visible="true" position="0px -165px"> + onClick="onClickList" visible="true" position="0px -165px" handleFocus="false"> <ListItem profile="fs25_mapContextButtonListItem"> <ThreePartBitmap profile="fs25_mapContextButtonListItemBg" /> <Text profile="fs25_mapButtonText" name="text" /> - <Button profile="fs25_mapContextButtonListItemButton" /> + <Button profile="cpCreateJobContextButtonListItemButton" name="button"/> </ListItem> </SmoothList> <!-- <GuiElement profile="fs25_mapButtonContainer" id="buttonStartJobContainer" width="340px" @@ -311,5 +311,11 @@ <imageSliceId value="gui.animalScreen_left" /> <!-- <imageSelectedColor value="$preset_colorBlack80" /> --> </Profile> + <Profile name="cpCreateJobContextButtonListItemButton" extends="fs25_mapContextButtonListItemButton"> + <iconColor value="$preset_fs25_colorMainDark" /> + <iconHighlightedColor value="$preset_fs25_colorMainDark" /> + <iconBgColor value="$preset_fs25_colorMainHighlight" /> + <iconBgHighlightedColor value="$preset_fs25_colorMainHighlight" /> + </Profile> </GUIProfiles> </GUI> diff --git a/scripts/gui/CpInGameMenu.lua b/scripts/gui/CpInGameMenu.lua index 6087f4d4a..ee8f70026 100644 --- a/scripts/gui/CpInGameMenu.lua +++ b/scripts/gui/CpInGameMenu.lua @@ -207,8 +207,6 @@ function CpInGameMenu:setupMenuButtonInfo() local onButtonBackFunction = self.clickBackCallback local onButtonPagePreviousFunction = self:makeSelfCallback(self.onPagePrevious) local onButtonPageNextFunction = self:makeSelfCallback(self.onPageNext) - - self.backButtonInfo = { inputAction = InputAction.MENU_BACK, text = g_i18n:getText(InGameMenu.L10N_SYMBOL.BUTTON_BACK), @@ -222,7 +220,6 @@ function CpInGameMenu:setupMenuButtonInfo() text = g_i18n:getText("ui_ingameMenuPrev"), callback = self.onPagePrevious } - self.defaultMenuButtonInfo = { self.backButtonInfo, self.nextPageButtonInfo, @@ -232,11 +229,11 @@ function CpInGameMenu:setupMenuButtonInfo() self.defaultMenuButtonInfoByActions[InputAction.MENU_BACK] = self.defaultMenuButtonInfo[1] self.defaultMenuButtonInfoByActions[InputAction.MENU_PAGE_NEXT] = self.defaultMenuButtonInfo[2] self.defaultMenuButtonInfoByActions[InputAction.MENU_PAGE_PREV] = self.defaultMenuButtonInfo[3] + self.defaultButtonActionCallbacks = { [InputAction.MENU_BACK] = onButtonBackFunction, [InputAction.MENU_PAGE_NEXT] = onButtonPageNextFunction, - [InputAction.MENU_PAGE_PREV] = onButtonPagePreviousFunction - } + [InputAction.MENU_PAGE_PREV] = onButtonPagePreviousFunction} end -- Lines 399-424 @@ -258,31 +255,8 @@ function CpInGameMenu:reset() end --- Lines 529-556 function CpInGameMenu:onMenuOpened() - -- if self.playerFarmId == FarmManager.SPECTATOR_FARM_ID then - -- self:setSoundSuppressed(true) - - -- local farmsPageId = self.pagingElement:getPageIdByElement(self.pageMultiplayerFarms) - -- local farmsPageIndex = self.pagingElement:getPageMappingIndex(farmsPageId) - - -- self.pageSelector:setState(farmsPageIndex, true) - -- self:setSoundSuppressed(false) - -- end - - -- if GS_IS_MOBILE_VERSION then - -- g_currentMission:setManualPause(true) - -- end - - -- if self.currentPage.dynamicMapImageLoading ~= nil then - -- if not self.currentPage.dynamicMapImageLoading:getIsVisible() then - -- self.messageCenter:publish(MessageType.GUI_INGAME_OPEN) - -- else - -- self.sendDelayedOpenMessage = true - -- end - -- else - -- self.messageCenter:publish(MessageType.GUI_INGAME_OPEN) - -- end + end function CpInGameMenu:onButtonBack() @@ -294,7 +268,6 @@ function CpInGameMenu:onButtonBack() CpInGameMenu:superClass().onButtonBack(self) end --- Lines 559-578 function CpInGameMenu:onClose(element) CpInGameMenu:superClass().onClose(self) self:unlockCurrentVehicle() @@ -305,30 +278,23 @@ function CpInGameMenu:onOpen() self:lockCurrentVehicle(CpUtil.getCurrentVehicle()) end --- Lines 650-699 function CpInGameMenu:update(dt) CpInGameMenu:superClass().update(self, dt) end --- Lines 820-823 function CpInGameMenu:onClickMenu() self:exitMenu() return true end --- Lines 844-866 function CpInGameMenu:onPageChange(pageIndex, pageMappingIndex, element, skipTabVisualUpdate) - CpInGameMenu:superClass().onPageChange(self, pageIndex, pageMappingIndex, element, skipTabVisualUpdate) - - -- self.header:applyProfile(self:getTabBarProfile()) self:updateBackground() end --- Lines 869-873 function CpInGameMenu:getPageButtonInfo(page) local buttonInfo = CpInGameMenu:superClass().getPageButtonInfo(self, page) @@ -357,137 +323,4 @@ function CpInGameMenu:onCurrentVehicleChanged() local index = self.pagingElement:getPageMappingIndexByElement(prevPage) self.pagingTabList:setSelectedIndex(index, true, 0) end -end - - -CpInGameMenu.TAB_UV = { - MAP = { - 0, - 0, - 65, - 65 - }, - AI = { - 910, - 65, - 65, - 65 - }, - CALENDAR = { - 65, - 0, - 65, - 65 - }, - WEATHER = { - 130, - 0, - 65, - 65 - }, - PRICES = { - 195, - 0, - 65, - 65 - }, - VEHICLES = { - 260, - 0, - 65, - 65 - }, - FINANCES = { - 325, - 0, - 65, - 65 - }, - ANIMALS = { - 390, - 0, - 65, - 65 - }, - CONTRACTS = { - 455, - 0, - 65, - 65 - }, - PRODUCTION = { - 520, - 0, - 65, - 65 - }, - STATISTICS = { - 585, - 0, - 65, - 65 - }, - GAME_SETTINGS = { - 650, - 0, - 65, - 65 - }, - GENERAL_SETTINGS = { - 715, - 0, - 65, - 65 - }, - CONTROLS_SETTINGS = { - 845, - 0, - 65, - 65 - }, - TOUR = { - 780, - 130, - 65, - 65 - }, - HELP = { - 0, - 65, - 65, - 65 - }, - FARMS = { - 260, - 65, - 65, - 65 - }, - USERS = { - 650, - 65, - 65, - 65 - } -} -CpInGameMenu.L10N_SYMBOL = { - END_WITHOUT_SAVING = "ui_endWithoutSaving", - BUTTON_SAVE_GAME = "button_saveGame", - BUTTON_RESTART = "button_restart", - SAVE_OVERWRITE = "dialog_savegameOverwrite", - TUTORIAL_NOT_SAVED = "ui_tutorialIsNotSaved", - END_GAME = "ui_youWantToQuitGame", - SAVING_CONTENT = "ui_savingContent", - SAVE_NO_SPACE = "ui_savegameSaveNoSpace", - SELECT_DEVICE = "dialog_savegameSelectDevice", - MASTER_SERVER_CONNECTION_LOST = "ui_masterServerConnectionLost", - BUTTON_CANCEL_GAME = "button_cancelGame", - END_TUTORIAL = "ui_endTutorial", - NOT_SAVED = "ui_savegameNotSaved", - SAVE_NO_DEVICE = "ui_savegameSaveNoDevice", - BUTTON_BACK = "button_back" -} -CpInGameMenu.PROFILES = { - TAB_BAR_DARK = "uiCpInGameMenuHeaderDark", - TAB_BAR_LIGHT = "uiCpInGameMenuHeader" -} +end \ No newline at end of file diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 81a981006..ed6a7bdc0 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -146,6 +146,16 @@ function CpCourseGeneratorFrame:initialize(menu) self.onClickBackCallback = menu.clickBackCallback self:initializeContextActions() + self.generateCourseMenuButton = { + ["inputAction"] = InputAction.MENU_EXTRA_1, + ["text"] = g_i18n:getText("button_blocklist"), + ["callback"] = function() + -- upvalues: (copy) p_u_39 + p_u_39:onButtonUnBan() + end + } + + self.booleanPrefab:unlinkElement() FocusManager:removeElement(self.booleanPrefab) self.multiTextPrefab:unlinkElement() @@ -473,6 +483,7 @@ function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) end function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) + self.menuButtonInfo = table.clone(self.cpMenu.defaultMenuButtonInfo) if visible then self.subCategoryPaging:setDisabled(false) for ix=1, self.NUM_CATEGORIES do @@ -481,6 +492,15 @@ function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) end end self:updateSettings(vehicle) + table.insert(self.menuButtonInfo, { + inputAction = InputAction.MENU_EXTRA_2, + text = g_i18n:getText("CP_ai_page_generate_course"), + callback = function () + if self.currentJob then + CpUtil.try(self.currentJob.onClickGenerateFieldWorkCourse, self.currentJob) + self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) + end + end}) else self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) self.subCategoryPaging:setDisabled(true) @@ -490,6 +510,7 @@ function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) end end end + self:setMenuButtonInfoDirty() end function CpCourseGeneratorFrame:updateSubCategoryPages(state) @@ -1027,6 +1048,12 @@ function CpCourseGeneratorFrame:getNumberOfItemsInSection(list, section) if self.mode ~= self.AI_MODE_CREATE then return 0 end + for _, index in pairs(self.contextActionMapping) do + local action = self.contextActions[index] + if action.isActive and action.action then + g_inputBinding:registerActionEvent(action.action, self, action.callback, false, true, false, true) + end + end return #self.contextActionMapping elseif list == self.infoTextList then self.activeInfoTexts = g_infoTextManager:getActiveInfoTexts() @@ -1075,6 +1102,7 @@ function CpCourseGeneratorFrame:populateCellForItemInSection(list, section, inde elseif list == self.createJobButtonList then local buttonInfo = self.contextActions[self.contextActionMapping[index]] cell:getAttribute("text"):setText(buttonInfo.text) + cell:getAttribute("button"):setInputAction(buttonInfo.action) cell.onClickCallback = buttonInfo.callback elseif list == self.infoTextList then cell:getAttribute("text"):setText(self.activeInfoTexts[index].text) @@ -1250,6 +1278,7 @@ function CpCourseGeneratorFrame:initializeContextActions() }, [self.CONTEXT_ACTIONS.START_JOB] = { text = g_i18n:getText("button_startJob"), + action = InputAction.MENU_EXTRA_1, callback = self.onStartCancelJob, isActive = false }, @@ -1260,6 +1289,7 @@ function CpCourseGeneratorFrame:initializeContextActions() }, [self.CONTEXT_ACTIONS.GENERATE_COURSE] = { text = g_i18n:getText("CP_ai_page_generate_course"), + action = InputAction.MENU_EXTRA_2, callback = function() if self.currentJob then CpUtil.try(self.currentJob.onClickGenerateFieldWorkCourse, self.currentJob) @@ -1286,6 +1316,11 @@ function CpCourseGeneratorFrame:updateContextActions() 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() + for _, action in ipairs(self.contextActions) do + if action.action then + g_inputBinding:removeActionEventsByActionName(action.action) + end + end self.contextButtonList:reloadData() self.createJobButtonList:reloadData() end diff --git a/scripts/gui/pages/CpCourseManagerFrame.lua b/scripts/gui/pages/CpCourseManagerFrame.lua index 419a98f20..d807c59a7 100644 --- a/scripts/gui/pages/CpCourseManagerFrame.lua +++ b/scripts/gui/pages/CpCourseManagerFrame.lua @@ -111,6 +111,7 @@ function CpCourseManagerFrame:getCurrentEntry() end function CpCourseManagerFrame:initialize(menu) + self.cpMenu = menu --- Changes the input actions. self.modeButton = { profile = "buttonActivate", @@ -483,14 +484,7 @@ function CpCourseManagerFrame:updateMenuButtons() local title = string.format(g_i18n:getText(self.translations.title), vehicle:getName(), courseName) self.categoryHeaderText:setText(title) - self.menuButtonInfo = { - { - inputAction = InputAction.MENU_BACK, - } - } - if self.courseStorage:getCanIterateBackwards() then - self.menuButtonInfo[1].callback = function () self:onClickIterateBack() end - end + self.menuButtonInfo = table.clone(self.cpMenu.defaultMenuButtonInfo) if self.activateButton.callbackDisabled == nil or not self.activateButton.callbackDisabled(self) then table.insert(self.menuButtonInfo, self.activateButton) end @@ -509,6 +503,14 @@ function CpCourseManagerFrame:updateMenuButtons() --self.rightToggleBtn:setText(text) end +function CpCourseManagerFrame:onClickBack() + if self.courseStorage:getCanIterateBackwards() then + self:onClickIterateBack() + return true + end + return false +end + --------------------------------------------------- --- Menu button click callbacks --------------------------------------------------- diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua index 0c1d1736e..648052c9d 100644 --- a/scripts/specializations/CpHud.lua +++ b/scripts/specializations/CpHud.lua @@ -116,22 +116,21 @@ function CpHud:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSe --- callback, triggerUp, triggerDown, triggerAlways, startActive, --- callbackState, customIconName, ignoreCollisions, reportAnyDeviceCollision) if self:getCpSettings().openHudWithMouse:getValue() then - local _, actionEventId = self:addActionEvent(spec.actionEvents, InputAction.CP_TOGGLE_MOUSE, self, - CpHud.actionEventMouse, false, true, false, true, nil, nil, true) - g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL) - g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) - g_inputBinding:setActionEventTextVisibility(actionEventId, - g_Courseplay.globalSettings.showActionEventHelp:getValue()) - end - if self.isActiveForInputIgnoreSelectionIgnoreAI then local _, actionEventId = self:addActionEvent(spec.actionEvents, - InputAction.CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY, self, - CpHud.actionEventMouse, false, true, false, true, nil) - g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH) + InputAction.CP_TOGGLE_MOUSE, self, + CpHud.actionEventMouse, false, true, false, true, nil, nil, true) + g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_NORMAL) g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) g_inputBinding:setActionEventTextVisibility(actionEventId, g_Courseplay.globalSettings.showActionEventHelp:getValue()) end + local _, actionEventId = self:addActionEvent(spec.actionEvents, + InputAction.CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY, self, + CpHud.actionEventMouse, false, true, false, true, nil) + g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH) + g_inputBinding:setActionEventText(actionEventId, spec.openCloseText) + g_inputBinding:setActionEventTextVisibility(actionEventId, + g_Courseplay.globalSettings.showActionEventHelp:getValue()) end end end From 667eaabc33f05eaf235bda03b6ac6cddd74002c5 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 21 Dec 2024 15:34:00 +0100 Subject: [PATCH 145/158] Fixes menu button position --- scripts/gui/pages/CpCourseGeneratorFrame.lua | 23 ++++++++++---------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index ed6a7bdc0..67120fcf7 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -483,7 +483,6 @@ function CpCourseGeneratorFrame:onClickCpMultiTextOption(_, guiElement) end function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) - self.menuButtonInfo = table.clone(self.cpMenu.defaultMenuButtonInfo) if visible then self.subCategoryPaging:setDisabled(false) for ix=1, self.NUM_CATEGORIES do @@ -492,15 +491,6 @@ function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) end end self:updateSettings(vehicle) - table.insert(self.menuButtonInfo, { - inputAction = InputAction.MENU_EXTRA_2, - text = g_i18n:getText("CP_ai_page_generate_course"), - callback = function () - if self.currentJob then - CpUtil.try(self.currentJob.onClickGenerateFieldWorkCourse, self.currentJob) - self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) - end - end}) else self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) self.subCategoryPaging:setDisabled(true) @@ -510,10 +500,10 @@ function CpCourseGeneratorFrame:updateCourseGenerator(visible, vehicle) end end end - self:setMenuButtonInfoDirty() end function CpCourseGeneratorFrame:updateSubCategoryPages(state) + self.menuButtonInfo = table.clone(self.cpMenu.defaultMenuButtonInfo) for i, _ in ipairs(self.subCategoryPages) do self.subCategoryPages[i]:setVisible(false) self.subCategoryTabs[i]:setSelected(false) @@ -528,11 +518,22 @@ function CpCourseGeneratorFrame:updateSubCategoryPages(state) layout:invalidateLayout() self.settingsSlider:setDataElement(layout) FocusManager:setFocus(self.subCategoryPages[state]) + table.insert(self.menuButtonInfo, { + inputAction = InputAction.MENU_EXTRA_2, + text = g_i18n:getText("CP_ai_page_generate_course"), + callback = function () + if self.currentJob then + CpUtil.try(self.currentJob.onClickGenerateFieldWorkCourse, self.currentJob) + self:updateSubCategoryPages(self.CATEGRORIES.IN_GAME_MAP) + end + end}) + else self.settingsSliderBox:setVisible(false) self.ingameMap:setVisible(true) self:openMap() end + self:setMenuButtonInfoDirty() end function CpCourseGeneratorFrame:delete() From e9cbb773ef0218d35ddcbcf55508f58f962eeb0d Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sat, 21 Dec 2024 15:46:47 +0100 Subject: [PATCH 146/158] ai status message bug fix and position adjustment --- config/gui/pages/CourseGeneratorFrame.xml | 5 ++++- scripts/gui/pages/CpCourseGeneratorFrame.lua | 11 +++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/config/gui/pages/CourseGeneratorFrame.xml b/config/gui/pages/CourseGeneratorFrame.xml index 4ee9669c1..8ad7572bf 100644 --- a/config/gui/pages/CourseGeneratorFrame.xml +++ b/config/gui/pages/CourseGeneratorFrame.xml @@ -159,7 +159,7 @@ <Text profile="fs25_shopBalance" text="$l10n_ui_balance:" /> <Text profile="fs25_shopMoney" id="currentBalanceText" /> </BoxLayout> --> - <Text profile="fs25_ingameMenuAIStatusText" id="statusMessage" /> + <Text profile="cpAIStatusText" id="statusMessage" /> </GuiElement> <GuiElement profile="fs25_mapContextBoxContainer" newLayer="true"> <GuiElement profile="fs25_mapContextBox" id="contextBox"> @@ -317,5 +317,8 @@ <iconBgColor value="$preset_fs25_colorMainHighlight" /> <iconBgHighlightedColor value="$preset_fs25_colorMainHighlight" /> </Profile> + <Profile name="cpAIStatusText" extends="fs25_ingameMenuAIStatusText" with="anchorTopRight"> + <position value="-300px 0px" /> + </Profile> </GUIProfiles> </GUI> diff --git a/scripts/gui/pages/CpCourseGeneratorFrame.lua b/scripts/gui/pages/CpCourseGeneratorFrame.lua index 67120fcf7..8b1f0b422 100644 --- a/scripts/gui/pages/CpCourseGeneratorFrame.lua +++ b/scripts/gui/pages/CpCourseGeneratorFrame.lua @@ -310,13 +310,16 @@ function CpCourseGeneratorFrame:onFrameOpen() if aiMessage ~= nil and job ~= nil and g_localPlayer ~= nil then if job.startedFarmId == g_localPlayer.farmId then local text = aiMessage:getMessage(job) - self:addStatusMessage(text) + local releaseMessage= g_infoTextManager:getInfoTextDataByAIMessage(aiMessage) + if g_Courseplay.globalSettings.infoTextHudActive:getValue() == g_Courseplay.globalSettings.DISABLED or + not releaseMessage then + self:addStatusMessage(text) + end end end end, self) g_messageCenter:subscribe(MessageType.AI_JOB_REMOVED, function(self, jobId) self.activeWorkerList:reloadData() - -- InGameMenuMapUtil.hideContextBox(self.contextBox) end, self) g_messageCenter:subscribe(MessageType.CP_INFO_TEXT_CHANGED, function (menu) @@ -1236,10 +1239,6 @@ function CpCourseGeneratorFrame:generateJobTypes() end function CpCourseGeneratorFrame:addStatusMessage(message) - if g_Courseplay.globalSettings.infoTextHudActive:getValue() > 0 then - --- Status Meldung werden als info text angezeigt .. - return - end table.insert(self.statusMessages, { removeTime = g_time + 5000, text = message From 0929577f8f09cbbe1a233137280e00b7c992106f Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 21 Dec 2024 16:41:10 +0100 Subject: [PATCH 147/158] Update modDesc.xml --- modDesc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modDesc.xml b/modDesc.xml index 2f83008ee..19fc08621 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" standalone="no" ?> -<modDesc descVersion="92"> +<modDesc descVersion="94"> <version>7.5.0.1</version> <author><![CDATA[Courseplay.devTeam]]></author> <title> From d5b8c71d0a0c3caf5a59b0bf4c997f1e3e8149b6 Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sat, 21 Dec 2024 16:43:22 +0100 Subject: [PATCH 148/158] Update MasterTranslations.xml --- config/MasterTranslations.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index 4d4b4719a..e5f26cbcf 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -3113,7 +3113,7 @@ TPS extension </Translation> <Translation name="input_CP_GENERATE_COURSE"> <Text language="de"><![CDATA[CP: Kurs generieren]]></Text> - <Text language="en"><![CDATA[CP: Generate course]></Text> + <Text language="en"><![CDATA[CP: Generate course]]></Text> </Translation> <Translation name="input_CP_CHANGE_SELECTED_JOB"> <Text language="de"><![CDATA[CP: Aktuell ausgewählten Job ändern]]></Text> From 9fc444551ea6a93cbcc7fcd86af745c67cd9fce8 Mon Sep 17 00:00:00 2001 From: Tensuko <Tensuko@users.noreply.github.com> Date: Sat, 21 Dec 2024 15:43:41 +0000 Subject: [PATCH 149/158] Updated translations --- translations/translation_br.xml | 1 + translations/translation_cs.xml | 1 + translations/translation_ct.xml | 1 + translations/translation_cz.xml | 1 + translations/translation_da.xml | 1 + translations/translation_de.xml | 1 + translations/translation_ea.xml | 1 + translations/translation_en.xml | 3 ++- translations/translation_es.xml | 1 + translations/translation_fc.xml | 1 + translations/translation_fi.xml | 1 + translations/translation_fr.xml | 1 + translations/translation_hu.xml | 1 + translations/translation_it.xml | 1 + translations/translation_jp.xml | 1 + translations/translation_kr.xml | 1 + translations/translation_nl.xml | 1 + translations/translation_no.xml | 1 + translations/translation_pl.xml | 1 + translations/translation_pt.xml | 1 + translations/translation_ro.xml | 1 + translations/translation_ru.xml | 1 + translations/translation_sv.xml | 1 + translations/translation_tr.xml | 1 + 24 files changed, 25 insertions(+), 1 deletion(-) diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 7588be1e7..58cadf562 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -1046,6 +1046,7 @@ Agora sua seleção deve ser semelhante à imagem. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir mini GUI"/> <text name="input_CP_START_STOP" text="CP: Dirigir/stop motorista"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Mudar ponto inicial"/> <text name="input_CP_CLEAR_COURSE" text="CP: Limpa a rota atual"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Ecibir a rota atual do veículo"/> diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index 201799ef4..d929b5e19 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -1015,6 +1015,7 @@ hud还显示助手工作时堆或思洛存储器的剩余填充水平。 <text name="input_CP_START_STOP" text="CP: 启动/停止司机 "/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: 更改起始点"/> <text name="input_CP_CLEAR_COURSE" text="CP: 清除当前加载的任务路线"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="显示车辆的当前路线"/> diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml index 9daef4b71..2358f9104 100644 --- a/translations/translation_ct.xml +++ b/translations/translation_ct.xml @@ -1015,6 +1015,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: 打開迷你HUB"/> <text name="input_CP_START_STOP" text="CP: 啟動/停止司機"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: 改變起始點"/> <text name="input_CP_CLEAR_COURSE" text="CP: 清除目前載入的任務"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="顯示車輛的目前路線"/> diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index c157eca8f..90076f3ae 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -1013,6 +1013,7 @@ Nyní by váš výběr měl vypadat podobně jako na obrázku. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Otevřt/zavřít nastavení vozidla"/> <text name="input_CP_START_STOP" text="CP: Spustit mini GUI"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Změnit startovní bod"/> <text name="input_CP_CLEAR_COURSE" text="CP: Vymazat aktuálně nahranou trasu"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Zobrazit aktuální trasu vozidla"/> diff --git a/translations/translation_da.xml b/translations/translation_da.xml index e62eae623..faf572a7d 100644 --- a/translations/translation_da.xml +++ b/translations/translation_da.xml @@ -1026,6 +1026,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Åbne mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop CP hjælper"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Skift start punkt"/> <text name="input_CP_CLEAR_COURSE" text="CP: Fjern nuværende aktive rute"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Vis den aktive rute for køretøjet"/> diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 6ce031caf..69ae898f4 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -1046,6 +1046,7 @@ Das Kreuz sollte jetzt, wie im Bild dargestellt, gelb sein. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Mini-GUI öffnen"/> <text name="input_CP_START_STOP" text="CP: CP-Helfer starten/stoppen"/> <text name="input_CP_GENERATE_COURSE" text="CP: Kurs generieren"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Aktuell ausgewählten Job ändern"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Startpunkt ändern"/> <text name="input_CP_CLEAR_COURSE" text="CP: Aktuell geladenen Kurs löschen"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Kurs Sichtbarkeit ändern"/> diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml index 2d0c19dc4..3b3fa4882 100644 --- a/translations/translation_ea.xml +++ b/translations/translation_ea.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 272c09736..ef9870b1e 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -1060,7 +1060,8 @@ Now your selection should look similar to the image. <text name="input_CP_DBG_CHANNEL_MENU_VISIBILITY" text="CP: Show/hide debug channel menu"/> <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> - <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_GENERATE_COURSE" text="CP: Generate course"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Change starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clear currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Toggle course visibility"/> diff --git a/translations/translation_es.xml b/translations/translation_es.xml index 835afce2d..b72ce9745 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -1024,6 +1024,7 @@ Ahora su selección debería verse similar a la imagen. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir/Cerrar GUI del vehículo."/> <text name="input_CP_START_STOP" text="CP: Iniciar/Detener conductor."/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Cambia el punto de inicio."/> <text name="input_CP_CLEAR_COURSE" text="CP: Borra el curso cargado actualmente"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Muestra el curso actual del vehículo"/> diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml index 0ff4776ad..a3d9b8c3d 100644 --- a/translations/translation_fc.xml +++ b/translations/translation_fc.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml index e14525c56..8ef5bab07 100644 --- a/translations/translation_fi.xml +++ b/translations/translation_fi.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index 37c663597..24b59e1de 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -1008,6 +1008,7 @@ Votre sélection devrait ressembler à l'illustration ci-contre. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Affiche mini ATH"/> <text name="input_CP_START_STOP" text="CP: Ouvrier Courseplay"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Change le point de départ"/> <text name="input_CP_CLEAR_COURSE" text="CP: Efface la course actuellement sélectionnée"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Affiche la course actuelle du véhicule"/> diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index d192c4190..3819f9428 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -1032,6 +1032,7 @@ A kijelölésnek hasonlónak kell lennie, mint a képen. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Nyissa meg a mini GUI-t"/> <text name="input_CP_START_STOP" text="CP: Segítő indítás/leállítás"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Kiindulópont módosítása"/> <text name="input_CP_CLEAR_COURSE" text="CP: Törli az aktuálisan betöltött útvonalat"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mutassa a jármű aktuális irányát"/> diff --git a/translations/translation_it.xml b/translations/translation_it.xml index 343d500f8..720387d2e 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -1028,6 +1028,7 @@ Ora la tua selezione dovrebbe essere simile all'immagine. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Apri mini GUI"/> <text name="input_CP_START_STOP" text="CP: Avvia/Ferma autista"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Cambia punto di partenza"/> <text name="input_CP_CLEAR_COURSE" text="CP: Cancella il percorso attualmente caricato"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mostra il percorso attuale del veicolo"/> diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index c492eadae..92623744e 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -1022,6 +1022,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="コースプレイ:ミニGUIを開く"/> <text name="input_CP_START_STOP" text="コースプレイ:開始/停止"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml index 377292516..99ba0e532 100644 --- a/translations/translation_kr.xml +++ b/translations/translation_kr.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index 458cb085a..582dba2d9 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -1022,6 +1022,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: mini GUI openen"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_no.xml b/translations/translation_no.xml index 683fdc313..d1575167e 100644 --- a/translations/translation_no.xml +++ b/translations/translation_no.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index e4a8809a4..5a5933d1b 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -992,6 +992,7 @@ Twój wybór powinien wyglądać podobnie do tego zdjęcia. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Otwórz mini GUI"/> <text name="input_CP_START_STOP" text="CP: Uruchom/Zatrzymaj kierowcę"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Zmień punkt startowy"/> <text name="input_CP_CLEAR_COURSE" text="CP: Wyczyść aktualnie załadowany kurs"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Pokaż aktualny kurs pojazdu"/> diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index b67f5b068..b2488cbf6 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -1017,6 +1017,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Abrir mini GUI"/> <text name="input_CP_START_STOP" text="CP: Iniciar/parar Condutor"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Altera Ponto de Ínicio"/> <text name="input_CP_CLEAR_COURSE" text="CP: Limpa actual rotas"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Mostra as rotas actuais do veículo"/> diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml index 1745d0621..29398c8ce 100644 --- a/translations/translation_ro.xml +++ b/translations/translation_ro.xml @@ -1023,6 +1023,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index 57cce5540..e8c85ad0b 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -1032,6 +1032,7 @@ HUD также показывает оставшийся уровень запо <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Открыть mini GUI"/> <text name="input_CP_START_STOP" text="CP: Отправить/Остановить водителя"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Изменить точку отправки"/> <text name="input_CP_CLEAR_COURSE" text="CP: Очистить текущий загруженный курс"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="CP: Показать текущий курс на технике"/> diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml index e2a52b0a8..c40cbaed5 100644 --- a/translations/translation_sv.xml +++ b/translations/translation_sv.xml @@ -1020,6 +1020,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Öppna mini GUI"/> <text name="input_CP_START_STOP" text="CP: Starta/Stoppa förare"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Ändrar startpunkt"/> <text name="input_CP_CLEAR_COURSE" text="CP: Tabort den aktuella inlästa kursen"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Visa fordonets aktuella kurs"/> diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml index b848090da..00ff2feea 100644 --- a/translations/translation_tr.xml +++ b/translations/translation_tr.xml @@ -1020,6 +1020,7 @@ Now your selection should look similar to the image. <text name="input_CP_OPEN_CLOSE_VEHICLE_SETTING_DISPLAY" text="CP: Open mini GUI"/> <text name="input_CP_START_STOP" text="CP: Start/stop driver"/> <text name="input_CP_GENERATE_COURSE" text="CP: Change current selected job"/> + <text name="input_CP_CHANGE_SELECTED_JOB" text="CP: Change current selected job"/> <text name="input_CP_CHANGE_STARTING_POINT" text="CP: Changes starting point"/> <text name="input_CP_CLEAR_COURSE" text="CP: Clears currently loaded course"/> <text name="input_CP_CHANGE_COURSE_VISIBILITY" text="Show the current course of the vehicle"/> From 9c82df20553d6421cab3b19ad7a4eb9a965bdcc0 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sat, 21 Dec 2024 14:15:37 -0500 Subject: [PATCH 150/158] doc: README updated --- README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 357be7141..73a2f97b6 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,34 @@ # Courseplay Beta for Farming Simulator 2025 -We are working on migrating Courseplay to Farming Simulator 2025. -We don't yet know how long it'll take, it depends on how much the Giants API changed, -when they make documentation and tools available, and of course, how much time we can spend on it. +<!-- [![Modhub release](https://img.shields.io/badge/Modhub%20Release-Modification-blue.svg)](https://www.farming-simulator.com/mod.php?mod_id=248390title=fs2022)--> +[![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Courseplay/Courseplay_FS25?include_prereleases&style=flat-square&label=Github+Release)](https://github.com/Courseplay/Courseplay_FS25/releases/latest) +[![GitHub Pre-Releases (by Asset)](https://img.shields.io/github/downloads-pre/Courseplay/Courseplay_FS25/latest/FS25_Courseplay.zip?style=flat-square)](https://github.com/Courseplay/Courseplay_FS22/releases/latest/download/FS22_Courseplay.zip) +[![GitHub issues](https://img.shields.io/github/issues/Courseplay/Courseplay_FS25?style=flat-square)](https://github.com/Courseplay/Courseplay_FS22/issues) + +**[Download the latest developer version](https://github.com/Courseplay/Courseplay_FS25/releases/latest)** (the file FS25_Courseplay.zip). + +<!-- **[Courseplay Website](https://courseplay.github.io/Courseplay_FS25.github.io/)** --> -We'll keep you updated on our progress here, please be patient. This (and later, the Giants modhub) is the **only official source for Courseplay**, if you see it anywhere else, it's a scam. +## Developer version + +Please be aware you're using a developer version, which may and will contain errors, bugs, mistakes and unfinished code. Chances are you computer will explode when using it. Twice. If you have no idea what "beta", "alpha", or "developer" means and entails, steer clear. The Courseplay team will not take any responsibility for crop destroyed, savegames deleted or baby pandas killed. + +You have been warned. + +If you're still ok with this, please remember to post possible issues that you find in the developer version. That's the only way we can find sources of error and fix them. +Be as specific as possible: + +* tell us the version number +* only use the vehicles necessary, not 10 other ones at a time +* which vehicles are involved, what is the intended action? +* Post! The! Log! to [Gist](https://gist.github.com/) or [PasteBin](http://pastebin.com/) +* For more details on how to post a proper bug report, visit our [Wiki](https://github.com/Courseplay/Courseplay_FS25/wiki) + + ## Help Us Out We work long, hard, in our own free time at developing and improving Courseplay. If you like the project, show us your undying love: From 951f2998553471497d0aa9f11b76e66000720028 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 22 Dec 2024 00:54:43 -0500 Subject: [PATCH 151/158] doc: README updated --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73a2f97b6..ff1ae2671 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ <!-- [![Modhub release](https://img.shields.io/badge/Modhub%20Release-Modification-blue.svg)](https://www.farming-simulator.com/mod.php?mod_id=248390title=fs2022)--> [![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/Courseplay/Courseplay_FS25?include_prereleases&style=flat-square&label=Github+Release)](https://github.com/Courseplay/Courseplay_FS25/releases/latest) -[![GitHub Pre-Releases (by Asset)](https://img.shields.io/github/downloads-pre/Courseplay/Courseplay_FS25/latest/FS25_Courseplay.zip?style=flat-square)](https://github.com/Courseplay/Courseplay_FS22/releases/latest/download/FS22_Courseplay.zip) -[![GitHub issues](https://img.shields.io/github/issues/Courseplay/Courseplay_FS25?style=flat-square)](https://github.com/Courseplay/Courseplay_FS22/issues) +[![GitHub Pre-Releases (by Asset)](https://img.shields.io/github/downloads-pre/Courseplay/Courseplay_FS25/latest/FS25_Courseplay.zip?style=flat-square)](https://github.com/Courseplay/Courseplay_FS25/releases/latest/download/FS25_Courseplay.zip) +[![GitHub issues](https://img.shields.io/github/issues/Courseplay/Courseplay_FS25?style=flat-square)](https://github.com/Courseplay/Courseplay_FS25/issues) **[Download the latest developer version](https://github.com/Courseplay/Courseplay_FS25/releases/latest)** (the file FS25_Courseplay.zip). From 1cf3eeb7972fc4867a2ac9b36afe7fc757541e15 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 22 Dec 2024 12:29:38 +0100 Subject: [PATCH 152/158] Moved strategies into new folder --- modDesc.xml | 24 +- .../AIDriveStrategyAttachHeader.lua | 0 .../AIDriveStrategyBunkerSilo.lua | 0 .../AIDriveStrategyCombineCourse.lua | 4308 ++++++++--------- .../AIDriveStrategyCourse.lua | 1410 +++--- .../AIDriveStrategyDriveToFieldWorkStart.lua | 550 +-- .../AIDriveStrategyFieldWorkCourse.lua | 1734 +++---- .../AIDriveStrategyFindBales.lua | 1336 ++--- .../AIDriveStrategyPlowCourse.lua | 404 +- .../AIDriveStrategyShovelSiloLoader.lua | 0 .../AIDriveStrategySiloLoader.lua | 0 .../AIDriveStrategyUnloadCombine.lua | 0 .../AIDriveStrategyVineFieldWorkCourse.lua | 0 13 files changed, 4883 insertions(+), 4883 deletions(-) rename scripts/ai/{ => strategies}/AIDriveStrategyAttachHeader.lua (100%) rename scripts/ai/{ => strategies}/AIDriveStrategyBunkerSilo.lua (100%) rename scripts/ai/{ => strategies}/AIDriveStrategyCombineCourse.lua (98%) rename scripts/ai/{ => strategies}/AIDriveStrategyCourse.lua (97%) rename scripts/ai/{ => strategies}/AIDriveStrategyDriveToFieldWorkStart.lua (97%) rename scripts/ai/{ => strategies}/AIDriveStrategyFieldWorkCourse.lua (98%) rename scripts/ai/{ => strategies}/AIDriveStrategyFindBales.lua (97%) rename scripts/ai/{ => strategies}/AIDriveStrategyPlowCourse.lua (97%) rename scripts/ai/{ => strategies}/AIDriveStrategyShovelSiloLoader.lua (100%) rename scripts/ai/{ => strategies}/AIDriveStrategySiloLoader.lua (100%) rename scripts/ai/{ => strategies}/AIDriveStrategyUnloadCombine.lua (100%) rename scripts/ai/{ => strategies}/AIDriveStrategyVineFieldWorkCourse.lua (100%) diff --git a/modDesc.xml b/modDesc.xml index 19fc08621..cc08fd7db 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -227,18 +227,18 @@ Changelog 8.0.0.0: <sourceFile filename="scripts/ai/controllers/PalletFillerController.lua"/> <sourceFile filename="scripts/ai/controllers/TreePlanterController.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyCourse.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyAttachHeader.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyFieldWorkCourse.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyPlowCourse.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyCombineCourse.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyFindBales.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyUnloadCombine.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyBunkerSilo.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategySiloLoader.lua"/> - <sourceFile filename="scripts/ai/AIDriveStrategyShovelSiloLoader.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyCourse.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyDriveToFieldWorkStart.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyAttachHeader.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyPlowCourse.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyCombineCourse.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyFindBales.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyVineFieldWorkCourse.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyBunkerSilo.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategySiloLoader.lua"/> + <sourceFile filename="scripts/ai/strategies/AIDriveStrategyShovelSiloLoader.lua"/> <sourceFile filename="scripts/ai/parameters/AIParameterSettingInterface.lua"/> <sourceFile filename="scripts/ai/parameters/AIParameterSetting.lua"/> diff --git a/scripts/ai/AIDriveStrategyAttachHeader.lua b/scripts/ai/strategies/AIDriveStrategyAttachHeader.lua similarity index 100% rename from scripts/ai/AIDriveStrategyAttachHeader.lua rename to scripts/ai/strategies/AIDriveStrategyAttachHeader.lua diff --git a/scripts/ai/AIDriveStrategyBunkerSilo.lua b/scripts/ai/strategies/AIDriveStrategyBunkerSilo.lua similarity index 100% rename from scripts/ai/AIDriveStrategyBunkerSilo.lua rename to scripts/ai/strategies/AIDriveStrategyBunkerSilo.lua diff --git a/scripts/ai/AIDriveStrategyCombineCourse.lua b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua similarity index 98% rename from scripts/ai/AIDriveStrategyCombineCourse.lua rename to scripts/ai/strategies/AIDriveStrategyCombineCourse.lua index f490c7da4..5d8c174c0 100644 --- a/scripts/ai/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua @@ -1,2154 +1,2154 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/courseplay) -Copyright (C) 2019-2021 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -]] - ----@class AIDriveStrategyCombineCourse : AIDriveStrategyFieldWorkCourse -AIDriveStrategyCombineCourse = CpObject(AIDriveStrategyFieldWorkCourse) - --- fill level when we start making a pocket to unload if we are on the outermost headland -AIDriveStrategyCombineCourse.pocketFillLevelFullPercentage = 95 -AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow = 30 --- when fill level is above this threshold, don't start the next row if the pipe would be --- in the fruit -AIDriveStrategyCombineCourse.waitForUnloadAtEndOfRowFillLevelThreshold = 95 - --- When to initiate a self unload before turning to the next row? --- if the fill level is above this threshold when about to begin a turn and there is a trailer --- close by, and ... -AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowFillLevelThreshold = 60 --- if the best trailer is less then this (meters) to the left or right -AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowMaxDistance = 50 ---- Percentage delta leftover until full, when the combine slows down. -AIDriveStrategyCombineCourse.startingSlowdownFillLevelThreshold = 1.5 ---- Minimum working speed, for slowdown. -AIDriveStrategyCombineCourse.normalMinimalWorkingSpeed = 5 - -AIDriveStrategyCombineCourse.myStates = { - -- main states - UNLOADING_ON_FIELD = {}, - -- unload sub-states - STOPPING_FOR_UNLOAD = {}, - WAITING_FOR_UNLOAD_ON_FIELD = {}, - PULLING_BACK_FOR_UNLOAD = {}, - WAITING_FOR_UNLOAD_AFTER_PULLED_BACK = {}, - RETURNING_FROM_PULL_BACK = {}, - REVERSING_TO_MAKE_A_POCKET = {}, - MAKING_POCKET = {}, - WAITING_FOR_UNLOAD_IN_POCKET = {}, - WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW = {}, - UNLOADING_BEFORE_STARTING_NEXT_ROW = {}, - WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED = {}, - WAITING_FOR_UNLOADER_TO_LEAVE = {}, - RETURNING_FROM_POCKET = {}, - DRIVING_TO_SELF_UNLOAD = {}, - DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW = {}, -- before turning into the next row, we unload into a nearby trailer - SELF_UNLOADING = {}, - SELF_UNLOADING_WAITING_FOR_DISCHARGE = {}, - DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED = {}, - SELF_UNLOADING_AFTER_FIELDWORK_ENDED = {}, - SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE = {}, - RETURNING_FROM_SELF_UNLOAD = {}, -} - --- stop limit we use for self unload to approach the trailer -AIDriveStrategyCombineCourse.proximityStopThresholdSelfUnload = 0.1 - --- Developer hack: to check the class of an object one should use the is_a() defined in CpObject.lua. --- However, when we reload classes on the fly during the development, the is_a() calls in other modules still --- have the old class definition (for example CombineUnloadManager.lua) of this class and thus, is_a() fails. --- Therefore, use this instead, this is safe after a reload. -AIDriveStrategyCombineCourse.isAAIDriveStrategyCombineCourse = true - -function AIDriveStrategyCombineCourse:init(task, job) - AIDriveStrategyFieldWorkCourse.init(self, task, job) - AIDriveStrategyCourse.initStates(self, AIDriveStrategyCombineCourse.myStates) - self.fruitLeft, self.fruitRight = 0, 0 - self.litersPerMeter = 0 - self.litersPerSecond = 0 - self.fillLevelAtLastWaypoint = 0 - self.beaconLightsActive = false - self.stopDisabledAfterEmpty = CpTemporaryObject(false) - self.stopDisabledAfterEmpty:set(false, 1) - self:initUnloadStates() - self.chopperCanDischarge = CpTemporaryObject(false) - -- hold the harvester temporarily - self.temporaryHold = CpTemporaryObject(false) - -- periodically check if we need to call an unloader - self.timeToCallUnloader = CpTemporaryObject(true) - self.unloaderRequestedToIgnoreProximity = CpTemporaryObject() - -- we want to keep to pipe open, even if there is no trailer under it - self.forcePipeOpen = CpTemporaryObject() - self.pocketHelperNode = HelperTerrainNode('pocketHelperNode') - --- Register info texts - self:registerInfoTextForStates(self:getFillLevelInfoText(), { - states = { - [self.states.UNLOADING_ON_FIELD] = true - }, - unloadStates = { - [self.states.WAITING_FOR_UNLOAD_ON_FIELD] = true, - [self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED] = true, - [self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK] = true, - [self.states.WAITING_FOR_UNLOAD_IN_POCKET] = true - } - }) -end - -function AIDriveStrategyCombineCourse:delete() - self.pocketHelperNode:destroy() - AIDriveStrategyFieldWorkCourse.delete(self) -end - -function AIDriveStrategyCombineCourse:getStateAsString() - local s = self.state.name - if self.state == self.states.UNLOADING_ON_FIELD then - s = s .. '/' .. self.unloadState.name - end - return s -end - ------------------------------------------------------------------------------------------------------------------------ ---- Initialization ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyCombineCourse:setAllStaticParameters() - AIDriveStrategyFieldWorkCourse.setAllStaticParameters(self) - self:debug('AIDriveStrategyCombineCourse set') - - if self:isChopper() then - self:debug('This is a chopper.') - end - - self:checkMarkers() - self:measureBackDistance() - Markers.setMarkerNodes(self.vehicle, self.measuredBackDistance) - - self.proximityController:registerBlockingVehicleListener(self, AIDriveStrategyCombineCourse.onBlockingVehicle) - self.proximityController:registerIgnoreObjectCallback(self, AIDriveStrategyCombineCourse.ignoreProximityObject) - - -- distance to keep to the right (>0) or left (<0) when pulling back to make room for the tractor - self.pullBackRightSideOffset = math.abs(self.pipeController:getPipeOffsetX()) - self:getWorkWidth() / 2 + 5 - self.pullBackRightSideOffset = self:isPipeOnLeft() and self.pullBackRightSideOffset or -self.pullBackRightSideOffset - -- should be at pullBackRightSideOffset to the right or left at pullBackDistanceStart - self.pullBackDistanceStart = 2 * AIUtil.getTurningRadius(self.vehicle) - -- and back up another bit - self.pullBackDistanceEnd = self.pullBackDistanceStart + 5 - -- when making a pocket, how far to back up before changing to forward - -- for very long vehicles, like potato/sugar beet harvesters the 20 meters may not be enough - self.pocketReverseDistance = AIUtil.getVehicleAndImplementsTotalLength(self.vehicle) * 2.2 - -- register ourselves at our boss - -- TODO_22 g_combineUnloadManager:addCombineToList(self.vehicle, self) - self.waitingForUnloaderAtEndOfRow = CpTemporaryObject() - --- My unloader. This expires in a few seconds, so unloaders have to renew their registration periodically - ---@type CpTemporaryObject - self.unloader = CpTemporaryObject(nil) - --- if this is not nil, we have a pending rendezvous with our unloader - ---@type CpTemporaryObject - self.unloaderToRendezvous = CpTemporaryObject(nil) - local total, pipeInFruit = self.vehicle:getFieldWorkCourse():setPipeInFruitMap(self.pipeController:getPipeOffsetX(), self:getWorkWidth()) - self:debug('Pipe in fruit map created, there are %d non-headland waypoints, of which at %d the pipe will be in the fruit', - total, pipeInFruit) - self.fillLevelFullPercentage = self.normalFillLevelFullPercentage -end - -function AIDriveStrategyCombineCourse:initializeImplementControllers(vehicle) - AIDriveStrategyFieldWorkCourse.initializeImplementControllers(self, vehicle) - local _ - self.implementWithPipe, self.pipeController = self:addImplementController(vehicle, - PipeController, Pipe, {}, nil) - self.combineController, self.combine = self:getFirstRegisteredImplementControllerByClass(CombineController) -end - -function AIDriveStrategyCombineCourse:getProximitySensorWidth() - -- proximity sensor width across the entire working width - return self:getWorkWidth() -end - --- This part of an ugly workaround to make the chopper pickups work -function AIDriveStrategyCombineCourse:checkMarkers() - for _, implement in pairs(AIUtil.getAllAIImplements(self.vehicle)) do - local aiLeftMarker, aiRightMarker, aiBackMarker = implement.object:getAIMarkers() - if not aiLeftMarker or not aiRightMarker or not aiBackMarker then - self.notAllImplementsHaveAiMarkers = true - return - end - end -end - -function AIDriveStrategyCombineCourse:getAllowReversePathfinding() - if self:isTurning() and self:hasAutoAimPipe() and self.unloader:get() ~= nil then - -- chopper with CP unloader turning, disable reverse for pathfinder turns - -- to make following the chopper through the turn easier - return false - else - return AIDriveStrategyFieldWorkCourse.getAllowReversePathfinding(self) - end -end - ---- Get the combine object, this can be different from the vehicle in case of tools towed or mounted on a tractor -function AIDriveStrategyCombineCourse:getCombine() - return self.combine -end - -function AIDriveStrategyCombineCourse:isAttachedHarvester() - return self.vehicle ~= self.combine -end - -function AIDriveStrategyCombineCourse:getPipeOffsetReferenceNode() - if self:isAttachedHarvester() then - -- for attached harvesters this gets the root node of the harvester as that is our reference point to the - -- pipe offsets - return self:getCombine().rootNode - else - return self.vehicle:getAIDirectionNode() - end -end - -function AIDriveStrategyCombineCourse:update(dt) - AIDriveStrategyFieldWorkCourse.update(self, dt) - self:updateChopperFillType() - self:onDraw() -end - ---- Hold the harvester for a period of periodMs milliseconds -function AIDriveStrategyCombineCourse:hold(periodMs) - if not self.temporaryHold:get() then - self:debug('Temporary hold request for %d milliseconds', periodMs) - end - self.temporaryHold:set(true, math.min(math.max(0, periodMs), 30000)) -end - -function AIDriveStrategyCombineCourse:getDriveData(dt, vX, vY, vZ) - self:handlePipe(dt) - if self.temporaryHold:get() then - self:setMaxSpeed(0) - end - if self.state == self.states.WORKING then - -- Harvesting - self:checkRendezvous() - self:checkBlockingUnloader() - - if self:isFull() then - self:changeToUnloadOnField() - elseif self:alwaysNeedsUnloader() then - if not self.pipeController:isFillableTrailerUnderPipe() then - self:debug('Need an unloader to work but have no fillable trailer under the pipe') - self:changeToUnloadOnField() - end - elseif self:shouldWaitAtEndOfRow() then - self:startWaitingForUnloadBeforeNextRow() - end - - if self:shouldStopForUnloading() then - -- player does not want us to move while discharging - self:setMaxSpeed(0) - else - -- Slowdown the combine near the last few percent, so no crops are leftover, - -- as the combine needs to stop in time, before it's full. - local leftoverPercentage = self.fillLevelFullPercentage - self.combineController:getFillLevelPercentage() - if (leftoverPercentage > 0 and leftoverPercentage < self.startingSlowdownFillLevelThreshold) then - local speed = leftoverPercentage * (self.vehicle:getSpeedLimit(true) / self.startingSlowdownFillLevelThreshold) + self.normalMinimalWorkingSpeed - self:setMaxSpeed(speed) - end - end - - - elseif self.state == self.states.TURNING then - self:checkBlockingUnloader() - elseif self.state == self.states.WAITING_FOR_LOWER then - if self:isFull() then - self:debug('Waiting for lower but full...') - self:changeToUnloadOnField() - end - elseif self.state == self.states.UNLOADING_ON_FIELD then - -- Unloading - self:driveUnloadOnField() - if self:isWaitingForUnload() then - self:callUnloaderWhenNeeded() - end - end - if self:isTurning() and not self:isFinishingRow() then - if self:shouldHoldInTurnManeuver() then - self:setMaxSpeed(0) - end - end - return AIDriveStrategyFieldWorkCourse.getDriveData(self, dt, vX, vY, vZ) -end - -function AIDriveStrategyCombineCourse:updateFieldworkOffset(course) - if self.state == self.states.UNLOADING_ON_FIELD and self:isUnloadStateOneOf(self.selfUnloadStates) then - -- do not apply fieldwork offset when not doing fieldwork - course:setOffset((self.aiOffsetX or 0) + (self.tightTurnOffset or 0), (self.aiOffsetZ or 0)) - else - course:setOffset(self.settings.toolOffsetX:getValue() + (self.aiOffsetX or 0) + (self.tightTurnOffset or 0), - (self.aiOffsetZ or 0)) - end -end - -function AIDriveStrategyCombineCourse:checkDistanceToOtherFieldWorkers() - -- do not slow down/stop for convoy while unloading - if self.state ~= self.states.UNLOADING_ON_FIELD then - self:setMaxSpeed(self.fieldWorkerProximityController:getMaxSpeed(self.settings.convoyDistance:getValue(), self.maxSpeed)) - end -end - ---- Take care of unloading on the field. This could be stopping and waiting for an unloader or ---- self unloading. ---- The output of this function is: ---- * set self.maxSpeed ---- * change the course to run, for example pulling back/making pocket or self unload ---- * this does not supply drive target point -function AIDriveStrategyCombineCourse:driveUnloadOnField() - if self.unloadState == self.states.STOPPING_FOR_UNLOAD then - self:setMaxSpeed(0) - -- wait until we stopped before raising the implements - if AIUtil.isStopped(self.vehicle) then - if self.raiseHeaderAfterStopped then - self:debug('Stopped, now raise implements and switch to next unload state') - self:raiseImplements() - end - self.unloadState = self.newUnloadStateAfterStopped - end - elseif self.unloadState == self.states.PULLING_BACK_FOR_UNLOAD then - self:setMaxSpeed(self.settings.reverseSpeed:getValue()) - elseif self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET then - self:setMaxSpeed(self.settings.reverseSpeed:getValue()) - elseif self.unloadState == self.states.MAKING_POCKET then - self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue()) - local _, _, dz = self.pocketHelperNode:localToLocal(self.vehicle:getAIDirectionNode(), 0, 0, - self.pipeController:getPipeOffsetZ()) - if dz > -18 then - -- we are close enough to the reference waypoint, so stop making the pocket and wait for unload. - self:debug('Waiting for unload in the pocket') - self.unloadState = self.states.WAITING_FOR_UNLOAD_IN_POCKET - end - elseif self.unloadState == self.states.RETURNING_FROM_PULL_BACK then - self:setMaxSpeed(self.settings.turnSpeed:getValue()) - elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK or - self.unloadState == self.states.UNLOADING_BEFORE_STARTING_NEXT_ROW then - if self:isUnloadFinished() then - -- reset offset to return to the original up/down row after we unloaded in the pocket - self.aiOffsetX = 0 - - -- wait a bit after the unload finished to give a chance to the unloader to move away - self.stateBeforeWaitingForUnloaderToLeave = self.unloadState - self.unloadState = self.states.WAITING_FOR_UNLOADER_TO_LEAVE - self.waitingForUnloaderSince = g_currentMission.time - self:debug('Unloading finished, wait for the unloader to leave...') - else - self:setMaxSpeed(0) - end - elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD then - if g_updateLoopIndex % 5 == 0 then - --small delay, to make sure no more fillLevel change is happening - if not self:isFull() and not self:shouldStopForUnloading() and not self:alwaysNeedsUnloader() then - self:debug('not full anymore, can continue working') - self:changeToFieldWork() - elseif self:alwaysNeedsUnloader() and self:isFillableTrailerUnderPipe() then - self:debug('Need an unloader to work, now have a trailer under the pipe, can continue working') - self:changeToFieldWork() - end - end - self:setMaxSpeed(0) - elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW then - self:setMaxSpeed(0) - if self:isDischarging() then - self:cancelRendezvous() - self.unloadState = self.states.UNLOADING_BEFORE_STARTING_NEXT_ROW - self:debug('Unloading started at end of row') - end - if not self.waitingForUnloaderAtEndOfRow:get() then - local unloaderWhoDidNotShowUp = self.unloaderToRendezvous:get() - self:cancelRendezvous() - if unloaderWhoDidNotShowUp then - unloaderWhoDidNotShowUp:getCpDriveStrategy():onMissedRendezvous(self) - end - self:debug('Waited for unloader at the end of the row but it did not show up, try to continue') - self:changeToFieldWork() - end - elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED then - local fillLevel = self.combineController:getFillLevel() - if fillLevel < 0.01 then - self:debug('Unloading finished after fieldwork ended, end course') - AIDriveStrategyFieldWorkCourse.finishFieldWork(self) - else - self:setMaxSpeed(0) - end - elseif self.unloadState == self.states.WAITING_FOR_UNLOADER_TO_LEAVE then - self:setMaxSpeed(0) - -- TODO: instead of just wait a few seconds we could check if the unloader has actually left - if self.waitingForUnloaderSince + 5000 < g_currentMission.time then - if self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK then - local pullBackReturnCourse = self.pathfinderController:findAnalyticPathFromVehicleToGoal( - self.positionToContinueAfterPullback, self:getAllowReversePathfinding()) - if pullBackReturnCourse then - self.unloadState = self.states.RETURNING_FROM_PULL_BACK - self:debug('Unloading finished, returning to fieldwork on return course') - self.ppc:setShortLookaheadDistance() - self:startCourse(pullBackReturnCourse, 1) - self:rememberCourse(self.courseAfterPullBack, self.ixAfterPullBack) - else - self:debug('Unloading finished, returning to fieldwork directly') - self:startCourse(self.courseAfterPullBack, self.ixAfterPullBack) - self.ppc:setNormalLookaheadDistance() - self:changeToFieldWork() - end - elseif self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_IN_POCKET then - self:debug('Unloading in pocket finished, returning to fieldwork') - self.fillLevelFullPercentage = self.normalFillLevelFullPercentage - self:changeToFieldWork() - elseif self.stateBeforeWaitingForUnloaderToLeave == self.states.UNLOADING_BEFORE_STARTING_NEXT_ROW then - self:debug('Unloading before next row finished, returning to fieldwork') - self:changeToFieldWork() - elseif self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_ON_FIELD then - self:debug('Unloading on field finished, returning to fieldwork') - self:changeToFieldWork() - else - self:debug('Unloading finished, previous state not known, returning to fieldwork') - self:changeToFieldWork() - end - end - elseif self:isUnloadStateOneOf(self.drivingToSelfUnloadStates) then - if self:isCloseToCourseEnd(25) then - -- slow down towards the end of the course, near the trailer - self:setMaxSpeed(math.min(10, 0.5 * self.settings.fieldSpeed:getValue())) - -- we'll be very close to the tractor/trailer, don't stop too soon - self.proximityController:setTemporaryStopThreshold(self.proximityStopThresholdSelfUnload, 3000) - if g_vehicleConfigurations:getRecursively(self.vehicle, 'openPipeEarly') then - if not self.pipeController:isPipeOpen() then - self:debug('Opening pipe early to not crash it into the trailer') - self.pipeController:openPipe() - end - self.forcePipeOpen:set(true, 1000) - end - else - self:setMaxSpeed(self.settings.fieldSpeed:getValue()) - end - elseif self.unloadState == self.states.SELF_UNLOADING_WAITING_FOR_DISCHARGE then - self:setMaxSpeed(0) - self:debugSparse('Waiting for the self unloading to start') - if self:isDischarging() then - self.unloadState = self.states.SELF_UNLOADING - end - elseif self.unloadState == self.states.SELF_UNLOADING then - self:setMaxSpeed(0) - if self:isUnloadFinished() then - if not self:continueSelfUnloadToNextTrailer() then - self:debug('Self unloading finished, returning to fieldwork') - self:returnToFieldworkAfterSelfUnload() - end - end - elseif self.unloadState == self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE then - self:setMaxSpeed(0) - self:debugSparse('Fieldwork ended, waiting for the self unloading to start') - if self:isDischarging() then - self.unloadState = self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED - end - elseif self.unloadState == self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED then - self:setMaxSpeed(0) - if self:isUnloadFinished() then - if not self:continueSelfUnloadToNextTrailer() then - self:debug('Self unloading finished after fieldwork ended, finishing fieldwork') - AIDriveStrategyFieldWorkCourse.finishFieldWork(self) - end - end - elseif self.unloadState == self.states.RETURNING_FROM_SELF_UNLOAD then - if self:isCloseToCourseStart(25) then - self:setMaxSpeed(0.5 * self.settings.fieldSpeed:getValue()) - -- we'll be very close to the tractor/trailer, don't stop too soon - self.proximityController:setTemporaryStopThreshold(self.proximityStopThresholdSelfUnload, 3000) - else - self:setMaxSpeed(self.settings.fieldSpeed:getValue()) - end - -- apply the work starter's speed limit (when approaching the work start) - local _, _, _, maxSpeed = self.workStarter:getDriveData() - if maxSpeed ~= nil then - self:setMaxSpeed(maxSpeed) - end - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- Event listeners ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyCombineCourse:onWaypointPassed(ix, course) - if self.state == self.states.UNLOADING_ON_FIELD and - (self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD or - self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW or - self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED or - self.unloadState == self.states.RETURNING_FROM_SELF_UNLOAD) then - -- nothing to do while driving to unload and back - return AIDriveStrategyFieldWorkCourse.onWaypointPassed(self, ix, course) - end - - self:checkFruit() - - -- make sure we start making a pocket while we still have some fill capacity left as we'll be - -- harvesting fruit while making the pocket unless we have self unload turned on - if self:shouldMakePocket() and not self.settings.selfUnload:getValue() then - self.fillLevelFullPercentage = self.pocketFillLevelFullPercentage - end - - local isOnHeadland = self.course:isOnHeadland(ix) - self.combineController:updateStrawSwath(isOnHeadland) - - if self.state == self.states.WORKING then - if not self:alwaysNeedsUnloader() then - self:estimateDistanceUntilFull(ix) - self:callUnloaderWhenNeeded() - end - end - - if self.returnedFromPocketIx and self.returnedFromPocketIx == ix then - -- back to normal look ahead distance for PPC, no tight turns are needed anymore - self:debug('Reset PPC to normal lookahead distance') - self.ppc:setNormalLookaheadDistance() - end - AIDriveStrategyFieldWorkCourse.onWaypointPassed(self, ix, course) -end - ---- Called when the last waypoint of a course is passed -function AIDriveStrategyCombineCourse:onLastWaypointPassed() - local fillLevel = self.combineController:getFillLevel() - if self.state == self.states.UNLOADING_ON_FIELD then - if self.unloadState == self.states.RETURNING_FROM_PULL_BACK then - self:debug('Pull back finished, returning to fieldwork') - self.ppc:setNormalLookaheadDistance() - self:startRememberedCourse() - self:changeToFieldWork() - elseif self.unloadState == self.states.RETURNING_FROM_SELF_UNLOAD then - self:debug('Back from self unload, returning to fieldwork') - self.workStarter:onLastWaypoint() - elseif self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET then - self:debug('Reversed, now start making a pocket') - self:lowerImplements() - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.states.MAKING_POCKET - -- offset the main fieldwork course and start on it - self.aiOffsetX = math.min(self.pullBackRightSideOffset, self:getWorkWidth()) - self:startRememberedCourse() - elseif self.unloadState == self.states.PULLING_BACK_FOR_UNLOAD then - -- pulled back, now wait for unload - self.unloadState = self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK - self:debug('Pulled back, now wait for unload') - elseif self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD or - self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW then - self:debug('Self unloading point reached, fill level %.1f, waiting for unload to start.', fillLevel) - self.unloadState = self.states.SELF_UNLOADING_WAITING_FOR_DISCHARGE - elseif self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED then - self:debug('Self unloading point reached after fieldwork ended, fill level %.1f, waiting for unload to start.', fillLevel) - self.unloadState = self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE - end - elseif self.state == self.states.WORKING and fillLevel > 0 then - -- reset offset we used for the course ending to not miss anything - self.aiOffsetZ = 0 - if self.settings.selfUnload:getValue() and - self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED) then - self:debug('Start self unload after fieldwork ended') - else - -- let AutoDrive know we are done and can unload - self:debug('Fieldwork done, fill level is %.1f, now waiting to be unloaded.', fillLevel) - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED - end - else - AIDriveStrategyFieldWorkCourse.onLastWaypointPassed(self) - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- State changes ------------------------------------------------------------------------------------------------------------------------ - ---- Some of our turns need a short look ahead distance, make sure we restore the normal after the turn -function AIDriveStrategyCombineCourse:resumeFieldworkAfterTurn(ix) - self.ppc:setNormalLookaheadDistance() - AIDriveStrategyFieldWorkCourse.resumeFieldworkAfterTurn(self, ix) -end - ---- Stop, raise the header (if needed) and then, and only then change to the new states. This is to avoid leaving ---- unharvested spots due to the header being lifted while the vehicle is still in motion. -function AIDriveStrategyCombineCourse:stopForUnload(newUnloadStateAfterStopped, raiseHeaderAfterStopped) - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.states.STOPPING_FOR_UNLOAD - self.newUnloadStateAfterStopped = newUnloadStateAfterStopped - self.raiseHeaderAfterStopped = raiseHeaderAfterStopped -end - ---- Start waiting for the implements to lower --- getCanAIVehicleContinueWork() seems to return false when the implement being lowered/raised (moving) but --- true otherwise. Due to some timing issues it may return true just after we started lowering it --- so the harvester misses some fruit when restarting after unloading ended -function AIDriveStrategyCombineCourse:startWaitingForLower() - -- force delayed lower - self.state = self.states.WAITING_FOR_LOWER_DELAYED - self:debug('waiting for lower delayed') -end - -function AIDriveStrategyCombineCourse:changeToUnloadOnField() - self:checkFruit() - -- TODO: check around turn maneuvers we may not want to pull back before a turn - self:rememberCourse(self.fieldWorkCourse, self:getBestWaypointToContinueFieldWork()) - if self.settings.selfUnload:getValue() and self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD) then - self:debug('Start self unload') - elseif self.settings.avoidFruit:getValue() and self:shouldMakePocket() then - -- I'm on the edge of the field or fruit is on both sides, make a pocket on the right side and wait there for the unload - local pocketCourse, nextIx = self:createPocketCourse() - if pocketCourse then - self:debug('No room to the left, making a pocket for unload') - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.states.REVERSING_TO_MAKE_A_POCKET - -- place a marker at the current pipe position, we'll use this to find out where to stop - -- making the pocket - self.pocketHelperNode:placeAtNode(Markers.getFrontMarkerNode(self.vehicle), 1, 0, 0, 0) - self:rememberCourse(self.course, nextIx) - -- raise header for reversing - self:raiseImplements() - self:startCourse(pocketCourse, 1) - -- tighter turns - self.ppc:setShortLookaheadDistance() - else - self:startWaitingForUnloadWhenFull() - end - elseif self.settings.avoidFruit:getValue() and self:shouldPullBack() then - -- is our pipe in the fruit? (assuming pipe is on the left side) - local pullBackCourse = self:createPullBackCourse() - if pullBackCourse then - self:debug('Pipe in fruit, pulling back to make room for unloading') - self:stopForUnload(self.states.PULLING_BACK_FOR_UNLOAD, true) - self.courseAfterPullBack = self.course - self.ixAfterPullBack = self.ppc:getLastPassedWaypointIx() or self.ppc:getCurrentWaypointIx() - -- remember where to start after the pull back, a bit further back just in case. - self.positionToContinueAfterPullback = PathfinderUtil.getVehiclePositionAsState3D(self.vehicle, 0, -2) - -- tighter turns - self.ppc:setShortLookaheadDistance() - self:startCourse(pullBackCourse, 1) - else - self:startWaitingForUnloadWhenFull() - end - else - self:startWaitingForUnloadWhenFull() - end -end - -function AIDriveStrategyCombineCourse:startWaitingForUnloadWhenFull() - self:stopForUnload(self.states.WAITING_FOR_UNLOAD_ON_FIELD, true) - self:debug('Waiting for the unloader on the field') - g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, - string.format(g_i18n:getText("ai_messageErrorGrainTankIsFull"), self.vehicle:getCurrentHelper().name)) -end - -function AIDriveStrategyCombineCourse:startWaitingForUnloadBeforeNextRow() - self:debug('Waiting for unload before starting the next row') - self.waitingForUnloaderAtEndOfRow:set(true, 30000) - self:stopForUnload(self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW, true) -end - ---- The unloader may call this repeatedly to confirm that the rendezvous still stands, making sure the ---- combine won't give up and keeps waiting -function AIDriveStrategyCombineCourse:reconfirmRendezvous() - if self.waitingForUnloaderAtEndOfRow:get() then - -- ok, we'll wait another 30 seconds - self.waitingForUnloaderAtEndOfRow:set(true, 30000) - end -end - -function AIDriveStrategyCombineCourse:isUnloadFinished() - local discharging = true - local dischargingNow = self:isDischarging() - --wait for 10 frames before taking discharging as false - if not dischargingNow then - self.notDischargingSinceLoopIndex = self.notDischargingSinceLoopIndex and self.notDischargingSinceLoopIndex or g_updateLoopIndex - if g_updateLoopIndex - self.notDischargingSinceLoopIndex > 10 then - discharging = false - end - else - self.notDischargingSinceLoopIndex = nil - end - local fillLevel = self.combineController:getFillLevel() - -- unload is done when fill levels are ok (not full) and not discharging anymore (either because we - -- are empty or the trailer is full) - return (not self:isFull() and not discharging) or fillLevel < 0.1 -end - -function AIDriveStrategyCombineCourse:isFull(fillLevelFullPercentage) - local fillLevelPercentage = self.combineController:getFillLevelPercentage() - if fillLevelPercentage >= 100 or fillLevelPercentage > (fillLevelFullPercentage or self.fillLevelFullPercentage) then - self:debugSparse('Full or refillUntilPct reached: %.2f', fillLevelPercentage) - return true - end - if fillLevelPercentage < 0.1 then - self.stopDisabledAfterEmpty:set(true, 2000) - end - return false -end - -function AIDriveStrategyCombineCourse:shouldMakePocket() - if self:alwaysNeedsUnloader() then - self:debug('Always need unloader so not making a pocket') - return false - end - if self.fruitLeft > 0.75 and self.fruitRight > 0.75 then - -- fruit both sides - return true - elseif self:isPipeOnLeft() then - -- on the outermost headland clockwise (field edge) - return not self.fieldOnLeft - else - -- on the outermost headland counterclockwise (field edge) - return not self.fieldOnRight - end -end - -function AIDriveStrategyCombineCourse:shouldPullBack() - if self:alwaysNeedsUnloader() then - self:debug('Always need unloader so not making a pocket') - return false - else - return self:isPipeInFruit() - end -end - -function AIDriveStrategyCombineCourse:isPipeOnLeft() - return self.pipeController:isPipeOnTheLeftSide() -end - -function AIDriveStrategyCombineCourse:isPipeInFruit() - -- is our pipe in the fruit? - if self:isPipeOnLeft() then - return self.fruitLeft > self.fruitRight - else - return self.fruitLeft < self.fruitRight - end -end - ----@return number, number the amount of fruit on left/right side of the combine. 0 is no fruit, and it is probably ---- a number up to 100, indicating how much fruit is there. -function AIDriveStrategyCombineCourse:getFruitAtSides() - return self.fruitLeft, self.fruitRight -end - -function AIDriveStrategyCombineCourse:checkFruit() - -- getValidityOfTurnDirections() wants to have the vehicle.aiDriveDirection, so get that here. - local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) - local length = MathUtil.vector2Length(dx, dz) - dx = dx / length - dz = dz / length - self.vehicle.aiDriveDirection = { dx, dz } - -- getValidityOfTurnDirections works only if all AI Implements have aiMarkers. Since - -- we make all Cutters AI implements, even the ones which do not have AI markers (such as the - -- chopper pickups which do not work with the Giants helper) we have to make sure we don't call - -- getValidityOfTurnDirections for those - if self.notAllImplementsHaveAiMarkers then - self.fruitLeft, self.fruitRight = 0, 0 - else - self.fruitLeft, self.fruitRight = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle) - end - local workWidth = self:getWorkWidth() - local x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), workWidth, 0, 0) - self.fieldOnLeft = CpFieldUtil.isOnField(x, z) - x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), -workWidth, 0, 0) - self.fieldOnRight = CpFieldUtil.isOnField(x, z) - self:debug('Fruit left: %.2f right %.2f, field on left %s, right %s', - self.fruitLeft, self.fruitRight, tostring(self.fieldOnLeft), tostring(self.fieldOnRight)) -end - ---- Estimate the waypoint where the combine will be full/reach the fill level where it should start unloading ---- (waypointIxWhenFull/waypointIxWhenCallUnloader), based on the current harvest rate -function AIDriveStrategyCombineCourse:estimateDistanceUntilFull(ix) - -- calculate fill rate so the combine driver knows if it can make the next row without unloading - local fillLevel = self.combineController:getFillLevel() - local capacity = self.combineController:getCapacity() - if ix > 1 then - local dToNext = self.course:getDistanceToNextWaypoint(ix - 1) - if self.fillLevelAtLastWaypoint and self.fillLevelAtLastWaypoint > 0 and self.fillLevelAtLastWaypoint <= fillLevel then - local litersPerMeter = (fillLevel - self.fillLevelAtLastWaypoint) / dToNext - -- make sure it won't end up being inf - local litersPerSecond = math.min(1000, (fillLevel - self.fillLevelAtLastWaypoint) / - ((g_currentMission.time - (self.fillLevelLastCheckedTime or g_currentMission.time)) / 1000)) - -- smooth everything a bit, also ignore 0 - self.litersPerMeter = litersPerMeter > 0 and ((self.litersPerMeter + litersPerMeter) / 2) or self.litersPerMeter - self.litersPerSecond = litersPerSecond > 0 and ((self.litersPerSecond + litersPerSecond) / 2) or self.litersPerSecond - else - -- no history yet, so make sure we don't end up with some unrealistic numbers - self.waypointIxWhenFull = nil - self.litersPerMeter = 0 - self.litersPerSecond = 0 - end - self:debug('Fill rate is %.1f l/m, %.1f l/s (fill level %.1f, last %.1f, dToNext = %.1f)', - self.litersPerMeter, self.litersPerSecond, fillLevel, self.fillLevelAtLastWaypoint, dToNext) - self.fillLevelLastCheckedTime = g_currentMission.time - self.fillLevelAtLastWaypoint = fillLevel - end - local litersUntilFull = capacity - fillLevel - local dUntilFull = CpMathUtil.divide(litersUntilFull, self.litersPerMeter) - local litersUntilCallUnloader = capacity * self.settings.callUnloaderPercent:getValue() / 100 - fillLevel - local dUntilCallUnloader = CpMathUtil.divide(litersUntilCallUnloader, self.litersPerMeter) - self.waypointIxWhenFull = self.course:getNextWaypointIxWithinDistance(ix, dUntilFull) or self.course:getNumberOfWaypoints() - local wpDistance - self.waypointIxWhenCallUnloader, wpDistance = self.course:getNextWaypointIxWithinDistance(ix, dUntilCallUnloader) - self:debug('Will be full at waypoint %d, fill level %d at waypoint %d (current waypoint %d), %.1f m and %.1f l until call (currently %.1f l), wp distance %.1f', - self.waypointIxWhenFull or -1, self.settings.callUnloaderPercent:getValue(), self.waypointIxWhenCallUnloader or -1, - self.course:getCurrentWaypointIx(), dUntilCallUnloader, litersUntilCallUnloader, fillLevel, wpDistance) -end - -function AIDriveStrategyCombineCourse:shouldWaitAtEndOfRow() - if self.settings.selfUnload:getValue() then - -- don't wait for anyone when self unloading - return false - end - local nextRowStartIx = self.course:getNextRowStartIx(self.ppc:getRelevantWaypointIx()) - local lastPassedWaypointIx = self.ppc:getLastPassedWaypointIx() or self.ppc:getRelevantWaypointIx() - local distanceToNextTurn = self.course:getDistanceToNextTurn(lastPassedWaypointIx) or math.huge - local closeToTurn = distanceToNextTurn < AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow - -- If close to the end of the row and the pipe would be in the fruit after the turn, and our fill level is high, - -- we always wait here for an unloader, regardless of having a rendezvous or not (unless we have our pipe in fruit here) - if nextRowStartIx and closeToTurn and - self:isPipeInFruitAt(nextRowStartIx) and - self:isFull(AIDriveStrategyCombineCourse.waitForUnloadAtEndOfRowFillLevelThreshold) then - self:checkFruit() - if not self:isPipeInFruit() then - self:debug('shouldWaitAtEndOfRow: Closer than %.1f m to a turn, pipe would be in fruit after turn at %d, fill level over %.1f', - AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow, nextRowStartIx, - AIDriveStrategyCombineCourse.waitForUnloadAtEndOfRowFillLevelThreshold) - return true - end - end - -- Or, if we are close to the turn and have a rendezvous waypoint before the turn - if nextRowStartIx and closeToTurn and - self.unloaderRendezvousWaypointIx and - nextRowStartIx > self.unloaderRendezvousWaypointIx then - self:debug('shouldWaitAtEndOfRow: Closer than %.1f m to a turn and rendezvous waypoint %d is before the turn, waiting for the unloader here', - AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow, self.unloaderRendezvousWaypointIx) - return true - end -end - ------------------------------------------------------------------------------------------------------------------------- ---- Unloader handling ---- ---- We need to answer the following questions: ---- 1. When to call the unloader? ---- 2. Where to meet the unloader? ---- 3. Which unloader to call? ---- ---- To question #3, we check the distance between the unloader and the target (combine and a rendezvous point) and based ---- on the unloader's distance to the target and its fill level, we calculate a score and call the unloader with ---- the best score. ---- ---- To questions #1 and #2, we have to cases: ---- ---- The easy case ---- ---- If the combine is stopped for unloading (either 100% full, or made a pocket or finished the course, etc.), ---- the unloader must drive to the combine immediately. ---- ---- The difficult case ---- ---- The combine is still harvesting, and periodically calculates the waypoint/distance where it will reach the fill ---- level threshold configured. This is the fill level when we want the unload process to start, so the unloader ---- is supposed to meet the combine at that (rendezvous) spot. This answers #2, where to meet the unloader. ---- But when to call the unloader? When it will reach the rendezvous point about the same time (or a little earlier) ---- as the combine reaches it. So we ask around all unloaders to figure out what their estimated time en-route (ETE) ---- to the rendezvous waypoint would be. Then pick the one with the shortest ETE, and if its ETE (plus some reserve) ---- is more than the combine's ETE, call it. ---- ------------------------------------------------------------------------------------------------------------------------- -function AIDriveStrategyCombineCourse:callUnloaderWhenNeeded() - if not self.timeToCallUnloader:get() then - return - end - -- check back again in a few seconds - self.timeToCallUnloader:set(false, 3000) - - if self.unloader:get() then - self:debug('callUnloaderWhenNeeded: already has an unloader assigned (%s)', CpUtil.getName(self.unloader:get())) - return - end - - local bestUnloader, bestEte - if self:isWaitingForUnload() then - self:debug('callUnloaderWhenNeeded: stopped, need unloader here') - bestUnloader, _ = self:findUnloader(self.vehicle, nil) - if bestUnloader then - bestUnloader:getCpDriveStrategy():call(self.vehicle, nil) - end - else - if not self.waypointIxWhenCallUnloader then - self:debug('callUnloaderWhenNeeded: don\'t know yet where to meet the unloader') - return - end - -- Find a good waypoint to unload, as the calculated one may have issues, like pipe would be in the fruit, - -- or in a turn, etc. - -- TODO: isPipeInFruitAllowed - local tentativeRendezvousWaypointIx = self:findBestWaypointToUnload(self.waypointIxWhenCallUnloader, false) - if not tentativeRendezvousWaypointIx then - self:debug('callUnloaderWhenNeeded: can\'t find a good waypoint to meet the unloader') - return - end - bestUnloader, bestEte = self:findUnloader(nil, self.course:getWaypoint(tentativeRendezvousWaypointIx)) - -- getSpeedLimit() may return math.huge (inf), when turning for example, not sure why, and that throws off - -- our ETE calculation - if bestUnloader and self.vehicle:getSpeedLimit(true) < 100 then - local dToUnloadWaypoint = self.course:getDistanceBetweenWaypoints(tentativeRendezvousWaypointIx, - self.course:getCurrentWaypointIx()) - local myEte = dToUnloadWaypoint / (self.vehicle:getSpeedLimit(true) / 3.6) - self:debug('callUnloaderWhenNeeded: best unloader ETE at waypoint %d %.1fs, my ETE %.1fs', - tentativeRendezvousWaypointIx, bestEte, myEte) - if bestEte - 5 > myEte then - -- I'll be at the rendezvous a lot earlier than the unloader which will almost certainly result in the - -- cancellation of the rendezvous. - -- So, set something up further away, with better chances, - -- using the unloader's ETE, knowing that 1) that ETE is for the current rendezvous point, 2) there - -- may be another unloader selected for that waypoint - local dToTentativeRendezvousWaypoint = bestEte * (self.vehicle:getSpeedLimit(true) / 3.6) - self:debug('callUnloaderWhenNeeded: too close to rendezvous waypoint, trying move it %.1fm', - dToTentativeRendezvousWaypoint) - tentativeRendezvousWaypointIx = self.course:getNextWaypointIxWithinDistance( - self.course:getCurrentWaypointIx(), dToTentativeRendezvousWaypoint) - if tentativeRendezvousWaypointIx then - bestUnloader, bestEte = self:findUnloader(nil, self.course:getWaypoint(tentativeRendezvousWaypointIx)) - if bestUnloader then - self:callUnloader(bestUnloader, tentativeRendezvousWaypointIx, bestEte) - end - else - self:debug('callUnloaderWhenNeeded: still can\'t find a good waypoint to meet the unloader') - end - elseif bestEte + 5 > myEte then - -- do not call too early (like minutes before we get there), only when it needs at least as - -- much time to get there as the combine (-5 seconds) - self:callUnloader(bestUnloader, tentativeRendezvousWaypointIx, bestEte) - end - end - end -end - -function AIDriveStrategyCombineCourse:callUnloader(bestUnloader, tentativeRendezvousWaypointIx, bestEte) - if bestUnloader:getCpDriveStrategy():call(self.vehicle, - self.course:getWaypoint(tentativeRendezvousWaypointIx)) then - self.unloaderToRendezvous:set(bestUnloader, 1000 * (bestEte + 30)) - self.unloaderRendezvousWaypointIx = tentativeRendezvousWaypointIx - self:debug('callUnloaderWhenNeeded: harvesting, unloader accepted rendezvous at waypoint %d', self.unloaderRendezvousWaypointIx) - else - self:debug('callUnloaderWhenNeeded: harvesting, unloader rejected rendezvous at waypoint %d', tentativeRendezvousWaypointIx) - end -end - ----@param vehicle table ----@return boolean true if vehicle is an active Courseplay controlled combine/harvester -function AIDriveStrategyCombineCourse.isActiveCpCombine(vehicle) - if not (vehicle.getIsCpActive and vehicle:getIsCpActive()) then - -- not driven by CP - return false - end - local driveStrategy = vehicle.getCpDriveStrategy and vehicle:getCpDriveStrategy() - return driveStrategy and driveStrategy.callUnloader ~= nil -end - ---- Find an unloader to drive to the target, which may either be the combine itself (when stopped and waiting for unload) ---- or a waypoint which the combine will reach in the future. Combine and waypoint parameters are mutually exclusive. ----@param combine table the combine vehicle if we need the unloader come to the combine, otherwise nil ----@param waypoint Waypoint the waypoint where the unloader should meet the combine, otherwise nil. ----@return table, number the best fitting unloader or nil, the estimated time en-route for the unloader to reach the ---- target (combine or waypoint) -function AIDriveStrategyCombineCourse:findUnloader(combine, waypoint) - local bestScore = -math.huge - local bestUnloader, bestEte - for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do - if AIDriveStrategyUnloadCombine.isActiveCpCombineUnloader(vehicle) then - local x, _, z = getWorldTranslation(self.vehicle.rootNode) - ---@type AIDriveStrategyUnloadCombine - local driveStrategy = vehicle:getCpDriveStrategy() - -- look a bit outside of the field as the harvester's root node may be just off the field (like in a turn, - -- or when starting. - if driveStrategy:isServingPosition(x, z, 10) then - local unloaderFillLevelPercentage = driveStrategy:getFillLevelPercentage() - if driveStrategy:isAllowedToBeCalled() and unloaderFillLevelPercentage < 99 then - local unloaderDistance, unloaderEte - if combine then - -- if already stopped, we want the unloader to come to us - unloaderDistance, unloaderEte = driveStrategy:getDistanceAndEteToVehicle(combine) - elseif self.waypointIxWhenCallUnloader then - -- if still going, we want the unloader to meet us at the waypoint - unloaderDistance, unloaderEte = driveStrategy:getDistanceAndEteToWaypoint(waypoint) - end - local score = unloaderFillLevelPercentage - 0.1 * unloaderDistance - self:debug('findUnloader: %s idle on my field, fill level %.1f, distance %.1f, ETE %.1f, score %.1f)', - CpUtil.getName(vehicle), unloaderFillLevelPercentage, unloaderDistance, unloaderEte, score) - if score > bestScore then - bestUnloader = vehicle - bestScore = score - bestEte = unloaderEte - end - else - self:debug('findUnloader: %s serving my field but already busy', CpUtil.getName(vehicle)) - end - else - self:debug('findUnloader: %s is not serving my field', CpUtil.getName(vehicle)) - end - end - end - if bestUnloader then - self:debug('findUnloader: best unloader is %s (score %.1f, ETE %.1f)', - CpUtil.getName(bestUnloader), bestScore, bestEte) - return bestUnloader, bestEte - else - self:debug('findUnloader: no idle unloader found') - end -end - -function AIDriveStrategyCombineCourse:checkRendezvous() - if self.unloaderToRendezvous:get() then - local lastPassedWaypointIx = self.ppc:getLastPassedWaypointIx() or self.ppc:getRelevantWaypointIx() - local d = self.course:getDistanceBetweenWaypoints(lastPassedWaypointIx, self.unloaderRendezvousWaypointIx) - if d < 10 then - self:debugSparse('Slow down around the unloader rendezvous waypoint %d to let the unloader catch up', - self.unloaderRendezvousWaypointIx) - self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue() / 2) - elseif lastPassedWaypointIx > self.unloaderRendezvousWaypointIx then - -- past the rendezvous waypoint - self:debug('Unloader missed the rendezvous at %d', self.unloaderRendezvousWaypointIx) - local unloaderWhoDidNotShowUp = self.unloaderToRendezvous:get() - -- need to call this before onMissedRendezvous as the unloader will call back to set up a new rendezvous - -- and we don't want to cancel that right away - self:cancelRendezvous() - unloaderWhoDidNotShowUp:getCpDriveStrategy():onMissedRendezvous(self.vehicle) - end - if self:isDischarging() then - self:debug('Discharging, cancelling unloader rendezvous') - self:cancelRendezvous() - end - end -end - -function AIDriveStrategyCombineCourse:hasRendezvousWith(unloader) - return self.unloaderToRendezvous:get() == unloader -end - -function AIDriveStrategyCombineCourse:cancelRendezvous() - local unloader = self.unloaderToRendezvous:get() - self:debug('Rendezvous with %s at waypoint %d cancelled', - unloader and CpUtil.getName(self.unloaderToRendezvous:get() or 'N/A'), - self.unloaderRendezvousWaypointIx or -1) - self.unloaderRendezvousWaypointIx = nil - self.unloaderToRendezvous:reset() -end - ---- Before the unloader asks for a rendezvous (which may result in a lengthy pathfinding to figure out ---- the distance), it should check if the combine is willing to rendezvous. -function AIDriveStrategyCombineCourse:isWillingToRendezvous() - if self.state ~= self.states.WORKING then - self:debug('not harvesting, will not rendezvous') - return nil - elseif not self.settings.unloadOnFirstHeadland:getValue() and - self.course:isOnHeadland(self.course:getCurrentWaypointIx(), 1) then - self:debug('on first headland and unload not allowed on first headland, will not rendezvous') - return nil - end - return true -end - ---- An area where the combine is expected to perform a turn between now and the rendezvous waypoint ----@return Waypoint a waypoint, the center of the maneuvering area ----@return number radius around the waypoint, defining a circular area -function AIDriveStrategyCombineCourse:getTurnArea() - if self.unloaderRendezvousWaypointIx then - for ix = self.course:getCurrentWaypointIx(), self.unloaderRendezvousWaypointIx do - if self.course:isTurnEndAtIx(ix) then - return self.course:getWaypoint(ix), self.turningRadius * 3 - end - end - end -end - -function AIDriveStrategyCombineCourse:canUnloadWhileMovingAtWaypoint(ix) - if self:isPipeInFruitAt(ix) then - self:debug('pipe would be in fruit at the planned rendezvous waypoint %d', ix) - return false - end - if not self.settings.unloadOnFirstHeadland:getValue() and self.course:isOnHeadland(ix, 1) then - self:debug('planned rendezvous waypoint %d is on first headland, no unloading of moving combine there', ix) - return false - end - return true -end - -function AIDriveStrategyCombineCourse:checkFruitAtNode(node, offsetX, offsetZ) - local x, _, z = localToWorld(node, offsetX, 0, offsetZ or 0) - local hasFruit, fruitValue = PathfinderUtil.hasFruit(x, z, 1, 1) - return hasFruit, fruitValue -end - ---- Is pipe in fruit according to the current field harvest state at waypoint? -function AIDriveStrategyCombineCourse:isPipeInFruitAtWaypointNow(course, ix) - if not self.storage.fruitCheckHelperWpNode then - self.storage.fruitCheckHelperWpNode = WaypointNode(CpUtil.getName(self.vehicle) .. 'fruitCheckHelperWpNode') - end - self.storage.fruitCheckHelperWpNode:setToWaypoint(course, ix) - local hasFruit, fruitValue = self:checkFruitAtNode(self.storage.fruitCheckHelperWpNode.node, self.pipeController:getPipeOffsetX()) - self:debug('at waypoint %d pipe in fruit %s (fruitValue %.1f)', ix, tostring(hasFruit), fruitValue or 0) - return hasFruit, fruitValue -end - -function AIDriveStrategyCombineCourse:isPipeInFruitAt(ix) - if self.course:getMultiTools() > 1 then - -- we don't have a reliable pipe in fruit map for multitools, so just check - -- if there is fruit at the waypoint now, to be on the safe side - return self:isPipeInFruitAtWaypointNow(self.course, ix) - else - return self.course:isPipeInFruitAt(ix) - end -end - ---- Find the best waypoint to unload. ----@param ix number waypoint index we want to start unloading, either because that's about where ---- we'll rendezvous the unloader or we'll be full there. ----@return number best waypoint to unload, ix may be adjusted to make sure it isn't in a turn or ---- the fruit is not in the pipe. -function AIDriveStrategyCombineCourse:findBestWaypointToUnload(ix, isPipeInFruitAllowed) - if self.course:isOnHeadland(ix) then - return self:findBestWaypointToUnloadOnHeadland(ix) - else - return self:findBestWaypointToUnloadOnUpDownRows(ix, isPipeInFruitAllowed) - end -end - -function AIDriveStrategyCombineCourse:findBestWaypointToUnloadOnHeadland(ix) - if not self.settings.unloadOnFirstHeadland:getValue() and - self.course:isOnHeadland(ix, 1) then - self:debug('planned rendezvous waypoint %d is on first headland, no unloading of moving combine there', ix) - return nil - end - if self.course:isTurnStartAtIx(ix) then - -- on the headland, use the wp after the turn, the one before may be very far, especially on a - -- transition from headland to up/down rows. - return ix + 1 - else - return ix - end -end - ---- We calculated a waypoint to meet the unloader (either because it asked for it or we think we'll need ---- to unload. Now make sure that this location is not around a turn or the pipe isn't in the fruit by ---- trying to move it up or down a bit. If that's not possible, just leave it and see what happens :) -function AIDriveStrategyCombineCourse:findBestWaypointToUnloadOnUpDownRows(ix, isPipeInFruitAllowed) - local dToNextTurn = self.course:getDistanceToNextTurn(ix) or math.huge - local lRow, ixAtRowStart = self.course:getRowLength(ix) - local pipeInFruit = self:isPipeInFruitAt(ix) - local currentIx = self.course:getCurrentWaypointIx() - local newWpIx = ix - self:debug('Looking for a waypoint to unload around %d on up/down row, pipe in fruit %s, dToNextTurn: %d m, lRow = %d m', - ix, tostring(pipeInFruit), dToNextTurn, lRow or 0) - if pipeInFruit and not isPipeInFruitAllowed then - --if the pipe is in fruit AND the user selects 'avoid fruit' - if ixAtRowStart then - if self.course:getMultiTools() > 1 then - self:debug('Pipe may be in fruit at waypoint %d, we have no reliable information as multitool active, rejecting rendezvous', ix) - newWpIx = nil - elseif ixAtRowStart > currentIx then - -- have not started the previous row yet - self:debug('Pipe would be in fruit at waypoint %d. Check previous row', ix) - pipeInFruit, _ = self:isPipeInFruitAt(ixAtRowStart - 2) -- wp before the turn start - if not pipeInFruit then - local lPreviousRow, ixAtPreviousRowStart = self.course:getRowLength(ixAtRowStart - 1) - self:debug('pipe not in fruit in the previous row (%d m, ending at wp %d), rendezvous at %d', - lPreviousRow, ixAtRowStart - 1, newWpIx) - newWpIx = math.max(ixAtRowStart - 3, ixAtPreviousRowStart, currentIx) - else - self:debug('Pipe in fruit in previous row too, rejecting rendezvous') - newWpIx = nil - end - else - -- previous row already started. Could check next row but that means the rendezvous would be after - -- the combine turns, and we'd be in the way during the turn, so rather not worry about the next row - -- until the combine gets there. - self:debug('Pipe would be in fruit at waypoint %d. Previous row is already started, no rendezvous', ix) - newWpIx = nil - end - else - self:debug('Could not determine row length, rejecting rendezvous') - newWpIx = nil - end - else - if (pipeInFruit) then - self:debug('pipe would be in fruit at waypoint %d, acceptable for user', ix) - else - self:debug('pipe is not in fruit at %d. If it is towards the end of the row, bring it up a bit', ix) - end - -- so we'll have some distance for unloading - if ixAtRowStart and dToNextTurn < AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow then - local safeIx = self.course:getPreviousWaypointIxWithinDistance(ix, - AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow) - newWpIx = math.max(ixAtRowStart + 1, safeIx or -1, ix - 4, currentIx) - end - end - -- no better idea, just use the original estimated, making sure we avoid turn start waypoints - if newWpIx and self.course:isTurnStartAtIx(newWpIx) then - self:debug('Calculated rendezvous waypoint is at turn start, moving it up') - -- make sure it is not on the turn start waypoint - return math.max(newWpIx - 1, currentIx) - else - return newWpIx - end -end - ---- Create a temporary course to pull back to the right when the pipe is in the fruit so the tractor does not have --- to drive in the fruit to get under the pipe -function AIDriveStrategyCombineCourse:createPullBackCourse() - -- all we need is a waypoint on our right side towards the back - self.returnPoint = {} - self.returnPoint.x, _, self.returnPoint.z = getWorldTranslation(self.vehicle.rootNode) - - local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) - self.returnPoint.rotation = MathUtil.getYRotationFromDirection(dx, dz) - dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, -1) - - local x1, _, z1 = localToWorld(self.vehicle:getAIDirectionNode(), -self.pullBackRightSideOffset, 0, -self.pullBackDistanceStart) - local x2, _, z2 = localToWorld(self.vehicle:getAIDirectionNode(), -self.pullBackRightSideOffset, 0, -self.pullBackDistanceEnd) - -- both points must be on the field - if CpFieldUtil.isOnField(x1, z1) and CpFieldUtil.isOnField(x2, z2) then - - local referenceNode, debugText = AIUtil.getReverserNode(self.vehicle) - if referenceNode then - self:debug('Using %s to start pull back course', debugText) - else - referenceNode = AIUtil.getDirectionNode(self.vehicle) - self:debug('Using the direction node to start pull back course') - end - -- don't make this too complicated, just create a straight line on the left/right side (depending on - -- where the pipe is and rely on the PPC, no need for generating fancy curves - return Course.createFromNode(self.vehicle, referenceNode, - -self.pullBackRightSideOffset, 0, -self.pullBackDistanceEnd, -2, true) - else - self:debug("Pull back course would be outside of the field") - return nil - end -end - ---- Get the area the unloader should avoid when approaching the combine. ---- Main (and for now, only) use case is to prevent the unloader to cross in front of the combine after the ---- combine pulled back full with pipe in the fruit, making room for the unloader on its left side. ---- @return table, number, number, number, number node, xOffset, zOffset, width, length : the area to avoid is ---- a length x width m rectangle, the rectangle's bottom right corner (when looking from node) is at xOffset/zOffset ---- from node. -function AIDriveStrategyCombineCourse:getAreaToAvoid() - if self:isWaitingForUnloadAfterPulledBack() then - local xOffset = self:getWorkWidth() / 2 - local zOffset = 0 - local length = self.pullBackDistanceEnd - local width = self.pullBackRightSideOffset - return PathfinderUtil.NodeArea(AIUtil.getDirectionNode(self.vehicle), xOffset, zOffset, width, length) - end -end - ---- Create a temporary course to make a pocket in the fruit on the right (or left), so we can move into that pocket and ---- wait for the unload there. This way the unload tractor does not have to leave the field. ---- We create a temporary course to reverse back far enough. After that, we return to the main course but ---- set an offset to the right (or left) -function AIDriveStrategyCombineCourse:createPocketCourse() - local startIx = self.ppc:getLastPassedWaypointIx() or self.ppc:getCurrentWaypointIx() - -- find the waypoint we want to back up to - local backIx = self.course:getPreviousWaypointIxWithinDistance(startIx, self.pocketReverseDistance) - if not backIx then - return nil - end - -- this where we are back on track after returning from the pocket - self.returnedFromPocketIx = self.ppc:getCurrentWaypointIx() - self:debug('Backing up %.1f meters from waypoint %d to %d to make a pocket', self.pocketReverseDistance, startIx, backIx) - if startIx - backIx > 2 then - local pocketReverseWaypoints = {} - for i = startIx, backIx, -1 do - if self.course:isTurnStartAtIx(i) then - self:debug('There is a turn behind me at waypoint %d, no pocket', i) - return nil - end - local x, _, z = self.course:getWaypointPosition(i) - table.insert(pocketReverseWaypoints, { x = x, z = z, rev = true }) - end - return Course(self.vehicle, pocketReverseWaypoints, true), backIx + 1 - else - self:debug('Not enough waypoints behind me, no pocket') - return nil - end -end - ---- Only allow fuel save, if no trailer is under the pipe and we are waiting for unloading. -function AIDriveStrategyCombineCourse:isFuelSaveAllowed() - if self.pipeController:isFillableTrailerInRange() then - -- Disables Fuel save, when a trailer is under the pipe. - return false - end - --- Enables fuel save, while waiting for the rain to stop. - if self.combine:getIsThreshingDuringRain() then - return true - end - return self:isWaitingForUnload() or self:isChopperWaitingForUnloader() - or AIDriveStrategyCourse.isFuelSaveAllowed(self) -end - ---- Check if the vehicle should stop during a turn for example while it ---- is held for unloading or waiting for the straw swath to stop -function AIDriveStrategyCombineCourse:shouldHoldInTurnManeuver() - --- Hold during discharge - local discharging = self:isDischarging() and not self:alwaysNeedsUnloader() - local stillProcessingFruit = self:alwaysNeedsUnloader() and self:isProcessingFruit() - local isFinishingRow = self.aiTurn and self.aiTurn:isFinishingRow() - local waitForStraw = self.combineController:isDroppingStrawSwath() and not isFinishingRow and not self:isOnHeadland() - - self:debugSparse('Turn maneuver=> Autoaim: %s, discharging: %s, wait for straw: %s, straw swath active: %s, processing: %s, finishing row: %s', - tostring(self:hasAutoAimPipe()), tostring(discharging), tostring(waitForStraw), - tostring(self.combineController:isDroppingStrawSwath()), tostring(stillProcessingFruit), tostring(isFinishingRow)) - -- choppers don't hold, they need to keep moving through turns and actively harvesting - return (discharging or waitForStraw or stillProcessingFruit) and not self:hasAutoAimPipe() -end - ---- Should we return to the first point of the course after we are done? -function AIDriveStrategyCombineCourse:shouldReturnToFirstPoint() - -- Combines stay where they are after finishing work - -- TODO: call unload driver - return false -end - ---- Interface for the unloader and AutoDrive ----@return boolean true when the combine is waiting to be unloaded (and won't move until the unloader gets there) -function AIDriveStrategyCombineCourse:isWaitingForUnload() - return self.state == self.states.UNLOADING_ON_FIELD and - (self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD or - self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK or - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED or - self.unloadState == self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW) -end - ---- Interface for AutoDrive ----@return boolean true when the combine is waiting to be unloaded after it ended the course -function AIDriveStrategyCombineCourse:isWaitingForUnloadAfterCourseEnded() - return self.state == self.states.UNLOADING_ON_FIELD and - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED -end - -function AIDriveStrategyCombineCourse:isWaitingInPocket() - return self.state == self.states.UNLOADING_ON_FIELD and - self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET -end - ---- Interface for Mode 2 ----@return boolean true when the combine is waiting to after it pulled back. -function AIDriveStrategyCombineCourse:isWaitingForUnloadAfterPulledBack() - return self.state == self.states.UNLOADING_ON_FIELD and - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK -end - ----@return boolean the combine is about to turn -function AIDriveStrategyCombineCourse:isAboutToTurn() - if self.state == self.states.WORKING and self.course then - return self.course:isCloseToNextTurn(10) - else - return false - end -end - ---- Can the cutter be turned off ? -function AIDriveStrategyCombineCourse:getCanCutterBeTurnedOff() - return self:isWaitingForUnload() or - (self.state == self.states.UNLOADING_ON_FIELD and self:isUnloadStateOneOf(self.selfUnloadStates) and - -- we want that cutter to be turned on when returning to fieldwork after self unload - self.unloadState ~= self.states.RETURNING_FROM_SELF_UNLOAD) -end - ------------------------------------------------------------------------------------------------------------------------ ---- Turns ------------------------------------------------------------------------------------------------------------------------ - ---- Will we be driving forward only (not reversing) during a turn -function AIDriveStrategyCombineCourse:isTurnForwardOnly() - return self:isTurning() and self.aiTurn and self.aiTurn:isForwardOnly() -end - -function AIDriveStrategyCombineCourse:getTurnCourse() - return self.aiTurn and self.aiTurn:getCourse() -end - -function AIDriveStrategyCombineCourse:startTurn(ix) - self:debug(' Starting a combine turn.') - - self.turnContext = TurnContext(self.vehicle, self.course, ix, ix + 1, self.turnNodes, self:getWorkWidth(), - self.frontMarkerDistance, self.backMarkerDistance, - self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) - - -- Combines drive special headland corner maneuvers, except potato and sugarbeet harvesters - if self.turnContext:isHeadlandCorner() then - if self.combineController:isTowed() then - self:debug('Headland turn but this is a towed harvester using normal turn maneuvers.') - AIDriveStrategyFieldWorkCourse.startTurn(self, ix) - -- The type of fruit being harvested isn't really the indicator if we can make a headland turn - -- TODO: either make disabling combine headland turns configurable, or - -- TODO: decide automatically based on the vehicle's properties, like turn radius, work width, etc. - -- and disable when such a turn does not make sense for the vehicle. - elseif self.combineController:isRootVegetableHarvester() then - self:debug('Headland turn but this harvester uses normal turn maneuvers.') - AIDriveStrategyFieldWorkCourse.startTurn(self, ix) - elseif self.course:isOnConnectingPath(ix) then - self:debug('Headland turn but this a connecting track, use normal turn maneuvers.') - AIDriveStrategyFieldWorkCourse.startTurn(self, ix) - elseif self.course:isOnOutermostHeadland(ix) and self:isTurnOnFieldActive() then - self:debug('Creating a pocket in the corner so the combine stays on the field during the turn') - self.aiTurn = CombinePocketHeadlandTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, - self.course, self:getWorkWidth()) - self.state = self.states.TURNING - self.ppc:setShortLookaheadDistance() - else - self:debug('Use combine headland turn.') - self.aiTurn = CombineHeadlandTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext) - self.state = self.states.TURNING - end - else - self:debug('Non headland turn.') - if self.settings.selfUnload:getValue() and self:shouldSelfUnloadBeforeNextRow() then - self:debug('Fill level over %.0f, attempt to self unload before continue with next row', - AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowFillLevelThreshold) - -- will continue at the turn end waypoint after the unload is finished - self:rememberCourse(self.fieldWorkCourse, ix + 1) - self.aiTurn = FinishRowOnly(self.vehicle, self, self.ppc, self.proximityController, self.turnContext) - self.aiTurn:registerTurnEndCallback(self, AIDriveStrategyCombineCourse.startSelfUnloadBeforeNextRow) - self.state = self.states.TURNING - -- continue at the turn end waypoint on failed pathfinding - self.waypointIxToContinueOnFailedSelfUnload = ix + 1 - else - AIDriveStrategyFieldWorkCourse.startTurn(self, ix) - end - end -end - -function AIDriveStrategyCombineCourse:isTurning() - return self.state == self.states.TURNING -end - --- Turning except in the ending turn phase which isn't really a turn, it is rather 'starting row' -function AIDriveStrategyCombineCourse:isTurningButNotEndingTurn() - return self:isTurning() and self.aiTurn and not self.aiTurn:isEndingTurn() -end - -function AIDriveStrategyCombineCourse:isFinishingRow() - return self:isTurning() and self.aiTurn and self.aiTurn:isFinishingRow() -end - -function AIDriveStrategyCombineCourse:getTurnStartWpIx() - return self.turnContext and self.turnContext.turnStartWpIx or nil -end - -function AIDriveStrategyCombineCourse:isTurningOnHeadland() - return self.state == self.states.TURNING and self.turnContext and self.turnContext:isHeadlandCorner() -end - -function AIDriveStrategyCombineCourse:isTurningLeft() - return self.state == self.states.TURNING and self.turnContext and self.turnContext:isLeftTurn() -end - -function AIDriveStrategyCombineCourse:getFieldworkCourse() - return self.course -end - -function AIDriveStrategyCombineCourse:alwaysNeedsUnloader() - return self.combineController:alwaysNeedsUnloader() -end - -function AIDriveStrategyCombineCourse:isProcessingFruit() - return self.combineController:isProcessingFruit() -end - -function AIDriveStrategyCombineCourse:isFillableTrailerUnderPipe() - return self.pipeController:isFillableTrailerUnderPipe() -end - -function AIDriveStrategyCombineCourse:hasAutoAimPipe() - return self.pipeController:isAutoAimPipe() -end - -function AIDriveStrategyCombineCourse:isChopper() - return self.combineController:isChopper() -end - ------------------------------------------------------------------------------------------------------------------------ ---- Pipe handling ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyCombineCourse:handlePipe(dt) - if self:isChopper() then - self.pipeController:handleChopperPipe() - else - self:handleCombinePipe(dt) - end -end - -function AIDriveStrategyCombineCourse:handleCombinePipe(dt) - -- don't open the pipe while turning - if self:isPipeOpenEnabled() and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then - self.pipeController:openPipe() - else - if not self.forcePipeOpen:get() then - -- when driving to self unload, we may open the pipe well before reaching the trailer to avoid - -- banging it into the trailer. This is configurable, and needed for harvester opening their pipe - -- upwards - self.pipeController:closePipe(true) - end - end -end - ---- we don't want random triggers to open the pipe while turning or driving to another trailer -function AIDriveStrategyCombineCourse:isPipeOpenEnabled() - if self:isTurning() then - return false - elseif self.state == self.states.UNLOADING_ON_FIELD and - self:isUnloadStateOneOf(self.drivingToSelfUnloadStates) and not self:isCloseToCourseEnd(10) then - return false - else - return true - end -end - -function AIDriveStrategyCombineCourse:isAGoodTrailerInRange() - local _, trailer = self.pipeController:isFillableTrailerInRange() - local unloaderVehicle = trailer and trailer:getRootVehicle() - - if unloaderVehicle == nil then - return false - end - - if AIDriveStrategyUnloadCombine.isActiveCpCombineUnloader(unloaderVehicle) then - return unloaderVehicle:getCpDriveStrategy():getCombineToUnload() == self.vehicle - else - return true - end -end - ---- Not exactly sure what this does, but without this the chopper just won't move. ---- Copied from AIDriveStrategyCombine:update() -function AIDriveStrategyCombineCourse:updateChopperFillType() - if self:isChopper() then - self.combineController:updateChopperFillType() - end -end - -function AIDriveStrategyCombineCourse:isChopperWaitingForUnloader() - return self.waitingForTrailer -end - -function AIDriveStrategyCombineCourse:isAnyWorkAreaProcessing() - for _, implement in pairs(self.vehicle:getChildVehicles()) do - if implement.spec_workArea ~= nil then - for i, workArea in pairs(implement.spec_workArea.workAreas) do - if implement:getIsWorkAreaProcessing(workArea) then - return true - end - end - end - end - return false -end - -function AIDriveStrategyCombineCourse:isPipeMoving() - return self.pipeController:isPipeMoving() -end - -function AIDriveStrategyCombineCourse:canLoadTrailer(trailer) - local fillType = self:getFillType() - return FillLevelManager.canLoadTrailer(trailer, fillType) -end - -function AIDriveStrategyCombineCourse:getCurrentDischargeNode() - return self.pipeController:getDischargeNode() -end - -function AIDriveStrategyCombineCourse:getFillLevelPercentage() - return self.combineController:getFillLevelPercentage() -end - ---- Support for AutoDrive mod: they'll only find us if we open the pipe -function AIDriveStrategyCombineCourse:isAutoDriveWaitingForPipe() - return self.vehicle.spec_autodrive and self.vehicle.spec_autodrive.combineIsCallingDriver and - self.vehicle.spec_autodrive:combineIsCallingDriver(self.vehicle) -end - -function AIDriveStrategyCombineCourse:shouldStopForUnloading() - if self.settings.stopForUnload:getValue() then - if self:isDischarging() and not self.stopDisabledAfterEmpty:get() then - -- stop only if the pipe is discharging AND we have been emptied a while ago. - -- this makes sure the combine will start driving after it is emptied but the trailer - -- is still under the pipe - return true - end - end - return false -end - -function AIDriveStrategyCombineCourse:getFillType() - return self.pipeController:getFillType() -end - --- even if there is a trailer in range, we should not start moving until the pipe is turned towards the --- trailer and can start discharging. This returning true does not mean there's a trailer under the pipe, --- this seems more like for choppers to check if there's a potential target around -function AIDriveStrategyCombineCourse:canDischarge() - return self.pipeController:getDischargeObject() -end - -function AIDriveStrategyCombineCourse:isDischarging() - return self.pipeController:isDischarging() -end - - ------------------------------------------------------------------------------------------------------------------------ ---- Self unload ---- When self unload is enabled, instead of calling an unloader when full, the combine will find ---- a suitable trailer on or around the field, drive to the trailer and empty the grain tank into the trailer. ---- See SelfUnloadHelper.lua on how exactly a trailer is picked. ---- ---- There are two events which can trigger a self unload: ---- 1. grain tank full ---- 2. grain tank at more than 60% when the combine just finished a row and there is a ---- trailer nearby. - ------------------------------------------------------------------------------------------------------------------------ ---- Find a path to the best trailer to unload -function AIDriveStrategyCombineCourse:startSelfUnload(unloadStateAfterPathfindingDoneForSelfUnload, - bestTrailer, fillRootNode) - - if not self.pathfinder or not self.pathfinder:isActive() then - self.pathfindingStartedAt = g_currentMission.time - self.courseAfterPathfinding = nil - self.waypointIxAfterPathfinding = nil - - local targetNode, alignLength, offsetX, trailer = SelfUnloadHelper:getTargetParameters( - self.fieldPolygon, - self.vehicle, - self.implementWithPipe, - self.pipeController, - bestTrailer, - fillRootNode) - - if not targetNode then - return false - end - - -- little straight section parallel to the trailer to align better - self.selfUnloadAlignCourse = Course.createFromNode(self.vehicle, targetNode, - offsetX, -alignLength + 1, -self.pipeController:getPipeOffsetZ() - 1, 1, false) - - self.unloadStateAfterPathfindingDoneForSelfUnload = unloadStateAfterPathfindingDoneForSelfUnload - - local fieldNum = CpFieldUtil.getFieldNumUnderVehicle(self.vehicle) - local context = PathfinderContext(self.vehicle):mustBeAccurate(true) - context:allowReverse(self:getAllowReversePathfinding()):useFieldNum(fieldNum) - -- use no field penalty around the trailer to encourage the pathfinder to bridge that gap between the field - -- and the trailer - context:areaToIgnoreOffFieldPenalty( - PathfinderUtil.NodeArea.createVehicleArea(trailer, 1.5 * SelfUnloadHelper.maxDistanceFromField)) - local result - -- require full accuracy from pathfinder as we must exactly line up with the trailer - self.pathfinder, result = PathfinderUtil.startPathfindingFromVehicleToNode( - targetNode, offsetX, -alignLength, context) - if result.done then - return self:onPathfindingDoneBeforeSelfUnload(result.path) - else - self.state = self.states.WAITING_FOR_PATHFINDER - self:setPathfindingDoneCallback(self, self.onPathfindingDoneBeforeSelfUnload) - end - else - self:debug('Pathfinder already active') - end - return true -end - -function AIDriveStrategyCombineCourse:onPathfindingDoneBeforeSelfUnload(path) - if path and #path > 2 then - self:debug('Pathfinding to self unload finished with %d waypoints (%d ms)', - #path, g_currentMission.time - (self.pathfindingStartedAt or 0)) - local selfUnloadCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) - if self.selfUnloadAlignCourse then - selfUnloadCourse:append(self.selfUnloadAlignCourse) - self.selfUnloadAlignCourse = nil - end - self:raiseImplements() - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.unloadStateAfterPathfindingDoneForSelfUnload - self.ppc:setShortLookaheadDistance() - self:startCourse(selfUnloadCourse, 1) - return true - else - self:debug('No path found to self unload in %d ms', - g_currentMission.time - (self.pathfindingStartedAt or 0)) - if self.unloadStateAfterPathfindingDoneForSelfUnload == self.states.DRIVING_TO_SELF_UNLOAD then - self.unloadState = self.states.WAITING_FOR_UNLOAD_ON_FIELD - elseif self.unloadStateAfterPathfindingDoneForSelfUnload == self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW then - -- continue turn as if nothing happened - self:startCourse(self.fieldWorkCourse, self.waypointIxToContinueOnFailedSelfUnload) - AIDriveStrategyFieldWorkCourse.startTurn(self, self.waypointIxToContinueOnFailedSelfUnload) - elseif self.unloadStateAfterPathfindingDoneForSelfUnload == self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED then - self.unloadState = self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED - end - return false - end -end - ---- Back to fieldwork after self unloading -function AIDriveStrategyCombineCourse:returnToFieldworkAfterSelfUnload() - if not self.pathfinder or not self.pathfinder:isActive() then - self.pathfindingStartedAt = g_currentMission.time - local fieldWorkCourse, ix = self:getRememberedCourseAndIx() - self:debug('Return to fieldwork after self unload at waypoint %d', ix) - local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) - local result - self.pathfinder, result = PathfinderUtil.startPathfindingFromVehicleToWaypoint( - fieldWorkCourse, ix, self.settings.toolOffsetX:getValue(), 0, context) - if result.done then - return self:onPathfindingDoneAfterSelfUnload(result.path) - else - self.state = self.states.WAITING_FOR_PATHFINDER - self:setPathfindingDoneCallback(self, self.onPathfindingDoneAfterSelfUnload) - end - else - self:debug('Pathfinder already active') - end - return true -end - -function AIDriveStrategyCombineCourse:onPathfindingDoneAfterSelfUnload(path) - -- TODO: for some reason, the combine lowers the header while unloading, that should be fixed, for now, raise it here - self:raiseImplements() - local course, ix = self:getRememberedCourseAndIx() - local fm, bm = self:getFrontAndBackMarkers() - self.turnContext = RowStartOrFinishContext(self.vehicle, course, ix, ix, self.turnNodes, self:getWorkWidth(), - fm, bm, 0, 0) - if path and #path > 2 then - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.states.RETURNING_FROM_SELF_UNLOAD - self.ppc:setShortLookaheadDistance() - self:debug('Pathfinding to return to fieldwork after self unload finished with %d waypoints (%d ms)', - #path, g_currentMission.time - (self.pathfindingStartedAt or 0)) - local returnCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) - returnCourse:adjustForTowedImplements(2) - self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, returnCourse) - self:startCourse(self.workStarter:getCourse(), 1) - return true - else - self:debug('No path found to return to fieldwork after self unload (%d ms)', - g_currentMission.time - (self.pathfindingStartedAt or 0)) - local returnCourse = AlignmentCourse(self.vehicle, self.vehicle:getAIDirectionNode(), - self.turningRadius, course, ix, 0):getCourse() - if returnCourse then - self.state = self.states.UNLOADING_ON_FIELD - self.unloadState = self.states.RETURNING_FROM_SELF_UNLOAD - self.ppc:setShortLookaheadDistance() - self:debug('Start an alignment course to fieldwork waypoint %d', ix) - self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, returnCourse) - self:startCourse(self.workStarter:getCourse(), 1) - else - self.state = self.states.WORKING - self:debug('Could not generate alignment course to fieldwork waypoint %d, starting course directly', ix) - self:startRememberedCourse() - end - return false - end -end - -function AIDriveStrategyCombineCourse:continueSelfUnloadToNextTrailer() - local fillLevel = self.combineController:getFillLevel() - if fillLevel > 20 then - self:debug('Self unloading finished, but fill level is %.1f, is there another trailer around we can unload to?', fillLevel) - if self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD) then - return true - end - end - return false -end - -function AIDriveStrategyCombineCourse:shouldSelfUnloadBeforeNextRow() - if self:isFull(AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowFillLevelThreshold) then - local distance - self.selfUnloadBestTrailer, self.selfUnloadFillRootNode, distance = SelfUnloadHelper:findBestTrailer( - self.fieldPolygon, - self.vehicle, - self.implementWithPipe, - self.pipeController.pipeOffsetX) - if not self.selfUnloadBestTrailer then - return false - end - local dx, _, dz = localToLocal(AIUtil.getDirectionNode(self.vehicle), - AIUtil.getDirectionNode(self.selfUnloadBestTrailer), 0, 0, 0) - self:debug('Best trailer dx: %.1f, dz: %.1f', dx, dz) - -- the best trailer is sort of in front of us and not too far to the left or right - if dz > -10 and math.abs(dx) < AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowMaxDistance then - self:debug('Best trailer for self unload is less than %.0f m, start self unload before continuing with next row', - AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowMaxDistance) - return true - end - end - return false -end - --- This is a callback from the "finish row only" turn initiated at the end of the row when there is a --- trailer nearby for self unloading. At this point, the row is done and we are ready to drive to the --- trailer before starting the next row -function AIDriveStrategyCombineCourse:startSelfUnloadBeforeNextRow(ix) - -- giving back control to the strategy, re-register these listeners - if self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW, - self.selfUnloadBestTrailer, self.selfUnloadFillRootNode) then - self:debug('Start self unload before continuing with next row') - else - -- giving up self unload, attempt to re-initialize the turn - self:startCourse(self.fieldWorkCourse, self.waypointIxToContinueOnFailedSelfUnload) - AIDriveStrategyFieldWorkCourse.startTurn(self, self.waypointIxToContinueOnFailedSelfUnload) - end -end - ---- Let unloaders register for events. This is different from the CombineUnloadManager registration, these ---- events are for the low level coordination between the combine and its unloader(s). CombineUnloadManager ---- takes care about coordinating the work between multiple combines. -function AIDriveStrategyCombineCourse:clearAllUnloaderInformation() - self:cancelRendezvous() - self.unloader:reset() -end - ---- Register a combine unload AI driver for notification about combine events ---- Unloaders can renew their registration as often as they want to make sure they remain registered. ----@param driver AIDriveStrategyUnloadCombine -function AIDriveStrategyCombineCourse:registerUnloader(driver) - self.unloader:set(driver, 1000) -end - ---- Deregister a combine unload AI driver from notifications ----@param driver AIDriveStrategyUnloadCombine -function AIDriveStrategyCombineCourse:deregisterUnloader(driver, noEventSend) - self:cancelRendezvous() - self.unloader:reset() -end - ---- Make life easier for unloaders, increases reach of the pipe ---- Old code ?? -function AIDriveStrategyCombineCourse:fixMaxRotationLimit() - if self.pipe then - local lastPipeNode = self.pipe.nodes and self.pipe.nodes[#self.pipe.nodes] - if self:isChopper() and lastPipeNode and lastPipeNode.maxRotationLimits then - self.oldLastPipeNodeMaxRotationLimit = lastPipeNode.maxRotationLimits - self:debug('Chopper fix maxRotationLimits, old Values: x=%s, y= %s, z =%s', tostring(lastPipeNode.maxRotationLimits[1]), tostring(lastPipeNode.maxRotationLimits[2]), tostring(lastPipeNode.maxRotationLimits[3])) - lastPipeNode.maxRotationLimits = nil - end - end -end - ---- Old code ?? -function AIDriveStrategyCombineCourse:resetFixMaxRotationLimit() - if self.pipe then - local lastPipeNode = self.pipe.nodes and self.pipe.nodes[#self.pipe.nodes] - if lastPipeNode and self.oldLastPipeNodeMaxRotationLimit then - lastPipeNode.maxRotationLimits = self.oldLastPipeNodeMaxRotationLimit - self:debug('Chopper: reset maxRotationLimits is x=%s, y= %s, z =%s', tostring(lastPipeNode.maxRotationLimits[1]), tostring(lastPipeNode.maxRotationLimits[3]), tostring(lastPipeNode.maxRotationLimits[3])) - self.oldLastPipeNodeMaxRotationLimit = nil - end - end -end - ---- Offset of the pipe from the combine implement's root node ----@param additionalOffsetX number add this to the offsetX if you don't want to be directly under the pipe. If ---- greater than 0 -> to the left, less than zero -> to the right ----@param additionalOffsetZ number forward (>0)/backward (<0) offset from the pipe ----@return number, number, boolean X and Z offset of pipe, true the pipe is auto aiming, that is, can discharge either side -function AIDriveStrategyCombineCourse:getPipeOffset(additionalOffsetX, additionalOffsetZ) - local pipeOffsetX, pipeOffsetZ = self.pipeController:getPipeOffset() - return pipeOffsetX + (additionalOffsetX or 0), pipeOffsetZ + (additionalOffsetZ or 0), self:hasAutoAimPipe() -end - ---- Pipe side offset relative to course. This is to help the unloader ---- to find the pipe when we are waiting in a pocket -function AIDriveStrategyCombineCourse:getPipeOffsetFromCourse() - return self.pipeController:getPipeOffset() -end - -function AIDriveStrategyCombineCourse:initUnloadStates() - self.safeUnloadFieldworkStates = { - self.states.WORKING, - self.states.WAITING_FOR_LOWER, - self.states.WAITING_FOR_LOWER_DELAYED, - self.states.WAITING_FOR_STOP, - } - - self.safeFieldworkUnloadOrRefillStates = { - self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED, - self.states.WAITING_FOR_UNLOAD_ON_FIELD, - self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK, - self.states.WAITING_FOR_UNLOAD_IN_POCKET, - self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW - } - - self.willWaitForUnloadToFinishFieldworkStates = { - self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK, - self.states.WAITING_FOR_UNLOAD_IN_POCKET, - self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED, - self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW - } - --- All self unload states. - self.selfUnloadStates = { - self.states.DRIVING_TO_SELF_UNLOAD, - self.states.SELF_UNLOADING, - self.states.SELF_UNLOADING_WAITING_FOR_DISCHARGE, - self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED, - self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW, - self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED, - self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE, - self.states.RETURNING_FROM_SELF_UNLOAD - } - self.drivingToSelfUnloadStates = { - self.states.DRIVING_TO_SELF_UNLOAD, - self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED, - self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW, - } -end - -function AIDriveStrategyCombineCourse:isStateOneOf(myState, states) - return CpUtil.isStateOneOf(myState, states) -end - -function AIDriveStrategyCombineCourse:isUnloadStateOneOf(states) - return self:isStateOneOf(self.unloadState, states) -end - --- TODO: this whole logic is more relevant to the unloader maybe move it there? -function AIDriveStrategyCombineCourse:getClosestFieldworkWaypointIx() - if self:isTurning() then - if self.turnContext then - -- send turn start wp, unloader will decide if it needs to move it to the turn end or not - return self.turnContext.turnStartWpIx - else - -- if for whatever reason we don't have a turn context, current waypoint is ok - return self.course:getCurrentWaypointIx() - end - elseif self.course:isTemporary() then - return self.course:getLastPassedWaypointIx() - else - -- if currently on the fieldwork course, this is the best estimate - return self.ppc:getRelevantWaypointIx() - end -end - ---- Maneuvering means turning or working on a pocket or pulling back due to the pipe in fruit ---- We don't want to get too close to a maneuvering combine until it is done -function AIDriveStrategyCombineCourse:isManeuvering() - return self:isTurning() or - ( - self.state == self.states.UNLOADING_ON_FIELD and - not self:isUnloadStateOneOf(self.safeFieldworkUnloadOrRefillStates) - ) -end - -function AIDriveStrategyCombineCourse:isOnHeadland(n) - return self.course:isOnHeadland(self.course:getCurrentWaypointIx(), n) -end - ---- Are we ready for an unloader? ---- @param noUnloadWithPipeInFruit boolean pipe must not be in fruit for unload -function AIDriveStrategyCombineCourse:isReadyToUnload(noUnloadWithPipeInFruit) - -- no unloading when not in a safe state (like turning) - -- in these states we are always ready - if self:willWaitForUnloadToFinish() then - return true - end - - -- but, if we are full and waiting for unload, we have no choice, we must be ready ... - if self.state == self.states.UNLOADING_ON_FIELD and self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD then - return true - end - - -- pipe is in the fruit. - if noUnloadWithPipeInFruit and self:isPipeInFruit() then - self:debugSparse('isReadyToUnload(): pipe in fruit') - return false - end - - if not self.course then - self:debugSparse('isReadyToUnload(): has no fieldwork course') - return false - end - - -- around a turn, for example already working on the next row but not done with the turn yet - - if self.course:isCloseToNextTurn(10) then - self:debugSparse('isReadyToUnload(): too close to turn') - return false - end - -- safe default, better than block unloading - self:debugSparse('isReadyToUnload(): defaulting to ready to unload') - return true -end - ---- Will not move until unload is done? Unloaders like to know this. -function AIDriveStrategyCombineCourse:willWaitForUnloadToFinish() - return self.state == self.states.UNLOADING_ON_FIELD and - ((self.settings.stopForUnload:getValue() and self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD) or - self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK or - self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED) -end - ---- Try to not hit our Unloader after Pocket. -function AIDriveStrategyCombineCourse:isAboutToReturnFromPocket() - return self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or - (self.unloadState == self.states.WAITING_FOR_UNLOADER_TO_LEAVE and - self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_IN_POCKET) -end - ------------------------------------------------------------------------------------------------------------------------- ---- Proximity ------------------------------------------------------------------------------------------------------------------------- - -AIDriveStrategyCombineCourse.maxBackDistance = 10 - -function AIDriveStrategyCombineCourse:getMeasuredBackDistance() - return self.measuredBackDistance -end - ---- Determine how far the back of the combine is from the direction node --- TODO: attached/towed harvesters -function AIDriveStrategyCombineCourse:measureBackDistance() - self.measuredBackDistance = 0 - -- raycast from a point behind the vehicle forward towards the direction node - local nx, ny, nz = localDirectionToWorld(AIUtil.getDirectionNode(self.vehicle), 0, 0, 1) - local x, y, z = localToWorld(AIUtil.getDirectionNode(self.vehicle), 0, 1.5, -self.maxBackDistance) - raycastAll(x, y, z, nx, ny, nz, self.maxBackDistance, 'raycastBackCallback', self) -end - --- I believe this tries to figure out how far the back of a combine is from its direction node. -function AIDriveStrategyCombineCourse:raycastBackCallback(hitObjectId, x, y, z, distance, nx, ny, nz, subShapeIndex) - if hitObjectId ~= 0 then - local object = g_currentMission:getNodeObject(hitObjectId) - if object and object == self.vehicle then - local d = self.maxBackDistance - distance - if d > self.measuredBackDistance then - self.measuredBackDistance = d - self:debug('Measured back distance is %.1f m', self.measuredBackDistance) - end - else - return true - end - end -end - -function AIDriveStrategyCombineCourse:onDraw() - if CpDebug:isChannelActive(CpDebug.DBG_IMPLEMENTS, self.vehicle) then - - local dischargeNode = self:getCurrentDischargeNode() - if dischargeNode then - local dx, _, dz = localToLocal(dischargeNode.node, self.vehicle:getAIDirectionNode(), 0, 0, 0) - DebugUtil.drawDebugNode(dischargeNode.node, string.format('discharge\n%.1f %.1f', dx, dz)) - end - end - - if CpDebug:isChannelActive(CpDebug.DBG_PATHFINDER, self.vehicle) then - local areaToAvoid = self:getAreaToAvoid() - if areaToAvoid then - local x, y, z = localToWorld(areaToAvoid.node, areaToAvoid.xOffset, 0, areaToAvoid.zOffset) - DebugUtil.drawDebugLine(x, y + 1.2, z, 10, 10, 10, x, y + 1.2, z + areaToAvoid.length) - DebugUtil.drawDebugLine(x + areaToAvoid.width, y + 1.2, z, 10, 10, 10, x + areaToAvoid.width, y + 1.2, z + areaToAvoid.length) - end - end - - if CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then - if self.state == self.states.UNLOADING_ON_FIELD and - (self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET or self.unloadState == self.states.MAKING_POCKET or - self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET) then - self.pocketHelperNode:draw() - end - end -end - ---- Don't slow down when discharging. This is a workaround for unloaders getting into the proximity ---- sensor's range. -function AIDriveStrategyCombineCourse:isProximitySlowDownEnabled(vehicle) - -- if not on fieldwork, always enable slowing down - if self.state ~= self.states.WORKING then - return true - end - -- TODO: check if vehicle is player or AD driven, or even better, check if this is the vehicle - -- we are discharging into - if vehicle and self:isDischarging() then - self:debugSparse('discharging, not slowing down for nearby %s', CpUtil.getName(vehicle)) - return false - else - return true - end -end - ---- This is called by the proximity controller if we have been blocked by another vehicle for a while -function AIDriveStrategyCombineCourse:onBlockingVehicle(vehicle, isBack) - if isBack then - self:debug('Proximity sensor: blocking vehicle %s behind us', CpUtil.getName(vehicle)) - self:checkBlockingUnloader() - else - self:debug('Proximity sensor: blocking vehicle %s in front of us', CpUtil.getName(vehicle)) - local strategy = vehicle.getCpDriveStrategy and vehicle:getCpDriveStrategy() - if strategy and strategy.requestToMoveOutOfWay then - strategy:requestToMoveOutOfWay(self.vehicle, isBack) - end - end -end - --- An unloader may request the harvester to ignore its proximity because the unloader will take care --- of staying away. -function AIDriveStrategyCombineCourse:requestToIgnoreProximity(vehicle) - self.unloaderRequestedToIgnoreProximity:set(vehicle, 1000) -end - --- Proximity controller checking if an object/vehicle should be ignored -function AIDriveStrategyCombineCourse:ignoreProximityObject(object, vehicle, moveForwards, hitTerrain) - return vehicle == self.unloaderRequestedToIgnoreProximity:get() -end - ---- Check if the unloader is blocking us when we are reversing in a turn and immediately notify it -function AIDriveStrategyCombineCourse:checkBlockingUnloader() - if not self.ppc:isReversing() and not AIUtil.isReversing(self.vehicle) then - return - end - local d, blockingVehicle = self.proximityController:checkBlockingVehicleBack() - if blockingVehicle ~= self.unloaderRequestedToIgnoreProximity:get() and d < 1000 and - blockingVehicle and AIUtil.isStopped(self.vehicle) and - not self:isWaitingForUnload() and not self:shouldHoldInTurnManeuver() then - -- try requesting only if the unloader really blocks us, that is we are actually backing up but - -- can't move because of the unloader, and not when we are stopped for other reasons - self:debugSparse('Can\'t reverse, %s at %.1f m is blocking', blockingVehicle:getName(), d) - local strategy = blockingVehicle.getCpDriveStrategy and blockingVehicle:getCpDriveStrategy() - if strategy and strategy.requestToBackupForReversingCombine then - strategy:requestToBackupForReversingCombine(self.vehicle) - end - end -end - -function AIDriveStrategyCombineCourse:getWorkingToolPositionsSetting() - local setting = self.settings.pipeToolPositions - return setting:getHasMoveablePipe() and setting:hasValidToolPositions() and setting -end - ------------------------------------------------------------------------------------------------------------------------- ---- Info texts, makes sure the unloadState also gets checked. ---------------------------------------------------------------------------------------------------------------------------- - -function AIDriveStrategyCombineCourse:updateInfoTexts() - for infoText, states in pairs(self.registeredInfoTexts) do - if states.states[self.state] and states.unloadStates[self.unloadState] then - self:setInfoText(infoText) - else - self:clearInfoText(infoText) - end - end -end +--[[ +This file is part of Courseplay (https://github.com/Courseplay/courseplay) +Copyright (C) 2019-2021 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +]] + +---@class AIDriveStrategyCombineCourse : AIDriveStrategyFieldWorkCourse +AIDriveStrategyCombineCourse = CpObject(AIDriveStrategyFieldWorkCourse) + +-- fill level when we start making a pocket to unload if we are on the outermost headland +AIDriveStrategyCombineCourse.pocketFillLevelFullPercentage = 95 +AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow = 30 +-- when fill level is above this threshold, don't start the next row if the pipe would be +-- in the fruit +AIDriveStrategyCombineCourse.waitForUnloadAtEndOfRowFillLevelThreshold = 95 + +-- When to initiate a self unload before turning to the next row? +-- if the fill level is above this threshold when about to begin a turn and there is a trailer +-- close by, and ... +AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowFillLevelThreshold = 60 +-- if the best trailer is less then this (meters) to the left or right +AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowMaxDistance = 50 +--- Percentage delta leftover until full, when the combine slows down. +AIDriveStrategyCombineCourse.startingSlowdownFillLevelThreshold = 1.5 +--- Minimum working speed, for slowdown. +AIDriveStrategyCombineCourse.normalMinimalWorkingSpeed = 5 + +AIDriveStrategyCombineCourse.myStates = { + -- main states + UNLOADING_ON_FIELD = {}, + -- unload sub-states + STOPPING_FOR_UNLOAD = {}, + WAITING_FOR_UNLOAD_ON_FIELD = {}, + PULLING_BACK_FOR_UNLOAD = {}, + WAITING_FOR_UNLOAD_AFTER_PULLED_BACK = {}, + RETURNING_FROM_PULL_BACK = {}, + REVERSING_TO_MAKE_A_POCKET = {}, + MAKING_POCKET = {}, + WAITING_FOR_UNLOAD_IN_POCKET = {}, + WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW = {}, + UNLOADING_BEFORE_STARTING_NEXT_ROW = {}, + WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED = {}, + WAITING_FOR_UNLOADER_TO_LEAVE = {}, + RETURNING_FROM_POCKET = {}, + DRIVING_TO_SELF_UNLOAD = {}, + DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW = {}, -- before turning into the next row, we unload into a nearby trailer + SELF_UNLOADING = {}, + SELF_UNLOADING_WAITING_FOR_DISCHARGE = {}, + DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED = {}, + SELF_UNLOADING_AFTER_FIELDWORK_ENDED = {}, + SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE = {}, + RETURNING_FROM_SELF_UNLOAD = {}, +} + +-- stop limit we use for self unload to approach the trailer +AIDriveStrategyCombineCourse.proximityStopThresholdSelfUnload = 0.1 + +-- Developer hack: to check the class of an object one should use the is_a() defined in CpObject.lua. +-- However, when we reload classes on the fly during the development, the is_a() calls in other modules still +-- have the old class definition (for example CombineUnloadManager.lua) of this class and thus, is_a() fails. +-- Therefore, use this instead, this is safe after a reload. +AIDriveStrategyCombineCourse.isAAIDriveStrategyCombineCourse = true + +function AIDriveStrategyCombineCourse:init(task, job) + AIDriveStrategyFieldWorkCourse.init(self, task, job) + AIDriveStrategyCourse.initStates(self, AIDriveStrategyCombineCourse.myStates) + self.fruitLeft, self.fruitRight = 0, 0 + self.litersPerMeter = 0 + self.litersPerSecond = 0 + self.fillLevelAtLastWaypoint = 0 + self.beaconLightsActive = false + self.stopDisabledAfterEmpty = CpTemporaryObject(false) + self.stopDisabledAfterEmpty:set(false, 1) + self:initUnloadStates() + self.chopperCanDischarge = CpTemporaryObject(false) + -- hold the harvester temporarily + self.temporaryHold = CpTemporaryObject(false) + -- periodically check if we need to call an unloader + self.timeToCallUnloader = CpTemporaryObject(true) + self.unloaderRequestedToIgnoreProximity = CpTemporaryObject() + -- we want to keep to pipe open, even if there is no trailer under it + self.forcePipeOpen = CpTemporaryObject() + self.pocketHelperNode = HelperTerrainNode('pocketHelperNode') + --- Register info texts + self:registerInfoTextForStates(self:getFillLevelInfoText(), { + states = { + [self.states.UNLOADING_ON_FIELD] = true + }, + unloadStates = { + [self.states.WAITING_FOR_UNLOAD_ON_FIELD] = true, + [self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED] = true, + [self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK] = true, + [self.states.WAITING_FOR_UNLOAD_IN_POCKET] = true + } + }) +end + +function AIDriveStrategyCombineCourse:delete() + self.pocketHelperNode:destroy() + AIDriveStrategyFieldWorkCourse.delete(self) +end + +function AIDriveStrategyCombineCourse:getStateAsString() + local s = self.state.name + if self.state == self.states.UNLOADING_ON_FIELD then + s = s .. '/' .. self.unloadState.name + end + return s +end + +----------------------------------------------------------------------------------------------------------------------- +--- Initialization +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCombineCourse:setAllStaticParameters() + AIDriveStrategyFieldWorkCourse.setAllStaticParameters(self) + self:debug('AIDriveStrategyCombineCourse set') + + if self:isChopper() then + self:debug('This is a chopper.') + end + + self:checkMarkers() + self:measureBackDistance() + Markers.setMarkerNodes(self.vehicle, self.measuredBackDistance) + + self.proximityController:registerBlockingVehicleListener(self, AIDriveStrategyCombineCourse.onBlockingVehicle) + self.proximityController:registerIgnoreObjectCallback(self, AIDriveStrategyCombineCourse.ignoreProximityObject) + + -- distance to keep to the right (>0) or left (<0) when pulling back to make room for the tractor + self.pullBackRightSideOffset = math.abs(self.pipeController:getPipeOffsetX()) - self:getWorkWidth() / 2 + 5 + self.pullBackRightSideOffset = self:isPipeOnLeft() and self.pullBackRightSideOffset or -self.pullBackRightSideOffset + -- should be at pullBackRightSideOffset to the right or left at pullBackDistanceStart + self.pullBackDistanceStart = 2 * AIUtil.getTurningRadius(self.vehicle) + -- and back up another bit + self.pullBackDistanceEnd = self.pullBackDistanceStart + 5 + -- when making a pocket, how far to back up before changing to forward + -- for very long vehicles, like potato/sugar beet harvesters the 20 meters may not be enough + self.pocketReverseDistance = AIUtil.getVehicleAndImplementsTotalLength(self.vehicle) * 2.2 + -- register ourselves at our boss + -- TODO_22 g_combineUnloadManager:addCombineToList(self.vehicle, self) + self.waitingForUnloaderAtEndOfRow = CpTemporaryObject() + --- My unloader. This expires in a few seconds, so unloaders have to renew their registration periodically + ---@type CpTemporaryObject + self.unloader = CpTemporaryObject(nil) + --- if this is not nil, we have a pending rendezvous with our unloader + ---@type CpTemporaryObject + self.unloaderToRendezvous = CpTemporaryObject(nil) + local total, pipeInFruit = self.vehicle:getFieldWorkCourse():setPipeInFruitMap(self.pipeController:getPipeOffsetX(), self:getWorkWidth()) + self:debug('Pipe in fruit map created, there are %d non-headland waypoints, of which at %d the pipe will be in the fruit', + total, pipeInFruit) + self.fillLevelFullPercentage = self.normalFillLevelFullPercentage +end + +function AIDriveStrategyCombineCourse:initializeImplementControllers(vehicle) + AIDriveStrategyFieldWorkCourse.initializeImplementControllers(self, vehicle) + local _ + self.implementWithPipe, self.pipeController = self:addImplementController(vehicle, + PipeController, Pipe, {}, nil) + self.combineController, self.combine = self:getFirstRegisteredImplementControllerByClass(CombineController) +end + +function AIDriveStrategyCombineCourse:getProximitySensorWidth() + -- proximity sensor width across the entire working width + return self:getWorkWidth() +end + +-- This part of an ugly workaround to make the chopper pickups work +function AIDriveStrategyCombineCourse:checkMarkers() + for _, implement in pairs(AIUtil.getAllAIImplements(self.vehicle)) do + local aiLeftMarker, aiRightMarker, aiBackMarker = implement.object:getAIMarkers() + if not aiLeftMarker or not aiRightMarker or not aiBackMarker then + self.notAllImplementsHaveAiMarkers = true + return + end + end +end + +function AIDriveStrategyCombineCourse:getAllowReversePathfinding() + if self:isTurning() and self:hasAutoAimPipe() and self.unloader:get() ~= nil then + -- chopper with CP unloader turning, disable reverse for pathfinder turns + -- to make following the chopper through the turn easier + return false + else + return AIDriveStrategyFieldWorkCourse.getAllowReversePathfinding(self) + end +end + +--- Get the combine object, this can be different from the vehicle in case of tools towed or mounted on a tractor +function AIDriveStrategyCombineCourse:getCombine() + return self.combine +end + +function AIDriveStrategyCombineCourse:isAttachedHarvester() + return self.vehicle ~= self.combine +end + +function AIDriveStrategyCombineCourse:getPipeOffsetReferenceNode() + if self:isAttachedHarvester() then + -- for attached harvesters this gets the root node of the harvester as that is our reference point to the + -- pipe offsets + return self:getCombine().rootNode + else + return self.vehicle:getAIDirectionNode() + end +end + +function AIDriveStrategyCombineCourse:update(dt) + AIDriveStrategyFieldWorkCourse.update(self, dt) + self:updateChopperFillType() + self:onDraw() +end + +--- Hold the harvester for a period of periodMs milliseconds +function AIDriveStrategyCombineCourse:hold(periodMs) + if not self.temporaryHold:get() then + self:debug('Temporary hold request for %d milliseconds', periodMs) + end + self.temporaryHold:set(true, math.min(math.max(0, periodMs), 30000)) +end + +function AIDriveStrategyCombineCourse:getDriveData(dt, vX, vY, vZ) + self:handlePipe(dt) + if self.temporaryHold:get() then + self:setMaxSpeed(0) + end + if self.state == self.states.WORKING then + -- Harvesting + self:checkRendezvous() + self:checkBlockingUnloader() + + if self:isFull() then + self:changeToUnloadOnField() + elseif self:alwaysNeedsUnloader() then + if not self.pipeController:isFillableTrailerUnderPipe() then + self:debug('Need an unloader to work but have no fillable trailer under the pipe') + self:changeToUnloadOnField() + end + elseif self:shouldWaitAtEndOfRow() then + self:startWaitingForUnloadBeforeNextRow() + end + + if self:shouldStopForUnloading() then + -- player does not want us to move while discharging + self:setMaxSpeed(0) + else + -- Slowdown the combine near the last few percent, so no crops are leftover, + -- as the combine needs to stop in time, before it's full. + local leftoverPercentage = self.fillLevelFullPercentage - self.combineController:getFillLevelPercentage() + if (leftoverPercentage > 0 and leftoverPercentage < self.startingSlowdownFillLevelThreshold) then + local speed = leftoverPercentage * (self.vehicle:getSpeedLimit(true) / self.startingSlowdownFillLevelThreshold) + self.normalMinimalWorkingSpeed + self:setMaxSpeed(speed) + end + end + + + elseif self.state == self.states.TURNING then + self:checkBlockingUnloader() + elseif self.state == self.states.WAITING_FOR_LOWER then + if self:isFull() then + self:debug('Waiting for lower but full...') + self:changeToUnloadOnField() + end + elseif self.state == self.states.UNLOADING_ON_FIELD then + -- Unloading + self:driveUnloadOnField() + if self:isWaitingForUnload() then + self:callUnloaderWhenNeeded() + end + end + if self:isTurning() and not self:isFinishingRow() then + if self:shouldHoldInTurnManeuver() then + self:setMaxSpeed(0) + end + end + return AIDriveStrategyFieldWorkCourse.getDriveData(self, dt, vX, vY, vZ) +end + +function AIDriveStrategyCombineCourse:updateFieldworkOffset(course) + if self.state == self.states.UNLOADING_ON_FIELD and self:isUnloadStateOneOf(self.selfUnloadStates) then + -- do not apply fieldwork offset when not doing fieldwork + course:setOffset((self.aiOffsetX or 0) + (self.tightTurnOffset or 0), (self.aiOffsetZ or 0)) + else + course:setOffset(self.settings.toolOffsetX:getValue() + (self.aiOffsetX or 0) + (self.tightTurnOffset or 0), + (self.aiOffsetZ or 0)) + end +end + +function AIDriveStrategyCombineCourse:checkDistanceToOtherFieldWorkers() + -- do not slow down/stop for convoy while unloading + if self.state ~= self.states.UNLOADING_ON_FIELD then + self:setMaxSpeed(self.fieldWorkerProximityController:getMaxSpeed(self.settings.convoyDistance:getValue(), self.maxSpeed)) + end +end + +--- Take care of unloading on the field. This could be stopping and waiting for an unloader or +--- self unloading. +--- The output of this function is: +--- * set self.maxSpeed +--- * change the course to run, for example pulling back/making pocket or self unload +--- * this does not supply drive target point +function AIDriveStrategyCombineCourse:driveUnloadOnField() + if self.unloadState == self.states.STOPPING_FOR_UNLOAD then + self:setMaxSpeed(0) + -- wait until we stopped before raising the implements + if AIUtil.isStopped(self.vehicle) then + if self.raiseHeaderAfterStopped then + self:debug('Stopped, now raise implements and switch to next unload state') + self:raiseImplements() + end + self.unloadState = self.newUnloadStateAfterStopped + end + elseif self.unloadState == self.states.PULLING_BACK_FOR_UNLOAD then + self:setMaxSpeed(self.settings.reverseSpeed:getValue()) + elseif self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET then + self:setMaxSpeed(self.settings.reverseSpeed:getValue()) + elseif self.unloadState == self.states.MAKING_POCKET then + self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue()) + local _, _, dz = self.pocketHelperNode:localToLocal(self.vehicle:getAIDirectionNode(), 0, 0, + self.pipeController:getPipeOffsetZ()) + if dz > -18 then + -- we are close enough to the reference waypoint, so stop making the pocket and wait for unload. + self:debug('Waiting for unload in the pocket') + self.unloadState = self.states.WAITING_FOR_UNLOAD_IN_POCKET + end + elseif self.unloadState == self.states.RETURNING_FROM_PULL_BACK then + self:setMaxSpeed(self.settings.turnSpeed:getValue()) + elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK or + self.unloadState == self.states.UNLOADING_BEFORE_STARTING_NEXT_ROW then + if self:isUnloadFinished() then + -- reset offset to return to the original up/down row after we unloaded in the pocket + self.aiOffsetX = 0 + + -- wait a bit after the unload finished to give a chance to the unloader to move away + self.stateBeforeWaitingForUnloaderToLeave = self.unloadState + self.unloadState = self.states.WAITING_FOR_UNLOADER_TO_LEAVE + self.waitingForUnloaderSince = g_currentMission.time + self:debug('Unloading finished, wait for the unloader to leave...') + else + self:setMaxSpeed(0) + end + elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD then + if g_updateLoopIndex % 5 == 0 then + --small delay, to make sure no more fillLevel change is happening + if not self:isFull() and not self:shouldStopForUnloading() and not self:alwaysNeedsUnloader() then + self:debug('not full anymore, can continue working') + self:changeToFieldWork() + elseif self:alwaysNeedsUnloader() and self:isFillableTrailerUnderPipe() then + self:debug('Need an unloader to work, now have a trailer under the pipe, can continue working') + self:changeToFieldWork() + end + end + self:setMaxSpeed(0) + elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW then + self:setMaxSpeed(0) + if self:isDischarging() then + self:cancelRendezvous() + self.unloadState = self.states.UNLOADING_BEFORE_STARTING_NEXT_ROW + self:debug('Unloading started at end of row') + end + if not self.waitingForUnloaderAtEndOfRow:get() then + local unloaderWhoDidNotShowUp = self.unloaderToRendezvous:get() + self:cancelRendezvous() + if unloaderWhoDidNotShowUp then + unloaderWhoDidNotShowUp:getCpDriveStrategy():onMissedRendezvous(self) + end + self:debug('Waited for unloader at the end of the row but it did not show up, try to continue') + self:changeToFieldWork() + end + elseif self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED then + local fillLevel = self.combineController:getFillLevel() + if fillLevel < 0.01 then + self:debug('Unloading finished after fieldwork ended, end course') + AIDriveStrategyFieldWorkCourse.finishFieldWork(self) + else + self:setMaxSpeed(0) + end + elseif self.unloadState == self.states.WAITING_FOR_UNLOADER_TO_LEAVE then + self:setMaxSpeed(0) + -- TODO: instead of just wait a few seconds we could check if the unloader has actually left + if self.waitingForUnloaderSince + 5000 < g_currentMission.time then + if self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK then + local pullBackReturnCourse = self.pathfinderController:findAnalyticPathFromVehicleToGoal( + self.positionToContinueAfterPullback, self:getAllowReversePathfinding()) + if pullBackReturnCourse then + self.unloadState = self.states.RETURNING_FROM_PULL_BACK + self:debug('Unloading finished, returning to fieldwork on return course') + self.ppc:setShortLookaheadDistance() + self:startCourse(pullBackReturnCourse, 1) + self:rememberCourse(self.courseAfterPullBack, self.ixAfterPullBack) + else + self:debug('Unloading finished, returning to fieldwork directly') + self:startCourse(self.courseAfterPullBack, self.ixAfterPullBack) + self.ppc:setNormalLookaheadDistance() + self:changeToFieldWork() + end + elseif self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_IN_POCKET then + self:debug('Unloading in pocket finished, returning to fieldwork') + self.fillLevelFullPercentage = self.normalFillLevelFullPercentage + self:changeToFieldWork() + elseif self.stateBeforeWaitingForUnloaderToLeave == self.states.UNLOADING_BEFORE_STARTING_NEXT_ROW then + self:debug('Unloading before next row finished, returning to fieldwork') + self:changeToFieldWork() + elseif self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_ON_FIELD then + self:debug('Unloading on field finished, returning to fieldwork') + self:changeToFieldWork() + else + self:debug('Unloading finished, previous state not known, returning to fieldwork') + self:changeToFieldWork() + end + end + elseif self:isUnloadStateOneOf(self.drivingToSelfUnloadStates) then + if self:isCloseToCourseEnd(25) then + -- slow down towards the end of the course, near the trailer + self:setMaxSpeed(math.min(10, 0.5 * self.settings.fieldSpeed:getValue())) + -- we'll be very close to the tractor/trailer, don't stop too soon + self.proximityController:setTemporaryStopThreshold(self.proximityStopThresholdSelfUnload, 3000) + if g_vehicleConfigurations:getRecursively(self.vehicle, 'openPipeEarly') then + if not self.pipeController:isPipeOpen() then + self:debug('Opening pipe early to not crash it into the trailer') + self.pipeController:openPipe() + end + self.forcePipeOpen:set(true, 1000) + end + else + self:setMaxSpeed(self.settings.fieldSpeed:getValue()) + end + elseif self.unloadState == self.states.SELF_UNLOADING_WAITING_FOR_DISCHARGE then + self:setMaxSpeed(0) + self:debugSparse('Waiting for the self unloading to start') + if self:isDischarging() then + self.unloadState = self.states.SELF_UNLOADING + end + elseif self.unloadState == self.states.SELF_UNLOADING then + self:setMaxSpeed(0) + if self:isUnloadFinished() then + if not self:continueSelfUnloadToNextTrailer() then + self:debug('Self unloading finished, returning to fieldwork') + self:returnToFieldworkAfterSelfUnload() + end + end + elseif self.unloadState == self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE then + self:setMaxSpeed(0) + self:debugSparse('Fieldwork ended, waiting for the self unloading to start') + if self:isDischarging() then + self.unloadState = self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED + end + elseif self.unloadState == self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED then + self:setMaxSpeed(0) + if self:isUnloadFinished() then + if not self:continueSelfUnloadToNextTrailer() then + self:debug('Self unloading finished after fieldwork ended, finishing fieldwork') + AIDriveStrategyFieldWorkCourse.finishFieldWork(self) + end + end + elseif self.unloadState == self.states.RETURNING_FROM_SELF_UNLOAD then + if self:isCloseToCourseStart(25) then + self:setMaxSpeed(0.5 * self.settings.fieldSpeed:getValue()) + -- we'll be very close to the tractor/trailer, don't stop too soon + self.proximityController:setTemporaryStopThreshold(self.proximityStopThresholdSelfUnload, 3000) + else + self:setMaxSpeed(self.settings.fieldSpeed:getValue()) + end + -- apply the work starter's speed limit (when approaching the work start) + local _, _, _, maxSpeed = self.workStarter:getDriveData() + if maxSpeed ~= nil then + self:setMaxSpeed(maxSpeed) + end + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- Event listeners +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCombineCourse:onWaypointPassed(ix, course) + if self.state == self.states.UNLOADING_ON_FIELD and + (self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD or + self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW or + self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED or + self.unloadState == self.states.RETURNING_FROM_SELF_UNLOAD) then + -- nothing to do while driving to unload and back + return AIDriveStrategyFieldWorkCourse.onWaypointPassed(self, ix, course) + end + + self:checkFruit() + + -- make sure we start making a pocket while we still have some fill capacity left as we'll be + -- harvesting fruit while making the pocket unless we have self unload turned on + if self:shouldMakePocket() and not self.settings.selfUnload:getValue() then + self.fillLevelFullPercentage = self.pocketFillLevelFullPercentage + end + + local isOnHeadland = self.course:isOnHeadland(ix) + self.combineController:updateStrawSwath(isOnHeadland) + + if self.state == self.states.WORKING then + if not self:alwaysNeedsUnloader() then + self:estimateDistanceUntilFull(ix) + self:callUnloaderWhenNeeded() + end + end + + if self.returnedFromPocketIx and self.returnedFromPocketIx == ix then + -- back to normal look ahead distance for PPC, no tight turns are needed anymore + self:debug('Reset PPC to normal lookahead distance') + self.ppc:setNormalLookaheadDistance() + end + AIDriveStrategyFieldWorkCourse.onWaypointPassed(self, ix, course) +end + +--- Called when the last waypoint of a course is passed +function AIDriveStrategyCombineCourse:onLastWaypointPassed() + local fillLevel = self.combineController:getFillLevel() + if self.state == self.states.UNLOADING_ON_FIELD then + if self.unloadState == self.states.RETURNING_FROM_PULL_BACK then + self:debug('Pull back finished, returning to fieldwork') + self.ppc:setNormalLookaheadDistance() + self:startRememberedCourse() + self:changeToFieldWork() + elseif self.unloadState == self.states.RETURNING_FROM_SELF_UNLOAD then + self:debug('Back from self unload, returning to fieldwork') + self.workStarter:onLastWaypoint() + elseif self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET then + self:debug('Reversed, now start making a pocket') + self:lowerImplements() + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.states.MAKING_POCKET + -- offset the main fieldwork course and start on it + self.aiOffsetX = math.min(self.pullBackRightSideOffset, self:getWorkWidth()) + self:startRememberedCourse() + elseif self.unloadState == self.states.PULLING_BACK_FOR_UNLOAD then + -- pulled back, now wait for unload + self.unloadState = self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK + self:debug('Pulled back, now wait for unload') + elseif self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD or + self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW then + self:debug('Self unloading point reached, fill level %.1f, waiting for unload to start.', fillLevel) + self.unloadState = self.states.SELF_UNLOADING_WAITING_FOR_DISCHARGE + elseif self.unloadState == self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED then + self:debug('Self unloading point reached after fieldwork ended, fill level %.1f, waiting for unload to start.', fillLevel) + self.unloadState = self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE + end + elseif self.state == self.states.WORKING and fillLevel > 0 then + -- reset offset we used for the course ending to not miss anything + self.aiOffsetZ = 0 + if self.settings.selfUnload:getValue() and + self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED) then + self:debug('Start self unload after fieldwork ended') + else + -- let AutoDrive know we are done and can unload + self:debug('Fieldwork done, fill level is %.1f, now waiting to be unloaded.', fillLevel) + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED + end + else + AIDriveStrategyFieldWorkCourse.onLastWaypointPassed(self) + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- State changes +----------------------------------------------------------------------------------------------------------------------- + +--- Some of our turns need a short look ahead distance, make sure we restore the normal after the turn +function AIDriveStrategyCombineCourse:resumeFieldworkAfterTurn(ix) + self.ppc:setNormalLookaheadDistance() + AIDriveStrategyFieldWorkCourse.resumeFieldworkAfterTurn(self, ix) +end + +--- Stop, raise the header (if needed) and then, and only then change to the new states. This is to avoid leaving +--- unharvested spots due to the header being lifted while the vehicle is still in motion. +function AIDriveStrategyCombineCourse:stopForUnload(newUnloadStateAfterStopped, raiseHeaderAfterStopped) + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.states.STOPPING_FOR_UNLOAD + self.newUnloadStateAfterStopped = newUnloadStateAfterStopped + self.raiseHeaderAfterStopped = raiseHeaderAfterStopped +end + +--- Start waiting for the implements to lower +-- getCanAIVehicleContinueWork() seems to return false when the implement being lowered/raised (moving) but +-- true otherwise. Due to some timing issues it may return true just after we started lowering it +-- so the harvester misses some fruit when restarting after unloading ended +function AIDriveStrategyCombineCourse:startWaitingForLower() + -- force delayed lower + self.state = self.states.WAITING_FOR_LOWER_DELAYED + self:debug('waiting for lower delayed') +end + +function AIDriveStrategyCombineCourse:changeToUnloadOnField() + self:checkFruit() + -- TODO: check around turn maneuvers we may not want to pull back before a turn + self:rememberCourse(self.fieldWorkCourse, self:getBestWaypointToContinueFieldWork()) + if self.settings.selfUnload:getValue() and self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD) then + self:debug('Start self unload') + elseif self.settings.avoidFruit:getValue() and self:shouldMakePocket() then + -- I'm on the edge of the field or fruit is on both sides, make a pocket on the right side and wait there for the unload + local pocketCourse, nextIx = self:createPocketCourse() + if pocketCourse then + self:debug('No room to the left, making a pocket for unload') + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.states.REVERSING_TO_MAKE_A_POCKET + -- place a marker at the current pipe position, we'll use this to find out where to stop + -- making the pocket + self.pocketHelperNode:placeAtNode(Markers.getFrontMarkerNode(self.vehicle), 1, 0, 0, 0) + self:rememberCourse(self.course, nextIx) + -- raise header for reversing + self:raiseImplements() + self:startCourse(pocketCourse, 1) + -- tighter turns + self.ppc:setShortLookaheadDistance() + else + self:startWaitingForUnloadWhenFull() + end + elseif self.settings.avoidFruit:getValue() and self:shouldPullBack() then + -- is our pipe in the fruit? (assuming pipe is on the left side) + local pullBackCourse = self:createPullBackCourse() + if pullBackCourse then + self:debug('Pipe in fruit, pulling back to make room for unloading') + self:stopForUnload(self.states.PULLING_BACK_FOR_UNLOAD, true) + self.courseAfterPullBack = self.course + self.ixAfterPullBack = self.ppc:getLastPassedWaypointIx() or self.ppc:getCurrentWaypointIx() + -- remember where to start after the pull back, a bit further back just in case. + self.positionToContinueAfterPullback = PathfinderUtil.getVehiclePositionAsState3D(self.vehicle, 0, -2) + -- tighter turns + self.ppc:setShortLookaheadDistance() + self:startCourse(pullBackCourse, 1) + else + self:startWaitingForUnloadWhenFull() + end + else + self:startWaitingForUnloadWhenFull() + end +end + +function AIDriveStrategyCombineCourse:startWaitingForUnloadWhenFull() + self:stopForUnload(self.states.WAITING_FOR_UNLOAD_ON_FIELD, true) + self:debug('Waiting for the unloader on the field') + g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, + string.format(g_i18n:getText("ai_messageErrorGrainTankIsFull"), self.vehicle:getCurrentHelper().name)) +end + +function AIDriveStrategyCombineCourse:startWaitingForUnloadBeforeNextRow() + self:debug('Waiting for unload before starting the next row') + self.waitingForUnloaderAtEndOfRow:set(true, 30000) + self:stopForUnload(self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW, true) +end + +--- The unloader may call this repeatedly to confirm that the rendezvous still stands, making sure the +--- combine won't give up and keeps waiting +function AIDriveStrategyCombineCourse:reconfirmRendezvous() + if self.waitingForUnloaderAtEndOfRow:get() then + -- ok, we'll wait another 30 seconds + self.waitingForUnloaderAtEndOfRow:set(true, 30000) + end +end + +function AIDriveStrategyCombineCourse:isUnloadFinished() + local discharging = true + local dischargingNow = self:isDischarging() + --wait for 10 frames before taking discharging as false + if not dischargingNow then + self.notDischargingSinceLoopIndex = self.notDischargingSinceLoopIndex and self.notDischargingSinceLoopIndex or g_updateLoopIndex + if g_updateLoopIndex - self.notDischargingSinceLoopIndex > 10 then + discharging = false + end + else + self.notDischargingSinceLoopIndex = nil + end + local fillLevel = self.combineController:getFillLevel() + -- unload is done when fill levels are ok (not full) and not discharging anymore (either because we + -- are empty or the trailer is full) + return (not self:isFull() and not discharging) or fillLevel < 0.1 +end + +function AIDriveStrategyCombineCourse:isFull(fillLevelFullPercentage) + local fillLevelPercentage = self.combineController:getFillLevelPercentage() + if fillLevelPercentage >= 100 or fillLevelPercentage > (fillLevelFullPercentage or self.fillLevelFullPercentage) then + self:debugSparse('Full or refillUntilPct reached: %.2f', fillLevelPercentage) + return true + end + if fillLevelPercentage < 0.1 then + self.stopDisabledAfterEmpty:set(true, 2000) + end + return false +end + +function AIDriveStrategyCombineCourse:shouldMakePocket() + if self:alwaysNeedsUnloader() then + self:debug('Always need unloader so not making a pocket') + return false + end + if self.fruitLeft > 0.75 and self.fruitRight > 0.75 then + -- fruit both sides + return true + elseif self:isPipeOnLeft() then + -- on the outermost headland clockwise (field edge) + return not self.fieldOnLeft + else + -- on the outermost headland counterclockwise (field edge) + return not self.fieldOnRight + end +end + +function AIDriveStrategyCombineCourse:shouldPullBack() + if self:alwaysNeedsUnloader() then + self:debug('Always need unloader so not making a pocket') + return false + else + return self:isPipeInFruit() + end +end + +function AIDriveStrategyCombineCourse:isPipeOnLeft() + return self.pipeController:isPipeOnTheLeftSide() +end + +function AIDriveStrategyCombineCourse:isPipeInFruit() + -- is our pipe in the fruit? + if self:isPipeOnLeft() then + return self.fruitLeft > self.fruitRight + else + return self.fruitLeft < self.fruitRight + end +end + +---@return number, number the amount of fruit on left/right side of the combine. 0 is no fruit, and it is probably +--- a number up to 100, indicating how much fruit is there. +function AIDriveStrategyCombineCourse:getFruitAtSides() + return self.fruitLeft, self.fruitRight +end + +function AIDriveStrategyCombineCourse:checkFruit() + -- getValidityOfTurnDirections() wants to have the vehicle.aiDriveDirection, so get that here. + local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) + local length = MathUtil.vector2Length(dx, dz) + dx = dx / length + dz = dz / length + self.vehicle.aiDriveDirection = { dx, dz } + -- getValidityOfTurnDirections works only if all AI Implements have aiMarkers. Since + -- we make all Cutters AI implements, even the ones which do not have AI markers (such as the + -- chopper pickups which do not work with the Giants helper) we have to make sure we don't call + -- getValidityOfTurnDirections for those + if self.notAllImplementsHaveAiMarkers then + self.fruitLeft, self.fruitRight = 0, 0 + else + self.fruitLeft, self.fruitRight = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle) + end + local workWidth = self:getWorkWidth() + local x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), workWidth, 0, 0) + self.fieldOnLeft = CpFieldUtil.isOnField(x, z) + x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), -workWidth, 0, 0) + self.fieldOnRight = CpFieldUtil.isOnField(x, z) + self:debug('Fruit left: %.2f right %.2f, field on left %s, right %s', + self.fruitLeft, self.fruitRight, tostring(self.fieldOnLeft), tostring(self.fieldOnRight)) +end + +--- Estimate the waypoint where the combine will be full/reach the fill level where it should start unloading +--- (waypointIxWhenFull/waypointIxWhenCallUnloader), based on the current harvest rate +function AIDriveStrategyCombineCourse:estimateDistanceUntilFull(ix) + -- calculate fill rate so the combine driver knows if it can make the next row without unloading + local fillLevel = self.combineController:getFillLevel() + local capacity = self.combineController:getCapacity() + if ix > 1 then + local dToNext = self.course:getDistanceToNextWaypoint(ix - 1) + if self.fillLevelAtLastWaypoint and self.fillLevelAtLastWaypoint > 0 and self.fillLevelAtLastWaypoint <= fillLevel then + local litersPerMeter = (fillLevel - self.fillLevelAtLastWaypoint) / dToNext + -- make sure it won't end up being inf + local litersPerSecond = math.min(1000, (fillLevel - self.fillLevelAtLastWaypoint) / + ((g_currentMission.time - (self.fillLevelLastCheckedTime or g_currentMission.time)) / 1000)) + -- smooth everything a bit, also ignore 0 + self.litersPerMeter = litersPerMeter > 0 and ((self.litersPerMeter + litersPerMeter) / 2) or self.litersPerMeter + self.litersPerSecond = litersPerSecond > 0 and ((self.litersPerSecond + litersPerSecond) / 2) or self.litersPerSecond + else + -- no history yet, so make sure we don't end up with some unrealistic numbers + self.waypointIxWhenFull = nil + self.litersPerMeter = 0 + self.litersPerSecond = 0 + end + self:debug('Fill rate is %.1f l/m, %.1f l/s (fill level %.1f, last %.1f, dToNext = %.1f)', + self.litersPerMeter, self.litersPerSecond, fillLevel, self.fillLevelAtLastWaypoint, dToNext) + self.fillLevelLastCheckedTime = g_currentMission.time + self.fillLevelAtLastWaypoint = fillLevel + end + local litersUntilFull = capacity - fillLevel + local dUntilFull = CpMathUtil.divide(litersUntilFull, self.litersPerMeter) + local litersUntilCallUnloader = capacity * self.settings.callUnloaderPercent:getValue() / 100 - fillLevel + local dUntilCallUnloader = CpMathUtil.divide(litersUntilCallUnloader, self.litersPerMeter) + self.waypointIxWhenFull = self.course:getNextWaypointIxWithinDistance(ix, dUntilFull) or self.course:getNumberOfWaypoints() + local wpDistance + self.waypointIxWhenCallUnloader, wpDistance = self.course:getNextWaypointIxWithinDistance(ix, dUntilCallUnloader) + self:debug('Will be full at waypoint %d, fill level %d at waypoint %d (current waypoint %d), %.1f m and %.1f l until call (currently %.1f l), wp distance %.1f', + self.waypointIxWhenFull or -1, self.settings.callUnloaderPercent:getValue(), self.waypointIxWhenCallUnloader or -1, + self.course:getCurrentWaypointIx(), dUntilCallUnloader, litersUntilCallUnloader, fillLevel, wpDistance) +end + +function AIDriveStrategyCombineCourse:shouldWaitAtEndOfRow() + if self.settings.selfUnload:getValue() then + -- don't wait for anyone when self unloading + return false + end + local nextRowStartIx = self.course:getNextRowStartIx(self.ppc:getRelevantWaypointIx()) + local lastPassedWaypointIx = self.ppc:getLastPassedWaypointIx() or self.ppc:getRelevantWaypointIx() + local distanceToNextTurn = self.course:getDistanceToNextTurn(lastPassedWaypointIx) or math.huge + local closeToTurn = distanceToNextTurn < AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow + -- If close to the end of the row and the pipe would be in the fruit after the turn, and our fill level is high, + -- we always wait here for an unloader, regardless of having a rendezvous or not (unless we have our pipe in fruit here) + if nextRowStartIx and closeToTurn and + self:isPipeInFruitAt(nextRowStartIx) and + self:isFull(AIDriveStrategyCombineCourse.waitForUnloadAtEndOfRowFillLevelThreshold) then + self:checkFruit() + if not self:isPipeInFruit() then + self:debug('shouldWaitAtEndOfRow: Closer than %.1f m to a turn, pipe would be in fruit after turn at %d, fill level over %.1f', + AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow, nextRowStartIx, + AIDriveStrategyCombineCourse.waitForUnloadAtEndOfRowFillLevelThreshold) + return true + end + end + -- Or, if we are close to the turn and have a rendezvous waypoint before the turn + if nextRowStartIx and closeToTurn and + self.unloaderRendezvousWaypointIx and + nextRowStartIx > self.unloaderRendezvousWaypointIx then + self:debug('shouldWaitAtEndOfRow: Closer than %.1f m to a turn and rendezvous waypoint %d is before the turn, waiting for the unloader here', + AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow, self.unloaderRendezvousWaypointIx) + return true + end +end + +------------------------------------------------------------------------------------------------------------------------ +--- Unloader handling +--- +--- We need to answer the following questions: +--- 1. When to call the unloader? +--- 2. Where to meet the unloader? +--- 3. Which unloader to call? +--- +--- To question #3, we check the distance between the unloader and the target (combine and a rendezvous point) and based +--- on the unloader's distance to the target and its fill level, we calculate a score and call the unloader with +--- the best score. +--- +--- To questions #1 and #2, we have to cases: +--- +--- The easy case +--- +--- If the combine is stopped for unloading (either 100% full, or made a pocket or finished the course, etc.), +--- the unloader must drive to the combine immediately. +--- +--- The difficult case +--- +--- The combine is still harvesting, and periodically calculates the waypoint/distance where it will reach the fill +--- level threshold configured. This is the fill level when we want the unload process to start, so the unloader +--- is supposed to meet the combine at that (rendezvous) spot. This answers #2, where to meet the unloader. +--- But when to call the unloader? When it will reach the rendezvous point about the same time (or a little earlier) +--- as the combine reaches it. So we ask around all unloaders to figure out what their estimated time en-route (ETE) +--- to the rendezvous waypoint would be. Then pick the one with the shortest ETE, and if its ETE (plus some reserve) +--- is more than the combine's ETE, call it. +--- +------------------------------------------------------------------------------------------------------------------------ +function AIDriveStrategyCombineCourse:callUnloaderWhenNeeded() + if not self.timeToCallUnloader:get() then + return + end + -- check back again in a few seconds + self.timeToCallUnloader:set(false, 3000) + + if self.unloader:get() then + self:debug('callUnloaderWhenNeeded: already has an unloader assigned (%s)', CpUtil.getName(self.unloader:get())) + return + end + + local bestUnloader, bestEte + if self:isWaitingForUnload() then + self:debug('callUnloaderWhenNeeded: stopped, need unloader here') + bestUnloader, _ = self:findUnloader(self.vehicle, nil) + if bestUnloader then + bestUnloader:getCpDriveStrategy():call(self.vehicle, nil) + end + else + if not self.waypointIxWhenCallUnloader then + self:debug('callUnloaderWhenNeeded: don\'t know yet where to meet the unloader') + return + end + -- Find a good waypoint to unload, as the calculated one may have issues, like pipe would be in the fruit, + -- or in a turn, etc. + -- TODO: isPipeInFruitAllowed + local tentativeRendezvousWaypointIx = self:findBestWaypointToUnload(self.waypointIxWhenCallUnloader, false) + if not tentativeRendezvousWaypointIx then + self:debug('callUnloaderWhenNeeded: can\'t find a good waypoint to meet the unloader') + return + end + bestUnloader, bestEte = self:findUnloader(nil, self.course:getWaypoint(tentativeRendezvousWaypointIx)) + -- getSpeedLimit() may return math.huge (inf), when turning for example, not sure why, and that throws off + -- our ETE calculation + if bestUnloader and self.vehicle:getSpeedLimit(true) < 100 then + local dToUnloadWaypoint = self.course:getDistanceBetweenWaypoints(tentativeRendezvousWaypointIx, + self.course:getCurrentWaypointIx()) + local myEte = dToUnloadWaypoint / (self.vehicle:getSpeedLimit(true) / 3.6) + self:debug('callUnloaderWhenNeeded: best unloader ETE at waypoint %d %.1fs, my ETE %.1fs', + tentativeRendezvousWaypointIx, bestEte, myEte) + if bestEte - 5 > myEte then + -- I'll be at the rendezvous a lot earlier than the unloader which will almost certainly result in the + -- cancellation of the rendezvous. + -- So, set something up further away, with better chances, + -- using the unloader's ETE, knowing that 1) that ETE is for the current rendezvous point, 2) there + -- may be another unloader selected for that waypoint + local dToTentativeRendezvousWaypoint = bestEte * (self.vehicle:getSpeedLimit(true) / 3.6) + self:debug('callUnloaderWhenNeeded: too close to rendezvous waypoint, trying move it %.1fm', + dToTentativeRendezvousWaypoint) + tentativeRendezvousWaypointIx = self.course:getNextWaypointIxWithinDistance( + self.course:getCurrentWaypointIx(), dToTentativeRendezvousWaypoint) + if tentativeRendezvousWaypointIx then + bestUnloader, bestEte = self:findUnloader(nil, self.course:getWaypoint(tentativeRendezvousWaypointIx)) + if bestUnloader then + self:callUnloader(bestUnloader, tentativeRendezvousWaypointIx, bestEte) + end + else + self:debug('callUnloaderWhenNeeded: still can\'t find a good waypoint to meet the unloader') + end + elseif bestEte + 5 > myEte then + -- do not call too early (like minutes before we get there), only when it needs at least as + -- much time to get there as the combine (-5 seconds) + self:callUnloader(bestUnloader, tentativeRendezvousWaypointIx, bestEte) + end + end + end +end + +function AIDriveStrategyCombineCourse:callUnloader(bestUnloader, tentativeRendezvousWaypointIx, bestEte) + if bestUnloader:getCpDriveStrategy():call(self.vehicle, + self.course:getWaypoint(tentativeRendezvousWaypointIx)) then + self.unloaderToRendezvous:set(bestUnloader, 1000 * (bestEte + 30)) + self.unloaderRendezvousWaypointIx = tentativeRendezvousWaypointIx + self:debug('callUnloaderWhenNeeded: harvesting, unloader accepted rendezvous at waypoint %d', self.unloaderRendezvousWaypointIx) + else + self:debug('callUnloaderWhenNeeded: harvesting, unloader rejected rendezvous at waypoint %d', tentativeRendezvousWaypointIx) + end +end + +---@param vehicle table +---@return boolean true if vehicle is an active Courseplay controlled combine/harvester +function AIDriveStrategyCombineCourse.isActiveCpCombine(vehicle) + if not (vehicle.getIsCpActive and vehicle:getIsCpActive()) then + -- not driven by CP + return false + end + local driveStrategy = vehicle.getCpDriveStrategy and vehicle:getCpDriveStrategy() + return driveStrategy and driveStrategy.callUnloader ~= nil +end + +--- Find an unloader to drive to the target, which may either be the combine itself (when stopped and waiting for unload) +--- or a waypoint which the combine will reach in the future. Combine and waypoint parameters are mutually exclusive. +---@param combine table the combine vehicle if we need the unloader come to the combine, otherwise nil +---@param waypoint Waypoint the waypoint where the unloader should meet the combine, otherwise nil. +---@return table, number the best fitting unloader or nil, the estimated time en-route for the unloader to reach the +--- target (combine or waypoint) +function AIDriveStrategyCombineCourse:findUnloader(combine, waypoint) + local bestScore = -math.huge + local bestUnloader, bestEte + for _, vehicle in pairs(g_currentMission.vehicleSystem.vehicles) do + if AIDriveStrategyUnloadCombine.isActiveCpCombineUnloader(vehicle) then + local x, _, z = getWorldTranslation(self.vehicle.rootNode) + ---@type AIDriveStrategyUnloadCombine + local driveStrategy = vehicle:getCpDriveStrategy() + -- look a bit outside of the field as the harvester's root node may be just off the field (like in a turn, + -- or when starting. + if driveStrategy:isServingPosition(x, z, 10) then + local unloaderFillLevelPercentage = driveStrategy:getFillLevelPercentage() + if driveStrategy:isAllowedToBeCalled() and unloaderFillLevelPercentage < 99 then + local unloaderDistance, unloaderEte + if combine then + -- if already stopped, we want the unloader to come to us + unloaderDistance, unloaderEte = driveStrategy:getDistanceAndEteToVehicle(combine) + elseif self.waypointIxWhenCallUnloader then + -- if still going, we want the unloader to meet us at the waypoint + unloaderDistance, unloaderEte = driveStrategy:getDistanceAndEteToWaypoint(waypoint) + end + local score = unloaderFillLevelPercentage - 0.1 * unloaderDistance + self:debug('findUnloader: %s idle on my field, fill level %.1f, distance %.1f, ETE %.1f, score %.1f)', + CpUtil.getName(vehicle), unloaderFillLevelPercentage, unloaderDistance, unloaderEte, score) + if score > bestScore then + bestUnloader = vehicle + bestScore = score + bestEte = unloaderEte + end + else + self:debug('findUnloader: %s serving my field but already busy', CpUtil.getName(vehicle)) + end + else + self:debug('findUnloader: %s is not serving my field', CpUtil.getName(vehicle)) + end + end + end + if bestUnloader then + self:debug('findUnloader: best unloader is %s (score %.1f, ETE %.1f)', + CpUtil.getName(bestUnloader), bestScore, bestEte) + return bestUnloader, bestEte + else + self:debug('findUnloader: no idle unloader found') + end +end + +function AIDriveStrategyCombineCourse:checkRendezvous() + if self.unloaderToRendezvous:get() then + local lastPassedWaypointIx = self.ppc:getLastPassedWaypointIx() or self.ppc:getRelevantWaypointIx() + local d = self.course:getDistanceBetweenWaypoints(lastPassedWaypointIx, self.unloaderRendezvousWaypointIx) + if d < 10 then + self:debugSparse('Slow down around the unloader rendezvous waypoint %d to let the unloader catch up', + self.unloaderRendezvousWaypointIx) + self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue() / 2) + elseif lastPassedWaypointIx > self.unloaderRendezvousWaypointIx then + -- past the rendezvous waypoint + self:debug('Unloader missed the rendezvous at %d', self.unloaderRendezvousWaypointIx) + local unloaderWhoDidNotShowUp = self.unloaderToRendezvous:get() + -- need to call this before onMissedRendezvous as the unloader will call back to set up a new rendezvous + -- and we don't want to cancel that right away + self:cancelRendezvous() + unloaderWhoDidNotShowUp:getCpDriveStrategy():onMissedRendezvous(self.vehicle) + end + if self:isDischarging() then + self:debug('Discharging, cancelling unloader rendezvous') + self:cancelRendezvous() + end + end +end + +function AIDriveStrategyCombineCourse:hasRendezvousWith(unloader) + return self.unloaderToRendezvous:get() == unloader +end + +function AIDriveStrategyCombineCourse:cancelRendezvous() + local unloader = self.unloaderToRendezvous:get() + self:debug('Rendezvous with %s at waypoint %d cancelled', + unloader and CpUtil.getName(self.unloaderToRendezvous:get() or 'N/A'), + self.unloaderRendezvousWaypointIx or -1) + self.unloaderRendezvousWaypointIx = nil + self.unloaderToRendezvous:reset() +end + +--- Before the unloader asks for a rendezvous (which may result in a lengthy pathfinding to figure out +--- the distance), it should check if the combine is willing to rendezvous. +function AIDriveStrategyCombineCourse:isWillingToRendezvous() + if self.state ~= self.states.WORKING then + self:debug('not harvesting, will not rendezvous') + return nil + elseif not self.settings.unloadOnFirstHeadland:getValue() and + self.course:isOnHeadland(self.course:getCurrentWaypointIx(), 1) then + self:debug('on first headland and unload not allowed on first headland, will not rendezvous') + return nil + end + return true +end + +--- An area where the combine is expected to perform a turn between now and the rendezvous waypoint +---@return Waypoint a waypoint, the center of the maneuvering area +---@return number radius around the waypoint, defining a circular area +function AIDriveStrategyCombineCourse:getTurnArea() + if self.unloaderRendezvousWaypointIx then + for ix = self.course:getCurrentWaypointIx(), self.unloaderRendezvousWaypointIx do + if self.course:isTurnEndAtIx(ix) then + return self.course:getWaypoint(ix), self.turningRadius * 3 + end + end + end +end + +function AIDriveStrategyCombineCourse:canUnloadWhileMovingAtWaypoint(ix) + if self:isPipeInFruitAt(ix) then + self:debug('pipe would be in fruit at the planned rendezvous waypoint %d', ix) + return false + end + if not self.settings.unloadOnFirstHeadland:getValue() and self.course:isOnHeadland(ix, 1) then + self:debug('planned rendezvous waypoint %d is on first headland, no unloading of moving combine there', ix) + return false + end + return true +end + +function AIDriveStrategyCombineCourse:checkFruitAtNode(node, offsetX, offsetZ) + local x, _, z = localToWorld(node, offsetX, 0, offsetZ or 0) + local hasFruit, fruitValue = PathfinderUtil.hasFruit(x, z, 1, 1) + return hasFruit, fruitValue +end + +--- Is pipe in fruit according to the current field harvest state at waypoint? +function AIDriveStrategyCombineCourse:isPipeInFruitAtWaypointNow(course, ix) + if not self.storage.fruitCheckHelperWpNode then + self.storage.fruitCheckHelperWpNode = WaypointNode(CpUtil.getName(self.vehicle) .. 'fruitCheckHelperWpNode') + end + self.storage.fruitCheckHelperWpNode:setToWaypoint(course, ix) + local hasFruit, fruitValue = self:checkFruitAtNode(self.storage.fruitCheckHelperWpNode.node, self.pipeController:getPipeOffsetX()) + self:debug('at waypoint %d pipe in fruit %s (fruitValue %.1f)', ix, tostring(hasFruit), fruitValue or 0) + return hasFruit, fruitValue +end + +function AIDriveStrategyCombineCourse:isPipeInFruitAt(ix) + if self.course:getMultiTools() > 1 then + -- we don't have a reliable pipe in fruit map for multitools, so just check + -- if there is fruit at the waypoint now, to be on the safe side + return self:isPipeInFruitAtWaypointNow(self.course, ix) + else + return self.course:isPipeInFruitAt(ix) + end +end + +--- Find the best waypoint to unload. +---@param ix number waypoint index we want to start unloading, either because that's about where +--- we'll rendezvous the unloader or we'll be full there. +---@return number best waypoint to unload, ix may be adjusted to make sure it isn't in a turn or +--- the fruit is not in the pipe. +function AIDriveStrategyCombineCourse:findBestWaypointToUnload(ix, isPipeInFruitAllowed) + if self.course:isOnHeadland(ix) then + return self:findBestWaypointToUnloadOnHeadland(ix) + else + return self:findBestWaypointToUnloadOnUpDownRows(ix, isPipeInFruitAllowed) + end +end + +function AIDriveStrategyCombineCourse:findBestWaypointToUnloadOnHeadland(ix) + if not self.settings.unloadOnFirstHeadland:getValue() and + self.course:isOnHeadland(ix, 1) then + self:debug('planned rendezvous waypoint %d is on first headland, no unloading of moving combine there', ix) + return nil + end + if self.course:isTurnStartAtIx(ix) then + -- on the headland, use the wp after the turn, the one before may be very far, especially on a + -- transition from headland to up/down rows. + return ix + 1 + else + return ix + end +end + +--- We calculated a waypoint to meet the unloader (either because it asked for it or we think we'll need +--- to unload. Now make sure that this location is not around a turn or the pipe isn't in the fruit by +--- trying to move it up or down a bit. If that's not possible, just leave it and see what happens :) +function AIDriveStrategyCombineCourse:findBestWaypointToUnloadOnUpDownRows(ix, isPipeInFruitAllowed) + local dToNextTurn = self.course:getDistanceToNextTurn(ix) or math.huge + local lRow, ixAtRowStart = self.course:getRowLength(ix) + local pipeInFruit = self:isPipeInFruitAt(ix) + local currentIx = self.course:getCurrentWaypointIx() + local newWpIx = ix + self:debug('Looking for a waypoint to unload around %d on up/down row, pipe in fruit %s, dToNextTurn: %d m, lRow = %d m', + ix, tostring(pipeInFruit), dToNextTurn, lRow or 0) + if pipeInFruit and not isPipeInFruitAllowed then + --if the pipe is in fruit AND the user selects 'avoid fruit' + if ixAtRowStart then + if self.course:getMultiTools() > 1 then + self:debug('Pipe may be in fruit at waypoint %d, we have no reliable information as multitool active, rejecting rendezvous', ix) + newWpIx = nil + elseif ixAtRowStart > currentIx then + -- have not started the previous row yet + self:debug('Pipe would be in fruit at waypoint %d. Check previous row', ix) + pipeInFruit, _ = self:isPipeInFruitAt(ixAtRowStart - 2) -- wp before the turn start + if not pipeInFruit then + local lPreviousRow, ixAtPreviousRowStart = self.course:getRowLength(ixAtRowStart - 1) + self:debug('pipe not in fruit in the previous row (%d m, ending at wp %d), rendezvous at %d', + lPreviousRow, ixAtRowStart - 1, newWpIx) + newWpIx = math.max(ixAtRowStart - 3, ixAtPreviousRowStart, currentIx) + else + self:debug('Pipe in fruit in previous row too, rejecting rendezvous') + newWpIx = nil + end + else + -- previous row already started. Could check next row but that means the rendezvous would be after + -- the combine turns, and we'd be in the way during the turn, so rather not worry about the next row + -- until the combine gets there. + self:debug('Pipe would be in fruit at waypoint %d. Previous row is already started, no rendezvous', ix) + newWpIx = nil + end + else + self:debug('Could not determine row length, rejecting rendezvous') + newWpIx = nil + end + else + if (pipeInFruit) then + self:debug('pipe would be in fruit at waypoint %d, acceptable for user', ix) + else + self:debug('pipe is not in fruit at %d. If it is towards the end of the row, bring it up a bit', ix) + end + -- so we'll have some distance for unloading + if ixAtRowStart and dToNextTurn < AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow then + local safeIx = self.course:getPreviousWaypointIxWithinDistance(ix, + AIDriveStrategyCombineCourse.safeUnloadDistanceBeforeEndOfRow) + newWpIx = math.max(ixAtRowStart + 1, safeIx or -1, ix - 4, currentIx) + end + end + -- no better idea, just use the original estimated, making sure we avoid turn start waypoints + if newWpIx and self.course:isTurnStartAtIx(newWpIx) then + self:debug('Calculated rendezvous waypoint is at turn start, moving it up') + -- make sure it is not on the turn start waypoint + return math.max(newWpIx - 1, currentIx) + else + return newWpIx + end +end + +--- Create a temporary course to pull back to the right when the pipe is in the fruit so the tractor does not have +-- to drive in the fruit to get under the pipe +function AIDriveStrategyCombineCourse:createPullBackCourse() + -- all we need is a waypoint on our right side towards the back + self.returnPoint = {} + self.returnPoint.x, _, self.returnPoint.z = getWorldTranslation(self.vehicle.rootNode) + + local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) + self.returnPoint.rotation = MathUtil.getYRotationFromDirection(dx, dz) + dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, -1) + + local x1, _, z1 = localToWorld(self.vehicle:getAIDirectionNode(), -self.pullBackRightSideOffset, 0, -self.pullBackDistanceStart) + local x2, _, z2 = localToWorld(self.vehicle:getAIDirectionNode(), -self.pullBackRightSideOffset, 0, -self.pullBackDistanceEnd) + -- both points must be on the field + if CpFieldUtil.isOnField(x1, z1) and CpFieldUtil.isOnField(x2, z2) then + + local referenceNode, debugText = AIUtil.getReverserNode(self.vehicle) + if referenceNode then + self:debug('Using %s to start pull back course', debugText) + else + referenceNode = AIUtil.getDirectionNode(self.vehicle) + self:debug('Using the direction node to start pull back course') + end + -- don't make this too complicated, just create a straight line on the left/right side (depending on + -- where the pipe is and rely on the PPC, no need for generating fancy curves + return Course.createFromNode(self.vehicle, referenceNode, + -self.pullBackRightSideOffset, 0, -self.pullBackDistanceEnd, -2, true) + else + self:debug("Pull back course would be outside of the field") + return nil + end +end + +--- Get the area the unloader should avoid when approaching the combine. +--- Main (and for now, only) use case is to prevent the unloader to cross in front of the combine after the +--- combine pulled back full with pipe in the fruit, making room for the unloader on its left side. +--- @return table, number, number, number, number node, xOffset, zOffset, width, length : the area to avoid is +--- a length x width m rectangle, the rectangle's bottom right corner (when looking from node) is at xOffset/zOffset +--- from node. +function AIDriveStrategyCombineCourse:getAreaToAvoid() + if self:isWaitingForUnloadAfterPulledBack() then + local xOffset = self:getWorkWidth() / 2 + local zOffset = 0 + local length = self.pullBackDistanceEnd + local width = self.pullBackRightSideOffset + return PathfinderUtil.NodeArea(AIUtil.getDirectionNode(self.vehicle), xOffset, zOffset, width, length) + end +end + +--- Create a temporary course to make a pocket in the fruit on the right (or left), so we can move into that pocket and +--- wait for the unload there. This way the unload tractor does not have to leave the field. +--- We create a temporary course to reverse back far enough. After that, we return to the main course but +--- set an offset to the right (or left) +function AIDriveStrategyCombineCourse:createPocketCourse() + local startIx = self.ppc:getLastPassedWaypointIx() or self.ppc:getCurrentWaypointIx() + -- find the waypoint we want to back up to + local backIx = self.course:getPreviousWaypointIxWithinDistance(startIx, self.pocketReverseDistance) + if not backIx then + return nil + end + -- this where we are back on track after returning from the pocket + self.returnedFromPocketIx = self.ppc:getCurrentWaypointIx() + self:debug('Backing up %.1f meters from waypoint %d to %d to make a pocket', self.pocketReverseDistance, startIx, backIx) + if startIx - backIx > 2 then + local pocketReverseWaypoints = {} + for i = startIx, backIx, -1 do + if self.course:isTurnStartAtIx(i) then + self:debug('There is a turn behind me at waypoint %d, no pocket', i) + return nil + end + local x, _, z = self.course:getWaypointPosition(i) + table.insert(pocketReverseWaypoints, { x = x, z = z, rev = true }) + end + return Course(self.vehicle, pocketReverseWaypoints, true), backIx + 1 + else + self:debug('Not enough waypoints behind me, no pocket') + return nil + end +end + +--- Only allow fuel save, if no trailer is under the pipe and we are waiting for unloading. +function AIDriveStrategyCombineCourse:isFuelSaveAllowed() + if self.pipeController:isFillableTrailerInRange() then + -- Disables Fuel save, when a trailer is under the pipe. + return false + end + --- Enables fuel save, while waiting for the rain to stop. + if self.combine:getIsThreshingDuringRain() then + return true + end + return self:isWaitingForUnload() or self:isChopperWaitingForUnloader() + or AIDriveStrategyCourse.isFuelSaveAllowed(self) +end + +--- Check if the vehicle should stop during a turn for example while it +--- is held for unloading or waiting for the straw swath to stop +function AIDriveStrategyCombineCourse:shouldHoldInTurnManeuver() + --- Hold during discharge + local discharging = self:isDischarging() and not self:alwaysNeedsUnloader() + local stillProcessingFruit = self:alwaysNeedsUnloader() and self:isProcessingFruit() + local isFinishingRow = self.aiTurn and self.aiTurn:isFinishingRow() + local waitForStraw = self.combineController:isDroppingStrawSwath() and not isFinishingRow and not self:isOnHeadland() + + self:debugSparse('Turn maneuver=> Autoaim: %s, discharging: %s, wait for straw: %s, straw swath active: %s, processing: %s, finishing row: %s', + tostring(self:hasAutoAimPipe()), tostring(discharging), tostring(waitForStraw), + tostring(self.combineController:isDroppingStrawSwath()), tostring(stillProcessingFruit), tostring(isFinishingRow)) + -- choppers don't hold, they need to keep moving through turns and actively harvesting + return (discharging or waitForStraw or stillProcessingFruit) and not self:hasAutoAimPipe() +end + +--- Should we return to the first point of the course after we are done? +function AIDriveStrategyCombineCourse:shouldReturnToFirstPoint() + -- Combines stay where they are after finishing work + -- TODO: call unload driver + return false +end + +--- Interface for the unloader and AutoDrive +---@return boolean true when the combine is waiting to be unloaded (and won't move until the unloader gets there) +function AIDriveStrategyCombineCourse:isWaitingForUnload() + return self.state == self.states.UNLOADING_ON_FIELD and + (self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD or + self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK or + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED or + self.unloadState == self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW) +end + +--- Interface for AutoDrive +---@return boolean true when the combine is waiting to be unloaded after it ended the course +function AIDriveStrategyCombineCourse:isWaitingForUnloadAfterCourseEnded() + return self.state == self.states.UNLOADING_ON_FIELD and + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED +end + +function AIDriveStrategyCombineCourse:isWaitingInPocket() + return self.state == self.states.UNLOADING_ON_FIELD and + self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET +end + +--- Interface for Mode 2 +---@return boolean true when the combine is waiting to after it pulled back. +function AIDriveStrategyCombineCourse:isWaitingForUnloadAfterPulledBack() + return self.state == self.states.UNLOADING_ON_FIELD and + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK +end + +---@return boolean the combine is about to turn +function AIDriveStrategyCombineCourse:isAboutToTurn() + if self.state == self.states.WORKING and self.course then + return self.course:isCloseToNextTurn(10) + else + return false + end +end + +--- Can the cutter be turned off ? +function AIDriveStrategyCombineCourse:getCanCutterBeTurnedOff() + return self:isWaitingForUnload() or + (self.state == self.states.UNLOADING_ON_FIELD and self:isUnloadStateOneOf(self.selfUnloadStates) and + -- we want that cutter to be turned on when returning to fieldwork after self unload + self.unloadState ~= self.states.RETURNING_FROM_SELF_UNLOAD) +end + +----------------------------------------------------------------------------------------------------------------------- +--- Turns +----------------------------------------------------------------------------------------------------------------------- + +--- Will we be driving forward only (not reversing) during a turn +function AIDriveStrategyCombineCourse:isTurnForwardOnly() + return self:isTurning() and self.aiTurn and self.aiTurn:isForwardOnly() +end + +function AIDriveStrategyCombineCourse:getTurnCourse() + return self.aiTurn and self.aiTurn:getCourse() +end + +function AIDriveStrategyCombineCourse:startTurn(ix) + self:debug(' Starting a combine turn.') + + self.turnContext = TurnContext(self.vehicle, self.course, ix, ix + 1, self.turnNodes, self:getWorkWidth(), + self.frontMarkerDistance, self.backMarkerDistance, + self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) + + -- Combines drive special headland corner maneuvers, except potato and sugarbeet harvesters + if self.turnContext:isHeadlandCorner() then + if self.combineController:isTowed() then + self:debug('Headland turn but this is a towed harvester using normal turn maneuvers.') + AIDriveStrategyFieldWorkCourse.startTurn(self, ix) + -- The type of fruit being harvested isn't really the indicator if we can make a headland turn + -- TODO: either make disabling combine headland turns configurable, or + -- TODO: decide automatically based on the vehicle's properties, like turn radius, work width, etc. + -- and disable when such a turn does not make sense for the vehicle. + elseif self.combineController:isRootVegetableHarvester() then + self:debug('Headland turn but this harvester uses normal turn maneuvers.') + AIDriveStrategyFieldWorkCourse.startTurn(self, ix) + elseif self.course:isOnConnectingPath(ix) then + self:debug('Headland turn but this a connecting track, use normal turn maneuvers.') + AIDriveStrategyFieldWorkCourse.startTurn(self, ix) + elseif self.course:isOnOutermostHeadland(ix) and self:isTurnOnFieldActive() then + self:debug('Creating a pocket in the corner so the combine stays on the field during the turn') + self.aiTurn = CombinePocketHeadlandTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, + self.course, self:getWorkWidth()) + self.state = self.states.TURNING + self.ppc:setShortLookaheadDistance() + else + self:debug('Use combine headland turn.') + self.aiTurn = CombineHeadlandTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext) + self.state = self.states.TURNING + end + else + self:debug('Non headland turn.') + if self.settings.selfUnload:getValue() and self:shouldSelfUnloadBeforeNextRow() then + self:debug('Fill level over %.0f, attempt to self unload before continue with next row', + AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowFillLevelThreshold) + -- will continue at the turn end waypoint after the unload is finished + self:rememberCourse(self.fieldWorkCourse, ix + 1) + self.aiTurn = FinishRowOnly(self.vehicle, self, self.ppc, self.proximityController, self.turnContext) + self.aiTurn:registerTurnEndCallback(self, AIDriveStrategyCombineCourse.startSelfUnloadBeforeNextRow) + self.state = self.states.TURNING + -- continue at the turn end waypoint on failed pathfinding + self.waypointIxToContinueOnFailedSelfUnload = ix + 1 + else + AIDriveStrategyFieldWorkCourse.startTurn(self, ix) + end + end +end + +function AIDriveStrategyCombineCourse:isTurning() + return self.state == self.states.TURNING +end + +-- Turning except in the ending turn phase which isn't really a turn, it is rather 'starting row' +function AIDriveStrategyCombineCourse:isTurningButNotEndingTurn() + return self:isTurning() and self.aiTurn and not self.aiTurn:isEndingTurn() +end + +function AIDriveStrategyCombineCourse:isFinishingRow() + return self:isTurning() and self.aiTurn and self.aiTurn:isFinishingRow() +end + +function AIDriveStrategyCombineCourse:getTurnStartWpIx() + return self.turnContext and self.turnContext.turnStartWpIx or nil +end + +function AIDriveStrategyCombineCourse:isTurningOnHeadland() + return self.state == self.states.TURNING and self.turnContext and self.turnContext:isHeadlandCorner() +end + +function AIDriveStrategyCombineCourse:isTurningLeft() + return self.state == self.states.TURNING and self.turnContext and self.turnContext:isLeftTurn() +end + +function AIDriveStrategyCombineCourse:getFieldworkCourse() + return self.course +end + +function AIDriveStrategyCombineCourse:alwaysNeedsUnloader() + return self.combineController:alwaysNeedsUnloader() +end + +function AIDriveStrategyCombineCourse:isProcessingFruit() + return self.combineController:isProcessingFruit() +end + +function AIDriveStrategyCombineCourse:isFillableTrailerUnderPipe() + return self.pipeController:isFillableTrailerUnderPipe() +end + +function AIDriveStrategyCombineCourse:hasAutoAimPipe() + return self.pipeController:isAutoAimPipe() +end + +function AIDriveStrategyCombineCourse:isChopper() + return self.combineController:isChopper() +end + +----------------------------------------------------------------------------------------------------------------------- +--- Pipe handling +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCombineCourse:handlePipe(dt) + if self:isChopper() then + self.pipeController:handleChopperPipe() + else + self:handleCombinePipe(dt) + end +end + +function AIDriveStrategyCombineCourse:handleCombinePipe(dt) + -- don't open the pipe while turning + if self:isPipeOpenEnabled() and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then + self.pipeController:openPipe() + else + if not self.forcePipeOpen:get() then + -- when driving to self unload, we may open the pipe well before reaching the trailer to avoid + -- banging it into the trailer. This is configurable, and needed for harvester opening their pipe + -- upwards + self.pipeController:closePipe(true) + end + end +end + +--- we don't want random triggers to open the pipe while turning or driving to another trailer +function AIDriveStrategyCombineCourse:isPipeOpenEnabled() + if self:isTurning() then + return false + elseif self.state == self.states.UNLOADING_ON_FIELD and + self:isUnloadStateOneOf(self.drivingToSelfUnloadStates) and not self:isCloseToCourseEnd(10) then + return false + else + return true + end +end + +function AIDriveStrategyCombineCourse:isAGoodTrailerInRange() + local _, trailer = self.pipeController:isFillableTrailerInRange() + local unloaderVehicle = trailer and trailer:getRootVehicle() + + if unloaderVehicle == nil then + return false + end + + if AIDriveStrategyUnloadCombine.isActiveCpCombineUnloader(unloaderVehicle) then + return unloaderVehicle:getCpDriveStrategy():getCombineToUnload() == self.vehicle + else + return true + end +end + +--- Not exactly sure what this does, but without this the chopper just won't move. +--- Copied from AIDriveStrategyCombine:update() +function AIDriveStrategyCombineCourse:updateChopperFillType() + if self:isChopper() then + self.combineController:updateChopperFillType() + end +end + +function AIDriveStrategyCombineCourse:isChopperWaitingForUnloader() + return self.waitingForTrailer +end + +function AIDriveStrategyCombineCourse:isAnyWorkAreaProcessing() + for _, implement in pairs(self.vehicle:getChildVehicles()) do + if implement.spec_workArea ~= nil then + for i, workArea in pairs(implement.spec_workArea.workAreas) do + if implement:getIsWorkAreaProcessing(workArea) then + return true + end + end + end + end + return false +end + +function AIDriveStrategyCombineCourse:isPipeMoving() + return self.pipeController:isPipeMoving() +end + +function AIDriveStrategyCombineCourse:canLoadTrailer(trailer) + local fillType = self:getFillType() + return FillLevelManager.canLoadTrailer(trailer, fillType) +end + +function AIDriveStrategyCombineCourse:getCurrentDischargeNode() + return self.pipeController:getDischargeNode() +end + +function AIDriveStrategyCombineCourse:getFillLevelPercentage() + return self.combineController:getFillLevelPercentage() +end + +--- Support for AutoDrive mod: they'll only find us if we open the pipe +function AIDriveStrategyCombineCourse:isAutoDriveWaitingForPipe() + return self.vehicle.spec_autodrive and self.vehicle.spec_autodrive.combineIsCallingDriver and + self.vehicle.spec_autodrive:combineIsCallingDriver(self.vehicle) +end + +function AIDriveStrategyCombineCourse:shouldStopForUnloading() + if self.settings.stopForUnload:getValue() then + if self:isDischarging() and not self.stopDisabledAfterEmpty:get() then + -- stop only if the pipe is discharging AND we have been emptied a while ago. + -- this makes sure the combine will start driving after it is emptied but the trailer + -- is still under the pipe + return true + end + end + return false +end + +function AIDriveStrategyCombineCourse:getFillType() + return self.pipeController:getFillType() +end + +-- even if there is a trailer in range, we should not start moving until the pipe is turned towards the +-- trailer and can start discharging. This returning true does not mean there's a trailer under the pipe, +-- this seems more like for choppers to check if there's a potential target around +function AIDriveStrategyCombineCourse:canDischarge() + return self.pipeController:getDischargeObject() +end + +function AIDriveStrategyCombineCourse:isDischarging() + return self.pipeController:isDischarging() +end + + +----------------------------------------------------------------------------------------------------------------------- +--- Self unload +--- When self unload is enabled, instead of calling an unloader when full, the combine will find +--- a suitable trailer on or around the field, drive to the trailer and empty the grain tank into the trailer. +--- See SelfUnloadHelper.lua on how exactly a trailer is picked. +--- +--- There are two events which can trigger a self unload: +--- 1. grain tank full +--- 2. grain tank at more than 60% when the combine just finished a row and there is a +--- trailer nearby. + +----------------------------------------------------------------------------------------------------------------------- +--- Find a path to the best trailer to unload +function AIDriveStrategyCombineCourse:startSelfUnload(unloadStateAfterPathfindingDoneForSelfUnload, + bestTrailer, fillRootNode) + + if not self.pathfinder or not self.pathfinder:isActive() then + self.pathfindingStartedAt = g_currentMission.time + self.courseAfterPathfinding = nil + self.waypointIxAfterPathfinding = nil + + local targetNode, alignLength, offsetX, trailer = SelfUnloadHelper:getTargetParameters( + self.fieldPolygon, + self.vehicle, + self.implementWithPipe, + self.pipeController, + bestTrailer, + fillRootNode) + + if not targetNode then + return false + end + + -- little straight section parallel to the trailer to align better + self.selfUnloadAlignCourse = Course.createFromNode(self.vehicle, targetNode, + offsetX, -alignLength + 1, -self.pipeController:getPipeOffsetZ() - 1, 1, false) + + self.unloadStateAfterPathfindingDoneForSelfUnload = unloadStateAfterPathfindingDoneForSelfUnload + + local fieldNum = CpFieldUtil.getFieldNumUnderVehicle(self.vehicle) + local context = PathfinderContext(self.vehicle):mustBeAccurate(true) + context:allowReverse(self:getAllowReversePathfinding()):useFieldNum(fieldNum) + -- use no field penalty around the trailer to encourage the pathfinder to bridge that gap between the field + -- and the trailer + context:areaToIgnoreOffFieldPenalty( + PathfinderUtil.NodeArea.createVehicleArea(trailer, 1.5 * SelfUnloadHelper.maxDistanceFromField)) + local result + -- require full accuracy from pathfinder as we must exactly line up with the trailer + self.pathfinder, result = PathfinderUtil.startPathfindingFromVehicleToNode( + targetNode, offsetX, -alignLength, context) + if result.done then + return self:onPathfindingDoneBeforeSelfUnload(result.path) + else + self.state = self.states.WAITING_FOR_PATHFINDER + self:setPathfindingDoneCallback(self, self.onPathfindingDoneBeforeSelfUnload) + end + else + self:debug('Pathfinder already active') + end + return true +end + +function AIDriveStrategyCombineCourse:onPathfindingDoneBeforeSelfUnload(path) + if path and #path > 2 then + self:debug('Pathfinding to self unload finished with %d waypoints (%d ms)', + #path, g_currentMission.time - (self.pathfindingStartedAt or 0)) + local selfUnloadCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) + if self.selfUnloadAlignCourse then + selfUnloadCourse:append(self.selfUnloadAlignCourse) + self.selfUnloadAlignCourse = nil + end + self:raiseImplements() + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.unloadStateAfterPathfindingDoneForSelfUnload + self.ppc:setShortLookaheadDistance() + self:startCourse(selfUnloadCourse, 1) + return true + else + self:debug('No path found to self unload in %d ms', + g_currentMission.time - (self.pathfindingStartedAt or 0)) + if self.unloadStateAfterPathfindingDoneForSelfUnload == self.states.DRIVING_TO_SELF_UNLOAD then + self.unloadState = self.states.WAITING_FOR_UNLOAD_ON_FIELD + elseif self.unloadStateAfterPathfindingDoneForSelfUnload == self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW then + -- continue turn as if nothing happened + self:startCourse(self.fieldWorkCourse, self.waypointIxToContinueOnFailedSelfUnload) + AIDriveStrategyFieldWorkCourse.startTurn(self, self.waypointIxToContinueOnFailedSelfUnload) + elseif self.unloadStateAfterPathfindingDoneForSelfUnload == self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED then + self.unloadState = self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED + end + return false + end +end + +--- Back to fieldwork after self unloading +function AIDriveStrategyCombineCourse:returnToFieldworkAfterSelfUnload() + if not self.pathfinder or not self.pathfinder:isActive() then + self.pathfindingStartedAt = g_currentMission.time + local fieldWorkCourse, ix = self:getRememberedCourseAndIx() + self:debug('Return to fieldwork after self unload at waypoint %d', ix) + local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) + local result + self.pathfinder, result = PathfinderUtil.startPathfindingFromVehicleToWaypoint( + fieldWorkCourse, ix, self.settings.toolOffsetX:getValue(), 0, context) + if result.done then + return self:onPathfindingDoneAfterSelfUnload(result.path) + else + self.state = self.states.WAITING_FOR_PATHFINDER + self:setPathfindingDoneCallback(self, self.onPathfindingDoneAfterSelfUnload) + end + else + self:debug('Pathfinder already active') + end + return true +end + +function AIDriveStrategyCombineCourse:onPathfindingDoneAfterSelfUnload(path) + -- TODO: for some reason, the combine lowers the header while unloading, that should be fixed, for now, raise it here + self:raiseImplements() + local course, ix = self:getRememberedCourseAndIx() + local fm, bm = self:getFrontAndBackMarkers() + self.turnContext = RowStartOrFinishContext(self.vehicle, course, ix, ix, self.turnNodes, self:getWorkWidth(), + fm, bm, 0, 0) + if path and #path > 2 then + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.states.RETURNING_FROM_SELF_UNLOAD + self.ppc:setShortLookaheadDistance() + self:debug('Pathfinding to return to fieldwork after self unload finished with %d waypoints (%d ms)', + #path, g_currentMission.time - (self.pathfindingStartedAt or 0)) + local returnCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) + returnCourse:adjustForTowedImplements(2) + self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, returnCourse) + self:startCourse(self.workStarter:getCourse(), 1) + return true + else + self:debug('No path found to return to fieldwork after self unload (%d ms)', + g_currentMission.time - (self.pathfindingStartedAt or 0)) + local returnCourse = AlignmentCourse(self.vehicle, self.vehicle:getAIDirectionNode(), + self.turningRadius, course, ix, 0):getCourse() + if returnCourse then + self.state = self.states.UNLOADING_ON_FIELD + self.unloadState = self.states.RETURNING_FROM_SELF_UNLOAD + self.ppc:setShortLookaheadDistance() + self:debug('Start an alignment course to fieldwork waypoint %d', ix) + self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, returnCourse) + self:startCourse(self.workStarter:getCourse(), 1) + else + self.state = self.states.WORKING + self:debug('Could not generate alignment course to fieldwork waypoint %d, starting course directly', ix) + self:startRememberedCourse() + end + return false + end +end + +function AIDriveStrategyCombineCourse:continueSelfUnloadToNextTrailer() + local fillLevel = self.combineController:getFillLevel() + if fillLevel > 20 then + self:debug('Self unloading finished, but fill level is %.1f, is there another trailer around we can unload to?', fillLevel) + if self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD) then + return true + end + end + return false +end + +function AIDriveStrategyCombineCourse:shouldSelfUnloadBeforeNextRow() + if self:isFull(AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowFillLevelThreshold) then + local distance + self.selfUnloadBestTrailer, self.selfUnloadFillRootNode, distance = SelfUnloadHelper:findBestTrailer( + self.fieldPolygon, + self.vehicle, + self.implementWithPipe, + self.pipeController.pipeOffsetX) + if not self.selfUnloadBestTrailer then + return false + end + local dx, _, dz = localToLocal(AIUtil.getDirectionNode(self.vehicle), + AIUtil.getDirectionNode(self.selfUnloadBestTrailer), 0, 0, 0) + self:debug('Best trailer dx: %.1f, dz: %.1f', dx, dz) + -- the best trailer is sort of in front of us and not too far to the left or right + if dz > -10 and math.abs(dx) < AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowMaxDistance then + self:debug('Best trailer for self unload is less than %.0f m, start self unload before continuing with next row', + AIDriveStrategyCombineCourse.selfUnloadBeforeNextRowMaxDistance) + return true + end + end + return false +end + +-- This is a callback from the "finish row only" turn initiated at the end of the row when there is a +-- trailer nearby for self unloading. At this point, the row is done and we are ready to drive to the +-- trailer before starting the next row +function AIDriveStrategyCombineCourse:startSelfUnloadBeforeNextRow(ix) + -- giving back control to the strategy, re-register these listeners + if self:startSelfUnload(self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW, + self.selfUnloadBestTrailer, self.selfUnloadFillRootNode) then + self:debug('Start self unload before continuing with next row') + else + -- giving up self unload, attempt to re-initialize the turn + self:startCourse(self.fieldWorkCourse, self.waypointIxToContinueOnFailedSelfUnload) + AIDriveStrategyFieldWorkCourse.startTurn(self, self.waypointIxToContinueOnFailedSelfUnload) + end +end + +--- Let unloaders register for events. This is different from the CombineUnloadManager registration, these +--- events are for the low level coordination between the combine and its unloader(s). CombineUnloadManager +--- takes care about coordinating the work between multiple combines. +function AIDriveStrategyCombineCourse:clearAllUnloaderInformation() + self:cancelRendezvous() + self.unloader:reset() +end + +--- Register a combine unload AI driver for notification about combine events +--- Unloaders can renew their registration as often as they want to make sure they remain registered. +---@param driver AIDriveStrategyUnloadCombine +function AIDriveStrategyCombineCourse:registerUnloader(driver) + self.unloader:set(driver, 1000) +end + +--- Deregister a combine unload AI driver from notifications +---@param driver AIDriveStrategyUnloadCombine +function AIDriveStrategyCombineCourse:deregisterUnloader(driver, noEventSend) + self:cancelRendezvous() + self.unloader:reset() +end + +--- Make life easier for unloaders, increases reach of the pipe +--- Old code ?? +function AIDriveStrategyCombineCourse:fixMaxRotationLimit() + if self.pipe then + local lastPipeNode = self.pipe.nodes and self.pipe.nodes[#self.pipe.nodes] + if self:isChopper() and lastPipeNode and lastPipeNode.maxRotationLimits then + self.oldLastPipeNodeMaxRotationLimit = lastPipeNode.maxRotationLimits + self:debug('Chopper fix maxRotationLimits, old Values: x=%s, y= %s, z =%s', tostring(lastPipeNode.maxRotationLimits[1]), tostring(lastPipeNode.maxRotationLimits[2]), tostring(lastPipeNode.maxRotationLimits[3])) + lastPipeNode.maxRotationLimits = nil + end + end +end + +--- Old code ?? +function AIDriveStrategyCombineCourse:resetFixMaxRotationLimit() + if self.pipe then + local lastPipeNode = self.pipe.nodes and self.pipe.nodes[#self.pipe.nodes] + if lastPipeNode and self.oldLastPipeNodeMaxRotationLimit then + lastPipeNode.maxRotationLimits = self.oldLastPipeNodeMaxRotationLimit + self:debug('Chopper: reset maxRotationLimits is x=%s, y= %s, z =%s', tostring(lastPipeNode.maxRotationLimits[1]), tostring(lastPipeNode.maxRotationLimits[3]), tostring(lastPipeNode.maxRotationLimits[3])) + self.oldLastPipeNodeMaxRotationLimit = nil + end + end +end + +--- Offset of the pipe from the combine implement's root node +---@param additionalOffsetX number add this to the offsetX if you don't want to be directly under the pipe. If +--- greater than 0 -> to the left, less than zero -> to the right +---@param additionalOffsetZ number forward (>0)/backward (<0) offset from the pipe +---@return number, number, boolean X and Z offset of pipe, true the pipe is auto aiming, that is, can discharge either side +function AIDriveStrategyCombineCourse:getPipeOffset(additionalOffsetX, additionalOffsetZ) + local pipeOffsetX, pipeOffsetZ = self.pipeController:getPipeOffset() + return pipeOffsetX + (additionalOffsetX or 0), pipeOffsetZ + (additionalOffsetZ or 0), self:hasAutoAimPipe() +end + +--- Pipe side offset relative to course. This is to help the unloader +--- to find the pipe when we are waiting in a pocket +function AIDriveStrategyCombineCourse:getPipeOffsetFromCourse() + return self.pipeController:getPipeOffset() +end + +function AIDriveStrategyCombineCourse:initUnloadStates() + self.safeUnloadFieldworkStates = { + self.states.WORKING, + self.states.WAITING_FOR_LOWER, + self.states.WAITING_FOR_LOWER_DELAYED, + self.states.WAITING_FOR_STOP, + } + + self.safeFieldworkUnloadOrRefillStates = { + self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED, + self.states.WAITING_FOR_UNLOAD_ON_FIELD, + self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK, + self.states.WAITING_FOR_UNLOAD_IN_POCKET, + self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW + } + + self.willWaitForUnloadToFinishFieldworkStates = { + self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK, + self.states.WAITING_FOR_UNLOAD_IN_POCKET, + self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED, + self.states.WAITING_FOR_UNLOAD_BEFORE_STARTING_NEXT_ROW + } + --- All self unload states. + self.selfUnloadStates = { + self.states.DRIVING_TO_SELF_UNLOAD, + self.states.SELF_UNLOADING, + self.states.SELF_UNLOADING_WAITING_FOR_DISCHARGE, + self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED, + self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW, + self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED, + self.states.SELF_UNLOADING_AFTER_FIELDWORK_ENDED_WAITING_FOR_DISCHARGE, + self.states.RETURNING_FROM_SELF_UNLOAD + } + self.drivingToSelfUnloadStates = { + self.states.DRIVING_TO_SELF_UNLOAD, + self.states.DRIVING_TO_SELF_UNLOAD_AFTER_FIELDWORK_ENDED, + self.states.DRIVING_TO_SELF_UNLOAD_BEFORE_NEXT_ROW, + } +end + +function AIDriveStrategyCombineCourse:isStateOneOf(myState, states) + return CpUtil.isStateOneOf(myState, states) +end + +function AIDriveStrategyCombineCourse:isUnloadStateOneOf(states) + return self:isStateOneOf(self.unloadState, states) +end + +-- TODO: this whole logic is more relevant to the unloader maybe move it there? +function AIDriveStrategyCombineCourse:getClosestFieldworkWaypointIx() + if self:isTurning() then + if self.turnContext then + -- send turn start wp, unloader will decide if it needs to move it to the turn end or not + return self.turnContext.turnStartWpIx + else + -- if for whatever reason we don't have a turn context, current waypoint is ok + return self.course:getCurrentWaypointIx() + end + elseif self.course:isTemporary() then + return self.course:getLastPassedWaypointIx() + else + -- if currently on the fieldwork course, this is the best estimate + return self.ppc:getRelevantWaypointIx() + end +end + +--- Maneuvering means turning or working on a pocket or pulling back due to the pipe in fruit +--- We don't want to get too close to a maneuvering combine until it is done +function AIDriveStrategyCombineCourse:isManeuvering() + return self:isTurning() or + ( + self.state == self.states.UNLOADING_ON_FIELD and + not self:isUnloadStateOneOf(self.safeFieldworkUnloadOrRefillStates) + ) +end + +function AIDriveStrategyCombineCourse:isOnHeadland(n) + return self.course:isOnHeadland(self.course:getCurrentWaypointIx(), n) +end + +--- Are we ready for an unloader? +--- @param noUnloadWithPipeInFruit boolean pipe must not be in fruit for unload +function AIDriveStrategyCombineCourse:isReadyToUnload(noUnloadWithPipeInFruit) + -- no unloading when not in a safe state (like turning) + -- in these states we are always ready + if self:willWaitForUnloadToFinish() then + return true + end + + -- but, if we are full and waiting for unload, we have no choice, we must be ready ... + if self.state == self.states.UNLOADING_ON_FIELD and self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD then + return true + end + + -- pipe is in the fruit. + if noUnloadWithPipeInFruit and self:isPipeInFruit() then + self:debugSparse('isReadyToUnload(): pipe in fruit') + return false + end + + if not self.course then + self:debugSparse('isReadyToUnload(): has no fieldwork course') + return false + end + + -- around a turn, for example already working on the next row but not done with the turn yet + + if self.course:isCloseToNextTurn(10) then + self:debugSparse('isReadyToUnload(): too close to turn') + return false + end + -- safe default, better than block unloading + self:debugSparse('isReadyToUnload(): defaulting to ready to unload') + return true +end + +--- Will not move until unload is done? Unloaders like to know this. +function AIDriveStrategyCombineCourse:willWaitForUnloadToFinish() + return self.state == self.states.UNLOADING_ON_FIELD and + ((self.settings.stopForUnload:getValue() and self.unloadState == self.states.WAITING_FOR_UNLOAD_ON_FIELD) or + self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_PULLED_BACK or + self.unloadState == self.states.WAITING_FOR_UNLOAD_AFTER_FIELDWORK_ENDED) +end + +--- Try to not hit our Unloader after Pocket. +function AIDriveStrategyCombineCourse:isAboutToReturnFromPocket() + return self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET or + (self.unloadState == self.states.WAITING_FOR_UNLOADER_TO_LEAVE and + self.stateBeforeWaitingForUnloaderToLeave == self.states.WAITING_FOR_UNLOAD_IN_POCKET) +end + +------------------------------------------------------------------------------------------------------------------------ +--- Proximity +------------------------------------------------------------------------------------------------------------------------ + +AIDriveStrategyCombineCourse.maxBackDistance = 10 + +function AIDriveStrategyCombineCourse:getMeasuredBackDistance() + return self.measuredBackDistance +end + +--- Determine how far the back of the combine is from the direction node +-- TODO: attached/towed harvesters +function AIDriveStrategyCombineCourse:measureBackDistance() + self.measuredBackDistance = 0 + -- raycast from a point behind the vehicle forward towards the direction node + local nx, ny, nz = localDirectionToWorld(AIUtil.getDirectionNode(self.vehicle), 0, 0, 1) + local x, y, z = localToWorld(AIUtil.getDirectionNode(self.vehicle), 0, 1.5, -self.maxBackDistance) + raycastAll(x, y, z, nx, ny, nz, self.maxBackDistance, 'raycastBackCallback', self) +end + +-- I believe this tries to figure out how far the back of a combine is from its direction node. +function AIDriveStrategyCombineCourse:raycastBackCallback(hitObjectId, x, y, z, distance, nx, ny, nz, subShapeIndex) + if hitObjectId ~= 0 then + local object = g_currentMission:getNodeObject(hitObjectId) + if object and object == self.vehicle then + local d = self.maxBackDistance - distance + if d > self.measuredBackDistance then + self.measuredBackDistance = d + self:debug('Measured back distance is %.1f m', self.measuredBackDistance) + end + else + return true + end + end +end + +function AIDriveStrategyCombineCourse:onDraw() + if CpDebug:isChannelActive(CpDebug.DBG_IMPLEMENTS, self.vehicle) then + + local dischargeNode = self:getCurrentDischargeNode() + if dischargeNode then + local dx, _, dz = localToLocal(dischargeNode.node, self.vehicle:getAIDirectionNode(), 0, 0, 0) + DebugUtil.drawDebugNode(dischargeNode.node, string.format('discharge\n%.1f %.1f', dx, dz)) + end + end + + if CpDebug:isChannelActive(CpDebug.DBG_PATHFINDER, self.vehicle) then + local areaToAvoid = self:getAreaToAvoid() + if areaToAvoid then + local x, y, z = localToWorld(areaToAvoid.node, areaToAvoid.xOffset, 0, areaToAvoid.zOffset) + DebugUtil.drawDebugLine(x, y + 1.2, z, 10, 10, 10, x, y + 1.2, z + areaToAvoid.length) + DebugUtil.drawDebugLine(x + areaToAvoid.width, y + 1.2, z, 10, 10, 10, x + areaToAvoid.width, y + 1.2, z + areaToAvoid.length) + end + end + + if CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then + if self.state == self.states.UNLOADING_ON_FIELD and + (self.unloadState == self.states.REVERSING_TO_MAKE_A_POCKET or self.unloadState == self.states.MAKING_POCKET or + self.unloadState == self.states.WAITING_FOR_UNLOAD_IN_POCKET) then + self.pocketHelperNode:draw() + end + end +end + +--- Don't slow down when discharging. This is a workaround for unloaders getting into the proximity +--- sensor's range. +function AIDriveStrategyCombineCourse:isProximitySlowDownEnabled(vehicle) + -- if not on fieldwork, always enable slowing down + if self.state ~= self.states.WORKING then + return true + end + -- TODO: check if vehicle is player or AD driven, or even better, check if this is the vehicle + -- we are discharging into + if vehicle and self:isDischarging() then + self:debugSparse('discharging, not slowing down for nearby %s', CpUtil.getName(vehicle)) + return false + else + return true + end +end + +--- This is called by the proximity controller if we have been blocked by another vehicle for a while +function AIDriveStrategyCombineCourse:onBlockingVehicle(vehicle, isBack) + if isBack then + self:debug('Proximity sensor: blocking vehicle %s behind us', CpUtil.getName(vehicle)) + self:checkBlockingUnloader() + else + self:debug('Proximity sensor: blocking vehicle %s in front of us', CpUtil.getName(vehicle)) + local strategy = vehicle.getCpDriveStrategy and vehicle:getCpDriveStrategy() + if strategy and strategy.requestToMoveOutOfWay then + strategy:requestToMoveOutOfWay(self.vehicle, isBack) + end + end +end + +-- An unloader may request the harvester to ignore its proximity because the unloader will take care +-- of staying away. +function AIDriveStrategyCombineCourse:requestToIgnoreProximity(vehicle) + self.unloaderRequestedToIgnoreProximity:set(vehicle, 1000) +end + +-- Proximity controller checking if an object/vehicle should be ignored +function AIDriveStrategyCombineCourse:ignoreProximityObject(object, vehicle, moveForwards, hitTerrain) + return vehicle == self.unloaderRequestedToIgnoreProximity:get() +end + +--- Check if the unloader is blocking us when we are reversing in a turn and immediately notify it +function AIDriveStrategyCombineCourse:checkBlockingUnloader() + if not self.ppc:isReversing() and not AIUtil.isReversing(self.vehicle) then + return + end + local d, blockingVehicle = self.proximityController:checkBlockingVehicleBack() + if blockingVehicle ~= self.unloaderRequestedToIgnoreProximity:get() and d < 1000 and + blockingVehicle and AIUtil.isStopped(self.vehicle) and + not self:isWaitingForUnload() and not self:shouldHoldInTurnManeuver() then + -- try requesting only if the unloader really blocks us, that is we are actually backing up but + -- can't move because of the unloader, and not when we are stopped for other reasons + self:debugSparse('Can\'t reverse, %s at %.1f m is blocking', blockingVehicle:getName(), d) + local strategy = blockingVehicle.getCpDriveStrategy and blockingVehicle:getCpDriveStrategy() + if strategy and strategy.requestToBackupForReversingCombine then + strategy:requestToBackupForReversingCombine(self.vehicle) + end + end +end + +function AIDriveStrategyCombineCourse:getWorkingToolPositionsSetting() + local setting = self.settings.pipeToolPositions + return setting:getHasMoveablePipe() and setting:hasValidToolPositions() and setting +end + +------------------------------------------------------------------------------------------------------------------------ +--- Info texts, makes sure the unloadState also gets checked. +--------------------------------------------------------------------------------------------------------------------------- + +function AIDriveStrategyCombineCourse:updateInfoTexts() + for infoText, states in pairs(self.registeredInfoTexts) do + if states.states[self.state] and states.unloadStates[self.unloadState] then + self:setInfoText(infoText) + else + self:clearInfoText(infoText) + end + end +end diff --git a/scripts/ai/AIDriveStrategyCourse.lua b/scripts/ai/strategies/AIDriveStrategyCourse.lua similarity index 97% rename from scripts/ai/AIDriveStrategyCourse.lua rename to scripts/ai/strategies/AIDriveStrategyCourse.lua index e7eb5f8ce..7bd9f5c92 100644 --- a/scripts/ai/AIDriveStrategyCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCourse.lua @@ -1,706 +1,706 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) -Copyright (C) 2021 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. - -Base class for all Courseplay drive strategies - -]] - ----@class AIDriveStrategyCourse -AIDriveStrategyCourse = CpObject() - -AIDriveStrategyCourse.myStates = { - INITIAL = {}, - WAITING_FOR_PATHFINDER = {}, -} - ---- Implement controller events. ---- TODO_25 a more generic implementation -AIDriveStrategyCourse.onRaisingEvent = "onRaising" -AIDriveStrategyCourse.onLoweringEvent = "onLowering" -AIDriveStrategyCourse.onFinishedEvent = "onFinished" -AIDriveStrategyCourse.onStartEvent = "onStart" -AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling" -AIDriveStrategyCourse.onStopRefillingEvent = "onStopRefilling" -AIDriveStrategyCourse.onUpdateRefillingEvent = "onUpdateRefilling" -AIDriveStrategyCourse.updateEvent = "update" -AIDriveStrategyCourse.deleteEvent = "delete" ---- A row has just been finished, implements are being raised and about to start the actual turn -AIDriveStrategyCourse.onFinishRowEvent = "onFinishRow" ---- The actual turn is done, now we are starting into the row and will lower the implements when ---- they reach the start of the row -AIDriveStrategyCourse.onTurnEndProgressEvent = "onTurnEndProgress" - ----@param task CpAITask ----@param job CpAIJob -function AIDriveStrategyCourse:init(task, job) - self.debugChannel = CpDebug.DBG_AI_DRIVER - self:initStates(AIDriveStrategyCourse.myStates) - ---@type ImplementController[] - self.controllers = {} - self.registeredInfoTexts = {} - --- To temporary hold a vehicle (will force speed to 0) - self.held = CpTemporaryObject() - self.fuelSaveActiveWhileHeld = false - - self.currentTask = task - self.job = job -end - -function AIDriveStrategyCourse:setCurrentTaskFinished() - self.currentTask:skip() -end - ---- Aggregation of states from this and all descendant classes -function AIDriveStrategyCourse:initStates(newStates) - self.states = CpUtil.initStates(self.states, newStates) -end - -function AIDriveStrategyCourse:getStateAsString() - return self.state.name -end - -function AIDriveStrategyCourse:getName() - return CpUtil.getName(self.vehicle) -end - -function AIDriveStrategyCourse:debug(...) - CpUtil.debugVehicle(self.debugChannel, self.vehicle, self:getStateAsString() .. ': ' .. string.format(...)) -end - -function AIDriveStrategyCourse:debugSparse(...) - local nowSecs = math.floor(g_time / 1000) - -- report every 5 seconds - -- TODO: make this a parameter in seconds? - if not self.lastLogSecs or (nowSecs > self.lastLogSecs and nowSecs % 5 == 0) then - self:debug(...) - self.lastLogSecs = nowSecs - end -end - -function AIDriveStrategyCourse:info(...) - CpUtil.infoVehicle(self.vehicle, self:getStateAsString() .. ': ' .. string.format(...)) -end - -function AIDriveStrategyCourse:error(...) - CpUtil.infoVehicle(self.vehicle, self:getStateAsString() .. ': ' .. string.format(...)) -end - ---- Sets an info text ----@param text CpInfoTextElement -function AIDriveStrategyCourse:setInfoText(text) - self.vehicle:setCpInfoTextActive(text) -end - ---- @param text CpInfoTextElement -function AIDriveStrategyCourse:clearInfoText(text) - if text then - self.vehicle:resetCpActiveInfoText(text) - end -end - -function AIDriveStrategyCourse:setAIVehicle(vehicle, jobParameters) - self.vehicle = vehicle - --self:fixTurnOnEvent() - self.jobParameters = jobParameters - self:initializeImplementControllers(vehicle) - self.ppc = PurePursuitController(vehicle) - self.ppc:registerListeners(self, 'onWaypointPassed', 'onWaypointChange') - - self.storage = vehicle.spec_cpAIWorker - - self.settings = vehicle:getCpSettings() - self.courseGeneratorSettings = vehicle:getCourseGeneratorSettings() - - -- for now, pathfinding generated courses can't be driven by towed tools - self.allowReversePathfinding = AIUtil.getFirstReversingImplementWithWheels(self.vehicle) == nil - self.turningRadius = AIUtil.getTurningRadius(vehicle) - - self.pathfinderController = PathfinderController(vehicle, self.turningRadius) - self.pathfinderController:registerListeners(self, self.onPathfindingFinished, self.onPathfindingFailed) - - self:setAllStaticParameters() - - -- TODO: this may or may not be the course we need for the strategy - local course = self:isGeneratedCourseNeeded() and self:getGeneratedCourse(jobParameters) - if course then - self:debug('Vehicle has a fieldwork course, figure out where to start') - if course:wasEditedByCourseEditor() then - self:info('The fieldwork course was edited by the course editor.') - end - local startIx = self:getStartingPointWaypointIx(course, jobParameters.startAt:getValue()) - self:start(course, startIx, jobParameters) - else - -- some strategies do not need a recorded or generated course to work, they - -- will create the courses on the fly. - self:debug('Vehicle has no course, start work without it.') - self:startWithoutCourse(jobParameters) - end - self:raiseControllerEvent(self.onStartEvent) -end - -function AIDriveStrategyCourse:fixTurnOnEvent() - for _, action in ipairs(self.vehicle.actionController.actions) do - for _, listener in ipairs(action.aiEventListener) do - if listener.eventName == 'onAIImplementStart' then - listener.direction = 1 - end - end - end -end - ---- Does the strategy need the current assigned course? -function AIDriveStrategyCourse:isGeneratedCourseNeeded() - return true -end - -function AIDriveStrategyCourse:delete() - self:raiseControllerEvent(self.deleteEvent) -end - -function AIDriveStrategyCourse:getGeneratedCourse(jobParameters) - return self.vehicle:getFieldWorkCourse() -end - -function AIDriveStrategyCourse:getStartingPointWaypointIx(course, startAt) - if startAt == CpFieldWorkJobParameters.START_AT_NEAREST_POINT then - local _, _, ixClosestRightDirection, _ = course:getNearestWaypoints(self.vehicle:getAIDirectionNode()) - self:debug('Starting course at the closest waypoint in the right direction %d', ixClosestRightDirection) - return ixClosestRightDirection - elseif startAt == CpFieldWorkJobParameters.START_AT_LAST_POINT then - local lastWpIx = self.vehicle:getCpLastRememberedWaypointIx() - if lastWpIx then - self:debug('Starting course at the last waypoint %d', lastWpIx) - return lastWpIx - end - end - self:debug('Starting course at the first waypoint') - return 1 -end - -function AIDriveStrategyCourse:start(course, startIx, jobParameters) - self:startCourse(course, startIx) - self.state = self.states.INITIAL -end - -function AIDriveStrategyCourse:startWithoutCourse(jobParameters) -end - -function AIDriveStrategyCourse:updateCpStatus(status) - --- override -end - ------------------------------------------------------------------------------------------------------------------------ ---- Implement handling ------------------------------------------------------------------------------------------------------------------------ ---- Adds implement controllers for every implement, that has the given specialization. ----@param vehicle table ----@param class ImplementController ----@param spec table|nil ----@param states table|nil ----@param specReference string|nil ----@return table last implement found. ----@return table last implement controller -function AIDriveStrategyCourse:addImplementController(vehicle, class, spec, states, specReference) - --- If multiple implements have this spec, then add a controller for each implement. - local lastImplement, lastController - for _, childVehicle in pairs(AIUtil.getAllChildVehiclesWithSpecialization(vehicle, spec, specReference)) do - local controller = class(vehicle, childVehicle) - self:appendImplementController(controller, states) - lastImplement, lastController = childVehicle, controller - end - return lastImplement, lastController -end - ---- Appends an implement controller ----@param controller ImplementController ----@param disabledStates table|nil -function AIDriveStrategyCourse:appendImplementController(controller, disabledStates) - if disabledStates then - controller:setDisabledStates(disabledStates) - end - controller:setDriveStrategy(self) - table.insert(self.controllers, controller) -end - ---- Gets all registered implement controller of a given type. ----@param controllerClass ImplementController ----@return boolean found? ----@return table all found implement controllers -function AIDriveStrategyCourse:getRegisteredImplementControllersByClass(controllerClass) - local foundControllers = {} - for _, controller in pairs(self.controllers) do - if controller:is_a(controllerClass) then - table.insert(foundControllers, controller) - end - end - return #foundControllers > 0, foundControllers -end - ---- Gets the first found registered implement controller and it's implement. ----@param controllerClass ImplementController ----@return ImplementController|nil found implement controller ----@return table|nil found implement -function AIDriveStrategyCourse:getFirstRegisteredImplementControllerByClass(controllerClass) - local found, controllers = self:getRegisteredImplementControllersByClass(controllerClass) - if found then - return controllers[1], controllers[1]:getImplement() - end -end - ---- Checks if any controller disables fuel save, for example a round baler that is dropping a bale. -function AIDriveStrategyCourse:isFuelSaveAllowed() - return self.fuelSaveActiveWhileHeld and self:isBeingHeld() -end - -function AIDriveStrategyCourse:initializeImplementControllers(vehicle) - --- override -end - ---- Normal update function called every frame. ---- For releasing the helper in the controller, use this one. -function AIDriveStrategyCourse:updateImplementControllers(dt) - self:raiseControllerEvent(self.updateEvent, dt) -end - ---- Called in the low frequency function for the helper. -function AIDriveStrategyCourse:updateLowFrequencyImplementControllers() - for _, controller in pairs(self.controllers) do - ---@type ImplementController - if controller:isEnabled() then - -- we don't know yet if we even need anything from the controller other than the speed. - local _, _, _, maxSpeed = controller:getDriveData() - if maxSpeed then - self:setMaxSpeed(maxSpeed) - end - end - end -end - -function AIDriveStrategyCourse:updateLowFrequencyPathfinder() - local _, _, _, maxSpeed = self.pathfinderController:getDriveData() - if maxSpeed then - self:setMaxSpeed(maxSpeed) - end -end - ---- Raises a event for the controllers. -function AIDriveStrategyCourse:raiseControllerEvent(eventName, ...) - self:raiseControllerEventWithLambda(eventName, function () end, ...) -end - -function AIDriveStrategyCourse:raiseControllerEventWithLambda(eventName, lambda, ...) - for _, controller in pairs(self.controllers) do - ---@type ImplementController - if controller:isEnabled() then - if controller[eventName] then - lambda(controller[eventName](controller, ...)) - end - end - end -end - -function AIDriveStrategyCourse:raiseImplements() - --- Raises all implements, that are available for the giants field worker. - for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do - implement.object:aiImplementEndLine() - end - self.vehicle:raiseStateChange(VehicleStateChange.AI_END_LINE) - --- Raises implements, that are not covered by giants. - self:raiseControllerEvent(self.onRaisingEvent) -end - -function AIDriveStrategyCourse:lowerImplements() - self:debug('Lowering all implements') - --- Lowers all implements, that are available for the giants field worker. - for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do - CpUtil.debugImplement(CpDebug.DBG_IMPLEMENTS, implement.object,'Lowering implement') - implement.object:aiImplementStartLine() - end - self.vehicle:raiseStateChange(VehicleStateChange.AI_START_LINE) - --- Lowers implements, that are not covered by giants. - self:raiseControllerEvent(self.onLoweringEvent) -end - ---- Can the ai worker continue working? ----@return boolean -function AIDriveStrategyCourse:getCanContinueWork() - --- Not every implement, for example balers or bale wrapper are handled by the giants function. - for _, controller in pairs(self.controllers) do - ---@type ImplementController - if not controller:canContinueWork() then - return false - end - end - return self.vehicle:getCanAIFieldWorkerContinueWork() -end - ------------------------------------------------------------------------------------------------------------------------ ---- Static parameters (won't change while driving) ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyCourse:setAllStaticParameters() - self.workWidth = self.vehicle:getCourseGeneratorSettings().workWidth:getValue() - self.reverser = AIReverseDriver(self.vehicle, self.ppc) - self.proximityController = ProximityController(self.vehicle, self:getProximitySensorWidth()) - self.proximityController:registerIgnoreObjectCallback(self, self.ignoreBaleInFrontWithBalePusher) - -- let all controllers register an ignore object callback if they want - for _, controller in pairs(self.controllers) do - controller:registerIgnoreProximityObjectCallback(self.proximityController) - end -end - ---- Find the foremost and rearmost AI marker -function AIDriveStrategyCourse:setFrontAndBackMarkers() - local markers = {} - local addMarkers = function(object, referenceNode) - self:debug('Finding AI markers of %s', CpUtil.getName(object)) - local aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkers(object) - if aiLeftMarker and aiBackMarker and aiRightMarker then - local leftMarkerDistance = ImplementUtil.getDistanceToImplementNode(referenceNode, object, aiLeftMarker) - local rightMarkerDistance = ImplementUtil.getDistanceToImplementNode(referenceNode, object, aiRightMarker) - local backMarkerDistance = ImplementUtil.getDistanceToImplementNode(referenceNode, object, aiBackMarker) - table.insert(markers, leftMarkerDistance) - table.insert(markers, rightMarkerDistance) - table.insert(markers, backMarkerDistance) - self:debug('%s: left = %.1f, right = %.1f, back = %.1f', CpUtil.getName(object), leftMarkerDistance, rightMarkerDistance, backMarkerDistance) - end - end - - local referenceNode = self.vehicle:getAIDirectionNode() - -- now go ahead and try to find the real markers - -- work areas of the vehicle itself - addMarkers(self.vehicle, referenceNode) - -- and then the work areas of all the implements - for _, implement in pairs(AIUtil.getAllAIImplements(self.vehicle)) do - addMarkers(implement.object, referenceNode) - end - - if #markers == 0 then - -- make sure we always have a default front/back marker, placed on the direction node if nothing else found - table.insert(markers, 0) - table.insert(markers, 3) - end - -- now that we have all, find the foremost and the last - self.frontMarkerDistance, self.backMarkerDistance = 0, 0 - local frontMarkerDistance, backMarkerDistance = -math.huge, math.huge - for _, d in pairs(markers) do - if d > frontMarkerDistance then - frontMarkerDistance = d - end - if d < backMarkerDistance then - backMarkerDistance = d - end - end - self.frontMarkerDistance = frontMarkerDistance - self.backMarkerDistance = backMarkerDistance - self:debug('front marker: %.1f, back marker: %.1f', frontMarkerDistance, backMarkerDistance) -end - ---- Gets the front and back marker offset relative to the direction node. These markers define the front ---- and the back of the work area. When negative, they are behind the direction node, when positive, in front of it. ----@return number distance of the foremost work area from the direction node, negative when behind the direction node ----@return number distance of the rearmost work area from the direction node, negative when behind the direction node -function AIDriveStrategyCourse:getFrontAndBackMarkers() - if not self.frontMarkerDistance then - self:setFrontAndBackMarkers() - end - return self.frontMarkerDistance, self.backMarkerDistance -end - -function AIDriveStrategyCourse:getWorkWidth() - return self.workWidth -end - ---- Get the currently active course (temporary or not) -function AIDriveStrategyCourse:getCurrentCourse() - return self.ppc:getCourse() or self.course -end - -function AIDriveStrategyCourse:update(dt) - self.ppc:update() - self.pathfinderController:update(dt) - self:updatePathfinding() - self:updateInfoTexts() -end - - -function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ) - local moveForwards = not self.ppc:isReversing() - local gx, _, gz = self.ppc:getGoalPointPosition() - return gx, gz, moveForwards, self.maxSpeed, 100 -end - -function AIDriveStrategyCourse:getReverseDriveData() - local gx, gz, _, maxSpeed = self.reverser:getDriveData() - if not gx then - -- simple reverse (not towing anything), just use PPC - gx, _, gz = self.ppc:getGoalPointPosition() - maxSpeed = self.settings.reverseSpeed:getValue() - end - return gx, gz, maxSpeed -end - ------------------------------------------------------------------------------------------------------------------------ ---- Proximity ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyCourse:getProximitySensorWidth() - -- a bit less as size.width always has plenty of buffer - return self.vehicle.size.width - 0.5 -end - -function AIDriveStrategyCourse:checkProximitySensors(moveForwards) - local _, _, _, maxSpeed = self.proximityController:getDriveData(self:getMaxSpeed(), moveForwards) - self:setMaxSpeed(maxSpeed) -end - ---- Is vehicle close to the front or rear proximity sensors? ----@param vehicle table ----@return boolean, number true if vehicle is in proximity, distance of vehicle -function AIDriveStrategyCourse:isVehicleInProximity(vehicle) - return self.proximityController:isVehicleInRange(vehicle) -end - ---- Ignoring bales in front when configured, bales back when not yet dropped from the baler -function AIDriveStrategyCourse:ignoreBaleInFrontWithBalePusher(object, vehicle, moveForwards) - if not object then - return - end - if object.isa and object:isa(Bale) and object.nodeId and entityExists(object.nodeId) then - -- this is a bale - if moveForwards and g_vehicleConfigurations:getRecursively(self.vehicle, 'ignoreBaleCollisionForward') then - -- when configured, ignore bales in front, for instance using a bale pusher - self:debugSparse('ignoring forward collision with bale') - return true - end - end - return false -end - ------------------------------------------------------------------------------------------------------------------------ ---- Speed control ------------------------------------------------------------------------------------------------------------------------ ---- Set the maximum speed. The idea is that self.maxSpeed is reset at the beginning of every loop and --- every function calls setMaxSpeed() and the speed will be set to the minimum --- speed set in this loop. -function AIDriveStrategyCourse:setMaxSpeed(speed) - if self.maxSpeedUpdatedLoopIndex == nil or self.maxSpeedUpdatedLoopIndex ~= g_updateLoopIndex then - -- new loop, reset max speed. Always 0 if frozen - self.maxSpeed = (self.frozen or self:isBeingHeld()) and 0 or self.vehicle:getSpeedLimit(true) - self.maxSpeedUpdatedLoopIndex = g_updateLoopIndex - end - self.maxSpeed = math.min(self.maxSpeed, speed) -end - -function AIDriveStrategyCourse:getMaxSpeed() - return self.maxSpeed or self.vehicle:getSpeedLimit(true) -end - ---- Hold the vehicle (set speed to 0) temporary. This is meant to be used for other vehicles to coordinate movements, ---- for instance tell a vehicle it should not move as the other vehicle is driving around it. ----@param milliseconds number milliseconds to hold ----@param fuelSaveAllowed boolean enables the fuel save, while the vehicle is being held. -function AIDriveStrategyCourse:hold(milliseconds, fuelSaveAllowed) - if not self.held:get() then - self:debug('Hold requested for %.1f seconds', milliseconds / 1000) - end - self.held:set(true, milliseconds) - self.fuelSaveActiveWhileHeld = fuelSaveAllowed -end - ---- Release a hold anytime, even before it is released automatically after the time given at hold() -function AIDriveStrategyCourse:unhold() - if self.held:get() then - self:debug("Hold reset") - end - self.held:reset() - self.fuelSaveActiveWhileHeld = false -end - ---- Are we currently being held? -function AIDriveStrategyCourse:isBeingHeld() - return self.held:get() -end - ---- Freeze (force speed to 0), but keep everything up and running otherwise, showing all debug ---- drawings, etc. This is for troubleshooting only. Unlike pausing the game, this still calls update() and ---- getDriveData() so all debug drawings remain visible during the freeze. -function AIDriveStrategyCourse:freeze() - self.frozen = true -end - -function AIDriveStrategyCourse:unfreeze() - self.frozen = false -end - ---- Slow down a bit towards the end of course or near direction changes, and later maybe where the turn radius is ---- small, unless we are reversing, as then (hopefully) we already have a slow speed set -function AIDriveStrategyCourse:limitSpeed() - if self.maxSpeed > self.settings.turnSpeed:getValue() and - not self.ppc:isReversing() and - (self.ppc:getCourse():isCloseToLastWaypoint(15) or - self.ppc:getCourse():isCloseToNextDirectionChange(15)) then - - local maxSpeed = self.maxSpeed - self:setMaxSpeed(self.settings.turnSpeed:getValue()) - self:debugSparse('speed %.1f limited to turn speed %.1f', maxSpeed, self.maxSpeed) - else - self:debugSparse('speed %.1f', self.maxSpeed) - end -end - ---- Start a course at waypoint ix ----@param course Course ----@param ix number -function AIDriveStrategyCourse:startCourse(course, ix) - self:debug('Starting a course, at waypoint %d (of %d).', ix, course:getNumberOfWaypoints()) - self.course = course - self.ppc:setCourse(self.course) - self.ppc:initialize(ix) -end - -function AIDriveStrategyCourse:getFillLevelInfoText() - return InfoTextManager.NEEDS_UNLOADING -end - ------------------------------------------------------------------------------------------------------------------------ ---- Event listeners ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyCourse:onWaypointChange(ix, course) -end - -function AIDriveStrategyCourse:onWaypointPassed(ix, course) -end - ---- Pathfinding has finished ----@param controller PathfinderController ----@param success boolean ----@param course Course|nil ----@param goalNodeInvalid boolean|nil -function AIDriveStrategyCourse:onPathfindingFinished(controller, success, course, goalNodeInvalid) - -- override -end - ---- Pathfinding failed, but a retry attempt is leftover. ----@param controller PathfinderController ----@param lastContext PathfinderContext ----@param wasLastRetry boolean ----@param currentRetryAttempt number -function AIDriveStrategyCourse:onPathfindingFailed(controller, lastContext, wasLastRetry, currentRetryAttempt) - -- override -end - ------------------------------------------------------------------------------------------------------------------------- ---- Pathfinding ---------------------------------------------------------------------------------------------------------------------------- -function AIDriveStrategyCourse:getAllowReversePathfinding() - return self.allowReversePathfinding and self.settings.allowReversePathfinding:getValue() -end - -function AIDriveStrategyCourse:setPathfindingDoneCallback(object, func) - self.pathfindingDoneObject = object - self.pathfindingDoneCallbackFunc = func -end - -function AIDriveStrategyCourse:updatePathfinding() - if self.pathfinder and self.pathfinder:isActive() then - self:setMaxSpeed(0) - local result = self.pathfinder:resume() - if result.done then - self.pathfindingDoneCallbackFunc(self.pathfindingDoneObject, result.path) - end - end -end - ---- Create an alignment course between the current vehicle position and waypoint endIx of the course ----@param course Course the course to start ----@param ix number the waypoint where start the course -function AIDriveStrategyCourse:createAlignmentCourse(course, ix) - self:debug('Generate alignment course to waypoint %d', ix) - local alignmentCourse = AlignmentCourse(self.vehicle, self.vehicle:getAIDirectionNode(), self.turningRadius, - course, ix, math.min(-self.frontMarkerDistance, -1)):getCourse() - return alignmentCourse -end - --- remember a course to start -function AIDriveStrategyCourse:rememberCourse(course, ix) - self.rememberedCourse = course - self.rememberedCourseStartIx = ix -end - --- start a remembered course -function AIDriveStrategyCourse:startRememberedCourse() - self:startCourse(self.rememberedCourse, self.rememberedCourseStartIx) -end - -function AIDriveStrategyCourse:getRememberedCourseAndIx() - return self.rememberedCourse, self.rememberedCourseStartIx -end - ------------------------------------------------------------------------------------------------------------------------- ---- Course helpers ---------------------------------------------------------------------------------------------------------------------------- - ---- Are we within distance meters of the last waypoint (measured on the course, not direct path)? -function AIDriveStrategyCourse:isCloseToCourseEnd(distance) - return self.course:getDistanceToLastWaypoint(self.ppc:getCurrentWaypointIx()) < distance -end - ---- Are we within distance meters of the first waypoint (measured on the course, not direct path)? -function AIDriveStrategyCourse:isCloseToCourseStart(distance) - return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance -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() - end -end - ---- This is to set the offsets on the course at start, or update those values ---- if the user changed them during the run or the AI driver wants to add an offset -function AIDriveStrategyCourse:updateFieldworkOffset(course) - course:setOffset(self.settings.toolOffsetX:getValue() + (self.aiOffsetX or 0) + (self.tightTurnOffset or 0), - (self.aiOffsetZ or 0)) -end - ------------------------------------------------------------------------------------------------------------------------- ---- Info texts ---------------------------------------------------------------------------------------------------------------------------- - ---- Registers info texts for specific states. ----@param infoText CpInfoTextElement ----@param states table -function AIDriveStrategyCourse:registerInfoTextForStates(infoText, states) - if self.registeredInfoTexts[infoText] == nil then - self.registeredInfoTexts[infoText] = states - end -end - ---- Enables/disables based on the state. -function AIDriveStrategyCourse:updateInfoTexts() - for infoText, states in pairs(self.registeredInfoTexts) do - if states[self.state] then - self:setInfoText(infoText) - else - self:clearInfoText(infoText) - end - end +--[[ +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) +Copyright (C) 2021 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Base class for all Courseplay drive strategies + +]] + +---@class AIDriveStrategyCourse +AIDriveStrategyCourse = CpObject() + +AIDriveStrategyCourse.myStates = { + INITIAL = {}, + WAITING_FOR_PATHFINDER = {}, +} + +--- Implement controller events. +--- TODO_25 a more generic implementation +AIDriveStrategyCourse.onRaisingEvent = "onRaising" +AIDriveStrategyCourse.onLoweringEvent = "onLowering" +AIDriveStrategyCourse.onFinishedEvent = "onFinished" +AIDriveStrategyCourse.onStartEvent = "onStart" +AIDriveStrategyCourse.onStartRefillingEvent = "onStartRefilling" +AIDriveStrategyCourse.onStopRefillingEvent = "onStopRefilling" +AIDriveStrategyCourse.onUpdateRefillingEvent = "onUpdateRefilling" +AIDriveStrategyCourse.updateEvent = "update" +AIDriveStrategyCourse.deleteEvent = "delete" +--- A row has just been finished, implements are being raised and about to start the actual turn +AIDriveStrategyCourse.onFinishRowEvent = "onFinishRow" +--- The actual turn is done, now we are starting into the row and will lower the implements when +--- they reach the start of the row +AIDriveStrategyCourse.onTurnEndProgressEvent = "onTurnEndProgress" + +---@param task CpAITask +---@param job CpAIJob +function AIDriveStrategyCourse:init(task, job) + self.debugChannel = CpDebug.DBG_AI_DRIVER + self:initStates(AIDriveStrategyCourse.myStates) + ---@type ImplementController[] + self.controllers = {} + self.registeredInfoTexts = {} + --- To temporary hold a vehicle (will force speed to 0) + self.held = CpTemporaryObject() + self.fuelSaveActiveWhileHeld = false + + self.currentTask = task + self.job = job +end + +function AIDriveStrategyCourse:setCurrentTaskFinished() + self.currentTask:skip() +end + +--- Aggregation of states from this and all descendant classes +function AIDriveStrategyCourse:initStates(newStates) + self.states = CpUtil.initStates(self.states, newStates) +end + +function AIDriveStrategyCourse:getStateAsString() + return self.state.name +end + +function AIDriveStrategyCourse:getName() + return CpUtil.getName(self.vehicle) +end + +function AIDriveStrategyCourse:debug(...) + CpUtil.debugVehicle(self.debugChannel, self.vehicle, self:getStateAsString() .. ': ' .. string.format(...)) +end + +function AIDriveStrategyCourse:debugSparse(...) + local nowSecs = math.floor(g_time / 1000) + -- report every 5 seconds + -- TODO: make this a parameter in seconds? + if not self.lastLogSecs or (nowSecs > self.lastLogSecs and nowSecs % 5 == 0) then + self:debug(...) + self.lastLogSecs = nowSecs + end +end + +function AIDriveStrategyCourse:info(...) + CpUtil.infoVehicle(self.vehicle, self:getStateAsString() .. ': ' .. string.format(...)) +end + +function AIDriveStrategyCourse:error(...) + CpUtil.infoVehicle(self.vehicle, self:getStateAsString() .. ': ' .. string.format(...)) +end + +--- Sets an info text +---@param text CpInfoTextElement +function AIDriveStrategyCourse:setInfoText(text) + self.vehicle:setCpInfoTextActive(text) +end + +--- @param text CpInfoTextElement +function AIDriveStrategyCourse:clearInfoText(text) + if text then + self.vehicle:resetCpActiveInfoText(text) + end +end + +function AIDriveStrategyCourse:setAIVehicle(vehicle, jobParameters) + self.vehicle = vehicle + --self:fixTurnOnEvent() + self.jobParameters = jobParameters + self:initializeImplementControllers(vehicle) + self.ppc = PurePursuitController(vehicle) + self.ppc:registerListeners(self, 'onWaypointPassed', 'onWaypointChange') + + self.storage = vehicle.spec_cpAIWorker + + self.settings = vehicle:getCpSettings() + self.courseGeneratorSettings = vehicle:getCourseGeneratorSettings() + + -- for now, pathfinding generated courses can't be driven by towed tools + self.allowReversePathfinding = AIUtil.getFirstReversingImplementWithWheels(self.vehicle) == nil + self.turningRadius = AIUtil.getTurningRadius(vehicle) + + self.pathfinderController = PathfinderController(vehicle, self.turningRadius) + self.pathfinderController:registerListeners(self, self.onPathfindingFinished, self.onPathfindingFailed) + + self:setAllStaticParameters() + + -- TODO: this may or may not be the course we need for the strategy + local course = self:isGeneratedCourseNeeded() and self:getGeneratedCourse(jobParameters) + if course then + self:debug('Vehicle has a fieldwork course, figure out where to start') + if course:wasEditedByCourseEditor() then + self:info('The fieldwork course was edited by the course editor.') + end + local startIx = self:getStartingPointWaypointIx(course, jobParameters.startAt:getValue()) + self:start(course, startIx, jobParameters) + else + -- some strategies do not need a recorded or generated course to work, they + -- will create the courses on the fly. + self:debug('Vehicle has no course, start work without it.') + self:startWithoutCourse(jobParameters) + end + self:raiseControllerEvent(self.onStartEvent) +end + +function AIDriveStrategyCourse:fixTurnOnEvent() + for _, action in ipairs(self.vehicle.actionController.actions) do + for _, listener in ipairs(action.aiEventListener) do + if listener.eventName == 'onAIImplementStart' then + listener.direction = 1 + end + end + end +end + +--- Does the strategy need the current assigned course? +function AIDriveStrategyCourse:isGeneratedCourseNeeded() + return true +end + +function AIDriveStrategyCourse:delete() + self:raiseControllerEvent(self.deleteEvent) +end + +function AIDriveStrategyCourse:getGeneratedCourse(jobParameters) + return self.vehicle:getFieldWorkCourse() +end + +function AIDriveStrategyCourse:getStartingPointWaypointIx(course, startAt) + if startAt == CpFieldWorkJobParameters.START_AT_NEAREST_POINT then + local _, _, ixClosestRightDirection, _ = course:getNearestWaypoints(self.vehicle:getAIDirectionNode()) + self:debug('Starting course at the closest waypoint in the right direction %d', ixClosestRightDirection) + return ixClosestRightDirection + elseif startAt == CpFieldWorkJobParameters.START_AT_LAST_POINT then + local lastWpIx = self.vehicle:getCpLastRememberedWaypointIx() + if lastWpIx then + self:debug('Starting course at the last waypoint %d', lastWpIx) + return lastWpIx + end + end + self:debug('Starting course at the first waypoint') + return 1 +end + +function AIDriveStrategyCourse:start(course, startIx, jobParameters) + self:startCourse(course, startIx) + self.state = self.states.INITIAL +end + +function AIDriveStrategyCourse:startWithoutCourse(jobParameters) +end + +function AIDriveStrategyCourse:updateCpStatus(status) + --- override +end + +----------------------------------------------------------------------------------------------------------------------- +--- Implement handling +----------------------------------------------------------------------------------------------------------------------- +--- Adds implement controllers for every implement, that has the given specialization. +---@param vehicle table +---@param class ImplementController +---@param spec table|nil +---@param states table|nil +---@param specReference string|nil +---@return table last implement found. +---@return table last implement controller +function AIDriveStrategyCourse:addImplementController(vehicle, class, spec, states, specReference) + --- If multiple implements have this spec, then add a controller for each implement. + local lastImplement, lastController + for _, childVehicle in pairs(AIUtil.getAllChildVehiclesWithSpecialization(vehicle, spec, specReference)) do + local controller = class(vehicle, childVehicle) + self:appendImplementController(controller, states) + lastImplement, lastController = childVehicle, controller + end + return lastImplement, lastController +end + +--- Appends an implement controller +---@param controller ImplementController +---@param disabledStates table|nil +function AIDriveStrategyCourse:appendImplementController(controller, disabledStates) + if disabledStates then + controller:setDisabledStates(disabledStates) + end + controller:setDriveStrategy(self) + table.insert(self.controllers, controller) +end + +--- Gets all registered implement controller of a given type. +---@param controllerClass ImplementController +---@return boolean found? +---@return table all found implement controllers +function AIDriveStrategyCourse:getRegisteredImplementControllersByClass(controllerClass) + local foundControllers = {} + for _, controller in pairs(self.controllers) do + if controller:is_a(controllerClass) then + table.insert(foundControllers, controller) + end + end + return #foundControllers > 0, foundControllers +end + +--- Gets the first found registered implement controller and it's implement. +---@param controllerClass ImplementController +---@return ImplementController|nil found implement controller +---@return table|nil found implement +function AIDriveStrategyCourse:getFirstRegisteredImplementControllerByClass(controllerClass) + local found, controllers = self:getRegisteredImplementControllersByClass(controllerClass) + if found then + return controllers[1], controllers[1]:getImplement() + end +end + +--- Checks if any controller disables fuel save, for example a round baler that is dropping a bale. +function AIDriveStrategyCourse:isFuelSaveAllowed() + return self.fuelSaveActiveWhileHeld and self:isBeingHeld() +end + +function AIDriveStrategyCourse:initializeImplementControllers(vehicle) + --- override +end + +--- Normal update function called every frame. +--- For releasing the helper in the controller, use this one. +function AIDriveStrategyCourse:updateImplementControllers(dt) + self:raiseControllerEvent(self.updateEvent, dt) +end + +--- Called in the low frequency function for the helper. +function AIDriveStrategyCourse:updateLowFrequencyImplementControllers() + for _, controller in pairs(self.controllers) do + ---@type ImplementController + if controller:isEnabled() then + -- we don't know yet if we even need anything from the controller other than the speed. + local _, _, _, maxSpeed = controller:getDriveData() + if maxSpeed then + self:setMaxSpeed(maxSpeed) + end + end + end +end + +function AIDriveStrategyCourse:updateLowFrequencyPathfinder() + local _, _, _, maxSpeed = self.pathfinderController:getDriveData() + if maxSpeed then + self:setMaxSpeed(maxSpeed) + end +end + +--- Raises a event for the controllers. +function AIDriveStrategyCourse:raiseControllerEvent(eventName, ...) + self:raiseControllerEventWithLambda(eventName, function () end, ...) +end + +function AIDriveStrategyCourse:raiseControllerEventWithLambda(eventName, lambda, ...) + for _, controller in pairs(self.controllers) do + ---@type ImplementController + if controller:isEnabled() then + if controller[eventName] then + lambda(controller[eventName](controller, ...)) + end + end + end +end + +function AIDriveStrategyCourse:raiseImplements() + --- Raises all implements, that are available for the giants field worker. + for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do + implement.object:aiImplementEndLine() + end + self.vehicle:raiseStateChange(VehicleStateChange.AI_END_LINE) + --- Raises implements, that are not covered by giants. + self:raiseControllerEvent(self.onRaisingEvent) +end + +function AIDriveStrategyCourse:lowerImplements() + self:debug('Lowering all implements') + --- Lowers all implements, that are available for the giants field worker. + for _, implement in pairs(self.vehicle:getAttachedAIImplements()) do + CpUtil.debugImplement(CpDebug.DBG_IMPLEMENTS, implement.object,'Lowering implement') + implement.object:aiImplementStartLine() + end + self.vehicle:raiseStateChange(VehicleStateChange.AI_START_LINE) + --- Lowers implements, that are not covered by giants. + self:raiseControllerEvent(self.onLoweringEvent) +end + +--- Can the ai worker continue working? +---@return boolean +function AIDriveStrategyCourse:getCanContinueWork() + --- Not every implement, for example balers or bale wrapper are handled by the giants function. + for _, controller in pairs(self.controllers) do + ---@type ImplementController + if not controller:canContinueWork() then + return false + end + end + return self.vehicle:getCanAIFieldWorkerContinueWork() +end + +----------------------------------------------------------------------------------------------------------------------- +--- Static parameters (won't change while driving) +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCourse:setAllStaticParameters() + self.workWidth = self.vehicle:getCourseGeneratorSettings().workWidth:getValue() + self.reverser = AIReverseDriver(self.vehicle, self.ppc) + self.proximityController = ProximityController(self.vehicle, self:getProximitySensorWidth()) + self.proximityController:registerIgnoreObjectCallback(self, self.ignoreBaleInFrontWithBalePusher) + -- let all controllers register an ignore object callback if they want + for _, controller in pairs(self.controllers) do + controller:registerIgnoreProximityObjectCallback(self.proximityController) + end +end + +--- Find the foremost and rearmost AI marker +function AIDriveStrategyCourse:setFrontAndBackMarkers() + local markers = {} + local addMarkers = function(object, referenceNode) + self:debug('Finding AI markers of %s', CpUtil.getName(object)) + local aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkers(object) + if aiLeftMarker and aiBackMarker and aiRightMarker then + local leftMarkerDistance = ImplementUtil.getDistanceToImplementNode(referenceNode, object, aiLeftMarker) + local rightMarkerDistance = ImplementUtil.getDistanceToImplementNode(referenceNode, object, aiRightMarker) + local backMarkerDistance = ImplementUtil.getDistanceToImplementNode(referenceNode, object, aiBackMarker) + table.insert(markers, leftMarkerDistance) + table.insert(markers, rightMarkerDistance) + table.insert(markers, backMarkerDistance) + self:debug('%s: left = %.1f, right = %.1f, back = %.1f', CpUtil.getName(object), leftMarkerDistance, rightMarkerDistance, backMarkerDistance) + end + end + + local referenceNode = self.vehicle:getAIDirectionNode() + -- now go ahead and try to find the real markers + -- work areas of the vehicle itself + addMarkers(self.vehicle, referenceNode) + -- and then the work areas of all the implements + for _, implement in pairs(AIUtil.getAllAIImplements(self.vehicle)) do + addMarkers(implement.object, referenceNode) + end + + if #markers == 0 then + -- make sure we always have a default front/back marker, placed on the direction node if nothing else found + table.insert(markers, 0) + table.insert(markers, 3) + end + -- now that we have all, find the foremost and the last + self.frontMarkerDistance, self.backMarkerDistance = 0, 0 + local frontMarkerDistance, backMarkerDistance = -math.huge, math.huge + for _, d in pairs(markers) do + if d > frontMarkerDistance then + frontMarkerDistance = d + end + if d < backMarkerDistance then + backMarkerDistance = d + end + end + self.frontMarkerDistance = frontMarkerDistance + self.backMarkerDistance = backMarkerDistance + self:debug('front marker: %.1f, back marker: %.1f', frontMarkerDistance, backMarkerDistance) +end + +--- Gets the front and back marker offset relative to the direction node. These markers define the front +--- and the back of the work area. When negative, they are behind the direction node, when positive, in front of it. +---@return number distance of the foremost work area from the direction node, negative when behind the direction node +---@return number distance of the rearmost work area from the direction node, negative when behind the direction node +function AIDriveStrategyCourse:getFrontAndBackMarkers() + if not self.frontMarkerDistance then + self:setFrontAndBackMarkers() + end + return self.frontMarkerDistance, self.backMarkerDistance +end + +function AIDriveStrategyCourse:getWorkWidth() + return self.workWidth +end + +--- Get the currently active course (temporary or not) +function AIDriveStrategyCourse:getCurrentCourse() + return self.ppc:getCourse() or self.course +end + +function AIDriveStrategyCourse:update(dt) + self.ppc:update() + self.pathfinderController:update(dt) + self:updatePathfinding() + self:updateInfoTexts() +end + + +function AIDriveStrategyCourse:getDriveData(dt, vX, vY, vZ) + local moveForwards = not self.ppc:isReversing() + local gx, _, gz = self.ppc:getGoalPointPosition() + return gx, gz, moveForwards, self.maxSpeed, 100 +end + +function AIDriveStrategyCourse:getReverseDriveData() + local gx, gz, _, maxSpeed = self.reverser:getDriveData() + if not gx then + -- simple reverse (not towing anything), just use PPC + gx, _, gz = self.ppc:getGoalPointPosition() + maxSpeed = self.settings.reverseSpeed:getValue() + end + return gx, gz, maxSpeed +end + +----------------------------------------------------------------------------------------------------------------------- +--- Proximity +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCourse:getProximitySensorWidth() + -- a bit less as size.width always has plenty of buffer + return self.vehicle.size.width - 0.5 +end + +function AIDriveStrategyCourse:checkProximitySensors(moveForwards) + local _, _, _, maxSpeed = self.proximityController:getDriveData(self:getMaxSpeed(), moveForwards) + self:setMaxSpeed(maxSpeed) +end + +--- Is vehicle close to the front or rear proximity sensors? +---@param vehicle table +---@return boolean, number true if vehicle is in proximity, distance of vehicle +function AIDriveStrategyCourse:isVehicleInProximity(vehicle) + return self.proximityController:isVehicleInRange(vehicle) +end + +--- Ignoring bales in front when configured, bales back when not yet dropped from the baler +function AIDriveStrategyCourse:ignoreBaleInFrontWithBalePusher(object, vehicle, moveForwards) + if not object then + return + end + if object.isa and object:isa(Bale) and object.nodeId and entityExists(object.nodeId) then + -- this is a bale + if moveForwards and g_vehicleConfigurations:getRecursively(self.vehicle, 'ignoreBaleCollisionForward') then + -- when configured, ignore bales in front, for instance using a bale pusher + self:debugSparse('ignoring forward collision with bale') + return true + end + end + return false +end + +----------------------------------------------------------------------------------------------------------------------- +--- Speed control +----------------------------------------------------------------------------------------------------------------------- +--- Set the maximum speed. The idea is that self.maxSpeed is reset at the beginning of every loop and +-- every function calls setMaxSpeed() and the speed will be set to the minimum +-- speed set in this loop. +function AIDriveStrategyCourse:setMaxSpeed(speed) + if self.maxSpeedUpdatedLoopIndex == nil or self.maxSpeedUpdatedLoopIndex ~= g_updateLoopIndex then + -- new loop, reset max speed. Always 0 if frozen + self.maxSpeed = (self.frozen or self:isBeingHeld()) and 0 or self.vehicle:getSpeedLimit(true) + self.maxSpeedUpdatedLoopIndex = g_updateLoopIndex + end + self.maxSpeed = math.min(self.maxSpeed, speed) +end + +function AIDriveStrategyCourse:getMaxSpeed() + return self.maxSpeed or self.vehicle:getSpeedLimit(true) +end + +--- Hold the vehicle (set speed to 0) temporary. This is meant to be used for other vehicles to coordinate movements, +--- for instance tell a vehicle it should not move as the other vehicle is driving around it. +---@param milliseconds number milliseconds to hold +---@param fuelSaveAllowed boolean enables the fuel save, while the vehicle is being held. +function AIDriveStrategyCourse:hold(milliseconds, fuelSaveAllowed) + if not self.held:get() then + self:debug('Hold requested for %.1f seconds', milliseconds / 1000) + end + self.held:set(true, milliseconds) + self.fuelSaveActiveWhileHeld = fuelSaveAllowed +end + +--- Release a hold anytime, even before it is released automatically after the time given at hold() +function AIDriveStrategyCourse:unhold() + if self.held:get() then + self:debug("Hold reset") + end + self.held:reset() + self.fuelSaveActiveWhileHeld = false +end + +--- Are we currently being held? +function AIDriveStrategyCourse:isBeingHeld() + return self.held:get() +end + +--- Freeze (force speed to 0), but keep everything up and running otherwise, showing all debug +--- drawings, etc. This is for troubleshooting only. Unlike pausing the game, this still calls update() and +--- getDriveData() so all debug drawings remain visible during the freeze. +function AIDriveStrategyCourse:freeze() + self.frozen = true +end + +function AIDriveStrategyCourse:unfreeze() + self.frozen = false +end + +--- Slow down a bit towards the end of course or near direction changes, and later maybe where the turn radius is +--- small, unless we are reversing, as then (hopefully) we already have a slow speed set +function AIDriveStrategyCourse:limitSpeed() + if self.maxSpeed > self.settings.turnSpeed:getValue() and + not self.ppc:isReversing() and + (self.ppc:getCourse():isCloseToLastWaypoint(15) or + self.ppc:getCourse():isCloseToNextDirectionChange(15)) then + + local maxSpeed = self.maxSpeed + self:setMaxSpeed(self.settings.turnSpeed:getValue()) + self:debugSparse('speed %.1f limited to turn speed %.1f', maxSpeed, self.maxSpeed) + else + self:debugSparse('speed %.1f', self.maxSpeed) + end +end + +--- Start a course at waypoint ix +---@param course Course +---@param ix number +function AIDriveStrategyCourse:startCourse(course, ix) + self:debug('Starting a course, at waypoint %d (of %d).', ix, course:getNumberOfWaypoints()) + self.course = course + self.ppc:setCourse(self.course) + self.ppc:initialize(ix) +end + +function AIDriveStrategyCourse:getFillLevelInfoText() + return InfoTextManager.NEEDS_UNLOADING +end + +----------------------------------------------------------------------------------------------------------------------- +--- Event listeners +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCourse:onWaypointChange(ix, course) +end + +function AIDriveStrategyCourse:onWaypointPassed(ix, course) +end + +--- Pathfinding has finished +---@param controller PathfinderController +---@param success boolean +---@param course Course|nil +---@param goalNodeInvalid boolean|nil +function AIDriveStrategyCourse:onPathfindingFinished(controller, success, course, goalNodeInvalid) + -- override +end + +--- Pathfinding failed, but a retry attempt is leftover. +---@param controller PathfinderController +---@param lastContext PathfinderContext +---@param wasLastRetry boolean +---@param currentRetryAttempt number +function AIDriveStrategyCourse:onPathfindingFailed(controller, lastContext, wasLastRetry, currentRetryAttempt) + -- override +end + +------------------------------------------------------------------------------------------------------------------------ +--- Pathfinding +--------------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyCourse:getAllowReversePathfinding() + return self.allowReversePathfinding and self.settings.allowReversePathfinding:getValue() +end + +function AIDriveStrategyCourse:setPathfindingDoneCallback(object, func) + self.pathfindingDoneObject = object + self.pathfindingDoneCallbackFunc = func +end + +function AIDriveStrategyCourse:updatePathfinding() + if self.pathfinder and self.pathfinder:isActive() then + self:setMaxSpeed(0) + local result = self.pathfinder:resume() + if result.done then + self.pathfindingDoneCallbackFunc(self.pathfindingDoneObject, result.path) + end + end +end + +--- Create an alignment course between the current vehicle position and waypoint endIx of the course +---@param course Course the course to start +---@param ix number the waypoint where start the course +function AIDriveStrategyCourse:createAlignmentCourse(course, ix) + self:debug('Generate alignment course to waypoint %d', ix) + local alignmentCourse = AlignmentCourse(self.vehicle, self.vehicle:getAIDirectionNode(), self.turningRadius, + course, ix, math.min(-self.frontMarkerDistance, -1)):getCourse() + return alignmentCourse +end + +-- remember a course to start +function AIDriveStrategyCourse:rememberCourse(course, ix) + self.rememberedCourse = course + self.rememberedCourseStartIx = ix +end + +-- start a remembered course +function AIDriveStrategyCourse:startRememberedCourse() + self:startCourse(self.rememberedCourse, self.rememberedCourseStartIx) +end + +function AIDriveStrategyCourse:getRememberedCourseAndIx() + return self.rememberedCourse, self.rememberedCourseStartIx +end + +------------------------------------------------------------------------------------------------------------------------ +--- Course helpers +--------------------------------------------------------------------------------------------------------------------------- + +--- Are we within distance meters of the last waypoint (measured on the course, not direct path)? +function AIDriveStrategyCourse:isCloseToCourseEnd(distance) + return self.course:getDistanceToLastWaypoint(self.ppc:getCurrentWaypointIx()) < distance +end + +--- Are we within distance meters of the first waypoint (measured on the course, not direct path)? +function AIDriveStrategyCourse:isCloseToCourseStart(distance) + return self.course:getDistanceFromFirstWaypoint(self.ppc:getCurrentWaypointIx()) < distance +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() + end +end + +--- This is to set the offsets on the course at start, or update those values +--- if the user changed them during the run or the AI driver wants to add an offset +function AIDriveStrategyCourse:updateFieldworkOffset(course) + course:setOffset(self.settings.toolOffsetX:getValue() + (self.aiOffsetX or 0) + (self.tightTurnOffset or 0), + (self.aiOffsetZ or 0)) +end + +------------------------------------------------------------------------------------------------------------------------ +--- Info texts +--------------------------------------------------------------------------------------------------------------------------- + +--- Registers info texts for specific states. +---@param infoText CpInfoTextElement +---@param states table +function AIDriveStrategyCourse:registerInfoTextForStates(infoText, states) + if self.registeredInfoTexts[infoText] == nil then + self.registeredInfoTexts[infoText] = states + end +end + +--- Enables/disables based on the state. +function AIDriveStrategyCourse:updateInfoTexts() + for infoText, states in pairs(self.registeredInfoTexts) do + if states[self.state] then + self:setInfoText(infoText) + else + self:clearInfoText(infoText) + end + end end \ No newline at end of file diff --git a/scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua b/scripts/ai/strategies/AIDriveStrategyDriveToFieldWorkStart.lua similarity index 97% rename from scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua rename to scripts/ai/strategies/AIDriveStrategyDriveToFieldWorkStart.lua index 85485e15f..b9372b70a 100644 --- a/scripts/ai/AIDriveStrategyDriveToFieldWorkStart.lua +++ b/scripts/ai/strategies/AIDriveStrategyDriveToFieldWorkStart.lua @@ -1,276 +1,276 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) -Copyright (C) 2022 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. - -Drive strategy for driving to the waypoint where we want to start the fieldwork. - -- Make sure everything is raised (maybe folded?) -- Find a path to the start waypoint (first or last worked on), avoiding fruit -- When getting close to the end of the course (to the work start waypoint), give control - to the field work strategy, which will then drive the last few meters making sure the - implements are in a working position when reaching the work start waypoint. - -]]-- - ----@class AIDriveStrategyDriveToFieldWorkStart : AIDriveStrategyCourse ----@field job CpAIJobFieldWork -AIDriveStrategyDriveToFieldWorkStart = CpObject(AIDriveStrategyCourse) - -AIDriveStrategyDriveToFieldWorkStart.myStates = { - PREPARE_TO_DRIVE = {}, - DRIVING_TO_WORK_START = {}, - WORK_START_REACHED = {}, -} - --- minimum distance to the target when this strategy is even used -AIDriveStrategyDriveToFieldWorkStart.minDistanceToDrive = 20 - -AIDriveStrategyDriveToFieldWorkStart.normalFillLevelFullPercentage = 99.5 - -function AIDriveStrategyDriveToFieldWorkStart:init(task, job) - AIDriveStrategyCourse.init(self, task, job) - AIDriveStrategyCourse.initStates(self, AIDriveStrategyDriveToFieldWorkStart.myStates) - self.state = self.states.INITIAL - self.debugChannel = CpDebug.DBG_FIELDWORK - self.prepareTimeout = 0 - self.emergencyBrake = CpTemporaryObject(true) -end - -function AIDriveStrategyDriveToFieldWorkStart:delete() - AIDriveStrategyCourse.delete(self) -end - -function AIDriveStrategyDriveToFieldWorkStart:initializeImplementControllers(vehicle) - -- these can't handle the standard Giants AI events to raise, so we need to have the controllers for them - self:addImplementController(vehicle, PickupController, Pickup, {}) - self:addImplementController(vehicle, CutterController, Cutter, {}) - self:addImplementController(vehicle, SowingMachineController, SowingMachine, {}) - - self:addImplementController(vehicle, MotorController, Motorized, {}) - self:addImplementController(vehicle, WearableController, Wearable, {}) - self:addImplementController(vehicle, FoldableController, Foldable, {}) -end - -function AIDriveStrategyDriveToFieldWorkStart:start(course, startIx, jobParameters) - self:updateFieldworkOffset(course) - --- Saves the course start position, so it can be given to the job instance. - local x, _, z = course:getWaypointPosition(startIx) - self.startPosition = {x = x, z = z} - local distance = course:getDistanceBetweenVehicleAndWaypoint(self.vehicle, startIx) - if distance < AIDriveStrategyDriveToFieldWorkStart.minDistanceToDrive then - self:debug('Closer than %.0f m to start waypoint (%d), start fieldwork directly', - AIDriveStrategyDriveToFieldWorkStart.minDistanceToDrive, startIx) - self.state = self.states.WORK_START_REACHED - self.job:setStartPosition(self.startPosition) - self:setCurrentTaskFinished() - else - self:debug('Start driving to work start waypoint') - local implement = AIUtil.getImplementWithSpecialization(self.vehicle, Cutter) - if self.settings.foldImplementAtEnd:getValue() then - self.vehicle:prepareForAIDriving() - end - if self:giantsPreFoldHeaderWithWheelsFix(implement) then - self:giantsPostFoldHeaderWithWheelsFix(implement) - end - self:startCourseWithPathfinding(course, startIx) - end -end - -function AIDriveStrategyDriveToFieldWorkStart:update(dt) - AIDriveStrategyCourse.update(self, dt) - self:updateImplementControllers(dt) - if CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then - if self.ppc:getCourse() and self.ppc:getCourse():isTemporary() and CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then - self.ppc:getCourse():draw() - end - end -end - -function AIDriveStrategyDriveToFieldWorkStart:getDriveData(dt, vX, vY, vZ) - - self:updateLowFrequencyImplementControllers() - self:updateLowFrequencyPathfinder() - - local moveForwards = not self.ppc:isReversing() - local gx, gz, _ - - if not moveForwards then - local maxSpeed - gx, gz, maxSpeed = self:getReverseDriveData() - self:setMaxSpeed(maxSpeed) - else - gx, _, gz = self.ppc:getGoalPointPosition() - end - - if self.state == self.states.PREPARE_TO_DRIVE then - self:setMaxSpeed(0) - local isReadyToDrive, blockingVehicle = self.vehicle:getIsAIReadyToDrive() - if isReadyToDrive or not self.settings.foldImplementAtEnd:getValue() then - self.state = self.states.DRIVING_TO_WORK_START - self:debug('Ready to drive to work start') - else - self:debugSparse('Not ready to drive because of %s, preparing ...', CpUtil.getName(blockingVehicle)) - if not self.vehicle:getIsAIPreparingToDrive() then - self.prepareTimeout = self.prepareTimeout + dt - if 2000 < self.prepareTimeout then - self:debug('Timeout preparing, continue anyway') - self.state = self.states.DRIVING_TO_WORK_START - end - end - end - elseif self.state == self.states.DRIVING_TO_WORK_START then - self:setMaxSpeed(self.settings.fieldSpeed:getValue()) - elseif self.state == self.states.WORK_START_REACHED then - if self.emergencyBrake:get() then - self:debugSparse('Work start reached but field work did not start...') - self:setMaxSpeed(0) - else - self:setMaxSpeed(self.settings.turnSpeed:getValue()) - end - end - - self:checkProximitySensors(moveForwards) - - return gx, gz, moveForwards, self.maxSpeed, 100 -end - ------------------------------------------------------------------------------------------------------------------------- ---- Pathfinding ---------------------------------------------------------------------------------------------------------------------------- ----@param course Course ----@param ix number -function AIDriveStrategyDriveToFieldWorkStart:startCourseWithPathfinding(course, ix) - self.course = course - self.ppc:setCourse(self.course) - self.ppc:initialize(ix) - self:rememberCourse(course, ix) - self:setFrontAndBackMarkers() - - local context = PathfinderContext(self.vehicle) - context:maxFruitPercent(self.settings.avoidFruit:getValue() and 10 or math.huge) - -- if there is fruit at the target, create an area around it where the pathfinder ignores the fruit - -- so there's no penalty driving there. This is to speed up pathfinding when start harvesting for instance - local x, _, z = course:getWaypointPosition(ix) - local fruitAtTarget = PathfinderUtil.hasFruit(x, z, self.workWidth, self.workWidth) - local fieldNum = CpFieldUtil.getFieldIdAtWorldPosition(x, z) - context:areaToIgnoreFruit(fruitAtTarget and PathfinderUtil.Area(x, z, 2 * self.workWidth) or nil) - context:useFieldNum(fieldNum):allowReverse(self:getAllowReversePathfinding()) - - local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) - -- always drive a behind the target waypoint so there's room to straighten out towed implements - -- a bit before start working - self.zOffset = math.min(-self.frontMarkerDistance, -steeringLength) - self:debug('Pathfinding to waypoint %d, with zOffset %.1f = min(%.1f, %.1f)', ix, self.zOffset, - -self.frontMarkerDistance, -steeringLength) - - self.pathfinderController:findPathToWaypoint(context, course, ix, 0, self.zOffset, 1) -end - ---- Pathfinding has finished ----@param controller PathfinderController ----@param success boolean ----@param course Course|nil ----@param goalNodeInvalid boolean|nil -function AIDriveStrategyDriveToFieldWorkStart:onPathfindingFinished(controller, success, course, goalNodeInvalid) - if success then - course:adjustForTowedImplements(2) - else - self:debug('Pathfinding to start fieldwork failed, using alignment course instead') - local fieldWorkCourse, ix = self:getRememberedCourseAndIx() - course = self:createAlignmentCourse(fieldWorkCourse, ix) - end - self.state = self.states.PREPARE_TO_DRIVE - self:startCourse(course, 1) -end - ---- Pathfinding failed, but a retry attempt is leftover. ----@param controller PathfinderController ----@param lastContext PathfinderContext ----@param wasLastRetry boolean ----@param currentRetryAttempt number -function AIDriveStrategyDriveToFieldWorkStart:onPathfindingFailed(controller, lastContext, wasLastRetry, currentRetryAttempt) - self:debug('Failed to find a path, trying with a reduced off-field penalty and no fruit avoidence again') - lastContext:offFieldPenalty(PathfinderContext.defaultOffFieldPenalty / 2):ignoreFruit():ignoreFruitHeaps() - controller:retry(lastContext) -end - ------------------------------------------------------------------------------------------------------------------------ ---- Event listeners ------------------------------------------------------------------------------------------------------------------------ ----@param course Course -function AIDriveStrategyDriveToFieldWorkStart:onWaypointChange(ix, course) - if course:isCloseToLastWaypoint(15) then - self.state = self.states.WORK_START_REACHED - -- just in case no one takes the wheel in a few seconds - self.emergencyBrake:set(false, 2000) - self:debug('Almost at the work start waypoint, preparing for work') - -- let the field work strategy know where to continue - self.job:setStartFieldWorkCourse(course, ix) - self.job:setStartPosition(self.startPosition) - self:setCurrentTaskFinished() - end -end - ---- For whatever reason giants decided to turn on sprayers in: ---- Sprayer:onStartWorkAreaProcessing(dt), when AI is active. ---- Even if the AI is not a fieldworker and so on ... -function AIDriveStrategyDriveToFieldWorkStart.giantsTurnOnFix(vehicle, superFunc, ...) - local rootVehicle = vehicle.rootVehicle - if rootVehicle.getIsCpDriveToFieldWorkActive and rootVehicle:getIsCpDriveToFieldWorkActive() then - return - end - return superFunc(vehicle, ...) -end -TurnOnVehicle.setIsTurnedOn = Utils.overwrittenFunction(TurnOnVehicle.setIsTurnedOn, - AIDriveStrategyDriveToFieldWorkStart.giantsTurnOnFix) - ---- Removes the fold ai prepare event, as these cutters with foldable wheels don't need to be folded. ----@param implement table|nil ----@return boolean|nil Event was removed -function AIDriveStrategyDriveToFieldWorkStart:giantsPreFoldHeaderWithWheelsFix(implement) - if not implement or not implement.spec_foldable or not implement.spec_attachable then - return - end - local controller = implement.spec_foldable.controlledActionFold - if not controller then - return - end - for _, attacherJoint in pairs(implement:getInputAttacherJoints()) do - if attacherJoint.jointType ~= AttacherJoints.JOINTTYPE_CUTTER and - attacherJoint.jointType ~= AttacherJoints.JOINTTYPE_CUTTERHARVESTER then - --- At least one attaching joint, which is not meant for a cutter was found. - --- This properly means a foldable cutter to trailer was found, like the New Holland Superflex Draper 45 ft. - local ixToDelete - for ix, listener in pairs(controller.aiEventListener) do - if listener.eventName == "onAIImplementPrepare" then - ixToDelete = ix - break - end - end - if ixToDelete ~= nil then - table.remove(controller.aiEventListener, ixToDelete) - implement:setFoldDirection(implement.spec_foldable.turnOnFoldDirection or 1) - return true - end - end - end -end - ---- Resets to status quo ----@param implement table -function AIDriveStrategyDriveToFieldWorkStart:giantsPostFoldHeaderWithWheelsFix(implement) - implement.spec_foldable.controlledActionFold:addAIEventListener(implement, "onAIImplementPrepare", -1, true) +--[[ +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) +Copyright (C) 2022 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Drive strategy for driving to the waypoint where we want to start the fieldwork. + +- Make sure everything is raised (maybe folded?) +- Find a path to the start waypoint (first or last worked on), avoiding fruit +- When getting close to the end of the course (to the work start waypoint), give control + to the field work strategy, which will then drive the last few meters making sure the + implements are in a working position when reaching the work start waypoint. + +]]-- + +---@class AIDriveStrategyDriveToFieldWorkStart : AIDriveStrategyCourse +---@field job CpAIJobFieldWork +AIDriveStrategyDriveToFieldWorkStart = CpObject(AIDriveStrategyCourse) + +AIDriveStrategyDriveToFieldWorkStart.myStates = { + PREPARE_TO_DRIVE = {}, + DRIVING_TO_WORK_START = {}, + WORK_START_REACHED = {}, +} + +-- minimum distance to the target when this strategy is even used +AIDriveStrategyDriveToFieldWorkStart.minDistanceToDrive = 20 + +AIDriveStrategyDriveToFieldWorkStart.normalFillLevelFullPercentage = 99.5 + +function AIDriveStrategyDriveToFieldWorkStart:init(task, job) + AIDriveStrategyCourse.init(self, task, job) + AIDriveStrategyCourse.initStates(self, AIDriveStrategyDriveToFieldWorkStart.myStates) + self.state = self.states.INITIAL + self.debugChannel = CpDebug.DBG_FIELDWORK + self.prepareTimeout = 0 + self.emergencyBrake = CpTemporaryObject(true) +end + +function AIDriveStrategyDriveToFieldWorkStart:delete() + AIDriveStrategyCourse.delete(self) +end + +function AIDriveStrategyDriveToFieldWorkStart:initializeImplementControllers(vehicle) + -- these can't handle the standard Giants AI events to raise, so we need to have the controllers for them + self:addImplementController(vehicle, PickupController, Pickup, {}) + self:addImplementController(vehicle, CutterController, Cutter, {}) + self:addImplementController(vehicle, SowingMachineController, SowingMachine, {}) + + self:addImplementController(vehicle, MotorController, Motorized, {}) + self:addImplementController(vehicle, WearableController, Wearable, {}) + self:addImplementController(vehicle, FoldableController, Foldable, {}) +end + +function AIDriveStrategyDriveToFieldWorkStart:start(course, startIx, jobParameters) + self:updateFieldworkOffset(course) + --- Saves the course start position, so it can be given to the job instance. + local x, _, z = course:getWaypointPosition(startIx) + self.startPosition = {x = x, z = z} + local distance = course:getDistanceBetweenVehicleAndWaypoint(self.vehicle, startIx) + if distance < AIDriveStrategyDriveToFieldWorkStart.minDistanceToDrive then + self:debug('Closer than %.0f m to start waypoint (%d), start fieldwork directly', + AIDriveStrategyDriveToFieldWorkStart.minDistanceToDrive, startIx) + self.state = self.states.WORK_START_REACHED + self.job:setStartPosition(self.startPosition) + self:setCurrentTaskFinished() + else + self:debug('Start driving to work start waypoint') + local implement = AIUtil.getImplementWithSpecialization(self.vehicle, Cutter) + if self.settings.foldImplementAtEnd:getValue() then + self.vehicle:prepareForAIDriving() + end + if self:giantsPreFoldHeaderWithWheelsFix(implement) then + self:giantsPostFoldHeaderWithWheelsFix(implement) + end + self:startCourseWithPathfinding(course, startIx) + end +end + +function AIDriveStrategyDriveToFieldWorkStart:update(dt) + AIDriveStrategyCourse.update(self, dt) + self:updateImplementControllers(dt) + if CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then + if self.ppc:getCourse() and self.ppc:getCourse():isTemporary() and CpDebug:isChannelActive(CpDebug.DBG_FIELDWORK, self.vehicle) then + self.ppc:getCourse():draw() + end + end +end + +function AIDriveStrategyDriveToFieldWorkStart:getDriveData(dt, vX, vY, vZ) + + self:updateLowFrequencyImplementControllers() + self:updateLowFrequencyPathfinder() + + local moveForwards = not self.ppc:isReversing() + local gx, gz, _ + + if not moveForwards then + local maxSpeed + gx, gz, maxSpeed = self:getReverseDriveData() + self:setMaxSpeed(maxSpeed) + else + gx, _, gz = self.ppc:getGoalPointPosition() + end + + if self.state == self.states.PREPARE_TO_DRIVE then + self:setMaxSpeed(0) + local isReadyToDrive, blockingVehicle = self.vehicle:getIsAIReadyToDrive() + if isReadyToDrive or not self.settings.foldImplementAtEnd:getValue() then + self.state = self.states.DRIVING_TO_WORK_START + self:debug('Ready to drive to work start') + else + self:debugSparse('Not ready to drive because of %s, preparing ...', CpUtil.getName(blockingVehicle)) + if not self.vehicle:getIsAIPreparingToDrive() then + self.prepareTimeout = self.prepareTimeout + dt + if 2000 < self.prepareTimeout then + self:debug('Timeout preparing, continue anyway') + self.state = self.states.DRIVING_TO_WORK_START + end + end + end + elseif self.state == self.states.DRIVING_TO_WORK_START then + self:setMaxSpeed(self.settings.fieldSpeed:getValue()) + elseif self.state == self.states.WORK_START_REACHED then + if self.emergencyBrake:get() then + self:debugSparse('Work start reached but field work did not start...') + self:setMaxSpeed(0) + else + self:setMaxSpeed(self.settings.turnSpeed:getValue()) + end + end + + self:checkProximitySensors(moveForwards) + + return gx, gz, moveForwards, self.maxSpeed, 100 +end + +------------------------------------------------------------------------------------------------------------------------ +--- Pathfinding +--------------------------------------------------------------------------------------------------------------------------- +---@param course Course +---@param ix number +function AIDriveStrategyDriveToFieldWorkStart:startCourseWithPathfinding(course, ix) + self.course = course + self.ppc:setCourse(self.course) + self.ppc:initialize(ix) + self:rememberCourse(course, ix) + self:setFrontAndBackMarkers() + + local context = PathfinderContext(self.vehicle) + context:maxFruitPercent(self.settings.avoidFruit:getValue() and 10 or math.huge) + -- if there is fruit at the target, create an area around it where the pathfinder ignores the fruit + -- so there's no penalty driving there. This is to speed up pathfinding when start harvesting for instance + local x, _, z = course:getWaypointPosition(ix) + local fruitAtTarget = PathfinderUtil.hasFruit(x, z, self.workWidth, self.workWidth) + local fieldNum = CpFieldUtil.getFieldIdAtWorldPosition(x, z) + context:areaToIgnoreFruit(fruitAtTarget and PathfinderUtil.Area(x, z, 2 * self.workWidth) or nil) + context:useFieldNum(fieldNum):allowReverse(self:getAllowReversePathfinding()) + + local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) + -- always drive a behind the target waypoint so there's room to straighten out towed implements + -- a bit before start working + self.zOffset = math.min(-self.frontMarkerDistance, -steeringLength) + self:debug('Pathfinding to waypoint %d, with zOffset %.1f = min(%.1f, %.1f)', ix, self.zOffset, + -self.frontMarkerDistance, -steeringLength) + + self.pathfinderController:findPathToWaypoint(context, course, ix, 0, self.zOffset, 1) +end + +--- Pathfinding has finished +---@param controller PathfinderController +---@param success boolean +---@param course Course|nil +---@param goalNodeInvalid boolean|nil +function AIDriveStrategyDriveToFieldWorkStart:onPathfindingFinished(controller, success, course, goalNodeInvalid) + if success then + course:adjustForTowedImplements(2) + else + self:debug('Pathfinding to start fieldwork failed, using alignment course instead') + local fieldWorkCourse, ix = self:getRememberedCourseAndIx() + course = self:createAlignmentCourse(fieldWorkCourse, ix) + end + self.state = self.states.PREPARE_TO_DRIVE + self:startCourse(course, 1) +end + +--- Pathfinding failed, but a retry attempt is leftover. +---@param controller PathfinderController +---@param lastContext PathfinderContext +---@param wasLastRetry boolean +---@param currentRetryAttempt number +function AIDriveStrategyDriveToFieldWorkStart:onPathfindingFailed(controller, lastContext, wasLastRetry, currentRetryAttempt) + self:debug('Failed to find a path, trying with a reduced off-field penalty and no fruit avoidence again') + lastContext:offFieldPenalty(PathfinderContext.defaultOffFieldPenalty / 2):ignoreFruit():ignoreFruitHeaps() + controller:retry(lastContext) +end + +----------------------------------------------------------------------------------------------------------------------- +--- Event listeners +----------------------------------------------------------------------------------------------------------------------- +---@param course Course +function AIDriveStrategyDriveToFieldWorkStart:onWaypointChange(ix, course) + if course:isCloseToLastWaypoint(15) then + self.state = self.states.WORK_START_REACHED + -- just in case no one takes the wheel in a few seconds + self.emergencyBrake:set(false, 2000) + self:debug('Almost at the work start waypoint, preparing for work') + -- let the field work strategy know where to continue + self.job:setStartFieldWorkCourse(course, ix) + self.job:setStartPosition(self.startPosition) + self:setCurrentTaskFinished() + end +end + +--- For whatever reason giants decided to turn on sprayers in: +--- Sprayer:onStartWorkAreaProcessing(dt), when AI is active. +--- Even if the AI is not a fieldworker and so on ... +function AIDriveStrategyDriveToFieldWorkStart.giantsTurnOnFix(vehicle, superFunc, ...) + local rootVehicle = vehicle.rootVehicle + if rootVehicle.getIsCpDriveToFieldWorkActive and rootVehicle:getIsCpDriveToFieldWorkActive() then + return + end + return superFunc(vehicle, ...) +end +TurnOnVehicle.setIsTurnedOn = Utils.overwrittenFunction(TurnOnVehicle.setIsTurnedOn, + AIDriveStrategyDriveToFieldWorkStart.giantsTurnOnFix) + +--- Removes the fold ai prepare event, as these cutters with foldable wheels don't need to be folded. +---@param implement table|nil +---@return boolean|nil Event was removed +function AIDriveStrategyDriveToFieldWorkStart:giantsPreFoldHeaderWithWheelsFix(implement) + if not implement or not implement.spec_foldable or not implement.spec_attachable then + return + end + local controller = implement.spec_foldable.controlledActionFold + if not controller then + return + end + for _, attacherJoint in pairs(implement:getInputAttacherJoints()) do + if attacherJoint.jointType ~= AttacherJoints.JOINTTYPE_CUTTER and + attacherJoint.jointType ~= AttacherJoints.JOINTTYPE_CUTTERHARVESTER then + --- At least one attaching joint, which is not meant for a cutter was found. + --- This properly means a foldable cutter to trailer was found, like the New Holland Superflex Draper 45 ft. + local ixToDelete + for ix, listener in pairs(controller.aiEventListener) do + if listener.eventName == "onAIImplementPrepare" then + ixToDelete = ix + break + end + end + if ixToDelete ~= nil then + table.remove(controller.aiEventListener, ixToDelete) + implement:setFoldDirection(implement.spec_foldable.turnOnFoldDirection or 1) + return true + end + end + end +end + +--- Resets to status quo +---@param implement table +function AIDriveStrategyDriveToFieldWorkStart:giantsPostFoldHeaderWithWheelsFix(implement) + implement.spec_foldable.controlledActionFold:addAIEventListener(implement, "onAIImplementPrepare", -1, true) end \ No newline at end of file diff --git a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua b/scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua similarity index 98% rename from scripts/ai/AIDriveStrategyFieldWorkCourse.lua rename to scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua index b4aee58d2..6be9c78ff 100644 --- a/scripts/ai/AIDriveStrategyFieldWorkCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyFieldWorkCourse.lua @@ -1,867 +1,867 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) -Copyright (C) 2021 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. - -Drive strategy for driving a field work course - -]]-- - ----@class AIDriveStrategyFieldWorkCourse : AIDriveStrategyCourse -AIDriveStrategyFieldWorkCourse = CpObject(AIDriveStrategyCourse) - -AIDriveStrategyFieldWorkCourse.myStates = { - WORKING = {}, - WAITING_FOR_LOWER = {}, - WAITING_FOR_LOWER_DELAYED = {}, - WAITING_FOR_STOP = {}, - WAITING_FOR_WEATHER = {}, - TURNING = {showTurnContextDebug = true}, - TEMPORARY = {}, - RETURNING_TO_START = {}, - DRIVING_TO_WORK_START_WAYPOINT = {showTurnContextDebug = true}, -} - -AIDriveStrategyFieldWorkCourse.normalFillLevelFullPercentage = 99.5 - -function AIDriveStrategyFieldWorkCourse:init(task, job) - AIDriveStrategyCourse.init(self, task, job) - AIDriveStrategyCourse.initStates(self, AIDriveStrategyFieldWorkCourse.myStates) - self.state = self.states.INITIAL - -- cache for the nodes created by TurnContext - self.turnNodes = {} - -- course offsets dynamically set by the AI and added to all tool and other offsets - self.aiOffsetX, self.aiOffsetZ = 0, 0 - self.debugChannel = CpDebug.DBG_FIELDWORK - self.waitingForPrepare = CpTemporaryObject(false) -end - -function AIDriveStrategyFieldWorkCourse:delete() - AIDriveStrategyCourse.delete(self) - self:raiseImplements() - TurnContext.deleteNodes(self.turnNodes) - self:rememberWaypointToContinueFieldWork() -end - ---- Start a fieldwork course. We expect that something else dropped us off close enough to startIx so ---- the most we need is an alignment course to lower the implements -function AIDriveStrategyFieldWorkCourse:start(course, startIx, jobParameters) - self:showAllInfo('Starting field work at waypoint %d', startIx) - self:updateFieldworkOffset(course) - self.fieldWorkCourse = course - self.fieldWorkCourse:setCurrentWaypointIx(startIx) - self.remainingTime = CpRemainingTime(self.vehicle, course, startIx) - -- remember at which waypoint we started, especially for the convoy - self.startWaypointIx = startIx - self.vehiclesInConvoy = {} - - local distance = course:getDistanceBetweenVehicleAndWaypoint(self.vehicle, startIx) - - ---@type CpAIJobFieldWork - local job = self.vehicle:getJob() - local alignmentCourse, alignmentCourseStartIx = job:getStartFieldWorkCourse() - - if alignmentCourse then - -- there is an alignment course already created by the AIDriveStrategyDriveToFieldWorkStart, - -- and we are supposed to continue on that one - self:debug('Continuing the alignment course at %d to start work.', alignmentCourseStartIx) - -- make sure the alignment course is used only once - job:setStartFieldWorkCourse(nil, nil) - self.course = course - self:startAlignmentTurn(course, startIx, alignmentCourse, alignmentCourseStartIx) - elseif distance > 2 * self.turningRadius then - self:debug('Start waypoint is far (%.1f m), use alignment course to get there.', distance) - self.course = course - self:startAlignmentTurn(course, startIx) - else - self:debug('Close enough to start waypoint %d, no alignment course needed', startIx) - self:startCourse(course, startIx) - self.state = self.states.INITIAL - self:prepareForFieldWork() - end - --- Store a reference to the original generated course - self.originalGeneratedFieldWorkCourse = self.vehicle:getFieldWorkCourse() - - if self.fieldPolygon == nil then - self:debug("No field polygon received, so regenerate it by the course.") - self.fieldPolygon = self.fieldWorkCourse:getFieldPolygon() - end -end - ---- Make sure all implements are in the working state -function AIDriveStrategyFieldWorkCourse:prepareForFieldWork() - self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', 'onAIImplementPrepareForWork') -end - ---- Event raised when the driver has finished. -function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished) - AIDriveStrategyCourse.onFinished(self, hasFinished) - self.remainingTime:reset() -end - -function AIDriveStrategyFieldWorkCourse:update(dt) - AIDriveStrategyCourse.update(self, dt) - if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then - if self.state == self.states.TURNING then - if self.aiTurn then - self.aiTurn:drawDebug() - end - end - if self.state.properties.showTurnContextDebug then - if self.turnContext then - self.turnContext:drawDebug() - end - end - -- TODO_22 check user setting - if self.course:isTemporary() then - self.course:draw() - elseif self.ppc:getCourse():isTemporary() then - self.ppc:getCourse():draw() - end - end - if CpDebug:isChannelActive(CpDebug.DBG_PATHFINDER, self.vehicle) then - if self.pathfinder then - PathfinderUtil.showNodes(self.pathfinder) - end - end - if self.fieldWorkerProximityController then - self.fieldWorkerProximityController:draw() - end - self:updateImplementControllers(dt) - self.remainingTime:update(dt) -end - ---- This is the interface to the Giant's AIFieldWorker specialization, telling it the direction and speed -function AIDriveStrategyFieldWorkCourse:getDriveData(dt, vX, vY, vZ) - - self:updateFieldworkOffset(self.course) - self:updateLowFrequencyImplementControllers() - - local moveForwards = not self.ppc:isReversing() - local gx, gz - - ---------------------------------------------------------------- - if not moveForwards then - local maxSpeed - gx, gz, maxSpeed = self:getReverseDriveData() - self:setMaxSpeed(maxSpeed) - else - gx, _, gz = self.ppc:getGoalPointPosition() - end - ---------------------------------------------------------------- - if self.state == self.states.INITIAL then - self:setMaxSpeed(0) - self:startWaitingForLower() - self:lowerImplements() - elseif self.state == self.states.WAITING_FOR_LOWER then - self:setMaxSpeed(0) - if self:getCanContinueWork() then - self:debug('all tools ready, start working') - self.state = self.states.WORKING - else - self:debugSparse('waiting for all tools to lower') - end - elseif self.state == self.states.WAITING_FOR_LOWER_DELAYED then - -- getCanAIVehicleContinueWork() seems to return false when the implement being lowered/raised (moving) but - -- true otherwise. Due to some timing issues it may return true just after we started lowering it, so this - -- here delays the check for another cycle. - self.state = self.states.WAITING_FOR_LOWER - self:setMaxSpeed(0) - elseif self.state == self.states.WAITING_FOR_PATHFINDER then - self:setMaxSpeed(0) - elseif self.state == self.states.WORKING then - self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue()) - elseif self.state == self.states.TURNING then - local turnGx, turnGz, turnMoveForwards, turnMaxSpeed = self.aiTurn:getDriveData(dt) - self:setMaxSpeed(turnMaxSpeed) - -- if turn tells us which way to go, use that, otherwise just do whatever PPC tells us - gx, gz = turnGx or gx, turnGz or gz - if turnMoveForwards ~= nil then moveForwards = turnMoveForwards end - elseif self.state == self.states.RETURNING_TO_START then - local isReadyToDrive, blockingVehicle = self.vehicle:getIsAIReadyToDrive() - if isReadyToDrive or not self.waitingForPrepare:get() then - -- ready to drive or we just timed out waiting to be ready - self:setMaxSpeed(self.settings.fieldSpeed:getValue()) - else - self:debugSparse('Not ready to drive because of %s, preparing ...', CpUtil.getName(blockingVehicle)) - end - elseif self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then - self:setMaxSpeed(self.settings.fieldSpeed:getValue()) - local _, _, _, maxSpeed = self.workStarter:getDriveData() - if maxSpeed ~= nil then - self:setMaxSpeed(maxSpeed) - end - end - - self:setAITarget() - self:limitSpeed() - self:checkProximitySensors(moveForwards) - self:checkDistanceToOtherFieldWorkers() - - return gx, gz, moveForwards, self.maxSpeed, 100 -end - -function AIDriveStrategyFieldWorkCourse:checkDistanceToOtherFieldWorkers() - -- keep away from others working on the same course - self:setMaxSpeed(self.fieldWorkerProximityController:getMaxSpeed(self.settings.convoyDistance:getValue(), self.maxSpeed)) -end - --- Seems like the Giants AIDriveStrategyCollision needs these variables on the vehicle to be set --- to calculate an accurate path prediction -function AIDriveStrategyFieldWorkCourse:setAITarget() - --local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) - local wp = self.ppc:getCurrentWaypoint() - --- TODO: For some reason wp.dx and wp.dz are nil sometimes - local dx, dz = wp.dx or 0, wp.dz or 0 - if wp.dx ~= 0 or wp.dz ~= 0 then - local length = MathUtil.vector2Length(dx, dz) - dx = dx / length - dz = dz / length - end - self.vehicle.aiDriveDirection = { dx, dz } - local x, _, z = getWorldTranslation(self.vehicle:getAIDirectionNode()) - self.vehicle.aiDriveTarget = { x, z } -end - ------------------------------------------------------------------------------------------------------------------------ ---- Implement handling ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:initializeImplementControllers(vehicle) - - local defaultDisabledStates = { - self.states.TEMPORARY, - self.states.TURNING, - self.states.DRIVING_TO_WORK_START_WAYPOINT - } - self:addImplementController(vehicle, BalerController, Baler, {}) - self:addImplementController(vehicle, BaleWrapperController, BaleWrapper, defaultDisabledStates) - self:addImplementController(vehicle, BaleLoaderController, BaleLoader, defaultDisabledStates) - self:addImplementController(vehicle, APalletAutoLoaderController, nil, {}, "spec_aPalletAutoLoader") - self:addImplementController(vehicle, UniversalAutoloadController, nil, {}, "spec_universalAutoload") - - self:addImplementController(vehicle, CombineController, Combine, defaultDisabledStates) - self:addImplementController(vehicle, FertilizingSowingMachineController, FertilizingSowingMachine, defaultDisabledStates) - self:addImplementController(vehicle, ForageWagonController, ForageWagon, defaultDisabledStates) - self:addImplementController(vehicle, SowingMachineController, SowingMachine, defaultDisabledStates) - self:addImplementController(vehicle, FertilizingCultivatorController, FertilizingCultivator, defaultDisabledStates) - self:addImplementController(vehicle, MowerController, Mower, defaultDisabledStates) - - self:addImplementController(vehicle, RidgeMarkerController, RidgeMarker, defaultDisabledStates) - self:addImplementController(vehicle, PlowController, Plow, defaultDisabledStates) - - self:addImplementController(vehicle, PickupController, Pickup, defaultDisabledStates) - self:addImplementController(vehicle, SprayerController, Sprayer, {}) - self:addImplementController(vehicle, CutterController, Cutter, {}) --- Makes sure the cutter timer gets reset always. - self:addImplementController(vehicle, StonePickerController, StonePicker, defaultDisabledStates) - - self:addImplementController(vehicle, FoldableController, Foldable, {}) - self:addImplementController(vehicle, MotorController, Motorized, {}) - self:addImplementController(vehicle, WearableController, Wearable, {}) - self:addImplementController(vehicle, VineCutterController, VineCutter, defaultDisabledStates) - self:addImplementController(vehicle, PalletFillerController, nil, defaultDisabledStates, "spec_pdlc_premiumExpansion.palletFiller") - - self:addImplementController(vehicle, SoilSamplerController, nil, defaultDisabledStates, "spec_soilSampler") - self:addImplementController(vehicle, StumpCutterController, StumpCutter, defaultDisabledStates) - self:addImplementController(vehicle, TreePlanterController, TreePlanter, {}) - -end - ---- Start waiting for the implements to lower --- getCanAIVehicleContinueWork() seems to return false when the implement being lowered/raised (moving) but --- true otherwise. Due to some timing issues it may return true just after we started lowering it, so we --- set a different state for those implements -function AIDriveStrategyFieldWorkCourse:startWaitingForLower() - -- TODO 25 looks like we always need to wait that extra cycle with FS25 - if true or AIUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine) or self.ppc:isReversing() then - -- sowing machines want to stop while the implement is being lowered - -- also, when reversing, we assume that we'll switch to forward, so stop while lowering, then start forward - self.state = self.states.WAITING_FOR_LOWER_DELAYED - self:debug('waiting for lower delayed') - else - self.state = self.states.WAITING_FOR_LOWER - self:debug('waiting for lower') - end -end - -function AIDriveStrategyFieldWorkCourse:shouldRaiseImplements(turnStartNode) - -- see if the vehicle has AI markers -> has work areas (built-in implements like a mower or cotton harvester) - local doRaise = self:shouldRaiseThisImplement(self.vehicle, turnStartNode) - -- and then check all implements - for _, implement in pairs(AIUtil.getAllAIImplements(self.vehicle)) do - -- only when _all_ implements can be raised will we raise them all, hence the 'and' - doRaise = doRaise and self:shouldRaiseThisImplement(implement.object, turnStartNode) - end - return doRaise -end - ----@param turnStartNode number at the last waypoint of the row, pointing in the direction of travel. This is where ---- the implement should be raised when beginning a turn -function AIDriveStrategyFieldWorkCourse:shouldRaiseThisImplement(object, turnStartNode) - local aiFrontMarker, _, aiBackMarker = WorkWidthUtil.getAIMarkers(object, true) - -- if something (like a combine) does not have an AI marker it should not prevent from raising other implements - -- like the header, which does have markers), therefore, return true here - if not aiBackMarker or not aiFrontMarker then return true end - local marker = self:getImplementRaiseLate() and aiBackMarker or aiFrontMarker - -- turn start node in the back marker node's coordinate system - local _, _, dz = localToLocal(marker, turnStartNode, 0, 0, 0) - self:debugSparse('%s: shouldRaiseImplements: dz = %.1f', CpUtil.getName(object), dz) - -- marker is just in front of the turn start node - return dz > 0 -end - - ---- When finishing a turn, is it time to lower all implements here? --- TODO: remove the reversing parameter and use ppc to find out once not called from turn.lua -function AIDriveStrategyFieldWorkCourse:shouldLowerImplements(turnEndNode, reversing) - -- see if the vehicle has AI markers -> has work areas (built-in implements like a mower or cotton harvester) - local doLower, vehicleHasMarkers, dz = self:shouldLowerThisImplement(self.vehicle, turnEndNode, reversing) - if not vehicleHasMarkers and reversing then - -- making sure the 'and' below will work if reversing and the vehicle has no markers - doLower = true - end - -- and then check all implements - for _, implement in ipairs(AIUtil.getAllAIImplements(self.vehicle)) do - if reversing then - -- when driving backward, all implements must reach the turn end node before lowering, hence the 'and' - doLower = doLower and self:shouldLowerThisImplement(implement.object, turnEndNode, reversing) - else - -- when driving forward, if it is time to lower any implement, we'll lower all, hence the 'or' - local lowerThis, _, thisDz = self:shouldLowerThisImplement(implement.object, turnEndNode, reversing) - dz = dz and math.max(dz, thisDz) or thisDz - doLower = doLower or lowerThis - end - end - return doLower, dz -end - ----@param object table is a vehicle or implement object with AI markers (marking the working area of the implement) ----@param workStartNode number node at the first waypoint of the row, pointing in the direction of travel. This is where ---- the implement should be in the working position after a turn ----@param reversing boolean are we reversing? When reversing towards the turn end point, we must lower the implements ---- when we are _behind_ the turn end node (dz < 0), otherwise once we reach it (dz > 0) ----@return boolean, boolean, number the second one is true when the first is valid, and the distance to the work start ---- in meters (<0) when driving forward, nil when driving backwards. -function AIDriveStrategyFieldWorkCourse:shouldLowerThisImplement(object, workStartNode, reversing) - local aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkers(object, true) - if not aiLeftMarker then return false, false, nil end - local dxLeft, _, dzLeft = localToLocal(aiLeftMarker, workStartNode, 0, 0, 0) - local dxRight, _, dzRight = localToLocal(aiRightMarker, workStartNode, 0, 0, 0) - local dxBack, _, dzBack = localToLocal(aiBackMarker, workStartNode, 0, 0, 0) - local loweringDistance - if AIUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine) then - -- sowing machines are stopped while lowering, but leave a little reserve to allow for stopping - -- TODO: rather slow down while approaching the lowering point - loweringDistance = 0.5 - else - -- others can be lowered without stopping so need to start lowering before we get to the turn end to be - -- in the working position by the time we get to the first waypoint of the next row - loweringDistance = math.min(self.vehicle.lastSpeed, self.settings.turnSpeed:getValue() / 3600) * - self.loweringDurationMs + 0.5 -- vehicle.lastSpeed is in meters per millisecond - end - local aligned = CpMathUtil.isSameDirection(object.rootNode, workStartNode, 15) - -- some implements, especially plows may have the left and right markers offset longitudinally - -- so if the implement is aligned with the row direction already, then just take the front one - -- if not aligned, work with an average - local dzFront = aligned and math.max(dzLeft, dzRight) or (dzLeft + dzRight) / 2 - local dxFront = (dxLeft + dxRight) / 2 - self:debug('%s: dzLeft = %.1f, dzRight = %.1f, aligned = %s, dzFront = %.1f, dxFront = %.1f, dzBack = %.1f, loweringDistance = %.1f, reversing %s', - CpUtil.getName(object), dzLeft, dzRight, aligned, dzFront, dxFront, dzBack, loweringDistance, tostring(reversing)) - local dz = self:getImplementLowerEarly() and dzFront or dzBack - if reversing then - return dz < 0, true, nil - else - -- dz will be negative as we are behind the target node. Also, dx must be close enough, otherwise - -- we'll lower them way too early if approaching the turn end from the side at about 90° (and we - -- want a constant value here, certainly not the loweringDistance which changes with the current speed - -- and thus introduces a feedback loop, causing the return value to oscillate, that is, we say should be - -- lowered, than the vehicle stops, but now the loweringDistance will be low, so we say should not be - -- lowering, vehicle starts again, and so on ... - local normalLoweringDistance = self.loweringDurationMs * self.settings.turnSpeed:getValue() / 3600 - return dz > -loweringDistance and math.abs(dxFront) < normalLoweringDistance * 1.5, true, dz - end -end - ---- Are all implements now aligned with the node? Can be used to find out if we are for instance aligned with the ---- turn end node direction in a question mark turn and can start reversing. -function AIDriveStrategyFieldWorkCourse:areAllImplementsAligned(node) - -- see if the vehicle has AI markers -> has work areas (built-in implements like a mower or cotton harvester) - local allAligned = self:isThisImplementAligned(self.vehicle, node) - -- and then check all implements - for _, implement in ipairs(AIUtil.getAllAIImplements(self.vehicle)) do - -- _all_ implements must be aligned, hence the 'and' - allAligned = allAligned and self:isThisImplementAligned(implement.object, node) - end - return allAligned -end - -function AIDriveStrategyFieldWorkCourse:isThisImplementAligned(object, node) - local aiFrontMarker, _, _ = WorkWidthUtil.getAIMarkers(object, true) - if not aiFrontMarker then return true end - return CpMathUtil.isSameDirection(aiFrontMarker, node, 5) -end - ------------------------------------------------------------------------------------------------------------------------ ---- Event listeners ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:onWaypointChange(ix, course) - self:calculateTightTurnOffset() - if not self.state ~= self.states.TURNING - and self.course:isTurnStartAtIx(ix) then - if self.state == self.states.INITIAL then - self:debug('Waypoint change (%d) to turn start right after starting work, lowering implements.', ix) - self:startWaitingForLower() - self:lowerImplements() - end - self:startTurn(ix) - elseif self.state == self.states.WORKING then - if self.course:isOnConnectingPath(ix + 1) or self.course:shouldUsePathfinderToNextWaypoint(ix) then - local fm, bm = self:getFrontAndBackMarkers() - self.turnContext = RowStartOrFinishContext(self.vehicle, self.course, ix, ix, self.turnNodes, self:getWorkWidth(), - fm, bm, 0, 0) - self.aiTurn = FinishRowOnly(self.vehicle, self, self.ppc, self.proximityController, self.turnContext) - self.state = self.states.TURNING - if self.course:isOnConnectingPath(ix + 1) then - self:debug('finishing work before starting on the connecting path') - self.aiTurn:registerTurnEndCallback(self, AIDriveStrategyFieldWorkCourse.startConnectingPath) - else - -- the generated course instructs the vehicle to use the pathfinder to the next waypoint - self:debug('finishing work before starting pathfinding to the next waypoint') - self.aiTurn:registerTurnEndCallback(self, AIDriveStrategyFieldWorkCourse.startPathfindingToNextWaypoint) - end - end - -- towards the end of the field course make sure the implement reaches the last waypoint - -- TODO: this needs refactoring, for now don't do this for temporary courses like a turn as it messes up reversing - if ix > self.course:getNumberOfWaypoints() - 3 and not self.course:isTemporary() then - local _, bm = self:getFrontAndBackMarkers() - self:debug('adding offset (%.1f front marker) to make sure we do not miss anything when the course ends', bm) - self.aiOffsetZ = -bm - end - end -end - -function AIDriveStrategyFieldWorkCourse:onWaypointPassed(ix, course) - if course:isLastWaypointIx(ix) then - self:onLastWaypointPassed() - end -end - ---- Called when the last waypoint of a course is passed -function AIDriveStrategyFieldWorkCourse:onLastWaypointPassed() - -- reset offset we used for the course ending to not miss anything - self.aiOffsetZ = 0 - self:debug('Last waypoint of the course reached.') - if self.state == self.states.RETURNING_TO_START then - self:debug('Returned to first waypoint after fieldwork done, stopping job') - self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) - elseif self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then - self.workStarter:onLastWaypoint() - else - -- by default, stop the job - self:finishFieldWork() - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- Turn ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:startTurn(ix) - self:debug('Starting a turn at waypoint %d', ix) - local fm, bm = self:getFrontAndBackMarkers() - self.ppc:setShortLookaheadDistance() - self.turnContext = TurnContext(self.vehicle, self.course, ix, ix + 1, self.turnNodes, self:getWorkWidth(), fm, bm, - self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) - if AITurn.canMakeKTurn(self.vehicle, self.turnContext, self.workWidth, self:isTurnOnFieldActive()) then - self.aiTurn = KTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.workWidth) - else - self.aiTurn = CourseTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.course, self.workWidth) - end - self.state = self.states.TURNING -end - -function AIDriveStrategyFieldWorkCourse:isTurning() - return self.state == self.states.TURNING -end - --- switch back to fieldwork after the turn ended. ----@param ix number waypoint to resume fieldwork after -function AIDriveStrategyFieldWorkCourse:resumeFieldworkAfterTurn(ix) - self.ppc:setNormalLookaheadDistance() - self:startWaitingForLower() - self:lowerImplements() - local startIx = self.fieldWorkCourse:getNextFwdWaypointIxFromVehiclePosition(ix, - self.vehicle:getAIDirectionNode(), self.workWidth / 2) - self:startCourse(self.fieldWorkCourse, startIx) -end - ---- Attempt to recover from a turn where the vehicle got blocked. This replaces the current turn with a ---- RecoveryTurn, which just backs up a bit and then uses the pathfinder to create a turn back to the ---- start of the next row. ----@param reverseDistance number|nil distance to back up before retrying pathfinding (default 10 m) ----@param retryCount number|nil how many times have we tried to recover so far? (to limit the number of retries) ----@return boolean true if a recovery turn could be created -function AIDriveStrategyFieldWorkCourse:startRecoveryTurn(reverseDistance, retryCount) - self:debug('Blocked in a turn, attempt to recover') - if self.turnContext then - self.aiTurn = RecoveryTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, - self.course, self.workWidth, reverseDistance, retryCount) - self.state = self.states.TURNING - return true - else - self:debug('Lost turn context to recover, remain blocked.') - return false - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- State changes ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:finishFieldWork() - if self.settings.returnToStart:getValue() and self.fieldWorkCourse:startsWithHeadland() then - self:debug('Fieldwork ended, returning to first waypoint.') - self.vehicle:prepareForAIDriving() - self:returnToStartAfterDone() - else - self:debug('Fieldwork ended, stopping job.') - self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) - end -end - -function AIDriveStrategyFieldWorkCourse:changeToFieldWork() - self:debug('change to fieldwork') - self:startWaitingForLower() - self:lowerImplements(self.vehicle) -end - ---- Start alignment turn, that is, a course to the waypoint of fieldWorkCourse where the ---- fieldwork should begin. This is performed as a turn maneuver, more specifically the end of the ---- turn maneuver where the work is started and has the logic to lower the implements exactly ---- where it needs to be. ---- ---- (It is called alignment because it makes sure the vehicle is aligned with the start waypoint so ---- that it points to the right direction and the implements can start working exactly at the waypoint) ---- ---- The caller can pass in an already created alignment course with an index. In that case, we'll use ---- that course, starting at alignmentStartIx for the turn, otherwise a new course is created from ---- the vehicle's current position to startIx in fieldWorkCourse. ---- ----@param fieldWorkCourse Course fieldwork course ----@param startIx number index of waypoint of fieldWorkCourse where the work should start ----@param alignmentCourse Course an optional course if the caller already has one ----@param alignmentStartIx number index to start the alignment course (if supplied) -function AIDriveStrategyFieldWorkCourse:startAlignmentTurn(fieldWorkCourse, startIx, alignmentCourse, alignmentStartIx) - if alignmentCourse then - -- there is an alignment course, use that one, if there is a start ix, then only - -- the part starting at startIx - alignmentCourse = alignmentCourse:copy(self.vehicle, alignmentStartIx) - else - -- no alignment course given, generate one - alignmentCourse = self:createAlignmentCourse(fieldWorkCourse, startIx) - end - self.ppc:setShortLookaheadDistance() - self:prepareForFieldWork() - if alignmentCourse then - local fm, bm = self:getFrontAndBackMarkers() - self.turnContext = RowStartOrFinishContext(self.vehicle, fieldWorkCourse, startIx, startIx, self.turnNodes, - self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) - self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, alignmentCourse) - self.state = self.states.DRIVING_TO_WORK_START_WAYPOINT - self:startCourse(self.workStarter:getCourse(), 1) - else - self:debug('Could not create alignment course to first up/down row waypoint, continue without it') - self:startCourse(fieldWorkCourse, startIx) - self.state = self.states.INITIAL - self:prepareForFieldWork() - end -end - ---- Back to the start waypoint after done -function AIDriveStrategyFieldWorkCourse:returnToStartAfterDone() - if not self.pathfinder or not self.pathfinder:isActive() then - self.pathfindingStartedAt = g_currentMission.time - self:debug('Return to first waypoint') - local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) - local result - self.pathfinder, result = PathfinderUtil.startPathfindingFromVehicleToWaypoint( - self.fieldWorkCourse, 1, 0, 0, context) - if result.done then - return self:onPathfindingDoneToReturnToStart(result.path) - else - self.state = self.states.WAITING_FOR_PATHFINDER - self:setPathfindingDoneCallback(self, self.onPathfindingDoneToReturnToStart) - end - else - self:debug('Pathfinder already active, stopping job') - self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) - end -end - -function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToReturnToStart(path) - if path and #path > 2 then - self:debug('Pathfinding to return to start finished with %d waypoints (%d ms)', - #path, g_currentMission.time - (self.pathfindingStartedAt or 0)) - local returnCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) - self.state = self.states.RETURNING_TO_START - self.waitingForPrepare:set(true, 10000) - self:startCourse(returnCourse, 1) - else - self:debug('No path found to return to fieldwork start after work is done (%d ms), stopping job', - g_currentMission.time - (self.pathfindingStartedAt or 0)) - self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) - end -end ------------------------------------------------------------------------------------------------------------------------ ---- Use pathfinder to next waypoint ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:startPathfindingToNextWaypoint(ix) - self:debug('start pathfinding to waypoint %d', ix + 1) - self:raiseImplements() - local fm, bm = self:getFrontAndBackMarkers() - self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, ix + 1, ix + 1, - self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) - local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) - local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength) - local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) - self.waypointToContinueOnFailedPathfinding = ix + 1 - self.pathfinderController:registerListeners(self, self.onPathfindingDoneToNextWaypoint, - self.onPathfindingFailedToNextWaypoint) - self:debug('Start pathfinding to target waypoint %d, zOffset %.1f', ix + 1, zOffset) - self.state = self.states.WAITING_FOR_PATHFINDER - -- to have a course set while waiting for the pathfinder - self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding) - self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset) -end - -function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToNextWaypoint() - self:debug('Pathfinding to next waypoint failed, continue directly at waypoint %d', self.waypointToContinueOnFailedPathfinding) - self.state = self.states.WORKING - self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding) -end - -function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToNextWaypoint(controller, success, course, goalNodeInvalid) - if success then - self:debug('Pathfinding to next waypoint finished') - self:startCourseToWorkStart(course) - else - self:onPathfindingFailedToNextWaypoint() - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- Connecting path ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:startConnectingPath(ix) - -- ix was the last waypoint to work before the connecting path, ix + 1 is the first on the connecting path - self:debug('on a connecting path now at waypoint %d, raising implements.', ix + 1) - self:raiseImplements() - -- gather the connecting path waypoints - local connectingPath = {} - local targetWaypointIx - for i = ix + 1, self.fieldWorkCourse:getNumberOfWaypoints() do - if self.fieldWorkCourse:isOnConnectingPath(i) then - local x, _, z = self.fieldWorkCourse:getWaypointPosition(i) - table.insert(connectingPath, {x = x, z = z}) - else - targetWaypointIx = i - break - end - end - if targetWaypointIx == nil then - self:onPathfindingFailedToConnectingPathEnd() - else - -- set up the turn context for the work starter to use when the pathfinding succeeds - local fm, bm = self:getFrontAndBackMarkers() - self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, targetWaypointIx, targetWaypointIx, - self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) - local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) - local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength) - local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) - context:preferredPath(connectingPath):mustBeAccurate(true) - self.waypointToContinueOnFailedPathfinding = ix + 1 - self.pathfinderController:registerListeners(self, self.onPathfindingDoneToConnectingPathEnd, - self.onPathfindingFailedToConnectingPathEnd) - self:debug('Connecting path has %d waypoints, start pathfinding to target waypoint %d, zOffset %.1f', - #connectingPath, targetWaypointIx, zOffset) - self.state = self.states.WAITING_FOR_PATHFINDER - self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset) - end -end - -function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToConnectingPathEnd() - self:debug('Pathfinding to end of connecting path failed, use the connecting path as is') - self.state = self.states.WORKING - self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding) -end - -function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToConnectingPathEnd(controller, success, course, goalNodeInvalid) - if success then - self:debug('Pathfinding to end of connecting path finished') - self:startCourseToWorkStart(course) - else - self:onPathfindingFailedToConnectingPathEnd() - end -end - -function AIDriveStrategyFieldWorkCourse:startCourseToWorkStart(course) - self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, course) - self.state = self.states.DRIVING_TO_WORK_START_WAYPOINT - self:raiseImplements() - self.ppc:setShortLookaheadDistance() - self:startCourse(self.workStarter:getCourse(), 1) -end - ------------------------------------------------------------------------------------------------------------------------ ---- Static parameters (won't change while driving) ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:setAllStaticParameters() - AIDriveStrategyCourse.setAllStaticParameters(self) - self:setFrontAndBackMarkers() - self.loweringDurationMs = AIUtil.findLoweringDurationMs(self.vehicle) - self.fieldWorkerProximityController = FieldWorkerProximityController(self.vehicle, self.workWidth) -end - -function AIDriveStrategyFieldWorkCourse:setFieldPolygon(polygon) - self.fieldPolygon = polygon -end - ------------------------------------------------------------------------------------------------------------------------ ---- Dynamic parameters (may change while driving) ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:getTurnEndSideOffset() - return 0 -end - -function AIDriveStrategyFieldWorkCourse:getTurnEndForwardOffset() - return 0 -end - -function AIDriveStrategyFieldWorkCourse:getImplementRaiseLate() - return self.settings.raiseImplementLate:getValue() -end - -function AIDriveStrategyFieldWorkCourse:getImplementLowerEarly() - return self.settings.lowerImplementEarly:getValue() -end - -function AIDriveStrategyFieldWorkCourse:rememberWaypointToContinueFieldWork() - local ix = self:getBestWaypointToContinueFieldWork() - self.vehicle:rememberCpLastWaypointIx(ix) -end - -function AIDriveStrategyFieldWorkCourse:getBestWaypointToContinueFieldWork() - local bestKnownCurrentWpIx = self.fieldWorkCourse:getLastPassedWaypointIx() or self.fieldWorkCourse:getCurrentWaypointIx() - -- after we return from a refill/unload, continue a bit before the point where we left to - -- make sure not leaving any unworked patches - local bestWpIx = self.fieldWorkCourse:getPreviousWaypointIxWithinDistanceOrToTurnEnd(bestKnownCurrentWpIx, 10) - self:debug('Best return to fieldwork waypoint is %s (back from %d)', bestWpIx, bestKnownCurrentWpIx) - return bestWpIx or bestKnownCurrentWpIx -end - -function AIDriveStrategyFieldWorkCourse:setOffsetX() - -- do nothing by default -end - -function AIDriveStrategyFieldWorkCourse:calculateTightTurnOffset() - if self.state == self.states.WORKING or self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then - -- when rounding small islands or to start on a course with curves - self.tightTurnOffset = AIUtil.calculateTightTurnOffset(self.vehicle, self.turningRadius, self.course, - self.tightTurnOffset) - else - self.tightTurnOffset = 0 - end -end - -function AIDriveStrategyFieldWorkCourse:isWorking() - return self.state == self.states.WORKING or self.state == self.states.TURNING -end - ---- Gets the current ridge marker state. -function AIDriveStrategyFieldWorkCourse:getRidgeMarkerState() - return self.course:getRidgeMarkerState(self.ppc:getCurrentWaypointIx()) or 0 -end - -function AIDriveStrategyFieldWorkCourse:showAllInfo(note, ...) - self:debug('%s: work width %.1f, turning radius %.1f, front marker %.1f, back marker %.1f', - string.format(note, ...), self.workWidth, self.turningRadius, self.frontMarkerDistance, self.backMarkerDistance) - self:debug(' - map: %s, field %s', g_currentMission.missionInfo.mapTitle, - CpFieldUtil.getFieldNumUnderVehicle(self.vehicle)) - for _, implement in pairs(self.vehicle:getAttachedImplements()) do - self:debug(' - %s', CpUtil.getName(implement.object)) - end -end - ---- Updates the status variables. ----@param status CpStatus -function AIDriveStrategyFieldWorkCourse:updateCpStatus(status) - ---@type Course - if self.fieldWorkCourse then - local ix = self.fieldWorkCourse:getCurrentWaypointIx() - local numWps = self.fieldWorkCourse:getNumberOfWaypoints() - status:setWaypointData(ix, numWps, self.remainingTime:getText()) - end -end - -function AIDriveStrategyFieldWorkCourse:isTurnOnFieldActive() - return self.settings.turnOnField:getValue() -end - ------------------------------------------------------------------------------------------------------------------------ ---- Convoy management ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFieldWorkCourse:getProgress() - return self.fieldWorkCourse:getProgress() -end - -function AIDriveStrategyFieldWorkCourse:isDone() - return self.fieldWorkCourse:getCurrentWaypointIx() == self.fieldWorkCourse:getNumberOfWaypoints() -end - -function AIDriveStrategyFieldWorkCourse:getFieldWorkProximity(node) - return self.fieldWorkerProximityController:getFieldWorkProximity(node) -end - ------------------------------------------------------------------------------------------------------------------------ ---- Overwrite implement functions, to enable a different cp functionality compared to giants fieldworker. ---- TODO: might have to find a better solution for these kind of problems. ------------------------------------------------------------------------------------------------------------------------ -local function emptyFunction(object, superFunc, ...) - local rootVehicle = object.rootVehicle - if rootVehicle.getJob then - if rootVehicle:getIsCpActive() then - return - end - end - return superFunc(object, ...) -end ---- Makes sure the automatic work width isn't being reset. -VariableWorkWidth.onAIFieldWorkerStart = Utils.overwrittenFunction(VariableWorkWidth.onAIFieldWorkerStart, emptyFunction) -VariableWorkWidth.onAIImplementStart = Utils.overwrittenFunction(VariableWorkWidth.onAIImplementStart, emptyFunction) - --- TODO 25 --- This seems to be called when the Giants AI is done generating a field course, but we don't want the stock --- AI to do anything when the course is loaded, so we just return if the vehicle is a CP vehicle. -local function noOpWhenCpActive(self, superFunc, ...) - -- TODO this also comes when we just stopped a CP vehicle right after it was started - if self.vehicle:getIsCpActive() then - return - end - return superFunc(self, ...) -end -AIDriveStrategyFieldCourse.onFieldCourseLoadedCallback = Utils.overwrittenFunction(AIDriveStrategyFieldCourse.onFieldCourseLoadedCallback, noOpWhenCpActive) - --- TODO 25 --- The other thing messing us up is the delete() when we remove the Giants strategies in CpAITaskFieldWork, --- because it triggers an AI end line event, raising all implements - -AIDriveStrategyFieldCourse.delete = Utils.overwrittenFunction(AIDriveStrategyFieldCourse.delete, noOpWhenCpActive) +--[[ +This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS25) +Copyright (C) 2021 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Drive strategy for driving a field work course + +]]-- + +---@class AIDriveStrategyFieldWorkCourse : AIDriveStrategyCourse +AIDriveStrategyFieldWorkCourse = CpObject(AIDriveStrategyCourse) + +AIDriveStrategyFieldWorkCourse.myStates = { + WORKING = {}, + WAITING_FOR_LOWER = {}, + WAITING_FOR_LOWER_DELAYED = {}, + WAITING_FOR_STOP = {}, + WAITING_FOR_WEATHER = {}, + TURNING = {showTurnContextDebug = true}, + TEMPORARY = {}, + RETURNING_TO_START = {}, + DRIVING_TO_WORK_START_WAYPOINT = {showTurnContextDebug = true}, +} + +AIDriveStrategyFieldWorkCourse.normalFillLevelFullPercentage = 99.5 + +function AIDriveStrategyFieldWorkCourse:init(task, job) + AIDriveStrategyCourse.init(self, task, job) + AIDriveStrategyCourse.initStates(self, AIDriveStrategyFieldWorkCourse.myStates) + self.state = self.states.INITIAL + -- cache for the nodes created by TurnContext + self.turnNodes = {} + -- course offsets dynamically set by the AI and added to all tool and other offsets + self.aiOffsetX, self.aiOffsetZ = 0, 0 + self.debugChannel = CpDebug.DBG_FIELDWORK + self.waitingForPrepare = CpTemporaryObject(false) +end + +function AIDriveStrategyFieldWorkCourse:delete() + AIDriveStrategyCourse.delete(self) + self:raiseImplements() + TurnContext.deleteNodes(self.turnNodes) + self:rememberWaypointToContinueFieldWork() +end + +--- Start a fieldwork course. We expect that something else dropped us off close enough to startIx so +--- the most we need is an alignment course to lower the implements +function AIDriveStrategyFieldWorkCourse:start(course, startIx, jobParameters) + self:showAllInfo('Starting field work at waypoint %d', startIx) + self:updateFieldworkOffset(course) + self.fieldWorkCourse = course + self.fieldWorkCourse:setCurrentWaypointIx(startIx) + self.remainingTime = CpRemainingTime(self.vehicle, course, startIx) + -- remember at which waypoint we started, especially for the convoy + self.startWaypointIx = startIx + self.vehiclesInConvoy = {} + + local distance = course:getDistanceBetweenVehicleAndWaypoint(self.vehicle, startIx) + + ---@type CpAIJobFieldWork + local job = self.vehicle:getJob() + local alignmentCourse, alignmentCourseStartIx = job:getStartFieldWorkCourse() + + if alignmentCourse then + -- there is an alignment course already created by the AIDriveStrategyDriveToFieldWorkStart, + -- and we are supposed to continue on that one + self:debug('Continuing the alignment course at %d to start work.', alignmentCourseStartIx) + -- make sure the alignment course is used only once + job:setStartFieldWorkCourse(nil, nil) + self.course = course + self:startAlignmentTurn(course, startIx, alignmentCourse, alignmentCourseStartIx) + elseif distance > 2 * self.turningRadius then + self:debug('Start waypoint is far (%.1f m), use alignment course to get there.', distance) + self.course = course + self:startAlignmentTurn(course, startIx) + else + self:debug('Close enough to start waypoint %d, no alignment course needed', startIx) + self:startCourse(course, startIx) + self.state = self.states.INITIAL + self:prepareForFieldWork() + end + --- Store a reference to the original generated course + self.originalGeneratedFieldWorkCourse = self.vehicle:getFieldWorkCourse() + + if self.fieldPolygon == nil then + self:debug("No field polygon received, so regenerate it by the course.") + self.fieldPolygon = self.fieldWorkCourse:getFieldPolygon() + end +end + +--- Make sure all implements are in the working state +function AIDriveStrategyFieldWorkCourse:prepareForFieldWork() + self.vehicle:raiseAIEvent('onAIFieldWorkerPrepareForWork', 'onAIImplementPrepareForWork') +end + +--- Event raised when the driver has finished. +function AIDriveStrategyFieldWorkCourse:onFinished(hasFinished) + AIDriveStrategyCourse.onFinished(self, hasFinished) + self.remainingTime:reset() +end + +function AIDriveStrategyFieldWorkCourse:update(dt) + AIDriveStrategyCourse.update(self, dt) + if CpDebug:isChannelActive(CpDebug.DBG_TURN, self.vehicle) then + if self.state == self.states.TURNING then + if self.aiTurn then + self.aiTurn:drawDebug() + end + end + if self.state.properties.showTurnContextDebug then + if self.turnContext then + self.turnContext:drawDebug() + end + end + -- TODO_22 check user setting + if self.course:isTemporary() then + self.course:draw() + elseif self.ppc:getCourse():isTemporary() then + self.ppc:getCourse():draw() + end + end + if CpDebug:isChannelActive(CpDebug.DBG_PATHFINDER, self.vehicle) then + if self.pathfinder then + PathfinderUtil.showNodes(self.pathfinder) + end + end + if self.fieldWorkerProximityController then + self.fieldWorkerProximityController:draw() + end + self:updateImplementControllers(dt) + self.remainingTime:update(dt) +end + +--- This is the interface to the Giant's AIFieldWorker specialization, telling it the direction and speed +function AIDriveStrategyFieldWorkCourse:getDriveData(dt, vX, vY, vZ) + + self:updateFieldworkOffset(self.course) + self:updateLowFrequencyImplementControllers() + + local moveForwards = not self.ppc:isReversing() + local gx, gz + + ---------------------------------------------------------------- + if not moveForwards then + local maxSpeed + gx, gz, maxSpeed = self:getReverseDriveData() + self:setMaxSpeed(maxSpeed) + else + gx, _, gz = self.ppc:getGoalPointPosition() + end + ---------------------------------------------------------------- + if self.state == self.states.INITIAL then + self:setMaxSpeed(0) + self:startWaitingForLower() + self:lowerImplements() + elseif self.state == self.states.WAITING_FOR_LOWER then + self:setMaxSpeed(0) + if self:getCanContinueWork() then + self:debug('all tools ready, start working') + self.state = self.states.WORKING + else + self:debugSparse('waiting for all tools to lower') + end + elseif self.state == self.states.WAITING_FOR_LOWER_DELAYED then + -- getCanAIVehicleContinueWork() seems to return false when the implement being lowered/raised (moving) but + -- true otherwise. Due to some timing issues it may return true just after we started lowering it, so this + -- here delays the check for another cycle. + self.state = self.states.WAITING_FOR_LOWER + self:setMaxSpeed(0) + elseif self.state == self.states.WAITING_FOR_PATHFINDER then + self:setMaxSpeed(0) + elseif self.state == self.states.WORKING then + self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue()) + elseif self.state == self.states.TURNING then + local turnGx, turnGz, turnMoveForwards, turnMaxSpeed = self.aiTurn:getDriveData(dt) + self:setMaxSpeed(turnMaxSpeed) + -- if turn tells us which way to go, use that, otherwise just do whatever PPC tells us + gx, gz = turnGx or gx, turnGz or gz + if turnMoveForwards ~= nil then moveForwards = turnMoveForwards end + elseif self.state == self.states.RETURNING_TO_START then + local isReadyToDrive, blockingVehicle = self.vehicle:getIsAIReadyToDrive() + if isReadyToDrive or not self.waitingForPrepare:get() then + -- ready to drive or we just timed out waiting to be ready + self:setMaxSpeed(self.settings.fieldSpeed:getValue()) + else + self:debugSparse('Not ready to drive because of %s, preparing ...', CpUtil.getName(blockingVehicle)) + end + elseif self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then + self:setMaxSpeed(self.settings.fieldSpeed:getValue()) + local _, _, _, maxSpeed = self.workStarter:getDriveData() + if maxSpeed ~= nil then + self:setMaxSpeed(maxSpeed) + end + end + + self:setAITarget() + self:limitSpeed() + self:checkProximitySensors(moveForwards) + self:checkDistanceToOtherFieldWorkers() + + return gx, gz, moveForwards, self.maxSpeed, 100 +end + +function AIDriveStrategyFieldWorkCourse:checkDistanceToOtherFieldWorkers() + -- keep away from others working on the same course + self:setMaxSpeed(self.fieldWorkerProximityController:getMaxSpeed(self.settings.convoyDistance:getValue(), self.maxSpeed)) +end + +-- Seems like the Giants AIDriveStrategyCollision needs these variables on the vehicle to be set +-- to calculate an accurate path prediction +function AIDriveStrategyFieldWorkCourse:setAITarget() + --local dx, _, dz = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1) + local wp = self.ppc:getCurrentWaypoint() + --- TODO: For some reason wp.dx and wp.dz are nil sometimes + local dx, dz = wp.dx or 0, wp.dz or 0 + if wp.dx ~= 0 or wp.dz ~= 0 then + local length = MathUtil.vector2Length(dx, dz) + dx = dx / length + dz = dz / length + end + self.vehicle.aiDriveDirection = { dx, dz } + local x, _, z = getWorldTranslation(self.vehicle:getAIDirectionNode()) + self.vehicle.aiDriveTarget = { x, z } +end + +----------------------------------------------------------------------------------------------------------------------- +--- Implement handling +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:initializeImplementControllers(vehicle) + + local defaultDisabledStates = { + self.states.TEMPORARY, + self.states.TURNING, + self.states.DRIVING_TO_WORK_START_WAYPOINT + } + self:addImplementController(vehicle, BalerController, Baler, {}) + self:addImplementController(vehicle, BaleWrapperController, BaleWrapper, defaultDisabledStates) + self:addImplementController(vehicle, BaleLoaderController, BaleLoader, defaultDisabledStates) + self:addImplementController(vehicle, APalletAutoLoaderController, nil, {}, "spec_aPalletAutoLoader") + self:addImplementController(vehicle, UniversalAutoloadController, nil, {}, "spec_universalAutoload") + + self:addImplementController(vehicle, CombineController, Combine, defaultDisabledStates) + self:addImplementController(vehicle, FertilizingSowingMachineController, FertilizingSowingMachine, defaultDisabledStates) + self:addImplementController(vehicle, ForageWagonController, ForageWagon, defaultDisabledStates) + self:addImplementController(vehicle, SowingMachineController, SowingMachine, defaultDisabledStates) + self:addImplementController(vehicle, FertilizingCultivatorController, FertilizingCultivator, defaultDisabledStates) + self:addImplementController(vehicle, MowerController, Mower, defaultDisabledStates) + + self:addImplementController(vehicle, RidgeMarkerController, RidgeMarker, defaultDisabledStates) + self:addImplementController(vehicle, PlowController, Plow, defaultDisabledStates) + + self:addImplementController(vehicle, PickupController, Pickup, defaultDisabledStates) + self:addImplementController(vehicle, SprayerController, Sprayer, {}) + self:addImplementController(vehicle, CutterController, Cutter, {}) --- Makes sure the cutter timer gets reset always. + self:addImplementController(vehicle, StonePickerController, StonePicker, defaultDisabledStates) + + self:addImplementController(vehicle, FoldableController, Foldable, {}) + self:addImplementController(vehicle, MotorController, Motorized, {}) + self:addImplementController(vehicle, WearableController, Wearable, {}) + self:addImplementController(vehicle, VineCutterController, VineCutter, defaultDisabledStates) + self:addImplementController(vehicle, PalletFillerController, nil, defaultDisabledStates, "spec_pdlc_premiumExpansion.palletFiller") + + self:addImplementController(vehicle, SoilSamplerController, nil, defaultDisabledStates, "spec_soilSampler") + self:addImplementController(vehicle, StumpCutterController, StumpCutter, defaultDisabledStates) + self:addImplementController(vehicle, TreePlanterController, TreePlanter, {}) + +end + +--- Start waiting for the implements to lower +-- getCanAIVehicleContinueWork() seems to return false when the implement being lowered/raised (moving) but +-- true otherwise. Due to some timing issues it may return true just after we started lowering it, so we +-- set a different state for those implements +function AIDriveStrategyFieldWorkCourse:startWaitingForLower() + -- TODO 25 looks like we always need to wait that extra cycle with FS25 + if true or AIUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine) or self.ppc:isReversing() then + -- sowing machines want to stop while the implement is being lowered + -- also, when reversing, we assume that we'll switch to forward, so stop while lowering, then start forward + self.state = self.states.WAITING_FOR_LOWER_DELAYED + self:debug('waiting for lower delayed') + else + self.state = self.states.WAITING_FOR_LOWER + self:debug('waiting for lower') + end +end + +function AIDriveStrategyFieldWorkCourse:shouldRaiseImplements(turnStartNode) + -- see if the vehicle has AI markers -> has work areas (built-in implements like a mower or cotton harvester) + local doRaise = self:shouldRaiseThisImplement(self.vehicle, turnStartNode) + -- and then check all implements + for _, implement in pairs(AIUtil.getAllAIImplements(self.vehicle)) do + -- only when _all_ implements can be raised will we raise them all, hence the 'and' + doRaise = doRaise and self:shouldRaiseThisImplement(implement.object, turnStartNode) + end + return doRaise +end + +---@param turnStartNode number at the last waypoint of the row, pointing in the direction of travel. This is where +--- the implement should be raised when beginning a turn +function AIDriveStrategyFieldWorkCourse:shouldRaiseThisImplement(object, turnStartNode) + local aiFrontMarker, _, aiBackMarker = WorkWidthUtil.getAIMarkers(object, true) + -- if something (like a combine) does not have an AI marker it should not prevent from raising other implements + -- like the header, which does have markers), therefore, return true here + if not aiBackMarker or not aiFrontMarker then return true end + local marker = self:getImplementRaiseLate() and aiBackMarker or aiFrontMarker + -- turn start node in the back marker node's coordinate system + local _, _, dz = localToLocal(marker, turnStartNode, 0, 0, 0) + self:debugSparse('%s: shouldRaiseImplements: dz = %.1f', CpUtil.getName(object), dz) + -- marker is just in front of the turn start node + return dz > 0 +end + + +--- When finishing a turn, is it time to lower all implements here? +-- TODO: remove the reversing parameter and use ppc to find out once not called from turn.lua +function AIDriveStrategyFieldWorkCourse:shouldLowerImplements(turnEndNode, reversing) + -- see if the vehicle has AI markers -> has work areas (built-in implements like a mower or cotton harvester) + local doLower, vehicleHasMarkers, dz = self:shouldLowerThisImplement(self.vehicle, turnEndNode, reversing) + if not vehicleHasMarkers and reversing then + -- making sure the 'and' below will work if reversing and the vehicle has no markers + doLower = true + end + -- and then check all implements + for _, implement in ipairs(AIUtil.getAllAIImplements(self.vehicle)) do + if reversing then + -- when driving backward, all implements must reach the turn end node before lowering, hence the 'and' + doLower = doLower and self:shouldLowerThisImplement(implement.object, turnEndNode, reversing) + else + -- when driving forward, if it is time to lower any implement, we'll lower all, hence the 'or' + local lowerThis, _, thisDz = self:shouldLowerThisImplement(implement.object, turnEndNode, reversing) + dz = dz and math.max(dz, thisDz) or thisDz + doLower = doLower or lowerThis + end + end + return doLower, dz +end + +---@param object table is a vehicle or implement object with AI markers (marking the working area of the implement) +---@param workStartNode number node at the first waypoint of the row, pointing in the direction of travel. This is where +--- the implement should be in the working position after a turn +---@param reversing boolean are we reversing? When reversing towards the turn end point, we must lower the implements +--- when we are _behind_ the turn end node (dz < 0), otherwise once we reach it (dz > 0) +---@return boolean, boolean, number the second one is true when the first is valid, and the distance to the work start +--- in meters (<0) when driving forward, nil when driving backwards. +function AIDriveStrategyFieldWorkCourse:shouldLowerThisImplement(object, workStartNode, reversing) + local aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkers(object, true) + if not aiLeftMarker then return false, false, nil end + local dxLeft, _, dzLeft = localToLocal(aiLeftMarker, workStartNode, 0, 0, 0) + local dxRight, _, dzRight = localToLocal(aiRightMarker, workStartNode, 0, 0, 0) + local dxBack, _, dzBack = localToLocal(aiBackMarker, workStartNode, 0, 0, 0) + local loweringDistance + if AIUtil.hasAIImplementWithSpecialization(self.vehicle, SowingMachine) then + -- sowing machines are stopped while lowering, but leave a little reserve to allow for stopping + -- TODO: rather slow down while approaching the lowering point + loweringDistance = 0.5 + else + -- others can be lowered without stopping so need to start lowering before we get to the turn end to be + -- in the working position by the time we get to the first waypoint of the next row + loweringDistance = math.min(self.vehicle.lastSpeed, self.settings.turnSpeed:getValue() / 3600) * + self.loweringDurationMs + 0.5 -- vehicle.lastSpeed is in meters per millisecond + end + local aligned = CpMathUtil.isSameDirection(object.rootNode, workStartNode, 15) + -- some implements, especially plows may have the left and right markers offset longitudinally + -- so if the implement is aligned with the row direction already, then just take the front one + -- if not aligned, work with an average + local dzFront = aligned and math.max(dzLeft, dzRight) or (dzLeft + dzRight) / 2 + local dxFront = (dxLeft + dxRight) / 2 + self:debug('%s: dzLeft = %.1f, dzRight = %.1f, aligned = %s, dzFront = %.1f, dxFront = %.1f, dzBack = %.1f, loweringDistance = %.1f, reversing %s', + CpUtil.getName(object), dzLeft, dzRight, aligned, dzFront, dxFront, dzBack, loweringDistance, tostring(reversing)) + local dz = self:getImplementLowerEarly() and dzFront or dzBack + if reversing then + return dz < 0, true, nil + else + -- dz will be negative as we are behind the target node. Also, dx must be close enough, otherwise + -- we'll lower them way too early if approaching the turn end from the side at about 90° (and we + -- want a constant value here, certainly not the loweringDistance which changes with the current speed + -- and thus introduces a feedback loop, causing the return value to oscillate, that is, we say should be + -- lowered, than the vehicle stops, but now the loweringDistance will be low, so we say should not be + -- lowering, vehicle starts again, and so on ... + local normalLoweringDistance = self.loweringDurationMs * self.settings.turnSpeed:getValue() / 3600 + return dz > -loweringDistance and math.abs(dxFront) < normalLoweringDistance * 1.5, true, dz + end +end + +--- Are all implements now aligned with the node? Can be used to find out if we are for instance aligned with the +--- turn end node direction in a question mark turn and can start reversing. +function AIDriveStrategyFieldWorkCourse:areAllImplementsAligned(node) + -- see if the vehicle has AI markers -> has work areas (built-in implements like a mower or cotton harvester) + local allAligned = self:isThisImplementAligned(self.vehicle, node) + -- and then check all implements + for _, implement in ipairs(AIUtil.getAllAIImplements(self.vehicle)) do + -- _all_ implements must be aligned, hence the 'and' + allAligned = allAligned and self:isThisImplementAligned(implement.object, node) + end + return allAligned +end + +function AIDriveStrategyFieldWorkCourse:isThisImplementAligned(object, node) + local aiFrontMarker, _, _ = WorkWidthUtil.getAIMarkers(object, true) + if not aiFrontMarker then return true end + return CpMathUtil.isSameDirection(aiFrontMarker, node, 5) +end + +----------------------------------------------------------------------------------------------------------------------- +--- Event listeners +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:onWaypointChange(ix, course) + self:calculateTightTurnOffset() + if not self.state ~= self.states.TURNING + and self.course:isTurnStartAtIx(ix) then + if self.state == self.states.INITIAL then + self:debug('Waypoint change (%d) to turn start right after starting work, lowering implements.', ix) + self:startWaitingForLower() + self:lowerImplements() + end + self:startTurn(ix) + elseif self.state == self.states.WORKING then + if self.course:isOnConnectingPath(ix + 1) or self.course:shouldUsePathfinderToNextWaypoint(ix) then + local fm, bm = self:getFrontAndBackMarkers() + self.turnContext = RowStartOrFinishContext(self.vehicle, self.course, ix, ix, self.turnNodes, self:getWorkWidth(), + fm, bm, 0, 0) + self.aiTurn = FinishRowOnly(self.vehicle, self, self.ppc, self.proximityController, self.turnContext) + self.state = self.states.TURNING + if self.course:isOnConnectingPath(ix + 1) then + self:debug('finishing work before starting on the connecting path') + self.aiTurn:registerTurnEndCallback(self, AIDriveStrategyFieldWorkCourse.startConnectingPath) + else + -- the generated course instructs the vehicle to use the pathfinder to the next waypoint + self:debug('finishing work before starting pathfinding to the next waypoint') + self.aiTurn:registerTurnEndCallback(self, AIDriveStrategyFieldWorkCourse.startPathfindingToNextWaypoint) + end + end + -- towards the end of the field course make sure the implement reaches the last waypoint + -- TODO: this needs refactoring, for now don't do this for temporary courses like a turn as it messes up reversing + if ix > self.course:getNumberOfWaypoints() - 3 and not self.course:isTemporary() then + local _, bm = self:getFrontAndBackMarkers() + self:debug('adding offset (%.1f front marker) to make sure we do not miss anything when the course ends', bm) + self.aiOffsetZ = -bm + end + end +end + +function AIDriveStrategyFieldWorkCourse:onWaypointPassed(ix, course) + if course:isLastWaypointIx(ix) then + self:onLastWaypointPassed() + end +end + +--- Called when the last waypoint of a course is passed +function AIDriveStrategyFieldWorkCourse:onLastWaypointPassed() + -- reset offset we used for the course ending to not miss anything + self.aiOffsetZ = 0 + self:debug('Last waypoint of the course reached.') + if self.state == self.states.RETURNING_TO_START then + self:debug('Returned to first waypoint after fieldwork done, stopping job') + self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) + elseif self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then + self.workStarter:onLastWaypoint() + else + -- by default, stop the job + self:finishFieldWork() + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- Turn +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:startTurn(ix) + self:debug('Starting a turn at waypoint %d', ix) + local fm, bm = self:getFrontAndBackMarkers() + self.ppc:setShortLookaheadDistance() + self.turnContext = TurnContext(self.vehicle, self.course, ix, ix + 1, self.turnNodes, self:getWorkWidth(), fm, bm, + self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) + if AITurn.canMakeKTurn(self.vehicle, self.turnContext, self.workWidth, self:isTurnOnFieldActive()) then + self.aiTurn = KTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.workWidth) + else + self.aiTurn = CourseTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, self.course, self.workWidth) + end + self.state = self.states.TURNING +end + +function AIDriveStrategyFieldWorkCourse:isTurning() + return self.state == self.states.TURNING +end + +-- switch back to fieldwork after the turn ended. +---@param ix number waypoint to resume fieldwork after +function AIDriveStrategyFieldWorkCourse:resumeFieldworkAfterTurn(ix) + self.ppc:setNormalLookaheadDistance() + self:startWaitingForLower() + self:lowerImplements() + local startIx = self.fieldWorkCourse:getNextFwdWaypointIxFromVehiclePosition(ix, + self.vehicle:getAIDirectionNode(), self.workWidth / 2) + self:startCourse(self.fieldWorkCourse, startIx) +end + +--- Attempt to recover from a turn where the vehicle got blocked. This replaces the current turn with a +--- RecoveryTurn, which just backs up a bit and then uses the pathfinder to create a turn back to the +--- start of the next row. +---@param reverseDistance number|nil distance to back up before retrying pathfinding (default 10 m) +---@param retryCount number|nil how many times have we tried to recover so far? (to limit the number of retries) +---@return boolean true if a recovery turn could be created +function AIDriveStrategyFieldWorkCourse:startRecoveryTurn(reverseDistance, retryCount) + self:debug('Blocked in a turn, attempt to recover') + if self.turnContext then + self.aiTurn = RecoveryTurn(self.vehicle, self, self.ppc, self.proximityController, self.turnContext, + self.course, self.workWidth, reverseDistance, retryCount) + self.state = self.states.TURNING + return true + else + self:debug('Lost turn context to recover, remain blocked.') + return false + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- State changes +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:finishFieldWork() + if self.settings.returnToStart:getValue() and self.fieldWorkCourse:startsWithHeadland() then + self:debug('Fieldwork ended, returning to first waypoint.') + self.vehicle:prepareForAIDriving() + self:returnToStartAfterDone() + else + self:debug('Fieldwork ended, stopping job.') + self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) + end +end + +function AIDriveStrategyFieldWorkCourse:changeToFieldWork() + self:debug('change to fieldwork') + self:startWaitingForLower() + self:lowerImplements(self.vehicle) +end + +--- Start alignment turn, that is, a course to the waypoint of fieldWorkCourse where the +--- fieldwork should begin. This is performed as a turn maneuver, more specifically the end of the +--- turn maneuver where the work is started and has the logic to lower the implements exactly +--- where it needs to be. +--- +--- (It is called alignment because it makes sure the vehicle is aligned with the start waypoint so +--- that it points to the right direction and the implements can start working exactly at the waypoint) +--- +--- The caller can pass in an already created alignment course with an index. In that case, we'll use +--- that course, starting at alignmentStartIx for the turn, otherwise a new course is created from +--- the vehicle's current position to startIx in fieldWorkCourse. +--- +---@param fieldWorkCourse Course fieldwork course +---@param startIx number index of waypoint of fieldWorkCourse where the work should start +---@param alignmentCourse Course an optional course if the caller already has one +---@param alignmentStartIx number index to start the alignment course (if supplied) +function AIDriveStrategyFieldWorkCourse:startAlignmentTurn(fieldWorkCourse, startIx, alignmentCourse, alignmentStartIx) + if alignmentCourse then + -- there is an alignment course, use that one, if there is a start ix, then only + -- the part starting at startIx + alignmentCourse = alignmentCourse:copy(self.vehicle, alignmentStartIx) + else + -- no alignment course given, generate one + alignmentCourse = self:createAlignmentCourse(fieldWorkCourse, startIx) + end + self.ppc:setShortLookaheadDistance() + self:prepareForFieldWork() + if alignmentCourse then + local fm, bm = self:getFrontAndBackMarkers() + self.turnContext = RowStartOrFinishContext(self.vehicle, fieldWorkCourse, startIx, startIx, self.turnNodes, + self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) + self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, alignmentCourse) + self.state = self.states.DRIVING_TO_WORK_START_WAYPOINT + self:startCourse(self.workStarter:getCourse(), 1) + else + self:debug('Could not create alignment course to first up/down row waypoint, continue without it') + self:startCourse(fieldWorkCourse, startIx) + self.state = self.states.INITIAL + self:prepareForFieldWork() + end +end + +--- Back to the start waypoint after done +function AIDriveStrategyFieldWorkCourse:returnToStartAfterDone() + if not self.pathfinder or not self.pathfinder:isActive() then + self.pathfindingStartedAt = g_currentMission.time + self:debug('Return to first waypoint') + local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) + local result + self.pathfinder, result = PathfinderUtil.startPathfindingFromVehicleToWaypoint( + self.fieldWorkCourse, 1, 0, 0, context) + if result.done then + return self:onPathfindingDoneToReturnToStart(result.path) + else + self.state = self.states.WAITING_FOR_PATHFINDER + self:setPathfindingDoneCallback(self, self.onPathfindingDoneToReturnToStart) + end + else + self:debug('Pathfinder already active, stopping job') + self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) + end +end + +function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToReturnToStart(path) + if path and #path > 2 then + self:debug('Pathfinding to return to start finished with %d waypoints (%d ms)', + #path, g_currentMission.time - (self.pathfindingStartedAt or 0)) + local returnCourse = Course(self.vehicle, CpMathUtil.pointsToGameInPlace(path), true) + self.state = self.states.RETURNING_TO_START + self.waitingForPrepare:set(true, 10000) + self:startCourse(returnCourse, 1) + else + self:debug('No path found to return to fieldwork start after work is done (%d ms), stopping job', + g_currentMission.time - (self.pathfindingStartedAt or 0)) + self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) + end +end +----------------------------------------------------------------------------------------------------------------------- +--- Use pathfinder to next waypoint +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:startPathfindingToNextWaypoint(ix) + self:debug('start pathfinding to waypoint %d', ix + 1) + self:raiseImplements() + local fm, bm = self:getFrontAndBackMarkers() + self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, ix + 1, ix + 1, + self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) + local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) + local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength) + local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) + self.waypointToContinueOnFailedPathfinding = ix + 1 + self.pathfinderController:registerListeners(self, self.onPathfindingDoneToNextWaypoint, + self.onPathfindingFailedToNextWaypoint) + self:debug('Start pathfinding to target waypoint %d, zOffset %.1f', ix + 1, zOffset) + self.state = self.states.WAITING_FOR_PATHFINDER + -- to have a course set while waiting for the pathfinder + self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding) + self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset) +end + +function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToNextWaypoint() + self:debug('Pathfinding to next waypoint failed, continue directly at waypoint %d', self.waypointToContinueOnFailedPathfinding) + self.state = self.states.WORKING + self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding) +end + +function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToNextWaypoint(controller, success, course, goalNodeInvalid) + if success then + self:debug('Pathfinding to next waypoint finished') + self:startCourseToWorkStart(course) + else + self:onPathfindingFailedToNextWaypoint() + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- Connecting path +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:startConnectingPath(ix) + -- ix was the last waypoint to work before the connecting path, ix + 1 is the first on the connecting path + self:debug('on a connecting path now at waypoint %d, raising implements.', ix + 1) + self:raiseImplements() + -- gather the connecting path waypoints + local connectingPath = {} + local targetWaypointIx + for i = ix + 1, self.fieldWorkCourse:getNumberOfWaypoints() do + if self.fieldWorkCourse:isOnConnectingPath(i) then + local x, _, z = self.fieldWorkCourse:getWaypointPosition(i) + table.insert(connectingPath, {x = x, z = z}) + else + targetWaypointIx = i + break + end + end + if targetWaypointIx == nil then + self:onPathfindingFailedToConnectingPathEnd() + else + -- set up the turn context for the work starter to use when the pathfinding succeeds + local fm, bm = self:getFrontAndBackMarkers() + self.turnContext = RowStartOrFinishContext(self.vehicle, self.fieldWorkCourse, targetWaypointIx, targetWaypointIx, + self.turnNodes, self:getWorkWidth(), fm, bm, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) + local _, steeringLength = AIUtil.getSteeringParameters(self.vehicle) + local targetNode, zOffset = self.turnContext:getTurnEndNodeAndOffsets(steeringLength) + local context = PathfinderContext(self.vehicle):allowReverse(self:getAllowReversePathfinding()) + context:preferredPath(connectingPath):mustBeAccurate(true) + self.waypointToContinueOnFailedPathfinding = ix + 1 + self.pathfinderController:registerListeners(self, self.onPathfindingDoneToConnectingPathEnd, + self.onPathfindingFailedToConnectingPathEnd) + self:debug('Connecting path has %d waypoints, start pathfinding to target waypoint %d, zOffset %.1f', + #connectingPath, targetWaypointIx, zOffset) + self.state = self.states.WAITING_FOR_PATHFINDER + self.pathfinderController:findPathToNode(context, targetNode, 0, zOffset) + end +end + +function AIDriveStrategyFieldWorkCourse:onPathfindingFailedToConnectingPathEnd() + self:debug('Pathfinding to end of connecting path failed, use the connecting path as is') + self.state = self.states.WORKING + self:startCourse(self.fieldWorkCourse, self.waypointToContinueOnFailedPathfinding) +end + +function AIDriveStrategyFieldWorkCourse:onPathfindingDoneToConnectingPathEnd(controller, success, course, goalNodeInvalid) + if success then + self:debug('Pathfinding to end of connecting path finished') + self:startCourseToWorkStart(course) + else + self:onPathfindingFailedToConnectingPathEnd() + end +end + +function AIDriveStrategyFieldWorkCourse:startCourseToWorkStart(course) + self.workStarter = StartRowOnly(self.vehicle, self, self.ppc, self.turnContext, course) + self.state = self.states.DRIVING_TO_WORK_START_WAYPOINT + self:raiseImplements() + self.ppc:setShortLookaheadDistance() + self:startCourse(self.workStarter:getCourse(), 1) +end + +----------------------------------------------------------------------------------------------------------------------- +--- Static parameters (won't change while driving) +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:setAllStaticParameters() + AIDriveStrategyCourse.setAllStaticParameters(self) + self:setFrontAndBackMarkers() + self.loweringDurationMs = AIUtil.findLoweringDurationMs(self.vehicle) + self.fieldWorkerProximityController = FieldWorkerProximityController(self.vehicle, self.workWidth) +end + +function AIDriveStrategyFieldWorkCourse:setFieldPolygon(polygon) + self.fieldPolygon = polygon +end + +----------------------------------------------------------------------------------------------------------------------- +--- Dynamic parameters (may change while driving) +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:getTurnEndSideOffset() + return 0 +end + +function AIDriveStrategyFieldWorkCourse:getTurnEndForwardOffset() + return 0 +end + +function AIDriveStrategyFieldWorkCourse:getImplementRaiseLate() + return self.settings.raiseImplementLate:getValue() +end + +function AIDriveStrategyFieldWorkCourse:getImplementLowerEarly() + return self.settings.lowerImplementEarly:getValue() +end + +function AIDriveStrategyFieldWorkCourse:rememberWaypointToContinueFieldWork() + local ix = self:getBestWaypointToContinueFieldWork() + self.vehicle:rememberCpLastWaypointIx(ix) +end + +function AIDriveStrategyFieldWorkCourse:getBestWaypointToContinueFieldWork() + local bestKnownCurrentWpIx = self.fieldWorkCourse:getLastPassedWaypointIx() or self.fieldWorkCourse:getCurrentWaypointIx() + -- after we return from a refill/unload, continue a bit before the point where we left to + -- make sure not leaving any unworked patches + local bestWpIx = self.fieldWorkCourse:getPreviousWaypointIxWithinDistanceOrToTurnEnd(bestKnownCurrentWpIx, 10) + self:debug('Best return to fieldwork waypoint is %s (back from %d)', bestWpIx, bestKnownCurrentWpIx) + return bestWpIx or bestKnownCurrentWpIx +end + +function AIDriveStrategyFieldWorkCourse:setOffsetX() + -- do nothing by default +end + +function AIDriveStrategyFieldWorkCourse:calculateTightTurnOffset() + if self.state == self.states.WORKING or self.state == self.states.DRIVING_TO_WORK_START_WAYPOINT then + -- when rounding small islands or to start on a course with curves + self.tightTurnOffset = AIUtil.calculateTightTurnOffset(self.vehicle, self.turningRadius, self.course, + self.tightTurnOffset) + else + self.tightTurnOffset = 0 + end +end + +function AIDriveStrategyFieldWorkCourse:isWorking() + return self.state == self.states.WORKING or self.state == self.states.TURNING +end + +--- Gets the current ridge marker state. +function AIDriveStrategyFieldWorkCourse:getRidgeMarkerState() + return self.course:getRidgeMarkerState(self.ppc:getCurrentWaypointIx()) or 0 +end + +function AIDriveStrategyFieldWorkCourse:showAllInfo(note, ...) + self:debug('%s: work width %.1f, turning radius %.1f, front marker %.1f, back marker %.1f', + string.format(note, ...), self.workWidth, self.turningRadius, self.frontMarkerDistance, self.backMarkerDistance) + self:debug(' - map: %s, field %s', g_currentMission.missionInfo.mapTitle, + CpFieldUtil.getFieldNumUnderVehicle(self.vehicle)) + for _, implement in pairs(self.vehicle:getAttachedImplements()) do + self:debug(' - %s', CpUtil.getName(implement.object)) + end +end + +--- Updates the status variables. +---@param status CpStatus +function AIDriveStrategyFieldWorkCourse:updateCpStatus(status) + ---@type Course + if self.fieldWorkCourse then + local ix = self.fieldWorkCourse:getCurrentWaypointIx() + local numWps = self.fieldWorkCourse:getNumberOfWaypoints() + status:setWaypointData(ix, numWps, self.remainingTime:getText()) + end +end + +function AIDriveStrategyFieldWorkCourse:isTurnOnFieldActive() + return self.settings.turnOnField:getValue() +end + +----------------------------------------------------------------------------------------------------------------------- +--- Convoy management +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFieldWorkCourse:getProgress() + return self.fieldWorkCourse:getProgress() +end + +function AIDriveStrategyFieldWorkCourse:isDone() + return self.fieldWorkCourse:getCurrentWaypointIx() == self.fieldWorkCourse:getNumberOfWaypoints() +end + +function AIDriveStrategyFieldWorkCourse:getFieldWorkProximity(node) + return self.fieldWorkerProximityController:getFieldWorkProximity(node) +end + +----------------------------------------------------------------------------------------------------------------------- +--- Overwrite implement functions, to enable a different cp functionality compared to giants fieldworker. +--- TODO: might have to find a better solution for these kind of problems. +----------------------------------------------------------------------------------------------------------------------- +local function emptyFunction(object, superFunc, ...) + local rootVehicle = object.rootVehicle + if rootVehicle.getJob then + if rootVehicle:getIsCpActive() then + return + end + end + return superFunc(object, ...) +end +--- Makes sure the automatic work width isn't being reset. +VariableWorkWidth.onAIFieldWorkerStart = Utils.overwrittenFunction(VariableWorkWidth.onAIFieldWorkerStart, emptyFunction) +VariableWorkWidth.onAIImplementStart = Utils.overwrittenFunction(VariableWorkWidth.onAIImplementStart, emptyFunction) + +-- TODO 25 +-- This seems to be called when the Giants AI is done generating a field course, but we don't want the stock +-- AI to do anything when the course is loaded, so we just return if the vehicle is a CP vehicle. +local function noOpWhenCpActive(self, superFunc, ...) + -- TODO this also comes when we just stopped a CP vehicle right after it was started + if self.vehicle:getIsCpActive() then + return + end + return superFunc(self, ...) +end +AIDriveStrategyFieldCourse.onFieldCourseLoadedCallback = Utils.overwrittenFunction(AIDriveStrategyFieldCourse.onFieldCourseLoadedCallback, noOpWhenCpActive) + +-- TODO 25 +-- The other thing messing us up is the delete() when we remove the Giants strategies in CpAITaskFieldWork, +-- because it triggers an AI end line event, raising all implements + +AIDriveStrategyFieldCourse.delete = Utils.overwrittenFunction(AIDriveStrategyFieldCourse.delete, noOpWhenCpActive) diff --git a/scripts/ai/AIDriveStrategyFindBales.lua b/scripts/ai/strategies/AIDriveStrategyFindBales.lua similarity index 97% rename from scripts/ai/AIDriveStrategyFindBales.lua rename to scripts/ai/strategies/AIDriveStrategyFindBales.lua index b656deb06..250e01903 100644 --- a/scripts/ai/AIDriveStrategyFindBales.lua +++ b/scripts/ai/strategies/AIDriveStrategyFindBales.lua @@ -1,669 +1,669 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/courseplay) -Copyright (C) 2022 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -]] - ---- Drive strategy to find bales on a field and collect or wrap them ---- TODO: Separate bale wrapper and bale loaders. ---- Might be a good idea to have the bale loader strategy derive from the find bales(only wrapper) strategy. - ----@class AIDriveStrategyFindBales : AIDriveStrategyCourse -AIDriveStrategyFindBales = CpObject(AIDriveStrategyCourse) - -AIDriveStrategyFindBales.myStates = { - SEARCHING_FOR_NEXT_BALE = {}, - WAITING_FOR_PATHFINDER = {}, - DRIVING_TO_NEXT_BALE = {}, - APPROACHING_BALE = {}, - WORKING_ON_BALE = {}, - REVERSING_AFTER_PATHFINDER_FAILURE = {}, - REVERSING_DUE_TO_OBSTACLE_AHEAD = {}, - DRIVING_TO_START_MARKER = {}, - WAITING_FOR_IMPLEMENTS_TO_FOLD = {} -} ---- 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 - -function AIDriveStrategyFindBales:init(task, job) - AIDriveStrategyCourse.init(self, task, job) - AIDriveStrategyCourse.initStates(self, AIDriveStrategyFindBales.myStates) - self.state = self.states.INITIAL - self.debugChannel = CpDebug.DBG_FIND_BALES - self.bales = {} -end - -function AIDriveStrategyFindBales:delete() - AIDriveStrategyCourse.delete(self) - g_baleToCollectManager:unlockBalesByDriver(self) - if self.invertedStartPositionMarkerNode then - CpUtil.destroyNode(self.invertedStartPositionMarkerNode) - end -end - -function AIDriveStrategyFindBales:getGeneratedCourse(jobParameters) - return nil -end - -function AIDriveStrategyFindBales:startWithoutCourse() - -- to always have a valid course (for the traffic conflict detector mainly) - self.course = Course.createStraightForwardCourse(self.vehicle, 25) - self:startCourse(self.course, 1) - - self:info('Starting bale collect/wrap') - - for _, implement in pairs(self.vehicle:getAttachedImplements()) do - self:info(' - %s', CpUtil.getName(implement.object)) - end - self.vehicle:raiseAIEvent("onAIFieldWorkerStart", "onAIImplementStart") - self:lowerImplements() -end - -function AIDriveStrategyFindBales:collectNextBale() - self.state = self.states.SEARCHING_FOR_NEXT_BALE - if #self.bales > 0 then - self:findPathToNextBale() - else - self:debug('No bales found, scan the field once more before leaving for the unload course.') - self.bales, self.wrongWrapTypeFound = self:findBales() - if #self.bales > 0 then - self:debug('Found more bales, collecting them') - self:findPathToNextBale() - return - end - self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- Implement handling ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFindBales:initializeImplementControllers(vehicle) - --- The bale loader/wrapper variable is used to check if a bale loader or wrapper was found. - self.baleWrapper, self.baleWrapperController = self:addImplementController(vehicle, BaleWrapperController, BaleWrapper, {}, nil) - self.baleLoader, self.baleLoaderController = self:addImplementController(vehicle, BaleLoaderController, BaleLoader, {}, nil) - self.baleLoader = self.baleLoader or self:addImplementController(vehicle, APalletAutoLoaderController, nil, {}, "spec_aPalletAutoLoader") - self.baleLoader = self.baleLoader or self:addImplementController(vehicle, UniversalAutoloadController, nil, {}, "spec_universalAutoload") - self:addImplementController(vehicle, MotorController, Motorized, {}, nil) - self:addImplementController(vehicle, WearableController, Wearable, {}, nil) - self:addImplementController(vehicle, FoldableController, Foldable, {}) -end - ---- Wait for the giants bale loader to finish grabbing the bale. -function AIDriveStrategyFindBales:isReadyToLoadNextBale() - local isGrabbingBale = false - for i, controller in pairs(self.controllers) do - if controller.isGrabbingBale then - isGrabbingBale = isGrabbingBale or controller:isGrabbingBale() - end - end - return not isGrabbingBale -end - -function AIDriveStrategyFindBales:isGrabbingBale() - return not self:isReadyToLoadNextBale() -end - ---- Have any bales been loaded? -function AIDriveStrategyFindBales:hasBalesLoaded() - local hasBales = false - for i, controller in pairs(self.controllers) do - if controller.hasBales then - hasBales = hasBales or controller:hasBales() - end - end - return hasBales -end - ---- Can all bale loaders be folded? -function AIDriveStrategyFindBales:isReadyToFoldImplements() - local canBeFolded = true - for i, controller in pairs(self.controllers) do - if controller.canBeFolded then - canBeFolded = canBeFolded and controller:canBeFolded() - end - end - return canBeFolded -end - -function AIDriveStrategyFindBales:areBaleLoadersFull() - local allBaleLoadersFilled = self.baleLoader ~= nil - for i, controller in pairs(self.controllers) do - if controller.isFull then - allBaleLoadersFilled = allBaleLoadersFilled and controller:isFull() - end - end - return allBaleLoadersFilled -end - -function AIDriveStrategyFindBales:getBalesToIgnore() - local objectsToIgnore = {} - if self.lastBale then - return { self.lastBale } - elseif self.baleLoaderController then - return self.baleLoaderController:getBalesToIgnore() - else - for i, controller in pairs(self.controllers) do - if controller.getBalesToIgnore then - for i, bale in pairs(controller:getBalesToIgnore()) do - table.insert(objectsToIgnore, bale) - end - - end - end - end - return objectsToIgnore -end - ------------------------------------------------------------------------------------------------------------------------ ---- Static parameters (won't change while driving) ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFindBales:setAllStaticParameters() - -- make sure we have a good turning radius set - self.turningRadius = AIUtil.getTurningRadius(self.vehicle, true) - -- Set the offset to 0, we'll take care of getting the grabber to the right place - self.settings.toolOffsetX:setFloatValue(0) - self.reverser = AIReverseDriver(self.vehicle, self.ppc) - -- list of bales we tried (or in the process of trying) to find path for - self.balesTried = {} - -- when everything fails, reverse and try again. This is reset only when a pathfinding succeeds to avoid - -- backing up forever - self.numBalesLeftOver = 0 -end - -function AIDriveStrategyFindBales:setFieldPolygon(fieldPolygon) - self.fieldPolygon = fieldPolygon -end - ---- Bale wrap type for the bale loader. -function AIDriveStrategyFindBales:setAIVehicle(vehicle, jobParameters) - AIDriveStrategyCourse.setAIVehicle(self, vehicle, jobParameters) - self.baleWrapType = jobParameters.baleWrapType:getValue() - self:debug("Bale type selected: %s", tostring(self.baleWrapType)) - - local x, z = jobParameters.startPosition:getPosition() - local angle = jobParameters.startPosition:getAngle() - if x ~= nil and z ~= nil and angle ~= nil then - --- Additionally safety check, if the position is on the field or near it. - if CpMathUtil.isPointInPolygon(self.fieldPolygon, x, z) - or CpMathUtil.getClosestDistanceToPolygonEdge(self.fieldPolygon, x, z) < 2 * CpAIJobBaleFinder.minStartDistanceToField then - --- Goal position marker set in the ai menu rotated by 180 degree. - self.invertedStartPositionMarkerNode = CpUtil.createNode("Inverted Start position marker", - x, z, angle + math.pi) - self:debug("Valid goal position marker was set.") - else - self:debug("Start position is too far away from the field for a valid goal position!") - end - else - self:debug("Invalid start position found!") - end -end ------------------------------------------------------------------------------------------------------------------------ ---- Bale finding ------------------------------------------------------------------------------------------------------------------------ ----@param bale BaleToCollect -function AIDriveStrategyFindBales:isBaleOnField(bale) - local x, _, z = bale:getPosition() - return CpMathUtil.isPointInPolygon(self.fieldPolygon, x, z) -end - ---- Find bales on field ----@return BaleToCollect[] list of bales found -function AIDriveStrategyFindBales:findBales() - local balesFound, baleWithWrongWrapType = {}, false - for _, object in pairs(g_baleToCollectManager:getBales()) do - local isValid, wrongWrapType = BaleToCollect.isValidBale(object, - self.baleWrapper, self.baleLoader, self.baleWrapType) - if isValid and g_baleToCollectManager:isValidBale(object) then - local bale = BaleToCollect(object) - -- if the bale has a mountObject it is already on the loader so ignore it - if not object.mountObject and object:getOwnerFarmId() == self.vehicle:getOwnerFarmId() and - self:isBaleOnField(bale) then - -- bales may .have multiple nodes, using the object.id deduplicates the list - balesFound[object.id] = bale - end - end - baleWithWrongWrapType = baleWithWrongWrapType or wrongWrapType - end - local bales = {} - for _, bale in pairs(balesFound) do - table.insert(bales, bale) - end - self:debug('Found %d bales.', #bales) - --- Saves the number of bales found for the cp status. - self.numBalesLeftOver = #bales - return bales, baleWithWrongWrapType -end - ----@param bales table[] ----@param balesToIgnore BaleToCollect[]|nil exclude bales on this list from the results ----@return BaleToCollect|nil closest bale ----@return number|nil distance to the closest bale ----@return number|nil index of the bale -function AIDriveStrategyFindBales:findClosestBale(bales, balesToIgnore) - if not bales then - return - end - local closestBale, minDistance, ix = nil, math.huge, 1 - local invalidBales = 0 - for i, bale in ipairs(bales) do - if bale:isStillValid() then - local _, _, _, d = bale:getPositionInfoFromNode(self.vehicle:getAIDirectionNode()) - self:debug('%d. bale (%d, %s) in %.1f m', i, bale:getId(), bale:getBaleObject(), d) - if d < self.turningRadius * 4 then - -- if it is really close, check the length of the Dubins path - -- as we may need to drive a loop first to get to it - d = self:getDubinsPathLengthToBale(bale) - self:debug(' Dubins length is %.1f m', d) - end - if d < minDistance then - if not table.hasElement(balesToIgnore or {}, bale) then - -- bale is not on the list of bales to ignore - closestBale = bale - minDistance = d - ix = i - else - self:debug(' IGNORED') - end - end - else - --- When a bale gets wrapped it changes its identity and the node becomes invalid. This can happen - --- when we pick up (and wrap) a bale other than the target bale, for example because there's another bale - --- in the grabber's way. That is now wrapped but our bale list does not know about it so let's rescan the field - self:debug('%d. bale (%d, %s) INVALID', i, bale:getId(), bale:getBaleObject()) - invalidBales = invalidBales + 1 - self:debug('Found invalid bale(s), rescanning field', invalidBales) - self.bales = self:findBales() - -- return empty, next time this is called everything should be ok - return - end - end - return closestBale, minDistance, ix -end - -function AIDriveStrategyFindBales:getDubinsPathLengthToBale(bale) - local start = PathfinderUtil.getVehiclePositionAsState3D(self.vehicle) - local goal = self:getBaleTarget(bale) - local solution = PathfinderUtil.dubinsSolver:solve(start, goal, self.turningRadius) - return solution:getLength(self.turningRadius) -end - -function AIDriveStrategyFindBales:findPathToNextBale() - if self.bumpedIntoAnotherBale then - self.bumpedIntoAnotherBale = false - self:debug("Bumped into a bale other than the target on the way, rescanning.") - self:findBales() - end - local bale, d, ix = self:findClosestBale(self.bales) - if bale then - if bale:isLoaded() then - self:debug('Bale %d is already loaded, skipping', bale:getId()) - table.remove(self.bales, ix) - else - self:startPathfindingToBale(bale) - -- remove bale from list - table.remove(self.bales, ix) - end - end -end - ---- The trick here is to get a target direction at the bale -function AIDriveStrategyFindBales:getBaleTarget(bale) - -- first figure out the direction at the goal, as the pathfinder needs that. - -- for now, just use the direction from our location towards the bale - local xb, zb, yRot, d = bale:getPositionInfoFromNode(self.vehicle:getAIDirectionNode()) - 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() - if self:areBaleLoadersFull() then - self:debug('All the bale loaders are full, so stopping the job.') - self.vehicle:stopCurrentAIJob(AIMessageErrorIsFull.new()) - elseif self:hasBalesLoaded() then - if self.baleLoaderController and self.baleLoaderController:isChangingBaleSize() then - self:debug('There really are no more bales on the field, so stopping the job') - self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) - else - self:debug('No more bales found on the field, so stopping the job and sending the loader to unload the bales.') - self.vehicle:stopCurrentAIJob(AIMessageErrorIsFull.new()) - end - elseif self.baleLoader and self.wrongWrapTypeFound then - self:debug('Only bales with a wrong wrap type are left on the field.') - self.vehicle:stopCurrentAIJob(AIMessageErrorWrongBaleWrapType.new()) - else - self:debug('No more bales left on the field and no bales are loader and so on ..') - self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- Pathfinding ------------------------------------------------------------------------------------------------------------------------ - ---- Pathfinding has finished ----@param controller PathfinderController ----@param success boolean ----@param course Course|nil ----@param goalNodeInvalid boolean|nil -function AIDriveStrategyFindBales:onPathfindingFinished(controller, - success, course, goalNodeInvalid) - if self.state == self.states.DRIVING_TO_NEXT_BALE then - if success then - self.balesTried = {} - self:startCourse(course, 1) - else - g_baleToCollectManager:unlockBalesByDriver(self) - if #self.balesTried < 5 and #self.bales > #self.balesTried then - if goalNodeInvalid then - -- there may be another bale too close to the previous one - self:debug('Finding path to next bale failed, goal node invalid.') - self:retryPathfindingWithAnotherBale() - elseif self:isNearFieldEdge() then - self.balesTried = {} - self:debug('Finding path to next bale failed, we are close to the field edge, back up a bit and then try again') - self:startReversing(self.states.REVERSING_AFTER_PATHFINDER_FAILURE) - else - self:debug('Finding path to next bale failed, but we are not too close to the field edge') - self:retryPathfindingWithAnotherBale() - end - else - self.balesTried = {} - self:info('Pathfinding failed five times, giving up') - self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new()) - end - end - elseif self.state == self.states.DRIVING_TO_START_MARKER then - if success then - --- Append a straight alignment segment - local x, _, z = course:getWaypointPosition(course:getNumberOfWaypoints()) - local dx, _, dz = localToWorld(self.invertedStartPositionMarkerNode, self.invertedGoalPositionOffset, 0, 0) - - course:append(Course.createFromTwoWorldPositions(self.vehicle, x, z, dx, dz, - 0, 0, 0, 3, false)) - self:startCourse(course, 1) - else - self:finishJob() - end - end -end - ---- After pathfinding failed, retry with another bale -function AIDriveStrategyFindBales:retryPathfindingWithAnotherBale() - self:debug("Retrying with another bale.") - local bale, d, ix = self:findClosestBale(self.bales, self.balesTried) - if bale then - self:startPathfindingToBale(bale) - else - self.balesTried = {} - self:debug("No valid bale found on retry!") - self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new()) - end -end - -function AIDriveStrategyFindBales:getPathfinderBaleTargetAsGoalNode(bale) - local safeDistanceFromBale = bale:getSafeDistance() - local halfVehicleWidth = AIUtil.getWidth(self.vehicle) / 2 - local goal = self:getBaleTarget(bale) - local configuredOffset = self:getConfiguredOffset() - local offset = Vector(0, safeDistanceFromBale + configuredOffset) - goal:add(offset:rotate(goal.t)) - self:debug('Start pathfinding to next bale (%d), safe distance from bale %.1f, half vehicle width %.1f, configured offset %s', - bale:getId(), safeDistanceFromBale, halfVehicleWidth, - configuredOffset and string.format('%.1f', configuredOffset) or 'n/a') - return goal -end - ----@param bale BaleToCollect -function AIDriveStrategyFindBales:startPathfindingToBale(bale) - self.state = self.states.DRIVING_TO_NEXT_BALE - g_baleToCollectManager:lockBale(bale:getBaleObject(), self) - local context = PathfinderContext(self.vehicle):objectsToIgnore(self:getBalesToIgnore()) - context:allowReverse(false):maxFruitPercent(self.settings.avoidFruit:getValue() and 10 or math.huge) - table.insert(self.balesTried, bale) - self.pathfinderController:registerListeners(self, self.onPathfindingFinished, nil, - self.onPathfindingObstacleAtStart) - self.pathfinderController:findPathToGoal(context, self:getPathfinderBaleTargetAsGoalNode(bale)) -end - ---- Searches for a path to the start marker in the inverted direction. -function AIDriveStrategyFindBales:startPathfindingToStartMarker() - self.state = self.states.DRIVING_TO_START_MARKER - local context = PathfinderContext(self.vehicle):objectsToIgnore(self:getBalesToIgnore()) - context:allowReverse(false):maxFruitPercent(self.settings.avoidFruit:getValue() and 10 or math.huge) - self.pathfinderController:findPathToNode(context, self.invertedStartPositionMarkerNode, - self.invertedGoalPositionOffset, -1.5 * AIUtil.getLength(self.vehicle), 2) -end - -function AIDriveStrategyFindBales:onPathfindingObstacleAtStart(controller, lastContext, maxDistance, trailerCollisionsOnly) - g_baleToCollectManager:unlockBalesByDriver(self) - self.balesTried = {} - self:debug('Pathfinding detected obstacle at start, back up and retry') - self:startReversing(self.states.REVERSING_DUE_TO_OBSTACLE_AHEAD) -end - -function AIDriveStrategyFindBales:startReversing(state) - self.state = state - self:startCourse(Course.createStraightReverseCourse(self.vehicle, 10), 1) -end - -function AIDriveStrategyFindBales:isNearFieldEdge() - local x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 0) - local vehicleIsOnField = CpFieldUtil.isOnField(x, z) - x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1.2 * self.turningRadius) - local isFieldInFrontOfVehicle = CpFieldUtil.isOnFieldArea(x, z) - self:debug('vehicle is on field: %s, field in front of vehicle: %s', - tostring(vehicleIsOnField), tostring(isFieldInFrontOfVehicle)) - return vehicleIsOnField and not isFieldInFrontOfVehicle -end - ------------------------------------------------------------------------------------------------------------------------ ---- Event listeners ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyFindBales:onWaypointPassed(ix, course) - if course:isLastWaypointIx(ix) then - if self.state == self.states.DRIVING_TO_NEXT_BALE then - self:debug('last waypoint while driving to next bale reached') - self:startApproachingBale() - elseif self.state == self.states.WORKING_ON_BALE then - self:debug('last waypoint on bale pickup reached, start collecting bales again') - self:collectNextBale() - elseif self.state == self.states.APPROACHING_BALE then - self:debug('looks like somehow we missed a bale, rescanning field') - self.bales = self:findBales() - self:collectNextBale() - elseif self.state == self.states.REVERSING_AFTER_PATHFINDER_FAILURE then - self:debug('backed up after pathfinder failed, trying again') - self.state = self.states.SEARCHING_FOR_NEXT_BALE - elseif self.state == self.states.REVERSING_DUE_TO_OBSTACLE_AHEAD then - self:debug('backed due to obstacle, trying again') - self.state = self.states.SEARCHING_FOR_NEXT_BALE - elseif self.state == self.states.DRIVING_TO_START_MARKER then - self:debug("Inverted start marker position is reached.") - self:finishJob() - end - end -end - -function AIDriveStrategyFindBales:startApproachingBale() - self:debug('Approaching bale...') - self:startCourse(Course.createStraightForwardCourse(self.vehicle, 20), 1) - self.state = self.states.APPROACHING_BALE -end - ---- this the part doing the actual work on the field after/before all ---- implements are started/lowered etc. -function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ) - self:updateLowFrequencyImplementControllers() - self:updateLowFrequencyPathfinder() - if self.state == self.states.INITIAL then - if self:getCanContinueWork() then - self.state = self.states.SEARCHING_FOR_NEXT_BALE - else - --- Waiting until the unfolding has finished. - if self.bales == nil then - --- Makes sure the hud bale counter already gets updated - self.bales = self:findBales() - end - self:setMaxSpeed(0) - end - elseif self.state == self.states.SEARCHING_FOR_NEXT_BALE then - self:setMaxSpeed(0) - self:debug('work: searching for next bale') - self:collectNextBale() - elseif self.state == self.states.WAITING_FOR_PATHFINDER then - self:setMaxSpeed(0) - elseif self.state == self.states.DRIVING_TO_NEXT_BALE then - self:setMaxSpeed(self.settings.fieldSpeed:getValue()) - if not self.bumpedIntoAnotherBale and self:isGrabbingBale() then - -- we are not at the bale yet but grabbing something, likely bumped into another bale - self.bumpedIntoAnotherBale = true - end - elseif self.state == self.states.APPROACHING_BALE then - self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue() / 2) - self:approachBale() - elseif self.state == self.states.WORKING_ON_BALE then - self:workOnBale() - self:setMaxSpeed(0) - elseif self.state == self.states.REVERSING_AFTER_PATHFINDER_FAILURE then - self:setMaxSpeed(self.settings.reverseSpeed:getValue()) - elseif self.state == self.states.REVERSING_DUE_TO_OBSTACLE_AHEAD then - 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() - local gx, gz - if not moveForwards then - local maxSpeed - gx, gz, maxSpeed = self:getReverseDriveData() - self:setMaxSpeed(maxSpeed) - else - gx, _, gz = self.ppc:getGoalPointPosition() - end - - return gx, gz, moveForwards, self.maxSpeed, 100 -end - -function AIDriveStrategyFindBales:approachBale() - if self.baleLoader then - if not self:isReadyToLoadNextBale() then - self:debug('Start picking up bale') - self.state = self.states.WORKING_ON_BALE - self.numBalesLeftOver = math.max(self.numBalesLeftOver-1, 0) - end - end - if self.baleWrapper then - self.baleWrapperController:handleBaleWrapper() - if self.baleWrapperController:isWorking() then - self:debug('Start wrapping bale') - self.state = self.states.WORKING_ON_BALE - self.numBalesLeftOver = math.max(self.numBalesLeftOver-1, 0) - end - end -end - -function AIDriveStrategyFindBales:workOnBale() - if self.baleLoader then - if self:isReadyToLoadNextBale() then - self:debug('Bale picked up, moving on to the next') - self:collectNextBale() - end - end - if self.baleWrapper then - self.baleWrapperController:handleBaleWrapper() - if not self.baleWrapperController:isWorking() then - self.lastBale = self.baleWrapperController:getLastDroppedBale() - self:debug('Bale wrapped, moving on to the next, last dropped bale %s', self.lastBale) - self:collectNextBale() - end - end -end - -function AIDriveStrategyFindBales:calculateTightTurnOffset() - self.tightTurnOffset = 0 -end - -function AIDriveStrategyFindBales:getConfiguredOffset() - return self.settings.baleCollectorOffset:getValue() -end - -function AIDriveStrategyFindBales:isAutoContinueAtWaitPointEnabled() - return true -end - -function AIDriveStrategyFindBales:isStoppingAtWaitPointAllowed() - return true -end - -function AIDriveStrategyFindBales:update(dt) - AIDriveStrategyCourse.update(self, dt) - self:updateImplementControllers(dt) - - if CpDebug:isChannelActive(self.debugChannel, self.vehicle) then - if self.course then - self.course:draw() - elseif self.ppc:getCourse() then - 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:areBaleLoadersFull() then - self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD - end - end - --- Ignores the loaded auto loader bales. - --- TODO: Maybe add a delay here? - local loadedBales = self:getBalesToIgnore() - for _, bale in pairs(loadedBales) do - --- Makes sure these loaded bales from an autoload trailer, - --- can't be selected as a target by another bale loader. - g_baleToCollectManager:temporarilyLeaseBale(bale) - end -end - ----@param status CpStatus -function AIDriveStrategyFindBales:updateCpStatus(status) - status:setBaleData(self.numBalesLeftOver) +--[[ +This file is part of Courseplay (https://github.com/Courseplay/courseplay) +Copyright (C) 2022 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +]] + +--- Drive strategy to find bales on a field and collect or wrap them +--- TODO: Separate bale wrapper and bale loaders. +--- Might be a good idea to have the bale loader strategy derive from the find bales(only wrapper) strategy. + +---@class AIDriveStrategyFindBales : AIDriveStrategyCourse +AIDriveStrategyFindBales = CpObject(AIDriveStrategyCourse) + +AIDriveStrategyFindBales.myStates = { + SEARCHING_FOR_NEXT_BALE = {}, + WAITING_FOR_PATHFINDER = {}, + DRIVING_TO_NEXT_BALE = {}, + APPROACHING_BALE = {}, + WORKING_ON_BALE = {}, + REVERSING_AFTER_PATHFINDER_FAILURE = {}, + REVERSING_DUE_TO_OBSTACLE_AHEAD = {}, + DRIVING_TO_START_MARKER = {}, + WAITING_FOR_IMPLEMENTS_TO_FOLD = {} +} +--- 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 + +function AIDriveStrategyFindBales:init(task, job) + AIDriveStrategyCourse.init(self, task, job) + AIDriveStrategyCourse.initStates(self, AIDriveStrategyFindBales.myStates) + self.state = self.states.INITIAL + self.debugChannel = CpDebug.DBG_FIND_BALES + self.bales = {} +end + +function AIDriveStrategyFindBales:delete() + AIDriveStrategyCourse.delete(self) + g_baleToCollectManager:unlockBalesByDriver(self) + if self.invertedStartPositionMarkerNode then + CpUtil.destroyNode(self.invertedStartPositionMarkerNode) + end +end + +function AIDriveStrategyFindBales:getGeneratedCourse(jobParameters) + return nil +end + +function AIDriveStrategyFindBales:startWithoutCourse() + -- to always have a valid course (for the traffic conflict detector mainly) + self.course = Course.createStraightForwardCourse(self.vehicle, 25) + self:startCourse(self.course, 1) + + self:info('Starting bale collect/wrap') + + for _, implement in pairs(self.vehicle:getAttachedImplements()) do + self:info(' - %s', CpUtil.getName(implement.object)) + end + self.vehicle:raiseAIEvent("onAIFieldWorkerStart", "onAIImplementStart") + self:lowerImplements() +end + +function AIDriveStrategyFindBales:collectNextBale() + self.state = self.states.SEARCHING_FOR_NEXT_BALE + if #self.bales > 0 then + self:findPathToNextBale() + else + self:debug('No bales found, scan the field once more before leaving for the unload course.') + self.bales, self.wrongWrapTypeFound = self:findBales() + if #self.bales > 0 then + self:debug('Found more bales, collecting them') + self:findPathToNextBale() + return + end + self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- Implement handling +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFindBales:initializeImplementControllers(vehicle) + --- The bale loader/wrapper variable is used to check if a bale loader or wrapper was found. + self.baleWrapper, self.baleWrapperController = self:addImplementController(vehicle, BaleWrapperController, BaleWrapper, {}, nil) + self.baleLoader, self.baleLoaderController = self:addImplementController(vehicle, BaleLoaderController, BaleLoader, {}, nil) + self.baleLoader = self.baleLoader or self:addImplementController(vehicle, APalletAutoLoaderController, nil, {}, "spec_aPalletAutoLoader") + self.baleLoader = self.baleLoader or self:addImplementController(vehicle, UniversalAutoloadController, nil, {}, "spec_universalAutoload") + self:addImplementController(vehicle, MotorController, Motorized, {}, nil) + self:addImplementController(vehicle, WearableController, Wearable, {}, nil) + self:addImplementController(vehicle, FoldableController, Foldable, {}) +end + +--- Wait for the giants bale loader to finish grabbing the bale. +function AIDriveStrategyFindBales:isReadyToLoadNextBale() + local isGrabbingBale = false + for i, controller in pairs(self.controllers) do + if controller.isGrabbingBale then + isGrabbingBale = isGrabbingBale or controller:isGrabbingBale() + end + end + return not isGrabbingBale +end + +function AIDriveStrategyFindBales:isGrabbingBale() + return not self:isReadyToLoadNextBale() +end + +--- Have any bales been loaded? +function AIDriveStrategyFindBales:hasBalesLoaded() + local hasBales = false + for i, controller in pairs(self.controllers) do + if controller.hasBales then + hasBales = hasBales or controller:hasBales() + end + end + return hasBales +end + +--- Can all bale loaders be folded? +function AIDriveStrategyFindBales:isReadyToFoldImplements() + local canBeFolded = true + for i, controller in pairs(self.controllers) do + if controller.canBeFolded then + canBeFolded = canBeFolded and controller:canBeFolded() + end + end + return canBeFolded +end + +function AIDriveStrategyFindBales:areBaleLoadersFull() + local allBaleLoadersFilled = self.baleLoader ~= nil + for i, controller in pairs(self.controllers) do + if controller.isFull then + allBaleLoadersFilled = allBaleLoadersFilled and controller:isFull() + end + end + return allBaleLoadersFilled +end + +function AIDriveStrategyFindBales:getBalesToIgnore() + local objectsToIgnore = {} + if self.lastBale then + return { self.lastBale } + elseif self.baleLoaderController then + return self.baleLoaderController:getBalesToIgnore() + else + for i, controller in pairs(self.controllers) do + if controller.getBalesToIgnore then + for i, bale in pairs(controller:getBalesToIgnore()) do + table.insert(objectsToIgnore, bale) + end + + end + end + end + return objectsToIgnore +end + +----------------------------------------------------------------------------------------------------------------------- +--- Static parameters (won't change while driving) +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFindBales:setAllStaticParameters() + -- make sure we have a good turning radius set + self.turningRadius = AIUtil.getTurningRadius(self.vehicle, true) + -- Set the offset to 0, we'll take care of getting the grabber to the right place + self.settings.toolOffsetX:setFloatValue(0) + self.reverser = AIReverseDriver(self.vehicle, self.ppc) + -- list of bales we tried (or in the process of trying) to find path for + self.balesTried = {} + -- when everything fails, reverse and try again. This is reset only when a pathfinding succeeds to avoid + -- backing up forever + self.numBalesLeftOver = 0 +end + +function AIDriveStrategyFindBales:setFieldPolygon(fieldPolygon) + self.fieldPolygon = fieldPolygon +end + +--- Bale wrap type for the bale loader. +function AIDriveStrategyFindBales:setAIVehicle(vehicle, jobParameters) + AIDriveStrategyCourse.setAIVehicle(self, vehicle, jobParameters) + self.baleWrapType = jobParameters.baleWrapType:getValue() + self:debug("Bale type selected: %s", tostring(self.baleWrapType)) + + local x, z = jobParameters.startPosition:getPosition() + local angle = jobParameters.startPosition:getAngle() + if x ~= nil and z ~= nil and angle ~= nil then + --- Additionally safety check, if the position is on the field or near it. + if CpMathUtil.isPointInPolygon(self.fieldPolygon, x, z) + or CpMathUtil.getClosestDistanceToPolygonEdge(self.fieldPolygon, x, z) < 2 * CpAIJobBaleFinder.minStartDistanceToField then + --- Goal position marker set in the ai menu rotated by 180 degree. + self.invertedStartPositionMarkerNode = CpUtil.createNode("Inverted Start position marker", + x, z, angle + math.pi) + self:debug("Valid goal position marker was set.") + else + self:debug("Start position is too far away from the field for a valid goal position!") + end + else + self:debug("Invalid start position found!") + end +end +----------------------------------------------------------------------------------------------------------------------- +--- Bale finding +----------------------------------------------------------------------------------------------------------------------- +---@param bale BaleToCollect +function AIDriveStrategyFindBales:isBaleOnField(bale) + local x, _, z = bale:getPosition() + return CpMathUtil.isPointInPolygon(self.fieldPolygon, x, z) +end + +--- Find bales on field +---@return BaleToCollect[] list of bales found +function AIDriveStrategyFindBales:findBales() + local balesFound, baleWithWrongWrapType = {}, false + for _, object in pairs(g_baleToCollectManager:getBales()) do + local isValid, wrongWrapType = BaleToCollect.isValidBale(object, + self.baleWrapper, self.baleLoader, self.baleWrapType) + if isValid and g_baleToCollectManager:isValidBale(object) then + local bale = BaleToCollect(object) + -- if the bale has a mountObject it is already on the loader so ignore it + if not object.mountObject and object:getOwnerFarmId() == self.vehicle:getOwnerFarmId() and + self:isBaleOnField(bale) then + -- bales may .have multiple nodes, using the object.id deduplicates the list + balesFound[object.id] = bale + end + end + baleWithWrongWrapType = baleWithWrongWrapType or wrongWrapType + end + local bales = {} + for _, bale in pairs(balesFound) do + table.insert(bales, bale) + end + self:debug('Found %d bales.', #bales) + --- Saves the number of bales found for the cp status. + self.numBalesLeftOver = #bales + return bales, baleWithWrongWrapType +end + +---@param bales table[] +---@param balesToIgnore BaleToCollect[]|nil exclude bales on this list from the results +---@return BaleToCollect|nil closest bale +---@return number|nil distance to the closest bale +---@return number|nil index of the bale +function AIDriveStrategyFindBales:findClosestBale(bales, balesToIgnore) + if not bales then + return + end + local closestBale, minDistance, ix = nil, math.huge, 1 + local invalidBales = 0 + for i, bale in ipairs(bales) do + if bale:isStillValid() then + local _, _, _, d = bale:getPositionInfoFromNode(self.vehicle:getAIDirectionNode()) + self:debug('%d. bale (%d, %s) in %.1f m', i, bale:getId(), bale:getBaleObject(), d) + if d < self.turningRadius * 4 then + -- if it is really close, check the length of the Dubins path + -- as we may need to drive a loop first to get to it + d = self:getDubinsPathLengthToBale(bale) + self:debug(' Dubins length is %.1f m', d) + end + if d < minDistance then + if not table.hasElement(balesToIgnore or {}, bale) then + -- bale is not on the list of bales to ignore + closestBale = bale + minDistance = d + ix = i + else + self:debug(' IGNORED') + end + end + else + --- When a bale gets wrapped it changes its identity and the node becomes invalid. This can happen + --- when we pick up (and wrap) a bale other than the target bale, for example because there's another bale + --- in the grabber's way. That is now wrapped but our bale list does not know about it so let's rescan the field + self:debug('%d. bale (%d, %s) INVALID', i, bale:getId(), bale:getBaleObject()) + invalidBales = invalidBales + 1 + self:debug('Found invalid bale(s), rescanning field', invalidBales) + self.bales = self:findBales() + -- return empty, next time this is called everything should be ok + return + end + end + return closestBale, minDistance, ix +end + +function AIDriveStrategyFindBales:getDubinsPathLengthToBale(bale) + local start = PathfinderUtil.getVehiclePositionAsState3D(self.vehicle) + local goal = self:getBaleTarget(bale) + local solution = PathfinderUtil.dubinsSolver:solve(start, goal, self.turningRadius) + return solution:getLength(self.turningRadius) +end + +function AIDriveStrategyFindBales:findPathToNextBale() + if self.bumpedIntoAnotherBale then + self.bumpedIntoAnotherBale = false + self:debug("Bumped into a bale other than the target on the way, rescanning.") + self:findBales() + end + local bale, d, ix = self:findClosestBale(self.bales) + if bale then + if bale:isLoaded() then + self:debug('Bale %d is already loaded, skipping', bale:getId()) + table.remove(self.bales, ix) + else + self:startPathfindingToBale(bale) + -- remove bale from list + table.remove(self.bales, ix) + end + end +end + +--- The trick here is to get a target direction at the bale +function AIDriveStrategyFindBales:getBaleTarget(bale) + -- first figure out the direction at the goal, as the pathfinder needs that. + -- for now, just use the direction from our location towards the bale + local xb, zb, yRot, d = bale:getPositionInfoFromNode(self.vehicle:getAIDirectionNode()) + 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() + if self:areBaleLoadersFull() then + self:debug('All the bale loaders are full, so stopping the job.') + self.vehicle:stopCurrentAIJob(AIMessageErrorIsFull.new()) + elseif self:hasBalesLoaded() then + if self.baleLoaderController and self.baleLoaderController:isChangingBaleSize() then + self:debug('There really are no more bales on the field, so stopping the job') + self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) + else + self:debug('No more bales found on the field, so stopping the job and sending the loader to unload the bales.') + self.vehicle:stopCurrentAIJob(AIMessageErrorIsFull.new()) + end + elseif self.baleLoader and self.wrongWrapTypeFound then + self:debug('Only bales with a wrong wrap type are left on the field.') + self.vehicle:stopCurrentAIJob(AIMessageErrorWrongBaleWrapType.new()) + else + self:debug('No more bales left on the field and no bales are loader and so on ..') + self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new()) + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- Pathfinding +----------------------------------------------------------------------------------------------------------------------- + +--- Pathfinding has finished +---@param controller PathfinderController +---@param success boolean +---@param course Course|nil +---@param goalNodeInvalid boolean|nil +function AIDriveStrategyFindBales:onPathfindingFinished(controller, + success, course, goalNodeInvalid) + if self.state == self.states.DRIVING_TO_NEXT_BALE then + if success then + self.balesTried = {} + self:startCourse(course, 1) + else + g_baleToCollectManager:unlockBalesByDriver(self) + if #self.balesTried < 5 and #self.bales > #self.balesTried then + if goalNodeInvalid then + -- there may be another bale too close to the previous one + self:debug('Finding path to next bale failed, goal node invalid.') + self:retryPathfindingWithAnotherBale() + elseif self:isNearFieldEdge() then + self.balesTried = {} + self:debug('Finding path to next bale failed, we are close to the field edge, back up a bit and then try again') + self:startReversing(self.states.REVERSING_AFTER_PATHFINDER_FAILURE) + else + self:debug('Finding path to next bale failed, but we are not too close to the field edge') + self:retryPathfindingWithAnotherBale() + end + else + self.balesTried = {} + self:info('Pathfinding failed five times, giving up') + self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new()) + end + end + elseif self.state == self.states.DRIVING_TO_START_MARKER then + if success then + --- Append a straight alignment segment + local x, _, z = course:getWaypointPosition(course:getNumberOfWaypoints()) + local dx, _, dz = localToWorld(self.invertedStartPositionMarkerNode, self.invertedGoalPositionOffset, 0, 0) + + course:append(Course.createFromTwoWorldPositions(self.vehicle, x, z, dx, dz, + 0, 0, 0, 3, false)) + self:startCourse(course, 1) + else + self:finishJob() + end + end +end + +--- After pathfinding failed, retry with another bale +function AIDriveStrategyFindBales:retryPathfindingWithAnotherBale() + self:debug("Retrying with another bale.") + local bale, d, ix = self:findClosestBale(self.bales, self.balesTried) + if bale then + self:startPathfindingToBale(bale) + else + self.balesTried = {} + self:debug("No valid bale found on retry!") + self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new()) + end +end + +function AIDriveStrategyFindBales:getPathfinderBaleTargetAsGoalNode(bale) + local safeDistanceFromBale = bale:getSafeDistance() + local halfVehicleWidth = AIUtil.getWidth(self.vehicle) / 2 + local goal = self:getBaleTarget(bale) + local configuredOffset = self:getConfiguredOffset() + local offset = Vector(0, safeDistanceFromBale + configuredOffset) + goal:add(offset:rotate(goal.t)) + self:debug('Start pathfinding to next bale (%d), safe distance from bale %.1f, half vehicle width %.1f, configured offset %s', + bale:getId(), safeDistanceFromBale, halfVehicleWidth, + configuredOffset and string.format('%.1f', configuredOffset) or 'n/a') + return goal +end + +---@param bale BaleToCollect +function AIDriveStrategyFindBales:startPathfindingToBale(bale) + self.state = self.states.DRIVING_TO_NEXT_BALE + g_baleToCollectManager:lockBale(bale:getBaleObject(), self) + local context = PathfinderContext(self.vehicle):objectsToIgnore(self:getBalesToIgnore()) + context:allowReverse(false):maxFruitPercent(self.settings.avoidFruit:getValue() and 10 or math.huge) + table.insert(self.balesTried, bale) + self.pathfinderController:registerListeners(self, self.onPathfindingFinished, nil, + self.onPathfindingObstacleAtStart) + self.pathfinderController:findPathToGoal(context, self:getPathfinderBaleTargetAsGoalNode(bale)) +end + +--- Searches for a path to the start marker in the inverted direction. +function AIDriveStrategyFindBales:startPathfindingToStartMarker() + self.state = self.states.DRIVING_TO_START_MARKER + local context = PathfinderContext(self.vehicle):objectsToIgnore(self:getBalesToIgnore()) + context:allowReverse(false):maxFruitPercent(self.settings.avoidFruit:getValue() and 10 or math.huge) + self.pathfinderController:findPathToNode(context, self.invertedStartPositionMarkerNode, + self.invertedGoalPositionOffset, -1.5 * AIUtil.getLength(self.vehicle), 2) +end + +function AIDriveStrategyFindBales:onPathfindingObstacleAtStart(controller, lastContext, maxDistance, trailerCollisionsOnly) + g_baleToCollectManager:unlockBalesByDriver(self) + self.balesTried = {} + self:debug('Pathfinding detected obstacle at start, back up and retry') + self:startReversing(self.states.REVERSING_DUE_TO_OBSTACLE_AHEAD) +end + +function AIDriveStrategyFindBales:startReversing(state) + self.state = state + self:startCourse(Course.createStraightReverseCourse(self.vehicle, 10), 1) +end + +function AIDriveStrategyFindBales:isNearFieldEdge() + local x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 0) + local vehicleIsOnField = CpFieldUtil.isOnField(x, z) + x, _, z = localToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1.2 * self.turningRadius) + local isFieldInFrontOfVehicle = CpFieldUtil.isOnFieldArea(x, z) + self:debug('vehicle is on field: %s, field in front of vehicle: %s', + tostring(vehicleIsOnField), tostring(isFieldInFrontOfVehicle)) + return vehicleIsOnField and not isFieldInFrontOfVehicle +end + +----------------------------------------------------------------------------------------------------------------------- +--- Event listeners +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyFindBales:onWaypointPassed(ix, course) + if course:isLastWaypointIx(ix) then + if self.state == self.states.DRIVING_TO_NEXT_BALE then + self:debug('last waypoint while driving to next bale reached') + self:startApproachingBale() + elseif self.state == self.states.WORKING_ON_BALE then + self:debug('last waypoint on bale pickup reached, start collecting bales again') + self:collectNextBale() + elseif self.state == self.states.APPROACHING_BALE then + self:debug('looks like somehow we missed a bale, rescanning field') + self.bales = self:findBales() + self:collectNextBale() + elseif self.state == self.states.REVERSING_AFTER_PATHFINDER_FAILURE then + self:debug('backed up after pathfinder failed, trying again') + self.state = self.states.SEARCHING_FOR_NEXT_BALE + elseif self.state == self.states.REVERSING_DUE_TO_OBSTACLE_AHEAD then + self:debug('backed due to obstacle, trying again') + self.state = self.states.SEARCHING_FOR_NEXT_BALE + elseif self.state == self.states.DRIVING_TO_START_MARKER then + self:debug("Inverted start marker position is reached.") + self:finishJob() + end + end +end + +function AIDriveStrategyFindBales:startApproachingBale() + self:debug('Approaching bale...') + self:startCourse(Course.createStraightForwardCourse(self.vehicle, 20), 1) + self.state = self.states.APPROACHING_BALE +end + +--- this the part doing the actual work on the field after/before all +--- implements are started/lowered etc. +function AIDriveStrategyFindBales:getDriveData(dt, vX, vY, vZ) + self:updateLowFrequencyImplementControllers() + self:updateLowFrequencyPathfinder() + if self.state == self.states.INITIAL then + if self:getCanContinueWork() then + self.state = self.states.SEARCHING_FOR_NEXT_BALE + else + --- Waiting until the unfolding has finished. + if self.bales == nil then + --- Makes sure the hud bale counter already gets updated + self.bales = self:findBales() + end + self:setMaxSpeed(0) + end + elseif self.state == self.states.SEARCHING_FOR_NEXT_BALE then + self:setMaxSpeed(0) + self:debug('work: searching for next bale') + self:collectNextBale() + elseif self.state == self.states.WAITING_FOR_PATHFINDER then + self:setMaxSpeed(0) + elseif self.state == self.states.DRIVING_TO_NEXT_BALE then + self:setMaxSpeed(self.settings.fieldSpeed:getValue()) + if not self.bumpedIntoAnotherBale and self:isGrabbingBale() then + -- we are not at the bale yet but grabbing something, likely bumped into another bale + self.bumpedIntoAnotherBale = true + end + elseif self.state == self.states.APPROACHING_BALE then + self:setMaxSpeed(self.settings.fieldWorkSpeed:getValue() / 2) + self:approachBale() + elseif self.state == self.states.WORKING_ON_BALE then + self:workOnBale() + self:setMaxSpeed(0) + elseif self.state == self.states.REVERSING_AFTER_PATHFINDER_FAILURE then + self:setMaxSpeed(self.settings.reverseSpeed:getValue()) + elseif self.state == self.states.REVERSING_DUE_TO_OBSTACLE_AHEAD then + 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() + local gx, gz + if not moveForwards then + local maxSpeed + gx, gz, maxSpeed = self:getReverseDriveData() + self:setMaxSpeed(maxSpeed) + else + gx, _, gz = self.ppc:getGoalPointPosition() + end + + return gx, gz, moveForwards, self.maxSpeed, 100 +end + +function AIDriveStrategyFindBales:approachBale() + if self.baleLoader then + if not self:isReadyToLoadNextBale() then + self:debug('Start picking up bale') + self.state = self.states.WORKING_ON_BALE + self.numBalesLeftOver = math.max(self.numBalesLeftOver-1, 0) + end + end + if self.baleWrapper then + self.baleWrapperController:handleBaleWrapper() + if self.baleWrapperController:isWorking() then + self:debug('Start wrapping bale') + self.state = self.states.WORKING_ON_BALE + self.numBalesLeftOver = math.max(self.numBalesLeftOver-1, 0) + end + end +end + +function AIDriveStrategyFindBales:workOnBale() + if self.baleLoader then + if self:isReadyToLoadNextBale() then + self:debug('Bale picked up, moving on to the next') + self:collectNextBale() + end + end + if self.baleWrapper then + self.baleWrapperController:handleBaleWrapper() + if not self.baleWrapperController:isWorking() then + self.lastBale = self.baleWrapperController:getLastDroppedBale() + self:debug('Bale wrapped, moving on to the next, last dropped bale %s', self.lastBale) + self:collectNextBale() + end + end +end + +function AIDriveStrategyFindBales:calculateTightTurnOffset() + self.tightTurnOffset = 0 +end + +function AIDriveStrategyFindBales:getConfiguredOffset() + return self.settings.baleCollectorOffset:getValue() +end + +function AIDriveStrategyFindBales:isAutoContinueAtWaitPointEnabled() + return true +end + +function AIDriveStrategyFindBales:isStoppingAtWaitPointAllowed() + return true +end + +function AIDriveStrategyFindBales:update(dt) + AIDriveStrategyCourse.update(self, dt) + self:updateImplementControllers(dt) + + if CpDebug:isChannelActive(self.debugChannel, self.vehicle) then + if self.course then + self.course:draw() + elseif self.ppc:getCourse() then + 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:areBaleLoadersFull() then + self.state = self.states.WAITING_FOR_IMPLEMENTS_TO_FOLD + end + end + --- Ignores the loaded auto loader bales. + --- TODO: Maybe add a delay here? + local loadedBales = self:getBalesToIgnore() + for _, bale in pairs(loadedBales) do + --- Makes sure these loaded bales from an autoload trailer, + --- can't be selected as a target by another bale loader. + g_baleToCollectManager:temporarilyLeaseBale(bale) + end +end + +---@param status CpStatus +function AIDriveStrategyFindBales:updateCpStatus(status) + status:setBaleData(self.numBalesLeftOver) end \ No newline at end of file diff --git a/scripts/ai/AIDriveStrategyPlowCourse.lua b/scripts/ai/strategies/AIDriveStrategyPlowCourse.lua similarity index 97% rename from scripts/ai/AIDriveStrategyPlowCourse.lua rename to scripts/ai/strategies/AIDriveStrategyPlowCourse.lua index c54c9ca5b..a143f5882 100644 --- a/scripts/ai/AIDriveStrategyPlowCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyPlowCourse.lua @@ -1,203 +1,203 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/courseplay) -Copyright (C) 2021 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. - -Derived fieldwork course strategy, which handles plows. - -- Makes sure that all plows are unfolded and rotated in the correct direction, - which was determined by the course generator. -- Applies the automatic tool offset calculation for all attached plows. - - -]]-- - ----@class AIDriveStrategyPlowCourse : AIDriveStrategyFieldWorkCourse -AIDriveStrategyPlowCourse = CpObject(AIDriveStrategyFieldWorkCourse) - -AIDriveStrategyPlowCourse.myStates = { - ROTATING_PLOW = {}, - UNFOLDING_PLOW = {}, -} - -function AIDriveStrategyPlowCourse:init(task, job) - AIDriveStrategyFieldWorkCourse.init(self, task, job) - AIDriveStrategyCourse.initStates(self, AIDriveStrategyPlowCourse.myStates) - self.debugChannel = CpDebug.DBG_FIELDWORK - -- the plow offset is automatically calculated on each waypoint and if it wasn't calculated for a while - -- or when some event (like a turn) invalidated it - self.plowOffsetUnknown = CpTemporaryObject(true) -end - -function AIDriveStrategyPlowCourse:getDriveData(dt, vX, vY, vZ) - if self.state == self.states.INITIAL then - self:setMaxSpeed(0) - self:updatePlowOffset() - if self:isPlowRotating() then - --- If the plow is already being rotated at the start, - --- we still need to check if the rotation - --- is in correct direction, as the course generator directed. - self:rotatePlows() - self:debug("Needs to wait until the plow has finished rotating.") - self.state = self.states.ROTATING_PLOW - else - --- The plow can not be rotated, - --- so we check if the plow is unfolded - --- and try again to rotate the plow in the correct direction. - self:debug("Plows have to be unfolded first!") - self.state = self.states.UNFOLDING_PLOW - end - elseif self.state == self.states.ROTATING_PLOW then - self:setMaxSpeed(0) - if not self:isPlowRotating() then - --- Initial Rotation has finished and fieldwork can start. - self:updatePlowOffset() - self:startWaitingForLower() - self:lowerImplements() - self:debug('Plow initial rotation finished') - end - elseif self.state == self.states.UNFOLDING_PLOW then - self:setMaxSpeed(0) - if self:isPlowRotationAllowed() then - --- The Unfolding has finished and - --- we need to check if the rotation is correct. - self:rotatePlows() - self:debug("Plow was unfolded and rotation can begin") - self.state = self.states.ROTATING_PLOW - elseif self:getCanContinueWork() then - --- Unfolding has finished and no extra rotation is needed. - self:updatePlowOffset() - self:startWaitingForLower() - self:lowerImplements() - self:debug('Plow is unfolded and ready to start') - end - end - if self.plowOffsetUnknown:get() then - self:updatePlowOffset() - end - return AIDriveStrategyFieldWorkCourse.getDriveData(self, dt, vX, vY, vZ) -end - -function AIDriveStrategyPlowCourse:onWaypointPassed(ix, course) - -- readjust the tool offset every now and then. This is necessary as the offset is calculated from the - -- tractor's direction node which may need to result in incorrect values if the plow is not straight behind - -- the tractor (which may be the case when starting). When passing waypoints we'll most likely be driving - -- straight and thus calculating a proper tool offset - if self.state == self.states.WORKING then - self:updatePlowOffset() - end - AIDriveStrategyFieldWorkCourse.onWaypointPassed(self, ix, course) -end - ---- Updates the X Offset based on the plows attached. -function AIDriveStrategyPlowCourse:updatePlowOffset() - local xOffset = 0 - for _, controller in pairs(self.controllers) do - if controller.getAutomaticXOffset then - local autoOffset = controller:getAutomaticXOffset() - if autoOffset == nil then - self:debugSparse('Plow offset can\'t be calculated now, leaving offset at %.2f', self.aiOffsetX) - return - end - xOffset = xOffset + autoOffset - end - end - local oldOffset = self.aiOffsetX - -- set to the average of old and new to smooth a little bit to avoid oscillations - -- when we have a valid previous value - self.aiOffsetX = self.plowOffsetUnknown:get() and xOffset or ((0.5 * self.aiOffsetX + 1.5 * xOffset) / 2) - if math.abs(oldOffset - xOffset) > 0.05 then - self:debug("Plow offset calculated was %.2f and it changed from %.2f to %.2f", xOffset, oldOffset, self.aiOffsetX) - end - self.plowOffsetUnknown:set(false, 3000) -end - ---- Is a plow currently rotating? ----@return boolean -function AIDriveStrategyPlowCourse:isPlowRotating() - for _, controller in pairs(self.controllers) do - if controller.isRotationActive and controller:isRotationActive() then - return true - end - end - return false -end - ---- Are all plows allowed to be turned? ----@return boolean -function AIDriveStrategyPlowCourse:isPlowRotationAllowed() - local allowed = true - for _, controller in pairs(self.controllers) do - if controller.getIsPlowRotationAllowed and not controller:getIsPlowRotationAllowed() then - allowed = false - end - end - return allowed -end - ---- Initial plow rotation based on the ridge marker side selection by the course generator. -function AIDriveStrategyPlowCourse:rotatePlows() - self:debug('Starting work: check if plow needs to be turned.') - -- on the headland just check which side was worked last, on the center, check the direction of the next turn - -- as at the first pass both sides are unworked and need some other indication on which side the plow should be - local ix = self.ppc:getCurrentWaypointIx() - local plowShouldBeOnTheLeft - if self.course:isOnHeadland(ix) then - local clockwise = self.course:isOnClockwiseHeadland(ix) - plowShouldBeOnTheLeft = not clockwise - self:debug('On a headland (clockwise %s), plow should be on the left %s', tostring(clockwise), tostring(plowShouldBeOnTheLeft)) - else - local isNextTurnLeft = self.course:isNextTurnLeft(ix) - if isNextTurnLeft == nil then - -- don't know if left or right, so just use the last worked side - plowShouldBeOnTheLeft = self.course:isLeftSideWorked(ix) - self:debug('On the center, next turn direction unknown, plow should be on the left %s', tostring(plowShouldBeOnTheLeft)) - else - plowShouldBeOnTheLeft = not isNextTurnLeft - self:debug('On the center, plow should be on the left %s', tostring(plowShouldBeOnTheLeft)) - end - end - for _, controller in pairs(self.controllers) do - if controller.rotate then - controller:rotate(plowShouldBeOnTheLeft) - end - end -end - ------------------------------------------------------------------------------------------------------------------------ ---- Dynamic parameters (may change while driving) ------------------------------------------------------------------------------------------------------------------------ -function AIDriveStrategyPlowCourse:getTurnEndSideOffset() - if self:isWorking() then - self:updatePlowOffset() - -- need the double tool offset as the turn end still has the current offset, after the rotation it'll be - -- on the other side, (one toolOffsetX would put it to 0 only) - return 2 * self.aiOffsetX - else - return 0 - end -end - -function AIDriveStrategyPlowCourse:updateFieldworkOffset(course) - --- Ignore the tool offset setting. - course:setOffset((self.aiOffsetX or 0), (self.aiOffsetZ or 0)) -end - ---- When we return from a turn, the offset is reverted and should immediately set, not waiting ---- for the first waypoint to pass as it is on the wrong side right after the turn -function AIDriveStrategyPlowCourse:resumeFieldworkAfterTurn(ix) - self.plowOffsetUnknown:reset() - AIDriveStrategyFieldWorkCourse.resumeFieldworkAfterTurn(self, ix) +--[[ +This file is part of Courseplay (https://github.com/Courseplay/courseplay) +Copyright (C) 2021 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Derived fieldwork course strategy, which handles plows. + +- Makes sure that all plows are unfolded and rotated in the correct direction, + which was determined by the course generator. +- Applies the automatic tool offset calculation for all attached plows. + + +]]-- + +---@class AIDriveStrategyPlowCourse : AIDriveStrategyFieldWorkCourse +AIDriveStrategyPlowCourse = CpObject(AIDriveStrategyFieldWorkCourse) + +AIDriveStrategyPlowCourse.myStates = { + ROTATING_PLOW = {}, + UNFOLDING_PLOW = {}, +} + +function AIDriveStrategyPlowCourse:init(task, job) + AIDriveStrategyFieldWorkCourse.init(self, task, job) + AIDriveStrategyCourse.initStates(self, AIDriveStrategyPlowCourse.myStates) + self.debugChannel = CpDebug.DBG_FIELDWORK + -- the plow offset is automatically calculated on each waypoint and if it wasn't calculated for a while + -- or when some event (like a turn) invalidated it + self.plowOffsetUnknown = CpTemporaryObject(true) +end + +function AIDriveStrategyPlowCourse:getDriveData(dt, vX, vY, vZ) + if self.state == self.states.INITIAL then + self:setMaxSpeed(0) + self:updatePlowOffset() + if self:isPlowRotating() then + --- If the plow is already being rotated at the start, + --- we still need to check if the rotation + --- is in correct direction, as the course generator directed. + self:rotatePlows() + self:debug("Needs to wait until the plow has finished rotating.") + self.state = self.states.ROTATING_PLOW + else + --- The plow can not be rotated, + --- so we check if the plow is unfolded + --- and try again to rotate the plow in the correct direction. + self:debug("Plows have to be unfolded first!") + self.state = self.states.UNFOLDING_PLOW + end + elseif self.state == self.states.ROTATING_PLOW then + self:setMaxSpeed(0) + if not self:isPlowRotating() then + --- Initial Rotation has finished and fieldwork can start. + self:updatePlowOffset() + self:startWaitingForLower() + self:lowerImplements() + self:debug('Plow initial rotation finished') + end + elseif self.state == self.states.UNFOLDING_PLOW then + self:setMaxSpeed(0) + if self:isPlowRotationAllowed() then + --- The Unfolding has finished and + --- we need to check if the rotation is correct. + self:rotatePlows() + self:debug("Plow was unfolded and rotation can begin") + self.state = self.states.ROTATING_PLOW + elseif self:getCanContinueWork() then + --- Unfolding has finished and no extra rotation is needed. + self:updatePlowOffset() + self:startWaitingForLower() + self:lowerImplements() + self:debug('Plow is unfolded and ready to start') + end + end + if self.plowOffsetUnknown:get() then + self:updatePlowOffset() + end + return AIDriveStrategyFieldWorkCourse.getDriveData(self, dt, vX, vY, vZ) +end + +function AIDriveStrategyPlowCourse:onWaypointPassed(ix, course) + -- readjust the tool offset every now and then. This is necessary as the offset is calculated from the + -- tractor's direction node which may need to result in incorrect values if the plow is not straight behind + -- the tractor (which may be the case when starting). When passing waypoints we'll most likely be driving + -- straight and thus calculating a proper tool offset + if self.state == self.states.WORKING then + self:updatePlowOffset() + end + AIDriveStrategyFieldWorkCourse.onWaypointPassed(self, ix, course) +end + +--- Updates the X Offset based on the plows attached. +function AIDriveStrategyPlowCourse:updatePlowOffset() + local xOffset = 0 + for _, controller in pairs(self.controllers) do + if controller.getAutomaticXOffset then + local autoOffset = controller:getAutomaticXOffset() + if autoOffset == nil then + self:debugSparse('Plow offset can\'t be calculated now, leaving offset at %.2f', self.aiOffsetX) + return + end + xOffset = xOffset + autoOffset + end + end + local oldOffset = self.aiOffsetX + -- set to the average of old and new to smooth a little bit to avoid oscillations + -- when we have a valid previous value + self.aiOffsetX = self.plowOffsetUnknown:get() and xOffset or ((0.5 * self.aiOffsetX + 1.5 * xOffset) / 2) + if math.abs(oldOffset - xOffset) > 0.05 then + self:debug("Plow offset calculated was %.2f and it changed from %.2f to %.2f", xOffset, oldOffset, self.aiOffsetX) + end + self.plowOffsetUnknown:set(false, 3000) +end + +--- Is a plow currently rotating? +---@return boolean +function AIDriveStrategyPlowCourse:isPlowRotating() + for _, controller in pairs(self.controllers) do + if controller.isRotationActive and controller:isRotationActive() then + return true + end + end + return false +end + +--- Are all plows allowed to be turned? +---@return boolean +function AIDriveStrategyPlowCourse:isPlowRotationAllowed() + local allowed = true + for _, controller in pairs(self.controllers) do + if controller.getIsPlowRotationAllowed and not controller:getIsPlowRotationAllowed() then + allowed = false + end + end + return allowed +end + +--- Initial plow rotation based on the ridge marker side selection by the course generator. +function AIDriveStrategyPlowCourse:rotatePlows() + self:debug('Starting work: check if plow needs to be turned.') + -- on the headland just check which side was worked last, on the center, check the direction of the next turn + -- as at the first pass both sides are unworked and need some other indication on which side the plow should be + local ix = self.ppc:getCurrentWaypointIx() + local plowShouldBeOnTheLeft + if self.course:isOnHeadland(ix) then + local clockwise = self.course:isOnClockwiseHeadland(ix) + plowShouldBeOnTheLeft = not clockwise + self:debug('On a headland (clockwise %s), plow should be on the left %s', tostring(clockwise), tostring(plowShouldBeOnTheLeft)) + else + local isNextTurnLeft = self.course:isNextTurnLeft(ix) + if isNextTurnLeft == nil then + -- don't know if left or right, so just use the last worked side + plowShouldBeOnTheLeft = self.course:isLeftSideWorked(ix) + self:debug('On the center, next turn direction unknown, plow should be on the left %s', tostring(plowShouldBeOnTheLeft)) + else + plowShouldBeOnTheLeft = not isNextTurnLeft + self:debug('On the center, plow should be on the left %s', tostring(plowShouldBeOnTheLeft)) + end + end + for _, controller in pairs(self.controllers) do + if controller.rotate then + controller:rotate(plowShouldBeOnTheLeft) + end + end +end + +----------------------------------------------------------------------------------------------------------------------- +--- Dynamic parameters (may change while driving) +----------------------------------------------------------------------------------------------------------------------- +function AIDriveStrategyPlowCourse:getTurnEndSideOffset() + if self:isWorking() then + self:updatePlowOffset() + -- need the double tool offset as the turn end still has the current offset, after the rotation it'll be + -- on the other side, (one toolOffsetX would put it to 0 only) + return 2 * self.aiOffsetX + else + return 0 + end +end + +function AIDriveStrategyPlowCourse:updateFieldworkOffset(course) + --- Ignore the tool offset setting. + course:setOffset((self.aiOffsetX or 0), (self.aiOffsetZ or 0)) +end + +--- When we return from a turn, the offset is reverted and should immediately set, not waiting +--- for the first waypoint to pass as it is on the wrong side right after the turn +function AIDriveStrategyPlowCourse:resumeFieldworkAfterTurn(ix) + self.plowOffsetUnknown:reset() + AIDriveStrategyFieldWorkCourse.resumeFieldworkAfterTurn(self, ix) end \ No newline at end of file diff --git a/scripts/ai/AIDriveStrategyShovelSiloLoader.lua b/scripts/ai/strategies/AIDriveStrategyShovelSiloLoader.lua similarity index 100% rename from scripts/ai/AIDriveStrategyShovelSiloLoader.lua rename to scripts/ai/strategies/AIDriveStrategyShovelSiloLoader.lua diff --git a/scripts/ai/AIDriveStrategySiloLoader.lua b/scripts/ai/strategies/AIDriveStrategySiloLoader.lua similarity index 100% rename from scripts/ai/AIDriveStrategySiloLoader.lua rename to scripts/ai/strategies/AIDriveStrategySiloLoader.lua diff --git a/scripts/ai/AIDriveStrategyUnloadCombine.lua b/scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua similarity index 100% rename from scripts/ai/AIDriveStrategyUnloadCombine.lua rename to scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua diff --git a/scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua b/scripts/ai/strategies/AIDriveStrategyVineFieldWorkCourse.lua similarity index 100% rename from scripts/ai/AIDriveStrategyVineFieldWorkCourse.lua rename to scripts/ai/strategies/AIDriveStrategyVineFieldWorkCourse.lua From c969848f74d82cbd6cc98ed6a90961036b945d9b Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 22 Dec 2024 12:33:54 +0100 Subject: [PATCH 153/158] Renamed FilllevelManager to FillLevelUtil --- modDesc.xml | 2 +- ...FillLevelManager.lua => FillLevelUtil.lua} | 500 +++++++++--------- scripts/ai/controllers/PipeController.lua | 2 +- scripts/ai/jobs/CpAIJobCombineUnloader.lua | 2 +- .../AIDriveStrategyCombineCourse.lua | 2 +- .../AIDriveStrategyUnloadCombine.lua | 4 +- scripts/gui/hud/CpCombineUnloaderHudPage.lua | 2 +- 7 files changed, 257 insertions(+), 257 deletions(-) rename scripts/ai/{FillLevelManager.lua => FillLevelUtil.lua} (79%) diff --git a/modDesc.xml b/modDesc.xml index cc08fd7db..8a0b2f668 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -176,7 +176,7 @@ Changelog 8.0.0.0: <sourceFile filename="scripts/ai/Markers.lua"/> <sourceFile filename="scripts/ai/ProximitySensor.lua"/> <sourceFile filename="scripts/ai/BaleToCollect.lua"/> - <sourceFile filename="scripts/ai/FillLevelManager.lua"/> + <sourceFile filename="scripts/ai/FillLevelUtil.lua"/> <sourceFile filename="scripts/ai/InfoTextsManager.lua"/> <sourceFile filename="scripts/ai/PurePursuitController.lua"/> <sourceFile filename="scripts/ai/SelfUnloadHelper.lua"/> diff --git a/scripts/ai/FillLevelManager.lua b/scripts/ai/FillLevelUtil.lua similarity index 79% rename from scripts/ai/FillLevelManager.lua rename to scripts/ai/FillLevelUtil.lua index 2470411a9..1a4ca55f0 100644 --- a/scripts/ai/FillLevelManager.lua +++ b/scripts/ai/FillLevelUtil.lua @@ -1,251 +1,251 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/courseplay) -Copyright (C) 2019 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -]] - ---- Keeps track of all fill types/levels of a vehicle and attached implements -FillLevelManager = {} - ------------------------------------------------------------------------------------------------------------------------- ---- Fill Levels ---------------------------------------------------------------------------------------------------------------------------- -function FillLevelManager.getAllFillLevels(object, fillLevelInfo) - -- get own fill levels - if object.getFillUnits then - for index, fillUnit in pairs(object:getFillUnits()) do - local supportedFillTypes = object:getFillUnitSupportedFillTypes(index) - for fillType, _ in pairs(supportedFillTypes) do - local fillTypeName = g_fillTypeManager:getFillTypeNameByIndex(fillType) - --FillLevelManager:debugSparse('%s: Fill levels: %s: %.1f/%.1f', object:getName(), fillTypeName, fillUnit.fillLevel, fillUnit.capacity) - if not fillLevelInfo[fillType] then fillLevelInfo[fillType] = {fillLevel = 0, capacity = 0} end - fillLevelInfo[fillType].fillLevel = fillLevelInfo[fillType].fillLevel + fillUnit.fillLevel - fillLevelInfo[fillType].capacity = fillLevelInfo[fillType].capacity + fillUnit.capacity - --used to check treePlanter fillLevel - local treePlanterSpec = object.spec_treePlanter - if treePlanterSpec then - fillLevelInfo[fillType].treePlanterSpec = object.spec_treePlanter - end - end - end - end - -- collect fill levels from all attached implements recursively - for _,impl in pairs(object:getAttachedImplements()) do - FillLevelManager.getAllFillLevels(impl.object, fillLevelInfo) - end -end - -function FillLevelManager.getFillTypeFromFillUnit(fillUnit) - local fillType = fillUnit.lastValidFillType or fillUnit.fillType - -- TODO: do we need to check more supported fill types? This will probably cover 99.9% of the cases - if fillType == FillType.UNKNOWN then - -- just get the first valid supported fill type - for ft, valid in pairs(fillUnit.supportedFillTypes) do - if valid then return ft end - end - else - return fillType - end -end - ---- Gets the complete fill level and capacity without fuel, ----@param object table ----@return number totalFillLevel ----@return number totalCapacity -function FillLevelManager.getTotalFillLevelAndCapacity(object) - - local fillLevelInfo = {} - FillLevelManager.getAllFillLevels(object, fillLevelInfo) - - local totalFillLevel = 0 - local totalCapacity = 0 - for fillType, data in pairs(fillLevelInfo) do - if FillLevelManager.isValidFillType(object,fillType) then - totalFillLevel = totalFillLevel + data.fillLevel - totalCapacity = totalCapacity + data.capacity - end - end - return totalFillLevel,totalCapacity -end - ---- Gets the complete fill level and capacity without fuel for a single fillType ----@param object table ----@param fillTypeToFilter number fillTypeIndex to check for ----@return number totalFillLevel ----@return number totalCapacity - -function FillLevelManager.getTotalFillLevelAndCapacityForFillType(object, fillTypeToFilter) - local fillLevelInfo = {} - FillLevelManager.getAllFillLevels(object, fillLevelInfo) - - local totalFillLevel = 0 - local totalCapacity = 0 - for fillType, data in pairs(fillLevelInfo) do - if FillLevelManager.isValidFillType(object, fillType) and fillType == fillTypeToFilter then - totalFillLevel = totalFillLevel + data.fillLevel - totalCapacity = totalCapacity + data.capacity - end - end - - return totalFillLevel, totalCapacity -end - ---- Gets the total fill level percentage. ----@param object table -function FillLevelManager.getTotalFillLevelPercentage(object) - local fillLevel,capacity = FillLevelManager.getTotalFillLevelAndCapacity(object) - return 100 * CpMathUtil.divide(fillLevel, capacity) -end - -function FillLevelManager.getTotalFillLevelAndCapacityForObject(object) - local totalFillLevel = 0 - local totalCapacity = 0 - if object.getFillUnits then - for _, fillUnit in pairs(object:getFillUnits()) do - local fillType = FillLevelManager.getFillTypeFromFillUnit(fillUnit) - if FillLevelManager.isValidFillType(object, fillType) then - totalFillLevel = totalFillLevel + fillUnit.fillLevel - totalCapacity = totalCapacity + fillUnit.capacity - end - end - end - return totalFillLevel, totalCapacity -end - ----@param object table ----@param fillType number -function FillLevelManager.isValidFillType(object, fillType) - --- Ignore silage additives for now. - --- TODO: maybe implement a setting if it is necessary to enable/disable detection. - local spec = object.spec_combine or object.spec_forageWagon - if spec and spec.additives and spec.additives.fillUnitIndex then - local f = object:getFillUnitFillType(spec.additives.fillUnitIndex) - if f == fillType then - return false - end - end - - return not FillLevelManager.isValidFuelType(object, fillType) and fillType ~= FillType.DEF and fillType ~= FillType.AIR -end - ---- Is the fill type fuel ? ----@param object table ----@param fillType number ----@param fillUnitIndex number -function FillLevelManager.isValidFuelType(object, fillType, fillUnitIndex) - if object and object.getConsumerFillUnitIndex then - local index = object:getConsumerFillUnitIndex(fillType) - if fillUnitIndex ~= nil then - return fillUnitIndex and fillUnitIndex == index - end - return index - end -end - ---- Gets the fill level of an mixerWagon for a fill type. ----@param object table ----@param fillType number -function FillLevelManager.getMixerWagonFillLevelForFillTypes(object, fillType) - local spec = object.spec_mixerWagon - if spec then - for _, data in pairs(spec.mixerWagonFillTypes) do - if data.fillTypes[fillType] then - return data.fillLevel - end - end - end -end - ----------------------------------------------------------------------------------------------------------- ---- Trailer util functions. ----------------------------------------------------------------------------------------------------------- - ---- Can load this fill type into the trailer? ----@param trailer table ----@param fillType number ----@return boolean true if this trailer has capacity for fill type ----@return number free capacity ----@return number fill unit index -function FillLevelManager.canLoadTrailer(trailer, fillType) - if fillType then - local fillUnits = trailer:getFillUnits() - for i = 1, #fillUnits do - local supportedFillTypes = trailer:getFillUnitSupportedFillTypes(i) - local freeCapacity = trailer:getFillUnitFreeCapacity(i) - if supportedFillTypes[fillType] and freeCapacity > 0 then - return true, freeCapacity, i - end - end - end - return false, 0 -end - ---- Are all trailers full? ----@param vehicle table ----@param fullThresholdPercentage number optional threshold, if fill level in percentage is greater than the threshold, ---- consider trailers full ----@return boolean -function FillLevelManager.areAllTrailersFull(vehicle, fullThresholdPercentage) - local totalFillLevel, totalCapacity, totalFreeCapacity = FillLevelManager.getAllTrailerFillLevels(vehicle) - local fillLevelPercentage = 100 * CpMathUtil.divide(totalFillLevel, totalCapacity) - return totalFreeCapacity <= 0 or fillLevelPercentage >= (fullThresholdPercentage or 100) -end - ---- Gets the total fill level percentage and total fill level percentage adjusted to the max fill volume mass adjusted. ----@param vehicle table ----@return number total fill level percentage in % ----@return number total fill level percentage in % relative to max mass adjusted capacity. -function FillLevelManager.getTotalTrailerFillLevelPercentage(vehicle) - local totalFillLevel, totalCapacity, totalCapacityMassAdjusted = FillLevelManager.getAllTrailerFillLevels(vehicle) - return 100 * CpMathUtil.divide(totalFillLevel, totalCapacity), 100 * CpMathUtil.divide(totalFillLevel, totalCapacityMassAdjusted) -end - ---- Gets the total fill level, capacity and mass adjusted capacity of all trailers. ----@param vehicle table ----@return number total fill level ----@return number total capacity ----@return number total free capacity -function FillLevelManager.getAllTrailerFillLevels(vehicle) - local totalFillLevel, totalCapacity, totalFreeCapacity = 0, 0, 0 - local trailers = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Trailer, nil) - for i, trailer in ipairs(trailers) do - local fillLevel, capacity, freeCapacity = FillLevelManager.getTrailerFillLevels(trailer) - totalFreeCapacity = totalFreeCapacity + freeCapacity - totalFillLevel = totalFillLevel + fillLevel - totalCapacity = totalCapacity + capacity - end - return totalFillLevel, totalCapacity, totalFreeCapacity -end - ---- Gets the total fill level, capacity and mass adjusted capacity of a trailer. ----@param trailer table ----@return number total fill level ----@return number total capacity ----@return number total free capacity -function FillLevelManager.getTrailerFillLevels(trailer) - local totalFillLevel, totalCapacity, totalFreeCapacity = 0, 0, 0 - local spec = trailer.spec_dischargeable - local fillUnitsUsed = {} - for i, dischargeNode in pairs( spec.dischargeNodes) do - local fillUnitIndex = dischargeNode.fillUnitIndex - if not fillUnitsUsed[fillUnitIndex] then - totalFreeCapacity = totalFreeCapacity + trailer:getFillUnitFreeCapacity(fillUnitIndex) - totalFillLevel = totalFillLevel + trailer:getFillUnitFillLevel(fillUnitIndex) - totalCapacity = totalCapacity + trailer:getFillUnitCapacity(fillUnitIndex) - fillUnitsUsed[fillUnitIndex] = true - end - end - return totalFillLevel, totalCapacity, totalFreeCapacity +--[[ +This file is part of Courseplay (https://github.com/Courseplay/courseplay) +Copyright (C) 2019 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +]] + +--- Keeps track of all fill types/levels of a vehicle and attached implements +FillLevelUtil = {} + +------------------------------------------------------------------------------------------------------------------------ +--- Fill Levels +--------------------------------------------------------------------------------------------------------------------------- +function FillLevelUtil.getAllFillLevels(object, fillLevelInfo) + -- get own fill levels + if object.getFillUnits then + for index, fillUnit in pairs(object:getFillUnits()) do + local supportedFillTypes = object:getFillUnitSupportedFillTypes(index) + for fillType, _ in pairs(supportedFillTypes) do + local fillTypeName = g_fillTypeManager:getFillTypeNameByIndex(fillType) + --FillLevelUtil:debugSparse('%s: Fill levels: %s: %.1f/%.1f', object:getName(), fillTypeName, fillUnit.fillLevel, fillUnit.capacity) + if not fillLevelInfo[fillType] then fillLevelInfo[fillType] = {fillLevel = 0, capacity = 0} end + fillLevelInfo[fillType].fillLevel = fillLevelInfo[fillType].fillLevel + fillUnit.fillLevel + fillLevelInfo[fillType].capacity = fillLevelInfo[fillType].capacity + fillUnit.capacity + --used to check treePlanter fillLevel + local treePlanterSpec = object.spec_treePlanter + if treePlanterSpec then + fillLevelInfo[fillType].treePlanterSpec = object.spec_treePlanter + end + end + end + end + -- collect fill levels from all attached implements recursively + for _,impl in pairs(object:getAttachedImplements()) do + FillLevelUtil.getAllFillLevels(impl.object, fillLevelInfo) + end +end + +function FillLevelUtil.getFillTypeFromFillUnit(fillUnit) + local fillType = fillUnit.lastValidFillType or fillUnit.fillType + -- TODO: do we need to check more supported fill types? This will probably cover 99.9% of the cases + if fillType == FillType.UNKNOWN then + -- just get the first valid supported fill type + for ft, valid in pairs(fillUnit.supportedFillTypes) do + if valid then return ft end + end + else + return fillType + end +end + +--- Gets the complete fill level and capacity without fuel, +---@param object table +---@return number totalFillLevel +---@return number totalCapacity +function FillLevelUtil.getTotalFillLevelAndCapacity(object) + + local fillLevelInfo = {} + FillLevelUtil.getAllFillLevels(object, fillLevelInfo) + + local totalFillLevel = 0 + local totalCapacity = 0 + for fillType, data in pairs(fillLevelInfo) do + if FillLevelUtil.isValidFillType(object,fillType) then + totalFillLevel = totalFillLevel + data.fillLevel + totalCapacity = totalCapacity + data.capacity + end + end + return totalFillLevel,totalCapacity +end + +--- Gets the complete fill level and capacity without fuel for a single fillType +---@param object table +---@param fillTypeToFilter number fillTypeIndex to check for +---@return number totalFillLevel +---@return number totalCapacity + +function FillLevelUtil.getTotalFillLevelAndCapacityForFillType(object, fillTypeToFilter) + local fillLevelInfo = {} + FillLevelUtil.getAllFillLevels(object, fillLevelInfo) + + local totalFillLevel = 0 + local totalCapacity = 0 + for fillType, data in pairs(fillLevelInfo) do + if FillLevelUtil.isValidFillType(object, fillType) and fillType == fillTypeToFilter then + totalFillLevel = totalFillLevel + data.fillLevel + totalCapacity = totalCapacity + data.capacity + end + end + + return totalFillLevel, totalCapacity +end + +--- Gets the total fill level percentage. +---@param object table +function FillLevelUtil.getTotalFillLevelPercentage(object) + local fillLevel,capacity = FillLevelUtil.getTotalFillLevelAndCapacity(object) + return 100 * CpMathUtil.divide(fillLevel, capacity) +end + +function FillLevelUtil.getTotalFillLevelAndCapacityForObject(object) + local totalFillLevel = 0 + local totalCapacity = 0 + if object.getFillUnits then + for _, fillUnit in pairs(object:getFillUnits()) do + local fillType = FillLevelUtil.getFillTypeFromFillUnit(fillUnit) + if FillLevelUtil.isValidFillType(object, fillType) then + totalFillLevel = totalFillLevel + fillUnit.fillLevel + totalCapacity = totalCapacity + fillUnit.capacity + end + end + end + return totalFillLevel, totalCapacity +end + +---@param object table +---@param fillType number +function FillLevelUtil.isValidFillType(object, fillType) + --- Ignore silage additives for now. + --- TODO: maybe implement a setting if it is necessary to enable/disable detection. + local spec = object.spec_combine or object.spec_forageWagon + if spec and spec.additives and spec.additives.fillUnitIndex then + local f = object:getFillUnitFillType(spec.additives.fillUnitIndex) + if f == fillType then + return false + end + end + + return not FillLevelUtil.isValidFuelType(object, fillType) and fillType ~= FillType.DEF and fillType ~= FillType.AIR +end + +--- Is the fill type fuel ? +---@param object table +---@param fillType number +---@param fillUnitIndex number +function FillLevelUtil.isValidFuelType(object, fillType, fillUnitIndex) + if object and object.getConsumerFillUnitIndex then + local index = object:getConsumerFillUnitIndex(fillType) + if fillUnitIndex ~= nil then + return fillUnitIndex and fillUnitIndex == index + end + return index + end +end + +--- Gets the fill level of an mixerWagon for a fill type. +---@param object table +---@param fillType number +function FillLevelUtil.getMixerWagonFillLevelForFillTypes(object, fillType) + local spec = object.spec_mixerWagon + if spec then + for _, data in pairs(spec.mixerWagonFillTypes) do + if data.fillTypes[fillType] then + return data.fillLevel + end + end + end +end + +---------------------------------------------------------------------------------------------------------- +--- Trailer util functions. +---------------------------------------------------------------------------------------------------------- + +--- Can load this fill type into the trailer? +---@param trailer table +---@param fillType number +---@return boolean true if this trailer has capacity for fill type +---@return number free capacity +---@return number fill unit index +function FillLevelUtil.canLoadTrailer(trailer, fillType) + if fillType then + local fillUnits = trailer:getFillUnits() + for i = 1, #fillUnits do + local supportedFillTypes = trailer:getFillUnitSupportedFillTypes(i) + local freeCapacity = trailer:getFillUnitFreeCapacity(i) + if supportedFillTypes[fillType] and freeCapacity > 0 then + return true, freeCapacity, i + end + end + end + return false, 0 +end + +--- Are all trailers full? +---@param vehicle table +---@param fullThresholdPercentage number optional threshold, if fill level in percentage is greater than the threshold, +--- consider trailers full +---@return boolean +function FillLevelUtil.areAllTrailersFull(vehicle, fullThresholdPercentage) + local totalFillLevel, totalCapacity, totalFreeCapacity = FillLevelUtil.getAllTrailerFillLevels(vehicle) + local fillLevelPercentage = 100 * CpMathUtil.divide(totalFillLevel, totalCapacity) + return totalFreeCapacity <= 0 or fillLevelPercentage >= (fullThresholdPercentage or 100) +end + +--- Gets the total fill level percentage and total fill level percentage adjusted to the max fill volume mass adjusted. +---@param vehicle table +---@return number total fill level percentage in % +---@return number total fill level percentage in % relative to max mass adjusted capacity. +function FillLevelUtil.getTotalTrailerFillLevelPercentage(vehicle) + local totalFillLevel, totalCapacity, totalCapacityMassAdjusted = FillLevelUtil.getAllTrailerFillLevels(vehicle) + return 100 * CpMathUtil.divide(totalFillLevel, totalCapacity), 100 * CpMathUtil.divide(totalFillLevel, totalCapacityMassAdjusted) +end + +--- Gets the total fill level, capacity and mass adjusted capacity of all trailers. +---@param vehicle table +---@return number total fill level +---@return number total capacity +---@return number total free capacity +function FillLevelUtil.getAllTrailerFillLevels(vehicle) + local totalFillLevel, totalCapacity, totalFreeCapacity = 0, 0, 0 + local trailers = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Trailer, nil) + for i, trailer in ipairs(trailers) do + local fillLevel, capacity, freeCapacity = FillLevelUtil.getTrailerFillLevels(trailer) + totalFreeCapacity = totalFreeCapacity + freeCapacity + totalFillLevel = totalFillLevel + fillLevel + totalCapacity = totalCapacity + capacity + end + return totalFillLevel, totalCapacity, totalFreeCapacity +end + +--- Gets the total fill level, capacity and mass adjusted capacity of a trailer. +---@param trailer table +---@return number total fill level +---@return number total capacity +---@return number total free capacity +function FillLevelUtil.getTrailerFillLevels(trailer) + local totalFillLevel, totalCapacity, totalFreeCapacity = 0, 0, 0 + local spec = trailer.spec_dischargeable + local fillUnitsUsed = {} + for i, dischargeNode in pairs( spec.dischargeNodes) do + local fillUnitIndex = dischargeNode.fillUnitIndex + if not fillUnitsUsed[fillUnitIndex] then + totalFreeCapacity = totalFreeCapacity + trailer:getFillUnitFreeCapacity(fillUnitIndex) + totalFillLevel = totalFillLevel + trailer:getFillUnitFillLevel(fillUnitIndex) + totalCapacity = totalCapacity + trailer:getFillUnitCapacity(fillUnitIndex) + fillUnitsUsed[fillUnitIndex] = true + end + end + return totalFillLevel, totalCapacity, totalFreeCapacity end \ No newline at end of file diff --git a/scripts/ai/controllers/PipeController.lua b/scripts/ai/controllers/PipeController.lua index 7a4b38586..9f511fee0 100644 --- a/scripts/ai/controllers/PipeController.lua +++ b/scripts/ai/controllers/PipeController.lua @@ -135,7 +135,7 @@ function PipeController:isFillableTrailerInRange() local myFillType = self:getFillType() for trailer, value in pairs(self.pipeSpec.objectsInTriggers) do if value > 0 then - if myFillType == FillType.UNKNOWN or FillLevelManager.canLoadTrailer(trailer, myFillType) then + if myFillType == FillType.UNKNOWN or FillLevelUtil.canLoadTrailer(trailer, myFillType) then return true, trailer end end diff --git a/scripts/ai/jobs/CpAIJobCombineUnloader.lua b/scripts/ai/jobs/CpAIJobCombineUnloader.lua index 3e218578d..41683b079 100644 --- a/scripts/ai/jobs/CpAIJobCombineUnloader.lua +++ b/scripts/ai/jobs/CpAIJobCombineUnloader.lua @@ -355,7 +355,7 @@ function CpAIJobCombineUnloader:getStartTaskIndex() local vehicle = self:getVehicle() local fieldPolygon = self:getFieldPolygon() local x, _, z = getWorldTranslation(vehicle.rootNode) - local fillLevelPercentage = FillLevelManager.getTotalTrailerFillLevelPercentage(vehicle) + local fillLevelPercentage = FillLevelUtil.getTotalTrailerFillLevelPercentage(vehicle) local readyToDriveUnloading = vehicle:getCpSettings().fullThreshold:getValue() <= fillLevelPercentage if readyToDriveUnloading then CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, vehicle, "Not close to the field and vehicle is full, so start driving to unload.") diff --git a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua index 5d8c174c0..9dbb23629 100644 --- a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua @@ -1565,7 +1565,7 @@ end function AIDriveStrategyCombineCourse:canLoadTrailer(trailer) local fillType = self:getFillType() - return FillLevelManager.canLoadTrailer(trailer, fillType) + return FillLevelUtil.canLoadTrailer(trailer, fillType) end function AIDriveStrategyCombineCourse:getCurrentDischargeNode() diff --git a/scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua b/scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua index 4334aabe6..efa851c9f 100644 --- a/scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua +++ b/scripts/ai/strategies/AIDriveStrategyUnloadCombine.lua @@ -1108,12 +1108,12 @@ function AIDriveStrategyUnloadCombine:getCombinesMeasuredBackDistance() end function AIDriveStrategyUnloadCombine:getAllTrailersFull(fullThresholdPercentage) - return FillLevelManager.areAllTrailersFull(self.vehicle, fullThresholdPercentage) + return FillLevelUtil.areAllTrailersFull(self.vehicle, fullThresholdPercentage) end --- Fill level in %. function AIDriveStrategyUnloadCombine:getFillLevelPercentage() - return FillLevelManager.getTotalTrailerFillLevelPercentage(self.vehicle) + return FillLevelUtil.getTotalTrailerFillLevelPercentage(self.vehicle) end function AIDriveStrategyUnloadCombine:isDriveUnloadNowRequested() diff --git a/scripts/gui/hud/CpCombineUnloaderHudPage.lua b/scripts/gui/hud/CpCombineUnloaderHudPage.lua index c8fd465b0..1b61ea8ac 100644 --- a/scripts/gui/hud/CpCombineUnloaderHudPage.lua +++ b/scripts/gui/hud/CpCombineUnloaderHudPage.lua @@ -102,7 +102,7 @@ function CpCombineUnloaderHudPageElement:updateContent(vehicle, status) local unloadModeSetting = vehicle:getCpCombineUnloaderJobParameters().unloadTarget self.unloadModeBtn:setTextDetails(unloadModeSetting:getTitle(), unloadModeSetting:getString()) - local fillLevelPercentage = FillLevelManager.getTotalTrailerFillLevelPercentage(vehicle) + local fillLevelPercentage = FillLevelUtil.getTotalTrailerFillLevelPercentage(vehicle) if fillLevelPercentage > 0.01 then self.driveNowBtn:setColor(unpack(CpBaseHud.SEMI_ON_COLOR)) else From f9825827e06a26fcceb4b6509e6d26ca52e80967 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 22 Dec 2024 12:35:50 +0100 Subject: [PATCH 154/158] Moved ai util files into util sub folder --- modDesc.xml | 6 +- scripts/ai/{ => util}/AIUtil.lua | 1592 +++++++++++------------ scripts/ai/{ => util}/FillLevelUtil.lua | 0 scripts/ai/{ => util}/ImplementUtil.lua | 1180 ++++++++--------- 4 files changed, 1389 insertions(+), 1389 deletions(-) rename scripts/ai/{ => util}/AIUtil.lua (97%) rename scripts/ai/{ => util}/FillLevelUtil.lua (100%) rename scripts/ai/{ => util}/ImplementUtil.lua (97%) diff --git a/modDesc.xml b/modDesc.xml index 8a0b2f668..fd542c075 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -166,8 +166,9 @@ Changelog 8.0.0.0: <sourceFile filename="scripts/pathfinder/PathfinderContext.lua"/> <sourceFile filename="scripts/pathfinder/PathfinderUtil.lua"/> - <sourceFile filename="scripts/ai/AIUtil.lua"/> - <sourceFile filename="scripts/ai/ImplementUtil.lua"/> + <sourceFile filename="scripts/ai/util/AIUtil.lua"/> + <sourceFile filename="scripts/ai/util/FillLevelUtil.lua"/> + <sourceFile filename="scripts/ai/util/ImplementUtil.lua"/> <sourceFile filename="scripts/ai/PathfinderController.lua"/> <sourceFile filename="scripts/ai/ProximityController.lua"/> <sourceFile filename="scripts/ai/FieldWorkerProximityController.lua"/> @@ -176,7 +177,6 @@ Changelog 8.0.0.0: <sourceFile filename="scripts/ai/Markers.lua"/> <sourceFile filename="scripts/ai/ProximitySensor.lua"/> <sourceFile filename="scripts/ai/BaleToCollect.lua"/> - <sourceFile filename="scripts/ai/FillLevelUtil.lua"/> <sourceFile filename="scripts/ai/InfoTextsManager.lua"/> <sourceFile filename="scripts/ai/PurePursuitController.lua"/> <sourceFile filename="scripts/ai/SelfUnloadHelper.lua"/> diff --git a/scripts/ai/AIUtil.lua b/scripts/ai/util/AIUtil.lua similarity index 97% rename from scripts/ai/AIUtil.lua rename to scripts/ai/util/AIUtil.lua index bf0e6dc48..7779b23e7 100644 --- a/scripts/ai/AIUtil.lua +++ b/scripts/ai/util/AIUtil.lua @@ -1,796 +1,796 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/courseplay) -Copyright (C) 2021 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -]] - ---- ---- High level (scope is the entire vehicle chain) utilities for the Courseplay AI - ----@class AIUtil -AIUtil = {} - -function AIUtil.isReverseDriving(vehicle) - if not vehicle then - printCallstack() - return false - end - return vehicle.spec_reverseDriving and vehicle.spec_reverseDriving.isReverseDriving -end - -function AIUtil.getDirectionNode(vehicle) - -- TODO: We used this to make sure to return a direction node that always points to the - -- forward direction, even if a vehicle had its direction reversed (cabin turned). Now we - -- think getAIDirectionNode() guarantees this, the only reason this is still here is that - -- we need to check if it is possible the call this with a vehicle which has no AI direction node - -- and fall back to the root node. - return vehicle.getAIDirectionNode and vehicle:getAIDirectionNode() or vehicle.rootNode -end - ----@return number the z offset of the root node in the direction node's coordinate system: if > 0, the direction node ---- is behind the root node, otherwise in front of it -function AIUtil.getDirectionNodeToRootNodeOffset(vehicle) - local _, _, dz = localToLocal(vehicle.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) - return dz -end - ---- If we are towing an implement, move to a bigger radius in tight turns ---- making sure that the towed implement's trajectory remains closer to the ---- course. ----@param course Course -function AIUtil.calculateTightTurnOffset(vehicle, vehicleTurningRadius, course, previousOffset) - local tightTurnOffset - - local function smoothOffset(offset) - return (offset + 4 * (previousOffset or 0 )) / 5 - end - - -- first of all, does the current waypoint have radius data? - local r = course:getCalculatedRadiusAtIx(course:getCurrentWaypointIx()) - if not r then - return smoothOffset(0) - end - - -- limit the radius we are trying to follow to the vehicle's turn radius. - -- TODO: there's some potential here as the towed implement can move on a radius less than the vehicle's - -- turn radius so this limit may be too pessimistic - r = math.max(r, vehicleTurningRadius) - - local towBarLength = AIUtil.getTowBarLength(vehicle) - - if towBarLength == nil then - -- not a towed implement, no offset - return 0 - end - -- Is this really a tight turn? It is when the tow bar is longer than radius / 3, otherwise - -- we ignore it. - if towBarLength < r / 3 then - return smoothOffset(0) - end - - -- Ok, looks like a tight turn, so we need to move a bit left or right of the course - -- to keep the tool on the course. Use a little less than the calculated, this is purely empirical and should probably - -- be reviewed why the calculated one seems to overshoot. - local offset = 0.75 * AIUtil.getOffsetForTowBarLength(r, towBarLength) - if offset ~= offset then - -- check for nan - return smoothOffset(0) - end - -- figure out left or right now? - local nextAngle = course:getWaypointAngleDeg(course:getCurrentWaypointIx() + 1) - local currentAngle = course:getWaypointAngleDeg(course:getCurrentWaypointIx()) - if not nextAngle or not currentAngle then - return smoothOffset(0) - end - - if CpMathUtil.getDeltaAngle(math.rad(nextAngle), math.rad(currentAngle)) > 0 then offset = -offset end - - -- smooth the offset a bit to avoid sudden changes - tightTurnOffset = smoothOffset(offset) - CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, - 'Tight turn, r = %.1f, tow bar = %.1f m, currentAngle = %.0f, nextAngle = %.0f, offset = %.1f, smoothOffset = %.1f', - r, towBarLength, currentAngle, nextAngle, offset, tightTurnOffset ) - -- remember the last value for smoothing - return tightTurnOffset -end - -function AIUtil.calculateTightTurnOffsetForTurnManeuver(vehicle, steeringLength, course, ix, previousOffset) - local tightTurnOffset - - local function smoothOffset(offset) - -- smooth more for articulated axis or track vehicle - -- as those usually have a very small turn radius anyway, causing jackknifing - -- TODO: use the vehicle's solo radius instead? - local factor = AIUtil.hasArticulatedAxis(vehicle) and 6 or 4 - return (offset + factor * (previousOffset or 0 )) / (factor + 1) - end - - -- first of all, does the current waypoint have radius data? - local r = course:getCalculatedRadiusAtIx(ix) - if not r then - return smoothOffset(0) - end - - local offset = AIUtil.getTractorRadiusFromImplementRadius(r, steeringLength) - r - if offset ~= offset then - -- check for nan - return smoothOffset(0) - end - -- figure out left or right now? - local nextAngle = course:getWaypointAngleDeg(ix + 1) - local currentAngle = course:getWaypointAngleDeg(ix) - if not nextAngle or not currentAngle then - return smoothOffset(0) - end - - if CpMathUtil.getDeltaAngle(math.rad(nextAngle), math.rad(currentAngle)) > 0 then offset = -offset end - - -- smooth the offset a bit to avoid sudden changes - tightTurnOffset = smoothOffset(offset) - CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, - 'Tight turn, r = %.1f, tow bar = %.1f m, currentAngle = %.0f, nextAngle = %.0f, offset = %.1f, smoothOffset = %.1f', - r, steeringLength, currentAngle, nextAngle, offset, tightTurnOffset ) - -- remember the last value for smoothing - return tightTurnOffset -end - - -function AIUtil.getTowBarLength(vehicle) - -- is there a wheeled implement behind the tractor and is it on a pivot? - local implement = AIUtil.getFirstReversingImplementWithWheels(vehicle, true) - if not implement then - return nil - elseif not implement.steeringAxleNode then - CpUtil.debugVehicle(CpDebug.DBG_AI_DRIVER, vehicle, 'could not get tow bar length, using default 3 m.') - -- default is not 0 as this is used to calculate trailer heading and 0 here may result in NaNs - return 3 - end - -- get the distance between the tractor and the towed implement's turn node - -- (not quite accurate when the angle between the tractor and the tool is high) - local tractorX, _, tractorZ = getWorldTranslation(AIUtil.getDirectionNode(vehicle)) - local toolX, _, toolZ = getWorldTranslation(implement.steeringAxleNode) - local towBarLength = MathUtil.getPointPointDistance( tractorX, tractorZ, toolX, toolZ ) - CpUtil.debugVehicle(CpDebug.DBG_AI_DRIVER, vehicle, 'tow bar length is %.1f.', towBarLength) - return towBarLength -end - ----@return boolean, number true if this is a towed reversing implement/steeringLength -function AIUtil.getSteeringParameters(vehicle) - local implement = AIUtil.getFirstReversingImplementWithWheels(vehicle) - if not implement then - return false, 0 - else - return true, AIUtil.getTowBarLength(vehicle) or 3 - end -end - -function AIUtil.getOffsetForTowBarLength(r, towBarLength) - return AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) - r -end - ---- When a tractor is towing an implement in a turn, on what radius will the implement be if ---- the radius the tractor is driving is known? ----@param r number the radius the tractor is on ----@param towBarLength number the length of the tow bar ----@return number the radius the implement will be on. Can be negative, meaning the implement will be ---- moving backwards in the turn -function AIUtil.getImplementRadiusFromTractorRadius(r, towBarLength) - local rSquared = r * r - towBarLength * towBarLength - local rImplement = rSquared > 0 and math.sqrt(rSquared) or -math.sqrt(-rSquared) - return rImplement -end - ---- When a tractor is towing an implement in a turn, on what radius will the tractor be if ---- the radius the implement is known? ----@param r number the radius the implement is following ----@param towBarLength number the length of the tow bar ----@return number the radius the tractor will be on -function AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) - local rTractor = math.sqrt( r * r + towBarLength * towBarLength ) - return rTractor -end - -function AIUtil.getArticulatedAxisVehicleReverserNode(vehicle) - local reverserNode, debugText - -- articulated axis vehicles have a special reverser node - -- and yes, Giants has a typo in there... - if vehicle.spec_articulatedAxis.aiRevereserNode ~= nil then - reverserNode = vehicle.spec_articulatedAxis.aiRevereserNode - debugText = 'vehicle articulated axis reverese' - elseif vehicle.spec_articulatedAxis.aiReverserNode ~= nil then - reverserNode = vehicle.spec_articulatedAxis.aiReverserNode - debugText = 'vehicle articulated axis reverse' - end - return reverserNode, debugText -end - --- Find the node to use by the PPC when driving in reverse -function AIUtil.getReverserNode(vehicle, reversingImplement, suppressLog) - local reverserNode, debugText - -- if there's a reverser node on the tool, use that - reverserNode, debugText = AIVehicleUtil.getAIToolReverserDirectionNode(vehicle), 'AIToolReverserDirectionNode' - if not reverserNode then - reversingImplement = reversingImplement and reversingImplement or AIUtil.getFirstReversingImplementWithWheels(vehicle, suppressLog) - if reversingImplement and reversingImplement.steeringAxleNode then - reverserNode, debugText = reversingImplement.steeringAxleNode, 'implement steering axle node' - end - end - if not reverserNode and vehicle.getAIReverserNode then - reverserNode, debugText = vehicle:getAIReverserNode(), 'AIReverserNode' - end - if not reverserNode and vehicle.spec_articulatedAxis ~= nil then - reverserNode, debugText = AIUtil.getArticulatedAxisVehicleReverserNode(vehicle) - end - return reverserNode, debugText -end - ----@return number the offset of the reverser node from the direction node, usually negative as the ---- reverser node is behind the direction node. If there is no reverser node, return 0 -function AIUtil.getDirectionNodeToReverserNodeOffset(vehicle) - local reverserNode = AIUtil.getReverserNode(vehicle) - if reverserNode then - local _, _, dz = localToLocal(reverserNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) - return dz - else - return 0 - end -end - --- Get the turning radius of the vehicle and its implements (copied from AIDriveStrategyStraight.updateTurnData()) ----@param vehicle table ----@param logEnabled boolean only write debug logs if this is true -function AIUtil.getTurningRadius(vehicle, logEnabled) - - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, 'Finding turn radius:') - - local radius = vehicle.maxTurningRadius or 6 - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' turnRadius set to %.1f', radius) - - if g_vehicleConfigurations:get(vehicle, 'turnRadius') then - radius = g_vehicleConfigurations:get(vehicle, 'turnRadius') - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' turnRadius set from config file to %.1f', radius) - end - - if vehicle:getAIMinTurningRadius() ~= nil then - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' AIMinTurningRadius by Giants is %.1f', vehicle:getAIMinTurningRadius()) - radius = math.max(radius, vehicle:getAIMinTurningRadius()) - end - - local maxToolRadius = 0 - - for _, implement in pairs(vehicle:getChildVehicles()) do - local turnRadius = 0 - if g_vehicleConfigurations:get(implement, 'turnRadius') then - turnRadius = g_vehicleConfigurations:get(implement, 'turnRadius') - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: using the configured turn radius %.1f', - implement:getName(), turnRadius) - elseif vehicle.isServer and SpecializationUtil.hasSpecialization(AIImplement, implement.specializations) then - --- Make sure this function only gets called on the server, as otherwise error might appear. - -- only call this for AIImplements, others may throw an error as the Giants code assumes AIImplement - turnRadius = AIVehicleUtil.getMaxToolRadius({object = implement}) -- Giants should fix their code and take the implement object as the parameter - if turnRadius > 0 then - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: using the Giants turn radius %.1f', - implement:getName(), turnRadius) - end - end - if turnRadius == 0 and implement ~= vehicle then - if AIUtil.isImplementTowed(vehicle, implement) then - if AIUtil.hasImplementWithSpecialization(vehicle, Trailer) and - AIUtil.hasImplementWithSpecialization(vehicle, Pipe) then - -- Auger wagons don't usually have a proper turn radius configured which causes problems when we - -- are calculating the path to a trailer when unloading. Use this as a minimum turn radius. - turnRadius = 10 - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: no Giants turn radius, auger wagon, we use a default %.1f', - implement:getName(), turnRadius) - else - turnRadius = 6 - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: no Giants turn radius, towed implement, we use a default %.1f', - implement:getName(), turnRadius) - end - else - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: no Giants turn radius, not towed, do not use turn radius', - implement:getName()) - end - end - maxToolRadius = math.max(maxToolRadius, turnRadius) - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: max tool radius now is %.1f', implement:getName(), maxToolRadius) - end - radius = math.max(radius, maxToolRadius) - CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, 'getTurningRadius: %.1f m', radius) - return radius -end - ----@param vehicle table ----@param implementObject table -function AIUtil.isImplementTowed(vehicle, implementObject) - if AIUtil.isObjectAttachedOnTheBack(vehicle, implementObject) then - if ImplementUtil.isWheeledImplement(implementObject) then - return true - end - end - return false -end - ----@return table implement object -function AIUtil.getFirstReversingImplementWithWheels(vehicle, suppressLog) - -- since some weird things like Seed Bigbag are also vehicles, check this first - if not vehicle.getAttachedImplements then return nil end - - -- Check all attached implements if we are a wheeled workTool behind the tractor - for _, imp in ipairs(vehicle:getAttachedImplements()) do - -- Check if the implement is behind the tractor - if AIUtil.isObjectAttachedOnTheBack(vehicle, imp.object) then - if ImplementUtil.isWheeledImplement(imp.object) then - if not suppressLog then - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Implement %s has wheels', CpUtil.getName(imp.object)) - end - -- If the implement is a wheeled workTool, then return the object - return imp.object - else - if not suppressLog then - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s has no wheels, check if anything attached to it', - CpUtil.getName(imp.object)) - end - -- If the implement is not a wheeled workTool, then check if that implement have an attached wheeled workTool and return that. - local nextAttachedImplement = AIUtil.getFirstReversingImplementWithWheels(imp.object) - if nextAttachedImplement then - return nextAttachedImplement - elseif not suppressLog then - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s has nothing attached, see what else is attached to %s', - CpUtil.getName(imp.object), CpUtil.getName(vehicle)) - end - end - end - end - -- If we didnt find any workTool, return nil - return nil -end - ----@return boolean true if there are any implements attached to the back of the vehicle -function AIUtil.hasImplementsOnTheBack(vehicle) - for _, implement in pairs(vehicle:getAttachedImplements()) do - if implement.object ~= nil then - local _, _, dz = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) - if dz < 0 then - return true - end - end - end - return false -end - ---- Is the object attached at the front of the vehicle. ----@param vehicle table ----@param object table ----@return boolean -function AIUtil.isObjectAttachedOnTheFront(vehicle,object) - local _, _, dz = localToLocal(object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) - if dz > 0 then - return true - end - return false -end - ---- Is the object attached at the back of the vehicle. ----@param vehicle table ----@param object table ----@return boolean -function AIUtil.isObjectAttachedOnTheBack(vehicle, object) - -- TODO: now in the implement's coordinate system, this is still not 100% reliable in turns - local _, _, dz = localToLocal(AIUtil.getDirectionNode(vehicle), object.rootNode, 0, 0, 0) - if dz > 0 then - return true - end - return false -end - -function AIUtil.getAllAttachedImplements(object, implements) - if not implements then implements = {} end - for _, implement in ipairs(object:getAttachedImplements()) do - table.insert(implements, implement) - AIUtil.getAllAttachedImplements(implement.object, implements) - end - return implements -end - ----@return table, number frontmost object and the distance between the front of that object and the direction node of the vehicle ---- when > 0 in front of the vehicle -function AIUtil.getFirstAttachedImplement(vehicle, suppressLog) - -- by default, it is the vehicle's front - local maxDistance = vehicle.size.length / 2 + vehicle.size.lengthOffset + AIUtil.getDirectionNodeToRootNodeOffset(vehicle) - local firstImplement = vehicle - for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do - if implement.object ~= nil then - -- the distance from the vehicle's root node to the front of the implement - local _, _, d = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, - implement.object.size.length / 2 + implement.object.size.lengthOffset) - if implement.object.spec_leveler then - local nodeData = ImplementUtil.getLevelerNode(implement.object) - if nodeData then - _, _, d = localToLocal(nodeData.node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) - end - end - if not suppressLog then - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s front distance %d', implement.object:getName(), d) - end - if d > maxDistance then - maxDistance = d - firstImplement = implement.object - end - end - end - return firstImplement, maxDistance -end - ----@return table, number rearmost object and the distance between the back of that object and the direction node of the object -function AIUtil.getLastAttachedImplement(vehicle,suppressLog) - -- by default, it is the vehicle's back - local minDistance = vehicle.size.length / 2 - vehicle.size.lengthOffset + AIUtil.getDirectionNodeToRootNodeOffset(vehicle) - -- size.lengthOffset > 0 if the root node is towards the back of the vehicle, < 0 if it is towards the front - local lastImplement = vehicle - for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do - if implement.object ~= nil then - -- the distance from the vehicle's root node to the back of the implement - local _, _, d = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, - - implement.object.size.length / 2 + implement.object.size.lengthOffset) - if implement.object.spec_leveler then - local nodeData = ImplementUtil.getLevelerNode(implement.object) - if nodeData then - _, _, d = localToLocal(nodeData.node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) - end - end - if not suppressLog then - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s back distance %d', implement.object:getName(), d) - end - if d < minDistance then - minDistance = d - lastImplement = implement.object - end - end - end - return lastImplement, minDistance -end - ---- These functions only find directly attached implements/trailer to the vehicle. ---- Implements of others for example a shovel attached to a front loader are not detected. -function AIUtil.hasAIImplementWithSpecialization(vehicle, specialization) - return AIUtil.getAIImplementWithSpecialization(vehicle, specialization) ~= nil -end - -function AIUtil.hasImplementWithSpecialization(vehicle, specialization) - return AIUtil.getImplementWithSpecialization(vehicle, specialization) ~= nil -end - -function AIUtil.getAIImplementWithSpecialization(vehicle, specialization) - local aiImplements = vehicle:getAttachedAIImplements() - return AIUtil.getImplementWithSpecializationFromList(specialization, aiImplements) -end - -function AIUtil.getImplementWithSpecialization(vehicle, specialization) - local implements = vehicle:getAttachedImplements() - return AIUtil.getImplementWithSpecializationFromList(specialization, implements) -end - ---- Gets a directly attached implement to the vehicle with a given specialization. ---- Additionally checks if the vehicle has the specialization and returns it, if no implement was found. ---- For example a self driving overloader. -function AIUtil.getImplementOrVehicleWithSpecialization(vehicle, specialization) - return AIUtil.getImplementWithSpecialization(vehicle, specialization) or ( - SpecializationUtil.hasSpecialization(specialization, vehicle.specializations) and vehicle or nil) -end - -function AIUtil.getImplementWithSpecializationFromList(specialization, implements) - for _, implement in ipairs(implements) do - if SpecializationUtil.hasSpecialization(specialization, implement.object.specializations) then - return implement.object - end - end -end - ---- Get number of child vehicles that have a certain specialization ----@param vehicle table ----@param specialization table specialization to check for ----@return number number of found vehicles -function AIUtil.getNumberOfChildVehiclesWithSpecialization(vehicle, specialization) - local vehicles = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, nil) - - return #vehicles -end - ---- Gets all child vehicles with a given specialization. ---- This can include the rootVehicle and implements ---- that are not directly attached to the rootVehicle. ----@param vehicle table ----@param specialization table ----@param specializationReference string|nil alternative for mod specializations, as their object is not accessible by us. ----@return table all found vehicles/implements ----@return boolean at least one vehicle/implement was found -function AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, specializationReference) - if vehicle == nil then - printCallstack() - CpUtil.info("Vehicle is nil!") - return {}, false - end - local validVehicles = {} - for _, childVehicle in pairs(vehicle:getChildVehicles()) do - if specializationReference and childVehicle[specializationReference] then - table.insert(validVehicles, childVehicle) - end - if specialization and SpecializationUtil.hasSpecialization(specialization, childVehicle.specializations) then - table.insert(validVehicles, childVehicle) - end - end - return validVehicles, #validVehicles>0 -end - ---- Was at least one child vehicle with the given specialization found ? ---- This can include the rootVehicle and implements, ---- that are not directly attached to the rootVehicle. ----@param vehicle table ----@param specialization table ----@param specializationReference string|nil ----@return boolean -function AIUtil.hasChildVehicleWithSpecialization(vehicle, specialization, specializationReference) - local _, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, specializationReference) - return found -end - -function AIUtil.getAllAIImplements(object, implements) - if not implements then implements = {} end - for _, implement in ipairs(object:getAttachedImplements()) do - -- ignore everything which has no work area - if AIUtil.isValidAIImplement(implement.object) then - table.insert(implements, implement) - end - AIUtil.getAllAIImplements(implement.object, implements) - end - return implements -end - --- Is this and implement we should consider when deciding when to lift/raise implements at the end/start of a row? -function AIUtil.isValidAIImplement(object) - if WorkWidthUtil.hasWorkAreas(object) then - -- has work areas, good. - return true - else - local aiLeftMarker, _, _ = WorkWidthUtil.getAIMarkers(object, true) - if aiLeftMarker then - -- has AI markers, good - return true - else - -- no work areas, no AI markers, can't use. - return false - end - end -end - ---- Is this a real wheel the implement is actually rolling on (and turning around) or just some auxiliary support ---- wheel? We need to know about the real wheels when finding the turn radius/distance between attacher joint and ---- wheels. -function AIUtil.isRealWheel(wheel) - return wheel.hasTireTracks and wheel.maxLatStiffnessLoad > 0.5 -end - -function AIUtil.isBehindOtherVehicle(vehicle, otherVehicle) - local _, _, dz = localToLocal(AIUtil.getDirectionNode(vehicle), AIUtil.getDirectionNode(otherVehicle), 0, 0, 0) - return dz < 0 -end - -function AIUtil.isStopped(vehicle) - -- giants supplied last speed is in m/ms - return math.abs(vehicle.lastSpeedReal) < 0.0001 -end - --- Note that this may temporarily return false even if it is reversing -function AIUtil.isReversing(vehicle) - if (AIUtil.isInReverseGear(vehicle) and math.abs(vehicle.lastSpeedReal) > 0.00001) then - return true - else - return false - end -end - -function AIUtil.isInReverseGear(vehicle) - return vehicle.getMotor and vehicle:getMotor():getGearRatio() < 0 -end - ---- Get the current normalized steering angle: ----@return number between -1 and +1, -1 full right steering, +1 full left steering -function AIUtil.getCurrentNormalizedSteeringAngle(vehicle) - if vehicle.rotatedTime >= 0 then - return vehicle.rotatedTime / vehicle.maxRotTime - elseif vehicle.rotatedTime < 0 then - return -vehicle.rotatedTime / vehicle.minRotTime - end -end - ---- Is a sugarcane trailer attached ? ----@param vehicle table -function AIUtil.hasSugarCaneTrailer(vehicle) - if vehicle.spec_shovel and vehicle.spec_trailer then - return true - end - for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do - local object = implement.object - if object.spec_shovel and object.spec_trailer then - return true - end - end -end - ---- Are there any trailer under the pipe ? ----@param pipe table ----@param shouldTrailerBeStandingStill boolean -function AIUtil.isTrailerUnderPipe(pipe, shouldTrailerBeStandingStill) - if not pipe then return end - for trailer, value in pairs(pipe.objectsInTriggers) do - if value > 0 then - if shouldTrailerBeStandingStill then - local rootVehicle = trailer:getRootVehicle() - if rootVehicle then - if AIUtil.isStopped(rootVehicle) then - return true - else - return false - end - end - end - return true - end - end - return false -end - ----Gets the total length of the vehicle and all it's implements. -function AIUtil.getVehicleAndImplementsTotalLength(vehicle) - local totalLength = vehicle.size.length - for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do - if implement.object ~= nil then - totalLength = totalLength + implement.object.size.length - end - end - return totalLength -end - -function AIUtil.findLoweringDurationMs(vehicle) - local function getLoweringDurationMs(object) - if object.spec_animatedVehicle then - -- TODO: implement these in the specifications? - return math.max(object.spec_animatedVehicle:getAnimationDuration('lowerAnimation'), - object.spec_animatedVehicle:getAnimationDuration('rotatePickup')) - else - return 0 - end - end - - local loweringDurationMs = getLoweringDurationMs(vehicle) - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Lowering duration: %d ms', loweringDurationMs) - - -- check all implements first - local implements = vehicle:getAttachedImplements() - for _, implement in ipairs(implements) do - local implementLoweringDurationMs = getLoweringDurationMs(implement.object) - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Lowering duration (%s): %d ms', implement.object:getName(), implementLoweringDurationMs) - if implementLoweringDurationMs > loweringDurationMs then - loweringDurationMs = implementLoweringDurationMs - end - local jointDescIndex = implement.jointDescIndex - -- now check the attacher joints - if vehicle.spec_attacherJoints and jointDescIndex then - local ajs = vehicle.spec_attacherJoints:getAttacherJoints() - local ajLoweringDurationMs = ajs[jointDescIndex] and ajs[jointDescIndex].moveDefaultTime or 0 - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Lowering duration (%s attacher joint): %d ms', implement.object:getName(), ajLoweringDurationMs) - if ajLoweringDurationMs > loweringDurationMs then - loweringDurationMs = ajLoweringDurationMs - end - end - end - if not loweringDurationMs or loweringDurationMs <= 1 then - loweringDurationMs = 2000 - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'No lowering duration found, setting to: %d ms', loweringDurationMs) - end - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Final lowering duration: %d ms', loweringDurationMs) - return loweringDurationMs -end - -function AIUtil.getWidth(vehicle) - if vehicle.getAIAgentSize then - local width, length, lengthOffset, frontOffset, height = vehicle:getAIAgentSize() - return width - else - return vehicle.size.width - end -end - -function AIUtil.getLength(vehicle) - if vehicle.getAIAgentSize then - vehicle:updateAIAgentAttachments() - local width, length, lengthOffset, frontOffset, height = vehicle:getAIAgentSize() - for _, attachment in ipairs(vehicle.spec_aiDrivable.attachments) do - length = length + attachment.length - end - return length - else - return vehicle.size.length - end -end - ---- Can we reverse with whatever is attached to the vehicle? -function AIUtil.canReverse(vehicle) - if AIVehicleUtil.getAttachedImplementsBlockTurnBackward(vehicle) then - -- Giants says no reverse - return false - elseif g_vehicleConfigurations:getRecursively(vehicle, 'noReverse') then - -- our configuration disabled reverse - return false - else - return true - end -end - ---- Checks if a valid Universal autoload trailer is attached. ---- FS22_UniversalAutoload from Loki79uk: https://github.com/loki79uk/FS22_UniversalAutoload -function AIUtil.hasValidUniversalTrailerAttached(vehicle) - local implements, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, nil, "spec_universalAutoload") - return found and implements[1].spec_universalAutoload.isAutoloadEnabled -end - ---- Checks if cutter on an trailer is attached. -function AIUtil.hasCutterOnTrailerAttached(vehicle) - local trailer = AIUtil.getImplementWithSpecialization(vehicle, DynamicMountAttacher) - return trailer and next(trailer.spec_dynamicMountAttacher.dynamicMountedObjects) ~= nil and next(trailer.spec_dynamicMountAttacher.dynamicMountedObjects).spec_cutter ~= nil -end - ---- Checks if a cutter is attached and it's not registered as a valid combine cutter. ---- A Example is the New Holland Superflex header, when it is attached as transport trailer. -function AIUtil.hasCutterAsTrailerAttached(vehicle) - local cutters, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Cutter) - if not found then - --- No attached cutter was found. - return false - end - local combines, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Combine) - if not found then - --- No valid combine object was found. - return false - end - local spec = combines[1].spec_combine - if spec.numAttachedCutters <= 0 then - --- The cutter is not available for threshing in this combination. - return true - end - return false -end - ---- SpecializationUtil.hasSpecialization(ArticulatedAxis, specialization) has no use as now every vehicle ---- seems to have a ArticulatedAxis specialization. Giants also using this check below. -function AIUtil.hasArticulatedAxis(vehicle) - return vehicle.spec_articulatedAxis and vehicle.spec_articulatedAxis.componentJoint -end - ------------------------------------------------------------------------------------------------------------------------- --- Is the other vehicle in front of us? ------------------------------------------------------------------------------------------------------------------------- -function AIUtil.isOtherVehicleAhead(vehicle, otherVehicle) - -- if we look straight left or right out of the window, is blockingVehicle in front of us or behind us? - -- but since we may have a trailer or other implement, don't use the tractor's direction node directly, instead, - -- a point behind it about the half length of the rig. - -- (using the front and back markers are probably better than getVehicleAndImplementsTotalLength() as that - -- assumes that there is no overlap between the vehicle and the implements) - local _, frontMarkerOffset = Markers.getFrontMarkerNode(vehicle) - local _, backMarkerOffset = Markers.getBackMarkerNode(vehicle) - local _, _, dz = localToLocal(otherVehicle.rootNode, vehicle:getAIDirectionNode(), 0, 0, 0) - return dz > (frontMarkerOffset + backMarkerOffset) / 2 -end +--[[ +This file is part of Courseplay (https://github.com/Courseplay/courseplay) +Copyright (C) 2021 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +]] + +--- +--- High level (scope is the entire vehicle chain) utilities for the Courseplay AI + +---@class AIUtil +AIUtil = {} + +function AIUtil.isReverseDriving(vehicle) + if not vehicle then + printCallstack() + return false + end + return vehicle.spec_reverseDriving and vehicle.spec_reverseDriving.isReverseDriving +end + +function AIUtil.getDirectionNode(vehicle) + -- TODO: We used this to make sure to return a direction node that always points to the + -- forward direction, even if a vehicle had its direction reversed (cabin turned). Now we + -- think getAIDirectionNode() guarantees this, the only reason this is still here is that + -- we need to check if it is possible the call this with a vehicle which has no AI direction node + -- and fall back to the root node. + return vehicle.getAIDirectionNode and vehicle:getAIDirectionNode() or vehicle.rootNode +end + +---@return number the z offset of the root node in the direction node's coordinate system: if > 0, the direction node +--- is behind the root node, otherwise in front of it +function AIUtil.getDirectionNodeToRootNodeOffset(vehicle) + local _, _, dz = localToLocal(vehicle.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + return dz +end + +--- If we are towing an implement, move to a bigger radius in tight turns +--- making sure that the towed implement's trajectory remains closer to the +--- course. +---@param course Course +function AIUtil.calculateTightTurnOffset(vehicle, vehicleTurningRadius, course, previousOffset) + local tightTurnOffset + + local function smoothOffset(offset) + return (offset + 4 * (previousOffset or 0 )) / 5 + end + + -- first of all, does the current waypoint have radius data? + local r = course:getCalculatedRadiusAtIx(course:getCurrentWaypointIx()) + if not r then + return smoothOffset(0) + end + + -- limit the radius we are trying to follow to the vehicle's turn radius. + -- TODO: there's some potential here as the towed implement can move on a radius less than the vehicle's + -- turn radius so this limit may be too pessimistic + r = math.max(r, vehicleTurningRadius) + + local towBarLength = AIUtil.getTowBarLength(vehicle) + + if towBarLength == nil then + -- not a towed implement, no offset + return 0 + end + -- Is this really a tight turn? It is when the tow bar is longer than radius / 3, otherwise + -- we ignore it. + if towBarLength < r / 3 then + return smoothOffset(0) + end + + -- Ok, looks like a tight turn, so we need to move a bit left or right of the course + -- to keep the tool on the course. Use a little less than the calculated, this is purely empirical and should probably + -- be reviewed why the calculated one seems to overshoot. + local offset = 0.75 * AIUtil.getOffsetForTowBarLength(r, towBarLength) + if offset ~= offset then + -- check for nan + return smoothOffset(0) + end + -- figure out left or right now? + local nextAngle = course:getWaypointAngleDeg(course:getCurrentWaypointIx() + 1) + local currentAngle = course:getWaypointAngleDeg(course:getCurrentWaypointIx()) + if not nextAngle or not currentAngle then + return smoothOffset(0) + end + + if CpMathUtil.getDeltaAngle(math.rad(nextAngle), math.rad(currentAngle)) > 0 then offset = -offset end + + -- smooth the offset a bit to avoid sudden changes + tightTurnOffset = smoothOffset(offset) + CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, + 'Tight turn, r = %.1f, tow bar = %.1f m, currentAngle = %.0f, nextAngle = %.0f, offset = %.1f, smoothOffset = %.1f', + r, towBarLength, currentAngle, nextAngle, offset, tightTurnOffset ) + -- remember the last value for smoothing + return tightTurnOffset +end + +function AIUtil.calculateTightTurnOffsetForTurnManeuver(vehicle, steeringLength, course, ix, previousOffset) + local tightTurnOffset + + local function smoothOffset(offset) + -- smooth more for articulated axis or track vehicle + -- as those usually have a very small turn radius anyway, causing jackknifing + -- TODO: use the vehicle's solo radius instead? + local factor = AIUtil.hasArticulatedAxis(vehicle) and 6 or 4 + return (offset + factor * (previousOffset or 0 )) / (factor + 1) + end + + -- first of all, does the current waypoint have radius data? + local r = course:getCalculatedRadiusAtIx(ix) + if not r then + return smoothOffset(0) + end + + local offset = AIUtil.getTractorRadiusFromImplementRadius(r, steeringLength) - r + if offset ~= offset then + -- check for nan + return smoothOffset(0) + end + -- figure out left or right now? + local nextAngle = course:getWaypointAngleDeg(ix + 1) + local currentAngle = course:getWaypointAngleDeg(ix) + if not nextAngle or not currentAngle then + return smoothOffset(0) + end + + if CpMathUtil.getDeltaAngle(math.rad(nextAngle), math.rad(currentAngle)) > 0 then offset = -offset end + + -- smooth the offset a bit to avoid sudden changes + tightTurnOffset = smoothOffset(offset) + CpUtil.debugVehicle(CpDebug.DBG_TURN, vehicle, + 'Tight turn, r = %.1f, tow bar = %.1f m, currentAngle = %.0f, nextAngle = %.0f, offset = %.1f, smoothOffset = %.1f', + r, steeringLength, currentAngle, nextAngle, offset, tightTurnOffset ) + -- remember the last value for smoothing + return tightTurnOffset +end + + +function AIUtil.getTowBarLength(vehicle) + -- is there a wheeled implement behind the tractor and is it on a pivot? + local implement = AIUtil.getFirstReversingImplementWithWheels(vehicle, true) + if not implement then + return nil + elseif not implement.steeringAxleNode then + CpUtil.debugVehicle(CpDebug.DBG_AI_DRIVER, vehicle, 'could not get tow bar length, using default 3 m.') + -- default is not 0 as this is used to calculate trailer heading and 0 here may result in NaNs + return 3 + end + -- get the distance between the tractor and the towed implement's turn node + -- (not quite accurate when the angle between the tractor and the tool is high) + local tractorX, _, tractorZ = getWorldTranslation(AIUtil.getDirectionNode(vehicle)) + local toolX, _, toolZ = getWorldTranslation(implement.steeringAxleNode) + local towBarLength = MathUtil.getPointPointDistance( tractorX, tractorZ, toolX, toolZ ) + CpUtil.debugVehicle(CpDebug.DBG_AI_DRIVER, vehicle, 'tow bar length is %.1f.', towBarLength) + return towBarLength +end + +---@return boolean, number true if this is a towed reversing implement/steeringLength +function AIUtil.getSteeringParameters(vehicle) + local implement = AIUtil.getFirstReversingImplementWithWheels(vehicle) + if not implement then + return false, 0 + else + return true, AIUtil.getTowBarLength(vehicle) or 3 + end +end + +function AIUtil.getOffsetForTowBarLength(r, towBarLength) + return AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) - r +end + +--- When a tractor is towing an implement in a turn, on what radius will the implement be if +--- the radius the tractor is driving is known? +---@param r number the radius the tractor is on +---@param towBarLength number the length of the tow bar +---@return number the radius the implement will be on. Can be negative, meaning the implement will be +--- moving backwards in the turn +function AIUtil.getImplementRadiusFromTractorRadius(r, towBarLength) + local rSquared = r * r - towBarLength * towBarLength + local rImplement = rSquared > 0 and math.sqrt(rSquared) or -math.sqrt(-rSquared) + return rImplement +end + +--- When a tractor is towing an implement in a turn, on what radius will the tractor be if +--- the radius the implement is known? +---@param r number the radius the implement is following +---@param towBarLength number the length of the tow bar +---@return number the radius the tractor will be on +function AIUtil.getTractorRadiusFromImplementRadius(r, towBarLength) + local rTractor = math.sqrt( r * r + towBarLength * towBarLength ) + return rTractor +end + +function AIUtil.getArticulatedAxisVehicleReverserNode(vehicle) + local reverserNode, debugText + -- articulated axis vehicles have a special reverser node + -- and yes, Giants has a typo in there... + if vehicle.spec_articulatedAxis.aiRevereserNode ~= nil then + reverserNode = vehicle.spec_articulatedAxis.aiRevereserNode + debugText = 'vehicle articulated axis reverese' + elseif vehicle.spec_articulatedAxis.aiReverserNode ~= nil then + reverserNode = vehicle.spec_articulatedAxis.aiReverserNode + debugText = 'vehicle articulated axis reverse' + end + return reverserNode, debugText +end + +-- Find the node to use by the PPC when driving in reverse +function AIUtil.getReverserNode(vehicle, reversingImplement, suppressLog) + local reverserNode, debugText + -- if there's a reverser node on the tool, use that + reverserNode, debugText = AIVehicleUtil.getAIToolReverserDirectionNode(vehicle), 'AIToolReverserDirectionNode' + if not reverserNode then + reversingImplement = reversingImplement and reversingImplement or AIUtil.getFirstReversingImplementWithWheels(vehicle, suppressLog) + if reversingImplement and reversingImplement.steeringAxleNode then + reverserNode, debugText = reversingImplement.steeringAxleNode, 'implement steering axle node' + end + end + if not reverserNode and vehicle.getAIReverserNode then + reverserNode, debugText = vehicle:getAIReverserNode(), 'AIReverserNode' + end + if not reverserNode and vehicle.spec_articulatedAxis ~= nil then + reverserNode, debugText = AIUtil.getArticulatedAxisVehicleReverserNode(vehicle) + end + return reverserNode, debugText +end + +---@return number the offset of the reverser node from the direction node, usually negative as the +--- reverser node is behind the direction node. If there is no reverser node, return 0 +function AIUtil.getDirectionNodeToReverserNodeOffset(vehicle) + local reverserNode = AIUtil.getReverserNode(vehicle) + if reverserNode then + local _, _, dz = localToLocal(reverserNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + return dz + else + return 0 + end +end + +-- Get the turning radius of the vehicle and its implements (copied from AIDriveStrategyStraight.updateTurnData()) +---@param vehicle table +---@param logEnabled boolean only write debug logs if this is true +function AIUtil.getTurningRadius(vehicle, logEnabled) + + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, 'Finding turn radius:') + + local radius = vehicle.maxTurningRadius or 6 + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' turnRadius set to %.1f', radius) + + if g_vehicleConfigurations:get(vehicle, 'turnRadius') then + radius = g_vehicleConfigurations:get(vehicle, 'turnRadius') + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' turnRadius set from config file to %.1f', radius) + end + + if vehicle:getAIMinTurningRadius() ~= nil then + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' AIMinTurningRadius by Giants is %.1f', vehicle:getAIMinTurningRadius()) + radius = math.max(radius, vehicle:getAIMinTurningRadius()) + end + + local maxToolRadius = 0 + + for _, implement in pairs(vehicle:getChildVehicles()) do + local turnRadius = 0 + if g_vehicleConfigurations:get(implement, 'turnRadius') then + turnRadius = g_vehicleConfigurations:get(implement, 'turnRadius') + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: using the configured turn radius %.1f', + implement:getName(), turnRadius) + elseif vehicle.isServer and SpecializationUtil.hasSpecialization(AIImplement, implement.specializations) then + --- Make sure this function only gets called on the server, as otherwise error might appear. + -- only call this for AIImplements, others may throw an error as the Giants code assumes AIImplement + turnRadius = AIVehicleUtil.getMaxToolRadius({object = implement}) -- Giants should fix their code and take the implement object as the parameter + if turnRadius > 0 then + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: using the Giants turn radius %.1f', + implement:getName(), turnRadius) + end + end + if turnRadius == 0 and implement ~= vehicle then + if AIUtil.isImplementTowed(vehicle, implement) then + if AIUtil.hasImplementWithSpecialization(vehicle, Trailer) and + AIUtil.hasImplementWithSpecialization(vehicle, Pipe) then + -- Auger wagons don't usually have a proper turn radius configured which causes problems when we + -- are calculating the path to a trailer when unloading. Use this as a minimum turn radius. + turnRadius = 10 + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: no Giants turn radius, auger wagon, we use a default %.1f', + implement:getName(), turnRadius) + else + turnRadius = 6 + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: no Giants turn radius, towed implement, we use a default %.1f', + implement:getName(), turnRadius) + end + else + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: no Giants turn radius, not towed, do not use turn radius', + implement:getName()) + end + end + maxToolRadius = math.max(maxToolRadius, turnRadius) + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, ' %s: max tool radius now is %.1f', implement:getName(), maxToolRadius) + end + radius = math.max(radius, maxToolRadius) + CpUtil.debugVehicleIf(logEnabled, CpDebug.DBG_IMPLEMENTS, vehicle, 'getTurningRadius: %.1f m', radius) + return radius +end + +---@param vehicle table +---@param implementObject table +function AIUtil.isImplementTowed(vehicle, implementObject) + if AIUtil.isObjectAttachedOnTheBack(vehicle, implementObject) then + if ImplementUtil.isWheeledImplement(implementObject) then + return true + end + end + return false +end + +---@return table implement object +function AIUtil.getFirstReversingImplementWithWheels(vehicle, suppressLog) + -- since some weird things like Seed Bigbag are also vehicles, check this first + if not vehicle.getAttachedImplements then return nil end + + -- Check all attached implements if we are a wheeled workTool behind the tractor + for _, imp in ipairs(vehicle:getAttachedImplements()) do + -- Check if the implement is behind the tractor + if AIUtil.isObjectAttachedOnTheBack(vehicle, imp.object) then + if ImplementUtil.isWheeledImplement(imp.object) then + if not suppressLog then + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Implement %s has wheels', CpUtil.getName(imp.object)) + end + -- If the implement is a wheeled workTool, then return the object + return imp.object + else + if not suppressLog then + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s has no wheels, check if anything attached to it', + CpUtil.getName(imp.object)) + end + -- If the implement is not a wheeled workTool, then check if that implement have an attached wheeled workTool and return that. + local nextAttachedImplement = AIUtil.getFirstReversingImplementWithWheels(imp.object) + if nextAttachedImplement then + return nextAttachedImplement + elseif not suppressLog then + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s has nothing attached, see what else is attached to %s', + CpUtil.getName(imp.object), CpUtil.getName(vehicle)) + end + end + end + end + -- If we didnt find any workTool, return nil + return nil +end + +---@return boolean true if there are any implements attached to the back of the vehicle +function AIUtil.hasImplementsOnTheBack(vehicle) + for _, implement in pairs(vehicle:getAttachedImplements()) do + if implement.object ~= nil then + local _, _, dz = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + if dz < 0 then + return true + end + end + end + return false +end + +--- Is the object attached at the front of the vehicle. +---@param vehicle table +---@param object table +---@return boolean +function AIUtil.isObjectAttachedOnTheFront(vehicle,object) + local _, _, dz = localToLocal(object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + if dz > 0 then + return true + end + return false +end + +--- Is the object attached at the back of the vehicle. +---@param vehicle table +---@param object table +---@return boolean +function AIUtil.isObjectAttachedOnTheBack(vehicle, object) + -- TODO: now in the implement's coordinate system, this is still not 100% reliable in turns + local _, _, dz = localToLocal(AIUtil.getDirectionNode(vehicle), object.rootNode, 0, 0, 0) + if dz > 0 then + return true + end + return false +end + +function AIUtil.getAllAttachedImplements(object, implements) + if not implements then implements = {} end + for _, implement in ipairs(object:getAttachedImplements()) do + table.insert(implements, implement) + AIUtil.getAllAttachedImplements(implement.object, implements) + end + return implements +end + +---@return table, number frontmost object and the distance between the front of that object and the direction node of the vehicle +--- when > 0 in front of the vehicle +function AIUtil.getFirstAttachedImplement(vehicle, suppressLog) + -- by default, it is the vehicle's front + local maxDistance = vehicle.size.length / 2 + vehicle.size.lengthOffset + AIUtil.getDirectionNodeToRootNodeOffset(vehicle) + local firstImplement = vehicle + for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do + if implement.object ~= nil then + -- the distance from the vehicle's root node to the front of the implement + local _, _, d = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, + implement.object.size.length / 2 + implement.object.size.lengthOffset) + if implement.object.spec_leveler then + local nodeData = ImplementUtil.getLevelerNode(implement.object) + if nodeData then + _, _, d = localToLocal(nodeData.node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + end + end + if not suppressLog then + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s front distance %d', implement.object:getName(), d) + end + if d > maxDistance then + maxDistance = d + firstImplement = implement.object + end + end + end + return firstImplement, maxDistance +end + +---@return table, number rearmost object and the distance between the back of that object and the direction node of the object +function AIUtil.getLastAttachedImplement(vehicle,suppressLog) + -- by default, it is the vehicle's back + local minDistance = vehicle.size.length / 2 - vehicle.size.lengthOffset + AIUtil.getDirectionNodeToRootNodeOffset(vehicle) + -- size.lengthOffset > 0 if the root node is towards the back of the vehicle, < 0 if it is towards the front + local lastImplement = vehicle + for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do + if implement.object ~= nil then + -- the distance from the vehicle's root node to the back of the implement + local _, _, d = localToLocal(implement.object.rootNode, AIUtil.getDirectionNode(vehicle), 0, 0, + - implement.object.size.length / 2 + implement.object.size.lengthOffset) + if implement.object.spec_leveler then + local nodeData = ImplementUtil.getLevelerNode(implement.object) + if nodeData then + _, _, d = localToLocal(nodeData.node, AIUtil.getDirectionNode(vehicle), 0, 0, 0) + end + end + if not suppressLog then + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, '%s back distance %d', implement.object:getName(), d) + end + if d < minDistance then + minDistance = d + lastImplement = implement.object + end + end + end + return lastImplement, minDistance +end + +--- These functions only find directly attached implements/trailer to the vehicle. +--- Implements of others for example a shovel attached to a front loader are not detected. +function AIUtil.hasAIImplementWithSpecialization(vehicle, specialization) + return AIUtil.getAIImplementWithSpecialization(vehicle, specialization) ~= nil +end + +function AIUtil.hasImplementWithSpecialization(vehicle, specialization) + return AIUtil.getImplementWithSpecialization(vehicle, specialization) ~= nil +end + +function AIUtil.getAIImplementWithSpecialization(vehicle, specialization) + local aiImplements = vehicle:getAttachedAIImplements() + return AIUtil.getImplementWithSpecializationFromList(specialization, aiImplements) +end + +function AIUtil.getImplementWithSpecialization(vehicle, specialization) + local implements = vehicle:getAttachedImplements() + return AIUtil.getImplementWithSpecializationFromList(specialization, implements) +end + +--- Gets a directly attached implement to the vehicle with a given specialization. +--- Additionally checks if the vehicle has the specialization and returns it, if no implement was found. +--- For example a self driving overloader. +function AIUtil.getImplementOrVehicleWithSpecialization(vehicle, specialization) + return AIUtil.getImplementWithSpecialization(vehicle, specialization) or ( + SpecializationUtil.hasSpecialization(specialization, vehicle.specializations) and vehicle or nil) +end + +function AIUtil.getImplementWithSpecializationFromList(specialization, implements) + for _, implement in ipairs(implements) do + if SpecializationUtil.hasSpecialization(specialization, implement.object.specializations) then + return implement.object + end + end +end + +--- Get number of child vehicles that have a certain specialization +---@param vehicle table +---@param specialization table specialization to check for +---@return number number of found vehicles +function AIUtil.getNumberOfChildVehiclesWithSpecialization(vehicle, specialization) + local vehicles = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, nil) + + return #vehicles +end + +--- Gets all child vehicles with a given specialization. +--- This can include the rootVehicle and implements +--- that are not directly attached to the rootVehicle. +---@param vehicle table +---@param specialization table +---@param specializationReference string|nil alternative for mod specializations, as their object is not accessible by us. +---@return table all found vehicles/implements +---@return boolean at least one vehicle/implement was found +function AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, specializationReference) + if vehicle == nil then + printCallstack() + CpUtil.info("Vehicle is nil!") + return {}, false + end + local validVehicles = {} + for _, childVehicle in pairs(vehicle:getChildVehicles()) do + if specializationReference and childVehicle[specializationReference] then + table.insert(validVehicles, childVehicle) + end + if specialization and SpecializationUtil.hasSpecialization(specialization, childVehicle.specializations) then + table.insert(validVehicles, childVehicle) + end + end + return validVehicles, #validVehicles>0 +end + +--- Was at least one child vehicle with the given specialization found ? +--- This can include the rootVehicle and implements, +--- that are not directly attached to the rootVehicle. +---@param vehicle table +---@param specialization table +---@param specializationReference string|nil +---@return boolean +function AIUtil.hasChildVehicleWithSpecialization(vehicle, specialization, specializationReference) + local _, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, specialization, specializationReference) + return found +end + +function AIUtil.getAllAIImplements(object, implements) + if not implements then implements = {} end + for _, implement in ipairs(object:getAttachedImplements()) do + -- ignore everything which has no work area + if AIUtil.isValidAIImplement(implement.object) then + table.insert(implements, implement) + end + AIUtil.getAllAIImplements(implement.object, implements) + end + return implements +end + +-- Is this and implement we should consider when deciding when to lift/raise implements at the end/start of a row? +function AIUtil.isValidAIImplement(object) + if WorkWidthUtil.hasWorkAreas(object) then + -- has work areas, good. + return true + else + local aiLeftMarker, _, _ = WorkWidthUtil.getAIMarkers(object, true) + if aiLeftMarker then + -- has AI markers, good + return true + else + -- no work areas, no AI markers, can't use. + return false + end + end +end + +--- Is this a real wheel the implement is actually rolling on (and turning around) or just some auxiliary support +--- wheel? We need to know about the real wheels when finding the turn radius/distance between attacher joint and +--- wheels. +function AIUtil.isRealWheel(wheel) + return wheel.hasTireTracks and wheel.maxLatStiffnessLoad > 0.5 +end + +function AIUtil.isBehindOtherVehicle(vehicle, otherVehicle) + local _, _, dz = localToLocal(AIUtil.getDirectionNode(vehicle), AIUtil.getDirectionNode(otherVehicle), 0, 0, 0) + return dz < 0 +end + +function AIUtil.isStopped(vehicle) + -- giants supplied last speed is in m/ms + return math.abs(vehicle.lastSpeedReal) < 0.0001 +end + +-- Note that this may temporarily return false even if it is reversing +function AIUtil.isReversing(vehicle) + if (AIUtil.isInReverseGear(vehicle) and math.abs(vehicle.lastSpeedReal) > 0.00001) then + return true + else + return false + end +end + +function AIUtil.isInReverseGear(vehicle) + return vehicle.getMotor and vehicle:getMotor():getGearRatio() < 0 +end + +--- Get the current normalized steering angle: +---@return number between -1 and +1, -1 full right steering, +1 full left steering +function AIUtil.getCurrentNormalizedSteeringAngle(vehicle) + if vehicle.rotatedTime >= 0 then + return vehicle.rotatedTime / vehicle.maxRotTime + elseif vehicle.rotatedTime < 0 then + return -vehicle.rotatedTime / vehicle.minRotTime + end +end + +--- Is a sugarcane trailer attached ? +---@param vehicle table +function AIUtil.hasSugarCaneTrailer(vehicle) + if vehicle.spec_shovel and vehicle.spec_trailer then + return true + end + for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do + local object = implement.object + if object.spec_shovel and object.spec_trailer then + return true + end + end +end + +--- Are there any trailer under the pipe ? +---@param pipe table +---@param shouldTrailerBeStandingStill boolean +function AIUtil.isTrailerUnderPipe(pipe, shouldTrailerBeStandingStill) + if not pipe then return end + for trailer, value in pairs(pipe.objectsInTriggers) do + if value > 0 then + if shouldTrailerBeStandingStill then + local rootVehicle = trailer:getRootVehicle() + if rootVehicle then + if AIUtil.isStopped(rootVehicle) then + return true + else + return false + end + end + end + return true + end + end + return false +end + +---Gets the total length of the vehicle and all it's implements. +function AIUtil.getVehicleAndImplementsTotalLength(vehicle) + local totalLength = vehicle.size.length + for _, implement in pairs(AIUtil.getAllAttachedImplements(vehicle)) do + if implement.object ~= nil then + totalLength = totalLength + implement.object.size.length + end + end + return totalLength +end + +function AIUtil.findLoweringDurationMs(vehicle) + local function getLoweringDurationMs(object) + if object.spec_animatedVehicle then + -- TODO: implement these in the specifications? + return math.max(object.spec_animatedVehicle:getAnimationDuration('lowerAnimation'), + object.spec_animatedVehicle:getAnimationDuration('rotatePickup')) + else + return 0 + end + end + + local loweringDurationMs = getLoweringDurationMs(vehicle) + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Lowering duration: %d ms', loweringDurationMs) + + -- check all implements first + local implements = vehicle:getAttachedImplements() + for _, implement in ipairs(implements) do + local implementLoweringDurationMs = getLoweringDurationMs(implement.object) + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Lowering duration (%s): %d ms', implement.object:getName(), implementLoweringDurationMs) + if implementLoweringDurationMs > loweringDurationMs then + loweringDurationMs = implementLoweringDurationMs + end + local jointDescIndex = implement.jointDescIndex + -- now check the attacher joints + if vehicle.spec_attacherJoints and jointDescIndex then + local ajs = vehicle.spec_attacherJoints:getAttacherJoints() + local ajLoweringDurationMs = ajs[jointDescIndex] and ajs[jointDescIndex].moveDefaultTime or 0 + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Lowering duration (%s attacher joint): %d ms', implement.object:getName(), ajLoweringDurationMs) + if ajLoweringDurationMs > loweringDurationMs then + loweringDurationMs = ajLoweringDurationMs + end + end + end + if not loweringDurationMs or loweringDurationMs <= 1 then + loweringDurationMs = 2000 + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'No lowering duration found, setting to: %d ms', loweringDurationMs) + end + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Final lowering duration: %d ms', loweringDurationMs) + return loweringDurationMs +end + +function AIUtil.getWidth(vehicle) + if vehicle.getAIAgentSize then + local width, length, lengthOffset, frontOffset, height = vehicle:getAIAgentSize() + return width + else + return vehicle.size.width + end +end + +function AIUtil.getLength(vehicle) + if vehicle.getAIAgentSize then + vehicle:updateAIAgentAttachments() + local width, length, lengthOffset, frontOffset, height = vehicle:getAIAgentSize() + for _, attachment in ipairs(vehicle.spec_aiDrivable.attachments) do + length = length + attachment.length + end + return length + else + return vehicle.size.length + end +end + +--- Can we reverse with whatever is attached to the vehicle? +function AIUtil.canReverse(vehicle) + if AIVehicleUtil.getAttachedImplementsBlockTurnBackward(vehicle) then + -- Giants says no reverse + return false + elseif g_vehicleConfigurations:getRecursively(vehicle, 'noReverse') then + -- our configuration disabled reverse + return false + else + return true + end +end + +--- Checks if a valid Universal autoload trailer is attached. +--- FS22_UniversalAutoload from Loki79uk: https://github.com/loki79uk/FS22_UniversalAutoload +function AIUtil.hasValidUniversalTrailerAttached(vehicle) + local implements, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, nil, "spec_universalAutoload") + return found and implements[1].spec_universalAutoload.isAutoloadEnabled +end + +--- Checks if cutter on an trailer is attached. +function AIUtil.hasCutterOnTrailerAttached(vehicle) + local trailer = AIUtil.getImplementWithSpecialization(vehicle, DynamicMountAttacher) + return trailer and next(trailer.spec_dynamicMountAttacher.dynamicMountedObjects) ~= nil and next(trailer.spec_dynamicMountAttacher.dynamicMountedObjects).spec_cutter ~= nil +end + +--- Checks if a cutter is attached and it's not registered as a valid combine cutter. +--- A Example is the New Holland Superflex header, when it is attached as transport trailer. +function AIUtil.hasCutterAsTrailerAttached(vehicle) + local cutters, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Cutter) + if not found then + --- No attached cutter was found. + return false + end + local combines, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Combine) + if not found then + --- No valid combine object was found. + return false + end + local spec = combines[1].spec_combine + if spec.numAttachedCutters <= 0 then + --- The cutter is not available for threshing in this combination. + return true + end + return false +end + +--- SpecializationUtil.hasSpecialization(ArticulatedAxis, specialization) has no use as now every vehicle +--- seems to have a ArticulatedAxis specialization. Giants also using this check below. +function AIUtil.hasArticulatedAxis(vehicle) + return vehicle.spec_articulatedAxis and vehicle.spec_articulatedAxis.componentJoint +end + +------------------------------------------------------------------------------------------------------------------------ +-- Is the other vehicle in front of us? +------------------------------------------------------------------------------------------------------------------------ +function AIUtil.isOtherVehicleAhead(vehicle, otherVehicle) + -- if we look straight left or right out of the window, is blockingVehicle in front of us or behind us? + -- but since we may have a trailer or other implement, don't use the tractor's direction node directly, instead, + -- a point behind it about the half length of the rig. + -- (using the front and back markers are probably better than getVehicleAndImplementsTotalLength() as that + -- assumes that there is no overlap between the vehicle and the implements) + local _, frontMarkerOffset = Markers.getFrontMarkerNode(vehicle) + local _, backMarkerOffset = Markers.getBackMarkerNode(vehicle) + local _, _, dz = localToLocal(otherVehicle.rootNode, vehicle:getAIDirectionNode(), 0, 0, 0) + return dz > (frontMarkerOffset + backMarkerOffset) / 2 +end diff --git a/scripts/ai/FillLevelUtil.lua b/scripts/ai/util/FillLevelUtil.lua similarity index 100% rename from scripts/ai/FillLevelUtil.lua rename to scripts/ai/util/FillLevelUtil.lua diff --git a/scripts/ai/ImplementUtil.lua b/scripts/ai/util/ImplementUtil.lua similarity index 97% rename from scripts/ai/ImplementUtil.lua rename to scripts/ai/util/ImplementUtil.lua index a4899b8d6..8b62ab894 100644 --- a/scripts/ai/ImplementUtil.lua +++ b/scripts/ai/util/ImplementUtil.lua @@ -1,591 +1,591 @@ ---[[ -This file is part of Courseplay (https://github.com/Courseplay/courseplay) -Copyright (C) 2021 Peter Vaiko - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. -]] - ---- ---- Implement utilities for the Courseplay AI - ----@class ImplementUtil -ImplementUtil = {} - -function ImplementUtil.isPartOfNode(node, parentNode) - -- Check if Node is part of partOfNode and not in a different component - while node ~= 0 and node ~= nil do - if node == parentNode then - return true - else - node = getParent(node) - end - end - return false -end - ---- ImplementUtil.findJointNodeConnectingToNode(workTool, fromNode, toNode, doReverse) --- Returns: (node, backtrack, rotLimits) --- node will return either: 1. The jointNode that connects to the toNode, --- 2. The toNode if no jointNode is found but the fromNode is inside the same component as the toNode --- 3. nil in case none of the above fails. --- backTrack will return either: 1. A table of all the jointNodes found from fromNode to toNode, if the jointNode that connects to the toNode is found. --- 2: nil if no jointNode is found. --- rotLimits will return either: 1. A table of all the rotLimits of the componentJoint, found from fromNode to toNode, if the jointNode that connects to the toNode is found. --- 2: nil if no jointNode is found. -function ImplementUtil.findJointNodeConnectingToNode(workTool, fromNode, toNode, doReverse) - if fromNode == toNode then - return toNode - end - - -- Attempt to find the jointNode by backtracking the compomentJoints. - for index, component in ipairs(workTool.components) do - if ImplementUtil.isPartOfNode(fromNode, component.node) then - if not doReverse then - for _, joint in ipairs(workTool.componentJoints) do - if joint.componentIndices[2] == index then - if workTool.components[joint.componentIndices[1]].node == toNode then - -- node backtrack rotLimits - return joint.jointNode, { joint.jointNode }, { joint.rotLimit } - else - local node, backTrack, rotLimits = ImplementUtil.findJointNodeConnectingToNode(workTool, workTool.components[joint.componentIndices[1]].node, toNode) - if backTrack then - table.insert(backTrack, 1, joint.jointNode) - end - if rotLimits then - table.insert(rotLimits, 1, joint.rotLimit) - end - return node, backTrack, rotLimits - end - end - end - end - - -- Do Reverse in case not found - for _, joint in ipairs(workTool.componentJoints) do - if joint.componentIndices[1] == index then - if workTool.components[joint.componentIndices[2]].node == toNode then - -- node backtrack rotLimits - return joint.jointNode, { joint.jointNode }, { joint.rotLimit } - else - local node, backTrack, rotLimits = ImplementUtil.findJointNodeConnectingToNode(workTool, workTool.components[joint.componentIndices[2]].node, toNode, true) - if backTrack then - table.insert(backTrack, 1, joint.jointNode) - end - if rotLimits then - table.insert(rotLimits, 1, joint.rotLimit) - end - return node, backTrack, rotLimits - end - end - end - end - end - - -- Last attempt to find the jointNode by getting parent of parent untill hit or the there is no more parents. - if ImplementUtil.isPartOfNode(fromNode, toNode) then - return toNode, nil - end - - -- If anything else fails, return nil - return nil, nil -end - -local allowedJointTypes = {} ----@param implement table implement object -function ImplementUtil.isWheeledImplement(implement) - if #allowedJointTypes == 0 then - local jointTypeList = { "implement", "trailer", "trailerLow", "semitrailer", "trailerSaddled" } - for _, jointType in ipairs(jointTypeList) do - local index = AttacherJoints.jointTypeNameToInt[jointType] - if index then - allowedJointTypes[index] = true - end - end - end - - local activeInputAttacherJoint = implement.getActiveInputAttacherJoint and implement:getActiveInputAttacherJoint() - if activeInputAttacherJoint and allowedJointTypes[activeInputAttacherJoint.jointType] and - implement.spec_wheels and implement.spec_wheels.wheels and #implement.spec_wheels.wheels > 0 then - -- Attempt to find the pivot node. - local node, _ = ImplementUtil.findJointNodeConnectingToNode(implement, activeInputAttacherJoint.rootNode, implement.rootNode) - if node then - -- Trailers - if (activeInputAttacherJoint.jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT) - -- Implements with pivot and wheels that do not lift the wheels from the ground. - or (node ~= implement.rootNode and activeInputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_IMPLEMENT and - (not activeInputAttacherJoint.topReferenceNode or - g_vehicleConfigurations:get(implement, 'implementWheelAlwaysOnGround'))) - then - return true - end - end - end - return false -end - ----@param implement table implement object -function ImplementUtil.getLastComponentNodeWithWheels(implement) - -- Check if there is more than 1 component - local wheels = implement:getWheels() - if wheels and #wheels > 0 and #implement.components > 1 then - -- Set default node to start from. - local node = implement.rootNode - - -- Loop through all the components. - for index, component in ipairs(implement.components) do - -- Don't use the component that is the rootNode. - if component.node ~= node then - -- Loop through all the wheels and see if they are attached to this component. - for i = 1, #wheels do - if AIUtil.isRealWheel(wheels[i]) then - if ImplementUtil.isPartOfNode(wheels[i].node, component.node) then - -- Check if they are linked together - for _, joint in ipairs(implement.componentJoints) do - if joint.componentIndices[2] == index then - if implement.components[joint.componentIndices[1]].node == node then - -- Check if the component is behind the node. - local xJoint, yJoint, zJoint = getWorldTranslation(joint.jointNode) - local offset, _, direction = worldToLocal(node, xJoint, yJoint, zJoint) - --offset check to make sure we are selecting a node that is centered - if direction < 0 and offset == 0 then - -- Component is behind, so set the node to the new component node. - node = component.node - end - end - end - end - break - end - end - end - end - end - - -- Return the found node. - return node - end - - -- Return default rootNode if none is found. - return implement.rootNode -end - ----@param implement table implement object -function ImplementUtil.getRealTrailerFrontNode(implement) - local activeInputAttacherJoint = implement:getActiveInputAttacherJoint() - local jointNode, backtrack = ImplementUtil.findJointNodeConnectingToNode(implement, activeInputAttacherJoint.rootNode, implement.rootNode) - local realFrontNode - if jointNode and backtrack and activeInputAttacherJoint.jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT then - local rootNode - for _, joint in ipairs(implement.componentJoints) do - if joint.jointNode == jointNode and joint.rotLimit ~= nil and joint.rotLimit[2] ~= nil and joint.rotLimit[2] > math.rad(15) then - rootNode = implement.components[joint.componentIndices[2]].node - break - end - end - - if rootNode then - realFrontNode = CpUtil.createNode("realFrontNode", 0, 0, 0, rootNode) - local x, y, z = getWorldTranslation(jointNode) - local _, _, delta = worldToLocal(rootNode, x, y, z) - setTranslation(realFrontNode, 0, 0, delta) - end - end - - if not realFrontNode then - realFrontNode = implement.steeringAxleNode - end - - return realFrontNode -end - ----@param implement table implement object -function ImplementUtil.isAttacherModule(implement) - if implement.spec_attacherJoints.attacherJoint then - local workToolsWheels = implement:getWheels() - return (implement.spec_attacherJoints.attacherJoint.jointType == AttacherJoints.JOINTTYPE_SEMITRAILER and - (not workToolsWheels or (workToolsWheels and #workToolsWheels == 0))) - end - return false -end - ----@param dolly table implement object -function ImplementUtil.getRealDollyFrontNode(dolly) - local frontNode - local activeInputAttacherJoint = dolly:getActiveInputAttacherJoint() - local node, _ = ImplementUtil.findJointNodeConnectingToNode(dolly, activeInputAttacherJoint.rootNode, dolly.rootNode) - if node then - -- Trailers without pivote - if (node == dolly.rootNode and activeInputAttacherJoint.jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT) - -- Implements with pivot and wheels that do not lift the wheels from the ground. - or (node ~= dolly.rootNode and activeInputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_IMPLEMENT and not activeInputAttacherJoint.topReferenceNode) then - frontNode = dolly.steeringAxleNode - else - frontNode = nil - end - end - - return frontNode -end - ----@param implement table implement object -function ImplementUtil.getRealTrailerDistanceToPivot(implement) - -- Attempt to find the pivot node. - local activeInputAttacherJoint = implement:getActiveInputAttacherJoint() - local node, backTrack = ImplementUtil.findJointNodeConnectingToNode(implement, activeInputAttacherJoint.rootNode, - ImplementUtil.getLastComponentNodeWithWheels(implement)) - if node then - local x, y, z - if node == implement.rootNode then - x, y, z = getWorldTranslation(activeInputAttacherJoint.node) - else - x, y, z = getWorldTranslation(node) - end - local _, _, tz = worldToLocal(implement.steeringAxleNode, x, y, z) - return tz - else - return 3 - end -end - -function ImplementUtil.getDirectionNodeToTurnNodeLength(vehicle) - - local totalDistance = 0 - - --- If this have not been set before after last stop command, we need to reset it again. - local distances = vehicle.cp.distances - - for _, imp in ipairs(vehicle:getAttachedImplements()) do - if AIUtil.isObjectAttachedOnTheBack(vehicle, imp.object) then - local workTool = imp.object - local activeInputAttacherJoint = workTool:getActiveInputAttacherJoint() - if ImplementUtil.isWheeledImplement(workTool) then - local workToolDistances = workTool.cp.distances - - if workToolDistances.attacherJointToPivot then - totalDistance = totalDistance + workToolDistances.attacherJointToPivot - ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: attacherJointToPivot=%.2fm'):format( - nameNum(workTool), workToolDistances.attacherJointToPivot), CpDebug.DBG_IMPLEMENTS) - end - - totalDistance = totalDistance + workToolDistances.attacherJointOrPivotToTurningNode - ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: attacherJointOrPivotToTurningNode=%.2fm'):format( - nameNum(workTool), workToolDistances.attacherJointOrPivotToTurningNode), CpDebug.DBG_IMPLEMENTS) - ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: attacherJointToTurningNode=%.2fm'):format( - nameNum(workTool), totalDistance), CpDebug.DBG_IMPLEMENTS) - else - if not distances.attacherJointOrPivotToTurningNode and distances.attacherJointToRearTrailerAttacherJoints then - totalDistance = totalDistance + distances.attacherJointToRearTrailerAttacherJoints[activeInputAttacherJoint.jointType] - end - totalDistance = totalDistance + ImplementUtil.getDirectionNodeToTurnNodeLength(workTool) - --ImplementUtil.debug(('%s: directionNodeToTurnNodeLength=%.2fm'):format(nameNum(workTool), totalDistance), CpDebug.DBG_IMPLEMENTS) - end - break - end - end - - if vehicle.cp.directionNode and totalDistance > 0 then - for _, imp in ipairs(vehicle:getAttachedImplements()) do - if ImplementUtil.isRearAttached(vehicle, imp.jointDescIndex) then - local workTool = imp.object - local activeInputAttacherJoint = workTool:getActiveInputAttacherJoint() - totalDistance = totalDistance + distances.turningNodeToRearTrailerAttacherJoints[activeInputAttacherJoint.jointType] - break - end - end - vehicle.cp.directionNodeToTurnNodeLength = totalDistance - ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: directionNodeToTurnNodeLength=%.2fm'):format( - nameNum(vehicle), totalDistance), CpDebug.DBG_IMPLEMENTS) - end - - return vehicle.cp.directionNodeToTurnNodeLength or totalDistance -end - ---- Get the distance between a reference node (usually tractor's direction node) and a node on the implement, ---- considering that the implement may not be aligned with the tractor so a simple localToLocal between the ---- tractor's node and the implement node would result in errors. ---- ---- Therefore, determine the distance in two steps: ---- 1. distance between the reference node and the attacher joint of the implement ---- 2. distance between the attacher joint and the implement node ---- ----@param referenceNode number node, usually the tractor's direction node ----@param implementObject table implement object ----@param implementNode number node on the implement we want to know the distance of -function ImplementUtil.getDistanceToImplementNode(referenceNode, implementObject, implementNode) - local rootToReferenceNodeOffset = 0 - local attacherJoint = implementObject.getActiveInputAttacherJoint and implementObject:getActiveInputAttacherJoint() - if attacherJoint and attacherJoint.node then - -- the implement may not be aligned with the vehicle so we need to calculate this distance in two - -- steps, first the distance between the vehicle's root node and the attacher joint and then - -- from the attacher joint to the implement's root node - -- < 0 when the attacher joint is behind the reference node - local _, _, referenceToAttacherJoint = localToLocal(attacherJoint.node, referenceNode, 0, 0, 0) - -- > 0 when the attacher node is in front of the implement's root node (we don't use the attacher joint node - -- as a reference as it may point to any direction, we know the implement's root node points forward - local _, _, attacherJointToImplementRoot = localToLocal(attacherJoint.node, implementNode, 0, 0, 0) - -- we call this offset, and is negative when behind the reference node, positive when in front of it - -- (need to reverse attacherJointToImplementRoot) - rootToReferenceNodeOffset = -attacherJointToImplementRoot + referenceToAttacherJoint - CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, implementObject, '%s: ref to attacher joint %.1f, att to implement root %.1f, impl root to ref %.1f', - implementObject:getName(), referenceToAttacherJoint, attacherJointToImplementRoot, rootToReferenceNodeOffset) - else - _, _, rootToReferenceNodeOffset = localToLocal(implementNode, referenceNode, 0, 0, 0) - end - return rootToReferenceNodeOffset -end - --- Bale loaders / wrappers have no AI markers -function ImplementUtil.getAIMarkersFromGrabberNode(object, spec) - -- use the grabber node for all markers if exists - if spec.baleGrabber and spec.baleGrabber.grabNode then - return spec.baleGrabber.grabNode, spec.baleGrabber.grabNode, spec.baleGrabber.grabNode - else - return object.rootNode, object.rootNode, object.rootNode - end -end - ---- Is the vehicle/implement a Chopper -function ImplementUtil.isChopper(implement) - local spec = implement and implement.spec_combine - return spec and implement:getFillUnitCapacity(spec.fillUnitIndex) > 10000000 -end - ---- Moves the moving tool rotation to a given rotation target. ----@param implement table ----@param tool table moving tool ----@param dt number ----@param rotTarget number target rotation in radiant ----@param minDiffNeeded number|nil if the difference is smaller than the moving tool is stopped. ----@return boolean -function ImplementUtil.moveMovingToolToRotation(implement, tool, dt, rotTarget, minDiffNeeded) - if minDiffNeeded == nil then - minDiffNeeded = 0.03 - end - if tool.rotSpeed == nil then - return false - end - local spec = implement.spec_cylindered - tool.curRot[1], tool.curRot[2], tool.curRot[3] = getRotation(tool.node) - local oldRot = tool.curRot[tool.rotationAxis] - local diff = rotTarget - oldRot - local dir = math.sign(diff) - local rotSpeed = CpMathUtil.clamp( math.abs(diff) * math.abs(tool.rotSpeed), math.abs(tool.rotSpeed)/3, 0.5 ) - rotSpeed = dir * rotSpeed - if math.abs(diff) < minDiffNeeded or rotSpeed == 0 then - ImplementUtil.stopMovingTool(implement, tool) - return false - end - if Cylindered.setToolRotation(implement, tool, rotSpeed, dt, diff) then - Cylindered.setDirty(implement, tool) - - implement:raiseDirtyFlags(tool.dirtyFlag) - implement:raiseDirtyFlags(spec.cylinderedDirtyFlag) - return true - end - return false -end - ---- Force stops the moving tool. ----@param implement table ----@param tool table moving tool -function ImplementUtil.stopMovingTool(implement, tool) - if tool == nil or tool.move == nil then - CpUtil.error("Invalid tool called this function!") - return - end - tool.move = 0 - Cylindered.setDirty(implement, tool) - local spec = implement.spec_cylindered - implement:raiseDirtyFlags(tool.dirtyFlag) - implement:raiseDirtyFlags(spec.cylinderedDirtyFlag) - local detachLock = spec.detachLockNodes and spec.detachLockNodes[tool] - if detachLock then - --- Fix shovel detach, as shovel might have angle requirements for detaching. - --- These limits are implemented without a hysteresis ... - --- So we need to force set the limit, if a difference of less than 1 degree was found. - local node = tool.node - local rot = { - getRotation(node) - } - if detachLock.detachingRotMinLimit ~=nil and - math.abs(MathUtil.getAngleDifference(detachLock.detachingRotMinLimit, rot[tool.rotationAxis])) < math.pi/180 then - Cylindered.setAbsoluteToolRotation(implement, tool, detachLock.detachingRotMinLimit) - end - if detachLock.detachingRotMaxLimit ~= nil and - math.abs(MathUtil.getAngleDifference(detachLock.detachingRotMaxLimit, rot[tool.rotationAxis])) < math.pi/180 then - Cylindered.setAbsoluteToolRotation(implement, tool, detachLock.detachingRotMaxLimit) - end - end - -end - -function ImplementUtil.getLevelerNode(object) - return object.spec_leveler and object.spec_leveler.nodes and object.spec_leveler.nodes[1] -end - -function ImplementUtil.getShovelNode(object) - return object.spec_shovel and object.spec_shovel.shovelNodes and object.spec_shovel.shovelNodes[1] -end - ---- Visually displays the bale collector offset ----@param vehicle table ----@param offset number -function ImplementUtil.showBaleCollectorOffset(vehicle, offset) - local implement = AIUtil.getImplementWithSpecialization(vehicle, BaleLoader) - if not implement then - implement = AIUtil.getImplementWithSpecialization(vehicle, BaleWrapper) - end - if implement then - local x, y, z = localToWorld(vehicle:getAIDirectionNode(), -offset, 3, -5) - local dx, dy, dz = localToWorld(vehicle:getAIDirectionNode(), -offset, 3, 2) - DebugUtil.drawDebugLine(x, y, z, dx, dy, dz, 1, 0, 0) - end -end - ---- Checks if loading from an implement to another is possible. ----@param loadTargetImplement table ----@param implementToLoadFrom table ----@param dischargeNode table|nil optional otherwise the current selected node is used. ----@param debugFunc function|nil ----@return boolean is loading possible? ----@return number|nil target implement fill unit ix to load into. ----@return number|nil fill type to load ----@return number|nil target exact fill root node ----@return number|nil alternative fill type, when the implement gets turned on -function ImplementUtil.getCanLoadTo(loadTargetImplement, implementToLoadFrom, dischargeNode, debugFunc) - - local function debug(str, ...) - if debugFunc then - debugFunc(str, ...) - end - end - - if dischargeNode == nil then - dischargeNode = implementToLoadFrom:getCurrentDischargeNode() - end - if dischargeNode == nil then - debug("No valid discharge node found!") - return false, nil, nil, nil - end - - local fillType = implementToLoadFrom:getDischargeFillType(dischargeNode) - local alternativeFillType - if implementToLoadFrom.spec_turnOnVehicle then - --- The discharge node flips when the implement gets turned on. - --- The fill type might be different then. - local turnOnDischargeNode = implementToLoadFrom.spec_turnOnVehicle.activateableDischargeNode - if turnOnDischargeNode then - alternativeFillType = implementToLoadFrom:getDischargeFillType(turnOnDischargeNode) - end - end - if fillType == nil or fillType == FillType.UNKNOWN then - debug("No valid fill type to load!") - return false, nil, nil, nil - end - - --- Is the fill unit a valid load target? - ---@param fillUnitIndex number - ---@return boolean - ---@return number|nil - ---@return number|nil - local function canLoad(fillUnitIndex) - if not loadTargetImplement:getFillUnitSupportsFillType(fillUnitIndex, fillType) and - not loadTargetImplement:getFillUnitSupportsFillType(fillUnitIndex, alternativeFillType) then - debug("Fill unit(%d) doesn't support fill type %s", fillUnitIndex, g_fillTypeManager:getFillTypeNameByIndex(fillType)) - return false - end - if not loadTargetImplement:getFillUnitAllowsFillType(fillUnitIndex, fillType) and - not loadTargetImplement:getFillUnitAllowsFillType(fillUnitIndex, alternativeFillType) then - debug("Fill unit(%d) doesn't allow fill type %s", fillUnitIndex, g_fillTypeManager:getFillTypeNameByIndex(fillType)) - return false - end - if loadTargetImplement.getFillUnitFreeCapacity and - loadTargetImplement:getFillUnitFreeCapacity(fillUnitIndex, fillType, implementToLoadFrom:getActiveFarm()) <= 0 and - loadTargetImplement:getFillUnitFreeCapacity(fillUnitIndex, alternativeFillType, implementToLoadFrom:getActiveFarm()) <= 0 then - debug("Fill unit(%d) is full with fill type %s!", fillUnitIndex, g_fillTypeManager:getFillTypeNameByIndex(fillType)) - return false - end - if loadTargetImplement.getIsFillAllowedFromFarm and - not loadTargetImplement:getIsFillAllowedFromFarm(implementToLoadFrom:getActiveFarm()) then - debug("Fill unit(%d) filling to target farm %s from %s not allowed!", - fillUnitIndex, loadTargetImplement:getOwnerFarmId(), implementToLoadFrom:getActiveFarm()) - return false - end - local exactFillRootNode = loadTargetImplement:getFillUnitExactFillRootNode(fillUnitIndex) - if not exactFillRootNode then - debug("Fill unit(%d) has no valid exact fill root node!", fillUnitIndex) - return false - end - return true, fillUnitIndex, exactFillRootNode - end - - local validTarget, targetFillUnitIndex, exactFillRootNode - for fillUnitIndex, fillUnit in pairs(loadTargetImplement:getFillUnits()) do - validTarget, targetFillUnitIndex, exactFillRootNode = canLoad(fillUnitIndex) - if validTarget then - break - end - end - - return validTarget, targetFillUnitIndex, fillType, exactFillRootNode, alternativeFillType -end - ---- Checks the passed in implements for a fill level change ---- and also caches the current fill levels. ---- Example input table format: {["implement"]["fillUnitIndex"] = 200} ----@param fillLevelData table<table, table<number, number>> ----@param reset boolean|nil -function ImplementUtil.hasFillLevelChanged(fillLevelData, reset) - local hasChanged = false - for implement, data in pairs(fillLevelData) do - for fillUnitIndex, fillLevel in pairs(data) do - local curFillLevel = implement:getFillUnitFillLevel(fillUnitIndex) - if reset then - fillLevelData[implement][fillUnitIndex] = -1 - else - if fillLevel > -1 and curFillLevel ~= fillLevel then - hasChanged = true - end - fillLevelData[implement][fillUnitIndex] = curFillLevel - end - end - end - return hasChanged -end - ---- Trys to start loading from triggers, pallets and so on nearby. ----@param implements table<table, ...> ----@return boolean refilling is currently refilling -function ImplementUtil.tryAndCheckRefillingFillUnits(implements) - local isFilling = false - for implement, _ in pairs(implements) do - local spec = implement.spec_fillUnit - local activatable = spec.fillTrigger.activatable - if not spec.fillTrigger.isFilling then - local rootVehicle = activatable.vehicle - if rootVehicle then - local oldFunc = rootVehicle.getIsActiveForInput - rootVehicle.getIsActiveForInput = function () - return true - end - if activatable:getIsActivatable() then - activatable:run() - end - rootVehicle.getIsActiveForInput = oldFunc - end - end - isFilling = isFilling or spec.fillTrigger.isFilling - end - return isFilling +--[[ +This file is part of Courseplay (https://github.com/Courseplay/courseplay) +Copyright (C) 2021 Peter Vaiko + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +]] + +--- +--- Implement utilities for the Courseplay AI + +---@class ImplementUtil +ImplementUtil = {} + +function ImplementUtil.isPartOfNode(node, parentNode) + -- Check if Node is part of partOfNode and not in a different component + while node ~= 0 and node ~= nil do + if node == parentNode then + return true + else + node = getParent(node) + end + end + return false +end + +--- ImplementUtil.findJointNodeConnectingToNode(workTool, fromNode, toNode, doReverse) +-- Returns: (node, backtrack, rotLimits) +-- node will return either: 1. The jointNode that connects to the toNode, +-- 2. The toNode if no jointNode is found but the fromNode is inside the same component as the toNode +-- 3. nil in case none of the above fails. +-- backTrack will return either: 1. A table of all the jointNodes found from fromNode to toNode, if the jointNode that connects to the toNode is found. +-- 2: nil if no jointNode is found. +-- rotLimits will return either: 1. A table of all the rotLimits of the componentJoint, found from fromNode to toNode, if the jointNode that connects to the toNode is found. +-- 2: nil if no jointNode is found. +function ImplementUtil.findJointNodeConnectingToNode(workTool, fromNode, toNode, doReverse) + if fromNode == toNode then + return toNode + end + + -- Attempt to find the jointNode by backtracking the compomentJoints. + for index, component in ipairs(workTool.components) do + if ImplementUtil.isPartOfNode(fromNode, component.node) then + if not doReverse then + for _, joint in ipairs(workTool.componentJoints) do + if joint.componentIndices[2] == index then + if workTool.components[joint.componentIndices[1]].node == toNode then + -- node backtrack rotLimits + return joint.jointNode, { joint.jointNode }, { joint.rotLimit } + else + local node, backTrack, rotLimits = ImplementUtil.findJointNodeConnectingToNode(workTool, workTool.components[joint.componentIndices[1]].node, toNode) + if backTrack then + table.insert(backTrack, 1, joint.jointNode) + end + if rotLimits then + table.insert(rotLimits, 1, joint.rotLimit) + end + return node, backTrack, rotLimits + end + end + end + end + + -- Do Reverse in case not found + for _, joint in ipairs(workTool.componentJoints) do + if joint.componentIndices[1] == index then + if workTool.components[joint.componentIndices[2]].node == toNode then + -- node backtrack rotLimits + return joint.jointNode, { joint.jointNode }, { joint.rotLimit } + else + local node, backTrack, rotLimits = ImplementUtil.findJointNodeConnectingToNode(workTool, workTool.components[joint.componentIndices[2]].node, toNode, true) + if backTrack then + table.insert(backTrack, 1, joint.jointNode) + end + if rotLimits then + table.insert(rotLimits, 1, joint.rotLimit) + end + return node, backTrack, rotLimits + end + end + end + end + end + + -- Last attempt to find the jointNode by getting parent of parent untill hit or the there is no more parents. + if ImplementUtil.isPartOfNode(fromNode, toNode) then + return toNode, nil + end + + -- If anything else fails, return nil + return nil, nil +end + +local allowedJointTypes = {} +---@param implement table implement object +function ImplementUtil.isWheeledImplement(implement) + if #allowedJointTypes == 0 then + local jointTypeList = { "implement", "trailer", "trailerLow", "semitrailer", "trailerSaddled" } + for _, jointType in ipairs(jointTypeList) do + local index = AttacherJoints.jointTypeNameToInt[jointType] + if index then + allowedJointTypes[index] = true + end + end + end + + local activeInputAttacherJoint = implement.getActiveInputAttacherJoint and implement:getActiveInputAttacherJoint() + if activeInputAttacherJoint and allowedJointTypes[activeInputAttacherJoint.jointType] and + implement.spec_wheels and implement.spec_wheels.wheels and #implement.spec_wheels.wheels > 0 then + -- Attempt to find the pivot node. + local node, _ = ImplementUtil.findJointNodeConnectingToNode(implement, activeInputAttacherJoint.rootNode, implement.rootNode) + if node then + -- Trailers + if (activeInputAttacherJoint.jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT) + -- Implements with pivot and wheels that do not lift the wheels from the ground. + or (node ~= implement.rootNode and activeInputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_IMPLEMENT and + (not activeInputAttacherJoint.topReferenceNode or + g_vehicleConfigurations:get(implement, 'implementWheelAlwaysOnGround'))) + then + return true + end + end + end + return false +end + +---@param implement table implement object +function ImplementUtil.getLastComponentNodeWithWheels(implement) + -- Check if there is more than 1 component + local wheels = implement:getWheels() + if wheels and #wheels > 0 and #implement.components > 1 then + -- Set default node to start from. + local node = implement.rootNode + + -- Loop through all the components. + for index, component in ipairs(implement.components) do + -- Don't use the component that is the rootNode. + if component.node ~= node then + -- Loop through all the wheels and see if they are attached to this component. + for i = 1, #wheels do + if AIUtil.isRealWheel(wheels[i]) then + if ImplementUtil.isPartOfNode(wheels[i].node, component.node) then + -- Check if they are linked together + for _, joint in ipairs(implement.componentJoints) do + if joint.componentIndices[2] == index then + if implement.components[joint.componentIndices[1]].node == node then + -- Check if the component is behind the node. + local xJoint, yJoint, zJoint = getWorldTranslation(joint.jointNode) + local offset, _, direction = worldToLocal(node, xJoint, yJoint, zJoint) + --offset check to make sure we are selecting a node that is centered + if direction < 0 and offset == 0 then + -- Component is behind, so set the node to the new component node. + node = component.node + end + end + end + end + break + end + end + end + end + end + + -- Return the found node. + return node + end + + -- Return default rootNode if none is found. + return implement.rootNode +end + +---@param implement table implement object +function ImplementUtil.getRealTrailerFrontNode(implement) + local activeInputAttacherJoint = implement:getActiveInputAttacherJoint() + local jointNode, backtrack = ImplementUtil.findJointNodeConnectingToNode(implement, activeInputAttacherJoint.rootNode, implement.rootNode) + local realFrontNode + if jointNode and backtrack and activeInputAttacherJoint.jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT then + local rootNode + for _, joint in ipairs(implement.componentJoints) do + if joint.jointNode == jointNode and joint.rotLimit ~= nil and joint.rotLimit[2] ~= nil and joint.rotLimit[2] > math.rad(15) then + rootNode = implement.components[joint.componentIndices[2]].node + break + end + end + + if rootNode then + realFrontNode = CpUtil.createNode("realFrontNode", 0, 0, 0, rootNode) + local x, y, z = getWorldTranslation(jointNode) + local _, _, delta = worldToLocal(rootNode, x, y, z) + setTranslation(realFrontNode, 0, 0, delta) + end + end + + if not realFrontNode then + realFrontNode = implement.steeringAxleNode + end + + return realFrontNode +end + +---@param implement table implement object +function ImplementUtil.isAttacherModule(implement) + if implement.spec_attacherJoints.attacherJoint then + local workToolsWheels = implement:getWheels() + return (implement.spec_attacherJoints.attacherJoint.jointType == AttacherJoints.JOINTTYPE_SEMITRAILER and + (not workToolsWheels or (workToolsWheels and #workToolsWheels == 0))) + end + return false +end + +---@param dolly table implement object +function ImplementUtil.getRealDollyFrontNode(dolly) + local frontNode + local activeInputAttacherJoint = dolly:getActiveInputAttacherJoint() + local node, _ = ImplementUtil.findJointNodeConnectingToNode(dolly, activeInputAttacherJoint.rootNode, dolly.rootNode) + if node then + -- Trailers without pivote + if (node == dolly.rootNode and activeInputAttacherJoint.jointType ~= AttacherJoints.JOINTTYPE_IMPLEMENT) + -- Implements with pivot and wheels that do not lift the wheels from the ground. + or (node ~= dolly.rootNode and activeInputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_IMPLEMENT and not activeInputAttacherJoint.topReferenceNode) then + frontNode = dolly.steeringAxleNode + else + frontNode = nil + end + end + + return frontNode +end + +---@param implement table implement object +function ImplementUtil.getRealTrailerDistanceToPivot(implement) + -- Attempt to find the pivot node. + local activeInputAttacherJoint = implement:getActiveInputAttacherJoint() + local node, backTrack = ImplementUtil.findJointNodeConnectingToNode(implement, activeInputAttacherJoint.rootNode, + ImplementUtil.getLastComponentNodeWithWheels(implement)) + if node then + local x, y, z + if node == implement.rootNode then + x, y, z = getWorldTranslation(activeInputAttacherJoint.node) + else + x, y, z = getWorldTranslation(node) + end + local _, _, tz = worldToLocal(implement.steeringAxleNode, x, y, z) + return tz + else + return 3 + end +end + +function ImplementUtil.getDirectionNodeToTurnNodeLength(vehicle) + + local totalDistance = 0 + + --- If this have not been set before after last stop command, we need to reset it again. + local distances = vehicle.cp.distances + + for _, imp in ipairs(vehicle:getAttachedImplements()) do + if AIUtil.isObjectAttachedOnTheBack(vehicle, imp.object) then + local workTool = imp.object + local activeInputAttacherJoint = workTool:getActiveInputAttacherJoint() + if ImplementUtil.isWheeledImplement(workTool) then + local workToolDistances = workTool.cp.distances + + if workToolDistances.attacherJointToPivot then + totalDistance = totalDistance + workToolDistances.attacherJointToPivot + ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: attacherJointToPivot=%.2fm'):format( + nameNum(workTool), workToolDistances.attacherJointToPivot), CpDebug.DBG_IMPLEMENTS) + end + + totalDistance = totalDistance + workToolDistances.attacherJointOrPivotToTurningNode + ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: attacherJointOrPivotToTurningNode=%.2fm'):format( + nameNum(workTool), workToolDistances.attacherJointOrPivotToTurningNode), CpDebug.DBG_IMPLEMENTS) + ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: attacherJointToTurningNode=%.2fm'):format( + nameNum(workTool), totalDistance), CpDebug.DBG_IMPLEMENTS) + else + if not distances.attacherJointOrPivotToTurningNode and distances.attacherJointToRearTrailerAttacherJoints then + totalDistance = totalDistance + distances.attacherJointToRearTrailerAttacherJoints[activeInputAttacherJoint.jointType] + end + totalDistance = totalDistance + ImplementUtil.getDirectionNodeToTurnNodeLength(workTool) + --ImplementUtil.debug(('%s: directionNodeToTurnNodeLength=%.2fm'):format(nameNum(workTool), totalDistance), CpDebug.DBG_IMPLEMENTS) + end + break + end + end + + if vehicle.cp.directionNode and totalDistance > 0 then + for _, imp in ipairs(vehicle:getAttachedImplements()) do + if ImplementUtil.isRearAttached(vehicle, imp.jointDescIndex) then + local workTool = imp.object + local activeInputAttacherJoint = workTool:getActiveInputAttacherJoint() + totalDistance = totalDistance + distances.turningNodeToRearTrailerAttacherJoints[activeInputAttacherJoint.jointType] + break + end + end + vehicle.cp.directionNodeToTurnNodeLength = totalDistance + ImplementUtil.debug(('getDirectionNodeToTurnNodeLength() -> %s: directionNodeToTurnNodeLength=%.2fm'):format( + nameNum(vehicle), totalDistance), CpDebug.DBG_IMPLEMENTS) + end + + return vehicle.cp.directionNodeToTurnNodeLength or totalDistance +end + +--- Get the distance between a reference node (usually tractor's direction node) and a node on the implement, +--- considering that the implement may not be aligned with the tractor so a simple localToLocal between the +--- tractor's node and the implement node would result in errors. +--- +--- Therefore, determine the distance in two steps: +--- 1. distance between the reference node and the attacher joint of the implement +--- 2. distance between the attacher joint and the implement node +--- +---@param referenceNode number node, usually the tractor's direction node +---@param implementObject table implement object +---@param implementNode number node on the implement we want to know the distance of +function ImplementUtil.getDistanceToImplementNode(referenceNode, implementObject, implementNode) + local rootToReferenceNodeOffset = 0 + local attacherJoint = implementObject.getActiveInputAttacherJoint and implementObject:getActiveInputAttacherJoint() + if attacherJoint and attacherJoint.node then + -- the implement may not be aligned with the vehicle so we need to calculate this distance in two + -- steps, first the distance between the vehicle's root node and the attacher joint and then + -- from the attacher joint to the implement's root node + -- < 0 when the attacher joint is behind the reference node + local _, _, referenceToAttacherJoint = localToLocal(attacherJoint.node, referenceNode, 0, 0, 0) + -- > 0 when the attacher node is in front of the implement's root node (we don't use the attacher joint node + -- as a reference as it may point to any direction, we know the implement's root node points forward + local _, _, attacherJointToImplementRoot = localToLocal(attacherJoint.node, implementNode, 0, 0, 0) + -- we call this offset, and is negative when behind the reference node, positive when in front of it + -- (need to reverse attacherJointToImplementRoot) + rootToReferenceNodeOffset = -attacherJointToImplementRoot + referenceToAttacherJoint + CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, implementObject, '%s: ref to attacher joint %.1f, att to implement root %.1f, impl root to ref %.1f', + implementObject:getName(), referenceToAttacherJoint, attacherJointToImplementRoot, rootToReferenceNodeOffset) + else + _, _, rootToReferenceNodeOffset = localToLocal(implementNode, referenceNode, 0, 0, 0) + end + return rootToReferenceNodeOffset +end + +-- Bale loaders / wrappers have no AI markers +function ImplementUtil.getAIMarkersFromGrabberNode(object, spec) + -- use the grabber node for all markers if exists + if spec.baleGrabber and spec.baleGrabber.grabNode then + return spec.baleGrabber.grabNode, spec.baleGrabber.grabNode, spec.baleGrabber.grabNode + else + return object.rootNode, object.rootNode, object.rootNode + end +end + +--- Is the vehicle/implement a Chopper +function ImplementUtil.isChopper(implement) + local spec = implement and implement.spec_combine + return spec and implement:getFillUnitCapacity(spec.fillUnitIndex) > 10000000 +end + +--- Moves the moving tool rotation to a given rotation target. +---@param implement table +---@param tool table moving tool +---@param dt number +---@param rotTarget number target rotation in radiant +---@param minDiffNeeded number|nil if the difference is smaller than the moving tool is stopped. +---@return boolean +function ImplementUtil.moveMovingToolToRotation(implement, tool, dt, rotTarget, minDiffNeeded) + if minDiffNeeded == nil then + minDiffNeeded = 0.03 + end + if tool.rotSpeed == nil then + return false + end + local spec = implement.spec_cylindered + tool.curRot[1], tool.curRot[2], tool.curRot[3] = getRotation(tool.node) + local oldRot = tool.curRot[tool.rotationAxis] + local diff = rotTarget - oldRot + local dir = math.sign(diff) + local rotSpeed = CpMathUtil.clamp( math.abs(diff) * math.abs(tool.rotSpeed), math.abs(tool.rotSpeed)/3, 0.5 ) + rotSpeed = dir * rotSpeed + if math.abs(diff) < minDiffNeeded or rotSpeed == 0 then + ImplementUtil.stopMovingTool(implement, tool) + return false + end + if Cylindered.setToolRotation(implement, tool, rotSpeed, dt, diff) then + Cylindered.setDirty(implement, tool) + + implement:raiseDirtyFlags(tool.dirtyFlag) + implement:raiseDirtyFlags(spec.cylinderedDirtyFlag) + return true + end + return false +end + +--- Force stops the moving tool. +---@param implement table +---@param tool table moving tool +function ImplementUtil.stopMovingTool(implement, tool) + if tool == nil or tool.move == nil then + CpUtil.error("Invalid tool called this function!") + return + end + tool.move = 0 + Cylindered.setDirty(implement, tool) + local spec = implement.spec_cylindered + implement:raiseDirtyFlags(tool.dirtyFlag) + implement:raiseDirtyFlags(spec.cylinderedDirtyFlag) + local detachLock = spec.detachLockNodes and spec.detachLockNodes[tool] + if detachLock then + --- Fix shovel detach, as shovel might have angle requirements for detaching. + --- These limits are implemented without a hysteresis ... + --- So we need to force set the limit, if a difference of less than 1 degree was found. + local node = tool.node + local rot = { + getRotation(node) + } + if detachLock.detachingRotMinLimit ~=nil and + math.abs(MathUtil.getAngleDifference(detachLock.detachingRotMinLimit, rot[tool.rotationAxis])) < math.pi/180 then + Cylindered.setAbsoluteToolRotation(implement, tool, detachLock.detachingRotMinLimit) + end + if detachLock.detachingRotMaxLimit ~= nil and + math.abs(MathUtil.getAngleDifference(detachLock.detachingRotMaxLimit, rot[tool.rotationAxis])) < math.pi/180 then + Cylindered.setAbsoluteToolRotation(implement, tool, detachLock.detachingRotMaxLimit) + end + end + +end + +function ImplementUtil.getLevelerNode(object) + return object.spec_leveler and object.spec_leveler.nodes and object.spec_leveler.nodes[1] +end + +function ImplementUtil.getShovelNode(object) + return object.spec_shovel and object.spec_shovel.shovelNodes and object.spec_shovel.shovelNodes[1] +end + +--- Visually displays the bale collector offset +---@param vehicle table +---@param offset number +function ImplementUtil.showBaleCollectorOffset(vehicle, offset) + local implement = AIUtil.getImplementWithSpecialization(vehicle, BaleLoader) + if not implement then + implement = AIUtil.getImplementWithSpecialization(vehicle, BaleWrapper) + end + if implement then + local x, y, z = localToWorld(vehicle:getAIDirectionNode(), -offset, 3, -5) + local dx, dy, dz = localToWorld(vehicle:getAIDirectionNode(), -offset, 3, 2) + DebugUtil.drawDebugLine(x, y, z, dx, dy, dz, 1, 0, 0) + end +end + +--- Checks if loading from an implement to another is possible. +---@param loadTargetImplement table +---@param implementToLoadFrom table +---@param dischargeNode table|nil optional otherwise the current selected node is used. +---@param debugFunc function|nil +---@return boolean is loading possible? +---@return number|nil target implement fill unit ix to load into. +---@return number|nil fill type to load +---@return number|nil target exact fill root node +---@return number|nil alternative fill type, when the implement gets turned on +function ImplementUtil.getCanLoadTo(loadTargetImplement, implementToLoadFrom, dischargeNode, debugFunc) + + local function debug(str, ...) + if debugFunc then + debugFunc(str, ...) + end + end + + if dischargeNode == nil then + dischargeNode = implementToLoadFrom:getCurrentDischargeNode() + end + if dischargeNode == nil then + debug("No valid discharge node found!") + return false, nil, nil, nil + end + + local fillType = implementToLoadFrom:getDischargeFillType(dischargeNode) + local alternativeFillType + if implementToLoadFrom.spec_turnOnVehicle then + --- The discharge node flips when the implement gets turned on. + --- The fill type might be different then. + local turnOnDischargeNode = implementToLoadFrom.spec_turnOnVehicle.activateableDischargeNode + if turnOnDischargeNode then + alternativeFillType = implementToLoadFrom:getDischargeFillType(turnOnDischargeNode) + end + end + if fillType == nil or fillType == FillType.UNKNOWN then + debug("No valid fill type to load!") + return false, nil, nil, nil + end + + --- Is the fill unit a valid load target? + ---@param fillUnitIndex number + ---@return boolean + ---@return number|nil + ---@return number|nil + local function canLoad(fillUnitIndex) + if not loadTargetImplement:getFillUnitSupportsFillType(fillUnitIndex, fillType) and + not loadTargetImplement:getFillUnitSupportsFillType(fillUnitIndex, alternativeFillType) then + debug("Fill unit(%d) doesn't support fill type %s", fillUnitIndex, g_fillTypeManager:getFillTypeNameByIndex(fillType)) + return false + end + if not loadTargetImplement:getFillUnitAllowsFillType(fillUnitIndex, fillType) and + not loadTargetImplement:getFillUnitAllowsFillType(fillUnitIndex, alternativeFillType) then + debug("Fill unit(%d) doesn't allow fill type %s", fillUnitIndex, g_fillTypeManager:getFillTypeNameByIndex(fillType)) + return false + end + if loadTargetImplement.getFillUnitFreeCapacity and + loadTargetImplement:getFillUnitFreeCapacity(fillUnitIndex, fillType, implementToLoadFrom:getActiveFarm()) <= 0 and + loadTargetImplement:getFillUnitFreeCapacity(fillUnitIndex, alternativeFillType, implementToLoadFrom:getActiveFarm()) <= 0 then + debug("Fill unit(%d) is full with fill type %s!", fillUnitIndex, g_fillTypeManager:getFillTypeNameByIndex(fillType)) + return false + end + if loadTargetImplement.getIsFillAllowedFromFarm and + not loadTargetImplement:getIsFillAllowedFromFarm(implementToLoadFrom:getActiveFarm()) then + debug("Fill unit(%d) filling to target farm %s from %s not allowed!", + fillUnitIndex, loadTargetImplement:getOwnerFarmId(), implementToLoadFrom:getActiveFarm()) + return false + end + local exactFillRootNode = loadTargetImplement:getFillUnitExactFillRootNode(fillUnitIndex) + if not exactFillRootNode then + debug("Fill unit(%d) has no valid exact fill root node!", fillUnitIndex) + return false + end + return true, fillUnitIndex, exactFillRootNode + end + + local validTarget, targetFillUnitIndex, exactFillRootNode + for fillUnitIndex, fillUnit in pairs(loadTargetImplement:getFillUnits()) do + validTarget, targetFillUnitIndex, exactFillRootNode = canLoad(fillUnitIndex) + if validTarget then + break + end + end + + return validTarget, targetFillUnitIndex, fillType, exactFillRootNode, alternativeFillType +end + +--- Checks the passed in implements for a fill level change +--- and also caches the current fill levels. +--- Example input table format: {["implement"]["fillUnitIndex"] = 200} +---@param fillLevelData table<table, table<number, number>> +---@param reset boolean|nil +function ImplementUtil.hasFillLevelChanged(fillLevelData, reset) + local hasChanged = false + for implement, data in pairs(fillLevelData) do + for fillUnitIndex, fillLevel in pairs(data) do + local curFillLevel = implement:getFillUnitFillLevel(fillUnitIndex) + if reset then + fillLevelData[implement][fillUnitIndex] = -1 + else + if fillLevel > -1 and curFillLevel ~= fillLevel then + hasChanged = true + end + fillLevelData[implement][fillUnitIndex] = curFillLevel + end + end + end + return hasChanged +end + +--- Trys to start loading from triggers, pallets and so on nearby. +---@param implements table<table, ...> +---@return boolean refilling is currently refilling +function ImplementUtil.tryAndCheckRefillingFillUnits(implements) + local isFilling = false + for implement, _ in pairs(implements) do + local spec = implement.spec_fillUnit + local activatable = spec.fillTrigger.activatable + if not spec.fillTrigger.isFilling then + local rootVehicle = activatable.vehicle + if rootVehicle then + local oldFunc = rootVehicle.getIsActiveForInput + rootVehicle.getIsActiveForInput = function () + return true + end + if activatable:getIsActivatable() then + activatable:run() + end + rootVehicle.getIsActiveForInput = oldFunc + end + end + isFilling = isFilling or spec.fillTrigger.isFilling + end + return isFilling end \ No newline at end of file From 86808ea10173ea33fa6e5ec97ac25d7c90781c56 Mon Sep 17 00:00:00 2001 From: David Schwietering <d-schwietering@gmx.de> Date: Sun, 22 Dec 2024 12:36:55 +0100 Subject: [PATCH 155/158] forgot workwidth util .. --- modDesc.xml | 2 +- scripts/ai/{ => util}/WorkWidthUtil.lua | 684 ++++++++++++------------ 2 files changed, 343 insertions(+), 343 deletions(-) rename scripts/ai/{ => util}/WorkWidthUtil.lua (97%) diff --git a/modDesc.xml b/modDesc.xml index fd542c075..b5e25f2a8 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -169,6 +169,7 @@ Changelog 8.0.0.0: <sourceFile filename="scripts/ai/util/AIUtil.lua"/> <sourceFile filename="scripts/ai/util/FillLevelUtil.lua"/> <sourceFile filename="scripts/ai/util/ImplementUtil.lua"/> + <sourceFile filename="scripts/ai/util/WorkWidthUtil.lua"/> <sourceFile filename="scripts/ai/PathfinderController.lua"/> <sourceFile filename="scripts/ai/ProximityController.lua"/> <sourceFile filename="scripts/ai/FieldWorkerProximityController.lua"/> @@ -181,7 +182,6 @@ Changelog 8.0.0.0: <sourceFile filename="scripts/ai/PurePursuitController.lua"/> <sourceFile filename="scripts/ai/SelfUnloadHelper.lua"/> <sourceFile filename="scripts/ai/VehicleScanner.lua"/> - <sourceFile filename="scripts/ai/WorkWidthUtil.lua"/> <sourceFile filename="scripts/ai/AIReverseDriver.lua"/> <sourceFile filename="scripts/ai/turns/AITurn.lua"/> diff --git a/scripts/ai/WorkWidthUtil.lua b/scripts/ai/util/WorkWidthUtil.lua similarity index 97% rename from scripts/ai/WorkWidthUtil.lua rename to scripts/ai/util/WorkWidthUtil.lua index 9878849e4..d7eb7db9c 100644 --- a/scripts/ai/WorkWidthUtil.lua +++ b/scripts/ai/util/WorkWidthUtil.lua @@ -1,342 +1,342 @@ ----@class WorkWidthUtil -WorkWidthUtil = {} - ---- Iterator for all work areas of an object ----@param object table -function WorkWidthUtil.workAreaIterator(object) - local i = 0 - return function() - i = i + 1 - local wa = WorkWidthUtil.hasValidWorkArea(object) and object:getWorkAreaByIndex(i) - if wa then - return i, wa - end - end -end - ---- Gets work areas if possible. ----@param object table -function WorkWidthUtil.hasWorkAreas(object) - return WorkWidthUtil.hasValidWorkArea(object) and object:getWorkAreaByIndex(1) -end - -function WorkWidthUtil.hasValidWorkArea(object) - return object and object.getWorkAreaByIndex and object.spec_workArea.workAreas -end - ---- Gets the working width and offset calculated from all built-in and attached implements. If an implement has ---- a width or offset configured, it takes precedence over the calculated values. ---- ---- Working width can be tricky if there are multiple implements attached. There is no guarantee that all work areas are ---- in the centerline of the vehicle, for instance mowers can be offset on one side. To get the true working width, we must ---- collect distance of the work area edges left and right for all implements and use the maximum left/right values to ---- calculate the width. This also results in an offset, which is the distance between the vehicle's centerline and the ---- middle of the work area. ---- ----@param object table ----@param referenceNode number the node for calculating the work width, if not supplied, use the object's root node ----@param ignoreObject table ignore this object when calculating the width (as it is being detached, for instance) ----@return number width ----@return number offset ----@return number|nil left ----@return number|nil right -function WorkWidthUtil.getAutomaticWorkWidthAndOffset(object, referenceNode, ignoreObject) - -- when first called for the vehicle, referenceNode is empty, so use the vehicle root node - referenceNode = referenceNode or object.rootNode - WorkWidthUtil.debug(object, 'getting working width...') - -- check if we have a manually configured working width - local configuredWidth = g_vehicleConfigurations:get(object, 'workingWidth') - local configuredOffset = g_vehicleConfigurations:get(object, 'toolOffsetX') - - local left, right - - if object.getVariableWorkWidth then - --- Gets the variable work width to the left + to the right. - local w1, _, isValid1 = object:getVariableWorkWidth(true) - local w2, _, isValid2 = object:getVariableWorkWidth() - if isValid1 and isValid2 then - left, right = w1, w2 - local width = math.abs(w1) + math.abs(w2) - WorkWidthUtil.debug(object, 'left = %.1f, right = %.1f, setting variable work width of %.1f.', - w1, w2, width) - end - end - - if object.spec_dynamicMountAttacher then - --- Enables fetching the work with of cutter, - --- which are attached on an separate header trailer - --- at the back of the harvester. - local spec = object.spec_dynamicMountAttacher - local obj = next(spec.dynamicMountedObjects) - if obj then - if obj.spec_cutter then - WorkWidthUtil.debug(obj, 'Using this cutter instead of the header trailer %s attached.', - CpUtil.getName(object)) - object = obj - referenceNode = obj.rootNode - end - end - end - - --- Work width for soil samplers. - if not left and object.spec_soilSampler then - if object.spec_soilSampler.samplingRadius then - local width = 2 * object.spec_soilSampler.samplingRadius / math.sqrt(2) - left, right = width / 2, -width / 2 - WorkWidthUtil.debug(object, 'using soil sampler width of %.1f (from sampling radius).', width) - else - WorkWidthUtil.debug(object, 'soil sampler has no sampling radius, can\'t calculate width') - end - end - - --- Work width levelers - if not left then - local width = WorkWidthUtil.getShieldWorkWidth(object) - if width then - left, right = width / 2, -width / 2 - end - end - - if not left then - -- no manual config, check AI markers - _, left, right = WorkWidthUtil.getAIMarkerWidth(object, referenceNode) - end - - if not left then - if WorkWidthUtil.hasWorkAreas(object) then - -- no AI markers, check work areas - left, right = WorkWidthUtil.getWorkAreaWidth(object, referenceNode) - if not left then - WorkWidthUtil.debug(object, 'has NO valid work areas') - end - else - WorkWidthUtil.debug(object, 'has NO work areas') - end - end - - local implements = object.getAttachedImplements and object:getAttachedImplements() - if implements then - -- get width of all implements - for _, implement in ipairs(implements) do - if implement.object ~= ignoreObject then - local _, _, thisLeft, thisRight = WorkWidthUtil.getAutomaticWorkWidthAndOffset(implement.object) - if thisLeft ~= nil and thisRight ~= nil then - left = math.max(thisLeft, left or -math.huge) - right = math.min(thisRight, right or math.huge) - end - end - end - end - - -- left > 0, right < 0. Offset > 0 and offset < 0 when the center line of all work areas are to the left and right, - -- respectively, of the vehicle. - local width, offset - if configuredWidth then - width = configuredWidth - -- for now, assuming offset 0 - left = width / 2 - right = - width / 2 - WorkWidthUtil.debug(object, 'using configured working width of %.1f, resulting left/right is %.1f/%.1f.', - configuredWidth, left, right) - elseif left and right then - width = left - right - WorkWidthUtil.debug(object, 'working width is %.1f, left %.1f, right %.1f.', width, left, right) - elseif not width then - width = 0 - left = nil - right = nil - WorkWidthUtil.debug(object, 'could not determine working width') - end - - if configuredOffset then - offset = configuredOffset - if width == 0 then - -- some vine tools have no working width but we do have a configured offset. Make sure that - -- the vehicle will inherit this offset by returning a left, right pair at offset - left, right = offset, offset - else - left = width / 2 + offset - right = - width / 2 + offset - end - WorkWidthUtil.debug(object, 'using configured tool offset of %.1f, resulting left/right is %.1f/%.1f.', - configuredOffset, left, right) - elseif width and left and right then - offset = left - width / 2 - WorkWidthUtil.debug(object, 'calculated tool offset is %.1f.', offset) - else - offset = 0 - WorkWidthUtil.debug(object, 'could not determine offset, using 0') - end - - return width, offset, left, right -end - ----@param object table -function WorkWidthUtil.getWorkAreaWidth(object, referenceNode) - -- TODO: check if there's a better way to find out if the implement has a work area - local hasValidWorkArea, maxLeft, minRight = false, -math.huge, math.huge - for i, wa in WorkWidthUtil.workAreaIterator(object) do - if WorkWidthUtil.isValidWorkArea(wa) then - hasValidWorkArea = true - -- work areas are defined by three nodes: start, width and height. These nodes - -- define a rectangular work area which you can make visible with the - -- gsVehicleDebugAttributes console command and then pressing F5 - local left, _, _ = localToLocal(wa.start, referenceNode, 0, 0, 0) - local right, _, _ = localToLocal(wa.width, referenceNode, 0, 0, 0) - maxLeft = math.max(maxLeft, left) - minRight = math.min(minRight, right) - WorkWidthUtil.debug(object, 'work area %d is %s, left = %.1f, right %.1f m', - i, g_workAreaTypeManager.workAreaTypes[wa.type].name, left, right) - end - end - if hasValidWorkArea then - return maxLeft, minRight - else - return nil, nil - end -end - ----@param object table -function WorkWidthUtil.getAIMarkerWidth(object, referenceNode) - if object.getAIMarkers then - local aiLeftMarker, aiRightMarker = object:getAIMarkers() - if aiLeftMarker and aiRightMarker then - -- left/right is just for the log - local left, _, _ = localToLocal(aiLeftMarker, referenceNode, 0, 0, 0) - local right, _, _ = localToLocal(aiRightMarker, referenceNode, 0, 0, 0) - local width = calcDistanceFrom(aiLeftMarker, aiRightMarker) - WorkWidthUtil.debug(object, 'aiMarkers: left=%.2f, right=%.2f (width %.2f)', left, right, width) - return width, left, right - end - end -end - ---- Gets ai markers for an object. ----@param object table -function WorkWidthUtil.getAIMarkers(object, suppressLog) - local aiLeftMarker, aiRightMarker, aiBackMarker = object:getAIMarkers() - if not aiLeftMarker or not aiRightMarker or not aiBackMarker then - -- use the root node if there are no AI markers - if not suppressLog then - WorkWidthUtil.debug(object, 'has no AI markers, try work areas') - end - aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkersFromWorkAreas(object, suppressLog) - if not aiLeftMarker or not aiRightMarker or not aiLeftMarker then - if g_vehicleConfigurations:get(object, 'useVehicleSizeForMarkers') or object.spec_leveler then - if not suppressLog then - WorkWidthUtil.debug(object, 'has no work areas, configured to use front/back markers') - end - return Markers.getFrontMarkerNode(object), Markers.getFrontMarkerNode(object), Markers.getBackMarkerNode(object) - else - if not suppressLog then - WorkWidthUtil.debug(object, 'has no work areas, giving up') - end - return nil, nil, nil - end - else - if not suppressLog then - WorkWidthUtil.debug(object, 'AI markers from work area set') - end - return aiLeftMarker, aiRightMarker, aiBackMarker - end - else - if not suppressLog then - WorkWidthUtil.debug(object, 'AI markers set') - end - return aiLeftMarker, aiRightMarker, aiBackMarker - end -end - ---- Calculate the front and back marker nodes of a work area ----@param object table -function WorkWidthUtil.getAIMarkersFromWorkAreas(object, suppressLog) - -- work areas are defined by three nodes: start, width and height. These nodes - -- define a rectangular work area which you can make visible with the - -- gsVehicleDebugAttributes console command and then pressing F5 - for _, area in WorkWidthUtil.workAreaIterator(object) do - if WorkWidthUtil.isValidWorkArea(area) then - -- for now, just use the first valid work area we find - if not suppressLog then - WorkWidthUtil.debug(object, 'Using %s work area markers as AIMarkers', - g_workAreaTypeManager.workAreaTypes[area.type].name) - end - return area.start, area.width, area.height - end - end -end - ----@param area table -function WorkWidthUtil.isValidWorkArea(area) - return area.start and area.height and area.width and - area.type ~= WorkAreaType.RIDGEMARKER and - area.type ~= WorkAreaType.COMBINESWATH and - area.type ~= WorkAreaType.COMBINECHOPPER -end - ----@param object table -function WorkWidthUtil.getShieldWorkWidth(object) - if object.spec_leveler and object.spec_leveler.nodes and object.spec_leveler.nodes[1] then - local width = object.spec_leveler.nodes[1].width - WorkWidthUtil.debug(object, 'is a shield with width: %.1f', width) - return width - end -end - ----@param object table -function WorkWidthUtil.getShovelWorkWidth(object) - if object.spec_shovel and object.spec_shovel.shovelNodes and object.spec_shovel.shovelNodes[1] then - local width = object.spec_shovel.shovelNodes[1].width - WorkWidthUtil.debug(object, 'is a shovel with width: %.1f', width) - return width - end -end - ---- Shows the current work width selected with the tool offsets applied. ----@param vehicle table ----@param workWidth number ----@param offsX number ----@param offsZ number -function WorkWidthUtil.showWorkWidth(vehicle, workWidth, offsX, offsZ) - local firstObject = AIUtil.getFirstAttachedImplement(vehicle, true) - local lastObject = AIUtil.getLastAttachedImplement(vehicle, true) - - local function show(object, workWidth, offsX, offsZ) - if object == nil then - return - end - local f, b = 0, 0 - local aiLeftMarker, _, aiBackMarker = object:getAIMarkers() - if aiLeftMarker and aiBackMarker then - _, _, b = localToLocal(aiBackMarker, object.rootNode, 0, 0, 0) - _, _, f = localToLocal(aiLeftMarker, object.rootNode, 0, 0, 0) - end - - local left = (workWidth * 0.5) + offsX - local right = (workWidth * -0.5) + offsX - - local p1x, p1y, p1z = localToWorld(object.rootNode, left, 1.6, b - offsZ) - local p2x, p2y, p2z = localToWorld(object.rootNode, right, 1.6, b - offsZ) - local p3x, p3y, p3z = localToWorld(object.rootNode, right, 1.6, f - offsZ) - local p4x, p4y, p4z = localToWorld(object.rootNode, left, 1.6, f - offsZ) - - -- cpDebug:drawPoint(p1x, p1y, p1z, 1, 1, 0) - -- cpDebug:drawPoint(p2x, p2y, p2z, 1, 1, 0) - -- cpDebug:drawPoint(p3x, p3y, p3z, 1, 1, 0) - -- cpDebug:drawPoint(p4x, p4y, p4z, 1, 1, 0) - - - DebugUtil.drawDebugLine(p1x, p1y, p1z, p2x, p2y, p2z, 1, 0, 0) - DebugUtil.drawDebugLine(p2x, p2y, p2z, p3x, p3y, p3z, 1, 0, 0) - DebugUtil.drawDebugLine(p3x, p3y, p3z, p4x, p4y, p4z, 1, 0, 0) - DebugUtil.drawDebugLine(p4x, p4y, p4z, p1x, p1y, p1z, 1, 0, 0) - end - show(firstObject, workWidth, offsX, offsZ) - if firstObject ~= lastObject then - show(lastObject, workWidth, offsX, offsZ) - end -end - ----@param implement table -function WorkWidthUtil.debug(implement, ...) - CpUtil.debugImplement(CpDebug.DBG_IMPLEMENTS, implement, ...) -end +---@class WorkWidthUtil +WorkWidthUtil = {} + +--- Iterator for all work areas of an object +---@param object table +function WorkWidthUtil.workAreaIterator(object) + local i = 0 + return function() + i = i + 1 + local wa = WorkWidthUtil.hasValidWorkArea(object) and object:getWorkAreaByIndex(i) + if wa then + return i, wa + end + end +end + +--- Gets work areas if possible. +---@param object table +function WorkWidthUtil.hasWorkAreas(object) + return WorkWidthUtil.hasValidWorkArea(object) and object:getWorkAreaByIndex(1) +end + +function WorkWidthUtil.hasValidWorkArea(object) + return object and object.getWorkAreaByIndex and object.spec_workArea.workAreas +end + +--- Gets the working width and offset calculated from all built-in and attached implements. If an implement has +--- a width or offset configured, it takes precedence over the calculated values. +--- +--- Working width can be tricky if there are multiple implements attached. There is no guarantee that all work areas are +--- in the centerline of the vehicle, for instance mowers can be offset on one side. To get the true working width, we must +--- collect distance of the work area edges left and right for all implements and use the maximum left/right values to +--- calculate the width. This also results in an offset, which is the distance between the vehicle's centerline and the +--- middle of the work area. +--- +---@param object table +---@param referenceNode number the node for calculating the work width, if not supplied, use the object's root node +---@param ignoreObject table ignore this object when calculating the width (as it is being detached, for instance) +---@return number width +---@return number offset +---@return number|nil left +---@return number|nil right +function WorkWidthUtil.getAutomaticWorkWidthAndOffset(object, referenceNode, ignoreObject) + -- when first called for the vehicle, referenceNode is empty, so use the vehicle root node + referenceNode = referenceNode or object.rootNode + WorkWidthUtil.debug(object, 'getting working width...') + -- check if we have a manually configured working width + local configuredWidth = g_vehicleConfigurations:get(object, 'workingWidth') + local configuredOffset = g_vehicleConfigurations:get(object, 'toolOffsetX') + + local left, right + + if object.getVariableWorkWidth then + --- Gets the variable work width to the left + to the right. + local w1, _, isValid1 = object:getVariableWorkWidth(true) + local w2, _, isValid2 = object:getVariableWorkWidth() + if isValid1 and isValid2 then + left, right = w1, w2 + local width = math.abs(w1) + math.abs(w2) + WorkWidthUtil.debug(object, 'left = %.1f, right = %.1f, setting variable work width of %.1f.', + w1, w2, width) + end + end + + if object.spec_dynamicMountAttacher then + --- Enables fetching the work with of cutter, + --- which are attached on an separate header trailer + --- at the back of the harvester. + local spec = object.spec_dynamicMountAttacher + local obj = next(spec.dynamicMountedObjects) + if obj then + if obj.spec_cutter then + WorkWidthUtil.debug(obj, 'Using this cutter instead of the header trailer %s attached.', + CpUtil.getName(object)) + object = obj + referenceNode = obj.rootNode + end + end + end + + --- Work width for soil samplers. + if not left and object.spec_soilSampler then + if object.spec_soilSampler.samplingRadius then + local width = 2 * object.spec_soilSampler.samplingRadius / math.sqrt(2) + left, right = width / 2, -width / 2 + WorkWidthUtil.debug(object, 'using soil sampler width of %.1f (from sampling radius).', width) + else + WorkWidthUtil.debug(object, 'soil sampler has no sampling radius, can\'t calculate width') + end + end + + --- Work width levelers + if not left then + local width = WorkWidthUtil.getShieldWorkWidth(object) + if width then + left, right = width / 2, -width / 2 + end + end + + if not left then + -- no manual config, check AI markers + _, left, right = WorkWidthUtil.getAIMarkerWidth(object, referenceNode) + end + + if not left then + if WorkWidthUtil.hasWorkAreas(object) then + -- no AI markers, check work areas + left, right = WorkWidthUtil.getWorkAreaWidth(object, referenceNode) + if not left then + WorkWidthUtil.debug(object, 'has NO valid work areas') + end + else + WorkWidthUtil.debug(object, 'has NO work areas') + end + end + + local implements = object.getAttachedImplements and object:getAttachedImplements() + if implements then + -- get width of all implements + for _, implement in ipairs(implements) do + if implement.object ~= ignoreObject then + local _, _, thisLeft, thisRight = WorkWidthUtil.getAutomaticWorkWidthAndOffset(implement.object) + if thisLeft ~= nil and thisRight ~= nil then + left = math.max(thisLeft, left or -math.huge) + right = math.min(thisRight, right or math.huge) + end + end + end + end + + -- left > 0, right < 0. Offset > 0 and offset < 0 when the center line of all work areas are to the left and right, + -- respectively, of the vehicle. + local width, offset + if configuredWidth then + width = configuredWidth + -- for now, assuming offset 0 + left = width / 2 + right = - width / 2 + WorkWidthUtil.debug(object, 'using configured working width of %.1f, resulting left/right is %.1f/%.1f.', + configuredWidth, left, right) + elseif left and right then + width = left - right + WorkWidthUtil.debug(object, 'working width is %.1f, left %.1f, right %.1f.', width, left, right) + elseif not width then + width = 0 + left = nil + right = nil + WorkWidthUtil.debug(object, 'could not determine working width') + end + + if configuredOffset then + offset = configuredOffset + if width == 0 then + -- some vine tools have no working width but we do have a configured offset. Make sure that + -- the vehicle will inherit this offset by returning a left, right pair at offset + left, right = offset, offset + else + left = width / 2 + offset + right = - width / 2 + offset + end + WorkWidthUtil.debug(object, 'using configured tool offset of %.1f, resulting left/right is %.1f/%.1f.', + configuredOffset, left, right) + elseif width and left and right then + offset = left - width / 2 + WorkWidthUtil.debug(object, 'calculated tool offset is %.1f.', offset) + else + offset = 0 + WorkWidthUtil.debug(object, 'could not determine offset, using 0') + end + + return width, offset, left, right +end + +---@param object table +function WorkWidthUtil.getWorkAreaWidth(object, referenceNode) + -- TODO: check if there's a better way to find out if the implement has a work area + local hasValidWorkArea, maxLeft, minRight = false, -math.huge, math.huge + for i, wa in WorkWidthUtil.workAreaIterator(object) do + if WorkWidthUtil.isValidWorkArea(wa) then + hasValidWorkArea = true + -- work areas are defined by three nodes: start, width and height. These nodes + -- define a rectangular work area which you can make visible with the + -- gsVehicleDebugAttributes console command and then pressing F5 + local left, _, _ = localToLocal(wa.start, referenceNode, 0, 0, 0) + local right, _, _ = localToLocal(wa.width, referenceNode, 0, 0, 0) + maxLeft = math.max(maxLeft, left) + minRight = math.min(minRight, right) + WorkWidthUtil.debug(object, 'work area %d is %s, left = %.1f, right %.1f m', + i, g_workAreaTypeManager.workAreaTypes[wa.type].name, left, right) + end + end + if hasValidWorkArea then + return maxLeft, minRight + else + return nil, nil + end +end + +---@param object table +function WorkWidthUtil.getAIMarkerWidth(object, referenceNode) + if object.getAIMarkers then + local aiLeftMarker, aiRightMarker = object:getAIMarkers() + if aiLeftMarker and aiRightMarker then + -- left/right is just for the log + local left, _, _ = localToLocal(aiLeftMarker, referenceNode, 0, 0, 0) + local right, _, _ = localToLocal(aiRightMarker, referenceNode, 0, 0, 0) + local width = calcDistanceFrom(aiLeftMarker, aiRightMarker) + WorkWidthUtil.debug(object, 'aiMarkers: left=%.2f, right=%.2f (width %.2f)', left, right, width) + return width, left, right + end + end +end + +--- Gets ai markers for an object. +---@param object table +function WorkWidthUtil.getAIMarkers(object, suppressLog) + local aiLeftMarker, aiRightMarker, aiBackMarker = object:getAIMarkers() + if not aiLeftMarker or not aiRightMarker or not aiBackMarker then + -- use the root node if there are no AI markers + if not suppressLog then + WorkWidthUtil.debug(object, 'has no AI markers, try work areas') + end + aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkersFromWorkAreas(object, suppressLog) + if not aiLeftMarker or not aiRightMarker or not aiLeftMarker then + if g_vehicleConfigurations:get(object, 'useVehicleSizeForMarkers') or object.spec_leveler then + if not suppressLog then + WorkWidthUtil.debug(object, 'has no work areas, configured to use front/back markers') + end + return Markers.getFrontMarkerNode(object), Markers.getFrontMarkerNode(object), Markers.getBackMarkerNode(object) + else + if not suppressLog then + WorkWidthUtil.debug(object, 'has no work areas, giving up') + end + return nil, nil, nil + end + else + if not suppressLog then + WorkWidthUtil.debug(object, 'AI markers from work area set') + end + return aiLeftMarker, aiRightMarker, aiBackMarker + end + else + if not suppressLog then + WorkWidthUtil.debug(object, 'AI markers set') + end + return aiLeftMarker, aiRightMarker, aiBackMarker + end +end + +--- Calculate the front and back marker nodes of a work area +---@param object table +function WorkWidthUtil.getAIMarkersFromWorkAreas(object, suppressLog) + -- work areas are defined by three nodes: start, width and height. These nodes + -- define a rectangular work area which you can make visible with the + -- gsVehicleDebugAttributes console command and then pressing F5 + for _, area in WorkWidthUtil.workAreaIterator(object) do + if WorkWidthUtil.isValidWorkArea(area) then + -- for now, just use the first valid work area we find + if not suppressLog then + WorkWidthUtil.debug(object, 'Using %s work area markers as AIMarkers', + g_workAreaTypeManager.workAreaTypes[area.type].name) + end + return area.start, area.width, area.height + end + end +end + +---@param area table +function WorkWidthUtil.isValidWorkArea(area) + return area.start and area.height and area.width and + area.type ~= WorkAreaType.RIDGEMARKER and + area.type ~= WorkAreaType.COMBINESWATH and + area.type ~= WorkAreaType.COMBINECHOPPER +end + +---@param object table +function WorkWidthUtil.getShieldWorkWidth(object) + if object.spec_leveler and object.spec_leveler.nodes and object.spec_leveler.nodes[1] then + local width = object.spec_leveler.nodes[1].width + WorkWidthUtil.debug(object, 'is a shield with width: %.1f', width) + return width + end +end + +---@param object table +function WorkWidthUtil.getShovelWorkWidth(object) + if object.spec_shovel and object.spec_shovel.shovelNodes and object.spec_shovel.shovelNodes[1] then + local width = object.spec_shovel.shovelNodes[1].width + WorkWidthUtil.debug(object, 'is a shovel with width: %.1f', width) + return width + end +end + +--- Shows the current work width selected with the tool offsets applied. +---@param vehicle table +---@param workWidth number +---@param offsX number +---@param offsZ number +function WorkWidthUtil.showWorkWidth(vehicle, workWidth, offsX, offsZ) + local firstObject = AIUtil.getFirstAttachedImplement(vehicle, true) + local lastObject = AIUtil.getLastAttachedImplement(vehicle, true) + + local function show(object, workWidth, offsX, offsZ) + if object == nil then + return + end + local f, b = 0, 0 + local aiLeftMarker, _, aiBackMarker = object:getAIMarkers() + if aiLeftMarker and aiBackMarker then + _, _, b = localToLocal(aiBackMarker, object.rootNode, 0, 0, 0) + _, _, f = localToLocal(aiLeftMarker, object.rootNode, 0, 0, 0) + end + + local left = (workWidth * 0.5) + offsX + local right = (workWidth * -0.5) + offsX + + local p1x, p1y, p1z = localToWorld(object.rootNode, left, 1.6, b - offsZ) + local p2x, p2y, p2z = localToWorld(object.rootNode, right, 1.6, b - offsZ) + local p3x, p3y, p3z = localToWorld(object.rootNode, right, 1.6, f - offsZ) + local p4x, p4y, p4z = localToWorld(object.rootNode, left, 1.6, f - offsZ) + + -- cpDebug:drawPoint(p1x, p1y, p1z, 1, 1, 0) + -- cpDebug:drawPoint(p2x, p2y, p2z, 1, 1, 0) + -- cpDebug:drawPoint(p3x, p3y, p3z, 1, 1, 0) + -- cpDebug:drawPoint(p4x, p4y, p4z, 1, 1, 0) + + + DebugUtil.drawDebugLine(p1x, p1y, p1z, p2x, p2y, p2z, 1, 0, 0) + DebugUtil.drawDebugLine(p2x, p2y, p2z, p3x, p3y, p3z, 1, 0, 0) + DebugUtil.drawDebugLine(p3x, p3y, p3z, p4x, p4y, p4z, 1, 0, 0) + DebugUtil.drawDebugLine(p4x, p4y, p4z, p1x, p1y, p1z, 1, 0, 0) + end + show(firstObject, workWidth, offsX, offsZ) + if firstObject ~= lastObject then + show(lastObject, workWidth, offsX, offsZ) + end +end + +---@param implement table +function WorkWidthUtil.debug(implement, ...) + CpUtil.debugImplement(CpDebug.DBG_IMPLEMENTS, implement, ...) +end From e24f80c9b62cfaa0f10429b8ed4136598239ee80 Mon Sep 17 00:00:00 2001 From: Peter Vaiko <peter.vaiko@siemens.com> Date: Sun, 22 Dec 2024 08:32:42 -0500 Subject: [PATCH 156/158] fix: Autodrive API Removed the call to Autodrive's combineIsCallingDriver() to check if the pipe needs to open. combineIsCallingDriver() isn't doing anything other than checking getIsCpHarvesterWaitingForUnload(), it isn't in any way depends on the Autodrive state of the unloader, therefore, it does not make a lot of sense, it just opens the pipe unnecessarily. Autodrive still can check if the CP harvester is waiting for unload, and drive to the harvester. The trailer being in the range will trigger opening the pipe. --- scripts/ai/strategies/AIDriveStrategyCombineCourse.lua | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua index 9dbb23629..2e0d68e23 100644 --- a/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua +++ b/scripts/ai/strategies/AIDriveStrategyCombineCourse.lua @@ -1495,7 +1495,7 @@ end function AIDriveStrategyCombineCourse:handleCombinePipe(dt) -- don't open the pipe while turning - if self:isPipeOpenEnabled() and (self:isAGoodTrailerInRange() or self:isAutoDriveWaitingForPipe()) then + if self:isPipeOpenEnabled() and self:isAGoodTrailerInRange() then self.pipeController:openPipe() else if not self.forcePipeOpen:get() then @@ -1576,12 +1576,6 @@ function AIDriveStrategyCombineCourse:getFillLevelPercentage() return self.combineController:getFillLevelPercentage() end ---- Support for AutoDrive mod: they'll only find us if we open the pipe -function AIDriveStrategyCombineCourse:isAutoDriveWaitingForPipe() - return self.vehicle.spec_autodrive and self.vehicle.spec_autodrive.combineIsCallingDriver and - self.vehicle.spec_autodrive:combineIsCallingDriver(self.vehicle) -end - function AIDriveStrategyCombineCourse:shouldStopForUnloading() if self.settings.stopForUnload:getValue() then if self:isDischarging() and not self.stopDisabledAfterEmpty:get() then From 5e562faef15ab963aa95c7d8a8570135385c749d Mon Sep 17 00:00:00 2001 From: Tensuko <theta-darkphoenix@gmx.net> Date: Sun, 22 Dec 2024 15:45:04 +0100 Subject: [PATCH 157/158] Changes from PR #21 Picked some thats totaly fine to change. --- config/MasterTranslations.xml | 86 +++++++++++++++++------------------ 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml index f7146e516..812322be8 100644 --- a/config/MasterTranslations.xml +++ b/config/MasterTranslations.xml @@ -154,8 +154,8 @@ <Text language="en"><![CDATA[Lane offset:]]></Text> </Translation> <Translation name="CP_fieldWorkJobParameters_laneOffset_tooltip"> - <Text language="de"><![CDATA[Bahnauswahl, wenn ein Multitoolkurs erstellt wurde.]]></Text> - <Text language="en"><![CDATA[Lane offset]]></Text> + <Text language="de"><![CDATA[Bahnauswahl, wenn ein Multitoolkurs erstellt oder geladen wurde.]]></Text> + <Text language="en"><![CDATA[Lane offset if a multitool course has been created or loaded.]]></Text> </Translation> <Translation name="CP_fieldWorkJobParameters_laneOffset_center"> <Text language="de"><![CDATA[Mitte]]></Text> @@ -701,7 +701,7 @@ </Translation> <Translation name="CP_vehicle_setting_refillOnTheField_waiting"> <Text language="de"><![CDATA[Warten]]></Text> - <Text language="en"><![CDATA[Waiting]]></Text> + <Text language="en"><![CDATA[Wait]]></Text> </Translation> <Translation name="CP_vehicle_setting_refillOnTheField_active"> <Text language="de"><![CDATA[Aktiv]]></Text> @@ -727,7 +727,7 @@ </Translation> <Translation name="CP_vehicle_setting_levelerHeightOffset_tooltip"> <Text language="de"><![CDATA[Höhenversatz vom Schild zum Boden. Wert: 0 - 1]]></Text> - <Text language="en"><![CDATA[Offset of the shield from the ground. Value: 0 - 1]]></Text> + <Text language="en"><![CDATA[Height offset from the shield to the ground. Value: 0 - 1]]></Text> </Translation> <Translation name="CP_vehicle_setting_loadingShovelHeightOffset_title"> <Text language="de"><![CDATA[Versatz Schaufelhöhe]]></Text> @@ -735,7 +735,7 @@ </Translation> <Translation name="CP_vehicle_setting_loadingShovelHeightOffset_tooltip"> <Text language="de"><![CDATA[Höhenversatz der Schaufel für die Ladeposition. Wert: -1 - 1]]></Text> - <Text language="en"><![CDATA[Offset for the shovel loading position. Value: -1 - 1]]></Text> + <Text language="en"><![CDATA[Height offset for the shovel loading position. Value: -1 - 1]]></Text> </Translation> </Category> <Category name="Vehicle settings speed"> @@ -870,8 +870,8 @@ </Category> <Category name="Course generator settings"> <Translation name="CP_vehicle_courseGeneratorSetting_title"> - <Text language="de"><![CDATA[Helfer Einstellungen von (%s)]]></Text> <Text language="en"><![CDATA[Helper settings of (%s)]]></Text> + <Text language="de"><![CDATA[Helfereinstellungen von (%s)]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_subTitle_basic"> <Text language="de"><![CDATA[Grundeinstellungen]]></Text> @@ -902,8 +902,8 @@ <Text language="en"><![CDATA[Work width]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_workWidth_tooltip"> - <Text language="de"><![CDATA[Arbeitsbreite. Wert: 0 - 50]]></Text> - <Text language="en"><![CDATA[Work width. Value: 0 - 50]]></Text> + <Text language="de"><![CDATA[Arbeitsbreite. Wert: 0 - 55]]></Text> + <Text language="en"><![CDATA[Working width. Value: 0 - 55]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_numberOfHeadlands_title"> <Text language="de"><![CDATA[Anzahl Vorgewende]]></Text> @@ -918,7 +918,7 @@ <Text language="en"><![CDATA[Headland overlap]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_headlandOverlapPercent_tooltip"> - <Text language="de"><![CDATA[Überlappung Vorgewende in % der Arbeitsbreite. Wert: 0 - 25]]></Text> + <Text language="de"><![CDATA[Überlappung der Vorgewende in % der Arbeitsbreite. Wert: 0 - 25]]></Text> <Text language="en"><![CDATA[Overlap headland tracks as percent of working width. Value: 0 - 25]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_fieldMargin_title"> @@ -950,7 +950,7 @@ <Text language="en"><![CDATA[Rows per land]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_rowsPerLand_tooltip"> - <Text language="de"><![CDATA[Wenn Zentrierung Beete ausgewählt ist, dann befinden sich so viele Reihen im Block. Wert: 0 - 24]]></Text> + <Text language="de"><![CDATA[Wenn Feldmitte Beete ausgewählt ist, dann befinden sich so viele Reihen im Block. Wert: 0 - 24]]></Text> <Text language="en"><![CDATA[When the center mode is 'lands', this many rows are in each block. Value: 0 - 24]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_headlandClockwise_title"> @@ -1054,7 +1054,7 @@ <Text language="en"><![CDATA[Number of round corners]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_headlandsWithRoundCorners_tooltip"> - <Text language="de"><![CDATA[Anzahl runde Ecken, beginnend mit dem ersten Vorgewende.]]></Text> + <Text language="de"><![CDATA[Anzahl der Vorgewende mit runden Ecken, beginnend mit dem ersten Vorgewende.]]></Text> <Text language="en"><![CDATA[Number of headlands with round corners, starting with the outermost headland.]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_turningRadius_title"> @@ -1102,7 +1102,7 @@ <Text language="en"><![CDATA[Island headlands]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_nIslandHeadlands_tooltip"> - <Text language="de"><![CDATA[Anzahl Vorgewende bei Inseln. Wert: 1 - 10]]></Text> + <Text language="de"><![CDATA[Anzahl der Vorgewende bei Inseln. Wert: 1 - 10]]></Text> <Text language="en"><![CDATA[Number of headlands around islands. Value: 1 - 10]]></Text> </Translation> <Translation name="CP_vehicle_courseGeneratorSetting_startOnHeadland_title"> @@ -1242,7 +1242,7 @@ <Text language="en"><![CDATA[Keep it healthy]]></Text> </Translation> <Translation name="CP_global_setting_fuelThreshold_title"> - <Text language="de"><![CDATA[Minimum Kraftstoff]]></Text> + <Text language="de"><![CDATA[Mindestfüllstand Kraftstoff]]></Text> <Text language="en"><![CDATA[Fuel threshold]]></Text> </Translation> <Translation name="CP_global_setting_fuelThreshold_tooltip"> @@ -1250,7 +1250,7 @@ <Text language="en"><![CDATA[Driver will be released, if the fuel percentage is less.]]></Text> </Translation> <Translation name="CP_global_setting_waitForRefueling_title"> - <Text language="de"><![CDATA[Wartet auf auftanken]]></Text> + <Text language="de"><![CDATA[Warten auf auftanken]]></Text> <Text language="en"><![CDATA[Waits for refueling]]></Text> </Translation> <Translation name="CP_global_setting_waitForRefueling_tooltip"> @@ -1258,7 +1258,7 @@ <Text language="en"><![CDATA[Vehicle waits until it is refueled.]]></Text> </Translation> <Translation name="CP_global_setting_brokenThreshold_title"> - <Text language="de"><![CDATA[Minimum Zustand]]></Text> + <Text language="de"><![CDATA[Mindestzustand]]></Text> <Text language="en"><![CDATA[Broken threshold]]></Text> </Translation> <Translation name="CP_global_setting_brokenThreshold_tooltip"> @@ -1851,8 +1851,8 @@ The course is saved automatically on closing of the editor and overrides the sel <Text language="en"><![CDATA[Courseplay]]></Text> </Translation> <Translation name="CP_help_subTitle"> - <Text language="de"><![CDATA[Modifikation für den Landwirtschafts-Simulator 22]]></Text> - <Text language="en"><![CDATA[A Modification for Farming Simulator 22 ]]></Text> + <Text language="de"><![CDATA[Modifikation für den Landwirtschafts-Simulator 25]]></Text> + <Text language="en"><![CDATA[A Modification for Farming Simulator 25]]></Text> </Translation> <Translation name="CP_help_page_blank"> <Text language="de"><![CDATA[]]></Text> @@ -1876,7 +1876,7 @@ Außerdem hat CP ein Interface für AutoDrive, dadurch ist es möglich Sämaschi Drescher-Abfahrer sind in der Lage, die geladene Frucht als Haufen am Feldrand abzulegen. Im Farming Simulator 25 besitzt Courseplay sein eigenes Menü, welches ihr über verschiedene Buttons im HUD erreicht. -Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS25 . +Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS25 . Dort könnt ihr Fragen stellen, Fehlerberichte einreichen oder Verbesserungen vorschlagen. Ein ganz großes Danke geht an unsere Übersetzer und an die Community für die zahlreichen Fehlermeldungen, Feedbacks und Ideen. @@ -1893,18 +1893,18 @@ Courseplay is also able to work on rice fields and vines. Fieldwork courses can be setup in multitool mode, which allows the use of up to 5 driver working in a convoy on the same field. It's also possible to have the combine unload in a trailer on/near the field automatically. Custom field borders can be assigned for Courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. -Lastly cp has a interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. -Combine unloader are able to create a heap near the field with their loaded fruit. -In Farming Simulator 25, Courseplay got its own menu wich you can access over different buttons on the HUD. +Lastly CP has an interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. +Combine unloaders are able to create a heap near the field with their loaded fruit. +In Farming Simulator 25, Courseplay got its own menu, which you can access over different buttons on the HUD. For more information please visit our GitHub: https://github.com/Courseplay/Courseplay_FS25 . There you can get help or report any issues you've experienced. Finally, we thank every translator and our community for reporting bugs, giving use feedback and new ideas. -What is the expert Mode: -When the expert Mode is deactivated, you have only access to some settings. -The other settings are hidden and set to default values, which works in most situations. -That way, we try to help Users to get easier into Courseplay without being overwhelmed from all the settings. +What is the expert mode: +When the expert mode is deactivated, you only have access to some settings. +The other settings are hidden and set to default values, which work in most situations. +That way, we try to make it easier for new Courseplay users to get started without overwhelming them with the various settings. ]]></Text> </Translation> <Translation name="CP_help_page_extendedJobMenu_title"> @@ -1914,11 +1914,11 @@ That way, we try to help Users to get easier into Courseplay without being overw <Translation name="CP_help_page_startJobMenuBase_text"> <Text language="de"><![CDATA[ Die Einstellungen für jeden Helfer finden jetzt im Menü "Helfer Einstellungen" statt. -Das Menü funktioniert grundlegend wie das alte Helfer Menü. +Das Menü funktioniert grundlegend wie das alte Helfermenü. Wird ein Fahrzeug auf der Map ausgewählt, könnt ihr einen Job erstellen, in Abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. -Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur verfügbar, wenn ihr ein gültiges Fahrzeug ausgewählt habt und einen Job erstellt habt. +Neben der Karte und den Job-Einstellungen findet ihr oben im Menü noch die Kursgenerator-Einstellungen für Feldarbeit und Reben. Diese sind nur verfügbar, wenn ihr ein gültiges Fahrzeug ausgewählt habt und einen Job erstellt habt. Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldarbeit auf der Helferkarte ausgewählt sein. -Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. +Tipp: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. ]]></Text> <Text language="en"><![CDATA[ Helper settings are now under our own new "Helper settings" menu. @@ -2011,7 +2011,7 @@ Basics: The first few values you see here are real basic stuff. - Work width: Courseplay automatically detects the working width for most tools, so you won't have to worry about this, but if automatic detection fails, you can set it manually. This setting has an impact on the overall course. -- Number of Headlands: The best way to keep your vehicles on the field while turning at the end of each row is to add headlands. +- Number of headlands: The best way to keep your vehicles on the field while turning at the end of each row is to add headlands. The Number of the Headlands multiplied by the work width should be at least the total length of your vehicle plus the attached tool. - Start work on: As mentioned in the extended AI Menu, the field position is used to set the start or end position for the fieldwork course. You want to start working on the headland when harvesting, and work towards the middle of the field. @@ -2168,7 +2168,7 @@ Bevor ihr euch mit den Einstellmöglichkeiten beschäftigt, solltet ihr euch ers - Enges Feld: Erzeugt nur an den kürzesten Feldrandseiten ein Vorgewende. Es lässt sich aktuell nicht vermeiden, dass Fahrzeuge an den langen Seiten im Vorgewende das Feld verlassen. - Vorgewende-Überlappung: Es ist ein Standardwert eingestellt, der immer verwendet wird. Dieser kann aber mit dieser Einstellung angepasst werden. Die Überlappung findet innerhalb des Feldes statt und ragt nicht über den Feldrand hinaus. - Feldgrenze: Mit dieser Einstellung lässt sich die Feldgrenze weiter nach innen oder außen verschieben und somit lässt sich der normale Feldrand verschieben. -- Feldrand für Bahnen: Ein ganz neues Feature, welches nur auf bestimmten Feldern funktioniert. Hat man ein Feld mit einem langen Bogen als Feldgrenze und setzt den Feldmarker in die Nähe dieses Feldrands, so werden die Bahnen in der Mitte den Verlauf des Feldrands annehmen. +- Bahnenform anhand der Feldkante: Ein ganz neues Feature, welches nur auf bestimmten Feldern funktioniert. Hat man ein Feld mit einem langen Bogen als Feldgrenze und setzt den Feldmarker in die Nähe dieses Feldrands, so werden die Bahnen in der Mitte den Verlauf des Feldrands annehmen. ANMERKUNG: Sollte ein Kurs nicht sauber generiert werden, funktioniert diese Funktion nicht auf dem Feld oder mit dem Feldrand. Es wird dafür auch kein Update geben! ]]></Text> <Text language="en"><![CDATA[ @@ -2176,7 +2176,7 @@ Some settings are only visible, if you set the expert mode to active on the glob Before you play around with those settings, you should make sure you know what the basic settings do. Some expert settings only work properly under some conditions. -- Multiple Tools: This setting is used when you want more than just one vehicle to work on your course. As this gets a bit more complicated, there is a separate help topic for it. +- Multiple tools: This setting is used when you want more than just one vehicle to work on your course. As this gets a bit more complicated, there is a separate help topic for it. - Switching lanes: With this option active, vehicles switch lanes at each turn, so every vehicle's turn is of the same width. See the extra help menu "Switching lanes" for more details. - Narrow field: Creates the headland only at two short edges of the field. In that case, it is not possible to keep your vehicle on the field on the longest edges at the headland. - Headland overlap: By default, headland passes overlap each other (but never the field edge) by a few percent to avoid missing fruit in some edge cases. You can change that overlap here. @@ -2186,20 +2186,20 @@ IMPORTANT NOTE: This is an experimental setting which may not work for all field ]]></Text> </Translation> <Translation name="CP_help_page_courseGeneratorHeadland_title"> - <Text language="de"><![CDATA[Kursgenerator Vorgewende]]></Text> - <Text language="en"><![CDATA[Course generator headland]]></Text> + <Text language="de"><![CDATA[Kursgenerator: Vorgewende]]></Text> + <Text language="en"><![CDATA[Course generator: headland]]></Text> </Translation> <Translation name="CP_help_page_courseGeneratorCenter_title"> - <Text language="de"><![CDATA[Kursgenerator Feldmitte]]></Text> - <Text language="en"><![CDATA[Course generator center]]></Text> + <Text language="de"><![CDATA[Kursgenerator: Feldmitte]]></Text> + <Text language="en"><![CDATA[Course generator: center]]></Text> </Translation> <Translation name="CP_help_page_courseGeneratorIsland_title"> - <Text language="de"><![CDATA[Kursgenerator Insel]]></Text> - <Text language="en"><![CDATA[Course generator island]]></Text> + <Text language="de"><![CDATA[Kursgenerator: Feldinseln]]></Text> + <Text language="en"><![CDATA[Course generator: islands]]></Text> </Translation> <Translation name="CP_help_page_courseGeneratorExpert_title"> - <Text language="de"><![CDATA[Kursgenerator Expert]]></Text> - <Text language="en"><![CDATA[Course generator expert]]></Text> + <Text language="de"><![CDATA[Kursgenerator: Experteneinstellungen]]></Text> + <Text language="en"><![CDATA[Course generator: expert settings]]></Text> </Translation> <Translation name="CP_help_page_courseManager_title"> <Text language="de"><![CDATA[Kursmanager]]></Text> @@ -2293,7 +2293,7 @@ A: Mit gedrückter linker Maustaste auf der Kopfzeile lässt sich das HUD auf ei B: Um schnell zu den globalen Einstellungen zu gelangen, kannst du mit der Maus auf das CP-Icon klicken. C: Hier wird der Name deines Fahrzeugs angezeigt, klickst du darauf, gelangst du direkt in die CP-Fahrzeugeinstellungen. D: Diese Symbole sind dafür da um (1) den aktuell geladenen Kurs zu löschen, (2a) die Anzeige des geladenen Kurs durch zu schalten (2b) wenn kein Kurs geladen ist, ein Aufnahme-Button um einen Feldrandkurs aufzuzeichnen, (3) den Helfer zu starten und zu stoppen. -E: Dieses Ziel Icon hat verschiedene Funktionen. Je nach dem gewähltem Modus gelangt man direkt auf die Helferkarte mit dem passenden Helferjob. Dort können dann mögliche fehlende Marker, wie die Feldposition platziert werden. Links davon wird bei der Feldarbeit die verbleibende Zeit vom Kurs angezeigt. +E: Dieses Ziel-Icon hat verschiedene Funktionen. Je nach dem gewähltem Modus gelangt man direkt auf die Helferkarte mit dem passenden Helferjob. Dort können dann mögliche fehlende Marker, wie die Feldposition platziert werden. Links davon wird bei der Feldarbeit die verbleibende Zeit vom Kurs angezeigt. F: Durch das Klicken auf diesen Text werden die erlaubten Modi je nach Fahrzeuggespann durchgeschaltet. G: Unterhalb dieser Linie befinden sich modusabhängige Einstellungen, die in den nachfolgenden Bildern erklärt werden. ]]></Text> @@ -2336,7 +2336,7 @@ E: Use the symbol on the right side to copy the current course to the clipboard. A: Da sowohl ein Drescher als auch ein Lader (z.B. ROPA Maus) gleichzeitig auf einem Feld arbeiten könnten, kann hier gewählt werden, welches Gerät abgetankt werden soll. B: Hier kannst du bestimmen, ab wie viel Prozent der Abfahrer zum Entladen fahren soll. Einstellbar zwischen 40% und 100%. Die Bedienung ist gleich wie bei der Arbeitsbreite vom Feldarbeitsmodus. C: Es kann vorkommen, dass die Position des Abfahrers nicht perfekt passt. Das kann am Anhänger oder am Abladerohr des Ernters liegen. Manchmal reicht auch ein Gefälle des Feldes aus. Hier kannst du den Abstand zum Ernter einstellen. -D: Das gleiche lässt sich auch für eine Korrektur nach vorne oder nach hinten zum Ernter einstellen. +D: Das Gleiche lässt sich auch für eine Korrektur nach vorne oder nach hinten zum Ernter einstellen. E: Ähnlich wie beim Kopieren eines Kurses, werden hier die Positionen der Marker für den Abfahrer kopiert und können dann auf weiteren Abfahrer übertragen werden. ]]></Text> <Text language="en"><![CDATA[ @@ -2382,7 +2382,7 @@ D: Just like with the unloader, you can copy the marker positions to another veh ]]></Text> </Translation> <Translation name="CP_help_page_minihud_siloworker_title"> - <Text language="de"><![CDATA[Silo Arbeiter]]></Text> + <Text language="de"><![CDATA[Siloarbeiter]]></Text> <Text language="en"><![CDATA[Silo worker]]></Text> </Translation> <Translation name="CP_help_page_minihud_siloworker_text"> @@ -2673,7 +2673,7 @@ The status messages are: - Driving to trailer - Ouf of money - Waiting for rain to finish -- Waits for snow to disappear +- Waiting for snow to disappear - Waiting on unloader - Left the course, stopped - Wrong bale type From a23af1406f31fce3043b6eddc8e6447ef04c8fd9 Mon Sep 17 00:00:00 2001 From: Tensuko <Tensuko@users.noreply.github.com> Date: Sun, 22 Dec 2024 14:45:21 +0000 Subject: [PATCH 158/158] Updated translations --- translations/translation_de.xml | 46 ++++++++++++++++----------------- translations/translation_en.xml | 40 ++++++++++++++-------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 5246702a2..ebe8d815a 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -46,7 +46,7 @@ <text name="CP_fieldWorkJobParameters_startAt_bales" text="Ballen sammeln/wickeln"/> <text name="CP_fieldWorkJobParameters_subTitle_laneOffset" text="Bahn:"/> <text name="CP_fieldWorkJobParameters_laneOffset_title" text="Bahn:"/> - <text name="CP_fieldWorkJobParameters_laneOffset_tooltip" text="Bahnauswahl, wenn ein Multitoolkurs erstellt wurde."/> + <text name="CP_fieldWorkJobParameters_laneOffset_tooltip" text="Bahnauswahl, wenn ein Multitoolkurs erstellt oder geladen wurde."/> <text name="CP_fieldWorkJobParameters_laneOffset_center" text="Mitte"/> <text name="CP_fieldWorkJobParameters_laneOffset_left" text="Links(1)"/> <text name="CP_fieldWorkJobParameters_laneOffset_right" text="Rechts(1)"/> @@ -270,7 +270,7 @@ <!----> <!--Course generator settings--> <!----> - <text name="CP_vehicle_courseGeneratorSetting_title" text="Helfer Einstellungen von (%s)"/> + <text name="CP_vehicle_courseGeneratorSetting_title" text="Helfereinstellungen von (%s)"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_basic" text="Grundeinstellungen"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_vineField" text="Reben Einstellungen"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_fieldwork" text="Feldarbeit Einstellungen"/> @@ -278,11 +278,11 @@ <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Einstellungen Mitte"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Einstellungen Insel"/> <text name="CP_vehicle_courseGeneratorSetting_workWidth_title" text="Arbeitsbreite"/> - <text name="CP_vehicle_courseGeneratorSetting_workWidth_tooltip" text="Arbeitsbreite. Wert: 0 - 50"/> + <text name="CP_vehicle_courseGeneratorSetting_workWidth_tooltip" text="Arbeitsbreite. Wert: 0 - 55"/> <text name="CP_vehicle_courseGeneratorSetting_numberOfHeadlands_title" text="Anzahl Vorgewende"/> <text name="CP_vehicle_courseGeneratorSetting_numberOfHeadlands_tooltip" text="Anzahl Vorgewende. Wert: 0 - 40"/> <text name="CP_vehicle_courseGeneratorSetting_headlandOverlapPercent_title" text="Vorgewende-Überlappung"/> - <text name="CP_vehicle_courseGeneratorSetting_headlandOverlapPercent_tooltip" text="Überlappung Vorgewende in % der Arbeitsbreite. Wert: 0 - 25"/> + <text name="CP_vehicle_courseGeneratorSetting_headlandOverlapPercent_tooltip" text="Überlappung der Vorgewende in % der Arbeitsbreite. Wert: 0 - 25"/> <text name="CP_vehicle_courseGeneratorSetting_fieldMargin_title" text="Feldgrenze"/> <text name="CP_vehicle_courseGeneratorSetting_fieldMargin_tooltip" text="Positive Werte reduzieren die Feldgröße, negative Werte vergrößern den Arbeitsbereich. Wert: -2 - 5.8"/> <text name="CP_vehicle_courseGeneratorSetting_rowsToSkip_title" text="Reihen überspringen"/> @@ -290,7 +290,7 @@ <text name="CP_vehicle_courseGeneratorSetting_numberOfCircles_title" text="Anzahl Runden"/> <text name="CP_vehicle_courseGeneratorSetting_numberOfCircles_tooltip" text="Anzahl Runden für kreisförmig. Wert: 1 - 12"/> <text name="CP_vehicle_courseGeneratorSetting_rowsPerLand_title" text="Reihen pro Beet"/> - <text name="CP_vehicle_courseGeneratorSetting_rowsPerLand_tooltip" text="Wenn Zentrierung Beete ausgewählt ist, dann befinden sich so viele Reihen im Block. Wert: 0 - 24"/> + <text name="CP_vehicle_courseGeneratorSetting_rowsPerLand_tooltip" text="Wenn Feldmitte Beete ausgewählt ist, dann befinden sich so viele Reihen im Block. Wert: 0 - 24"/> <text name="CP_vehicle_courseGeneratorSetting_headlandClockwise_title" text="Vorgewende-Richtung"/> <text name="CP_vehicle_courseGeneratorSetting_headlandClockwise_tooltip" text="Auf dem Vorgewende im oder gegen den Uhrzeigersinn fahren."/> <text name="CP_vehicle_courseGeneratorSetting_centerClockwise_title" text="Mitte-Richtung"/> @@ -316,7 +316,7 @@ <text name="CP_vehicle_courseGeneratorSetting_sharpenCorners_title" text="Vorgewende-Ecken"/> <text name="CP_vehicle_courseGeneratorSetting_sharpenCorners_tooltip" text="Wenn aktiv, werden die Ecken des Feldes als scharfe Ecken generiert."/> <text name="CP_vehicle_courseGeneratorSetting_headlandsWithRoundCorners_title" text="Anzahl runde Ecken"/> - <text name="CP_vehicle_courseGeneratorSetting_headlandsWithRoundCorners_tooltip" text="Anzahl runde Ecken, beginnend mit dem ersten Vorgewende."/> + <text name="CP_vehicle_courseGeneratorSetting_headlandsWithRoundCorners_tooltip" text="Anzahl der Vorgewende mit runden Ecken, beginnend mit dem ersten Vorgewende."/> <text name="CP_vehicle_courseGeneratorSetting_turningRadius_title" text="Eckenradius"/> <text name="CP_vehicle_courseGeneratorSetting_turningRadius_tooltip" text="Radius der runden Ecken, basierend auf dem Wenderadius des Fahrzeugs. Wert: 5 - 15"/> <text name="CP_vehicle_courseGeneratorSetting_autoRowAngle_title" text="Startrichtung"/> @@ -328,7 +328,7 @@ <text name="CP_vehicle_courseGeneratorSetting_bypassIslands_title" text="Inselumfahrung"/> <text name="CP_vehicle_courseGeneratorSetting_bypassIslands_tooltip" text="Inseln auf dem Feld umfahren oder ignorieren."/> <text name="CP_vehicle_courseGeneratorSetting_nIslandHeadlands_title" text="Insel Vorgewende"/> - <text name="CP_vehicle_courseGeneratorSetting_nIslandHeadlands_tooltip" text="Anzahl Vorgewende bei Inseln. Wert: 1 - 10"/> + <text name="CP_vehicle_courseGeneratorSetting_nIslandHeadlands_tooltip" text="Anzahl der Vorgewende bei Inseln. Wert: 1 - 10"/> <text name="CP_vehicle_courseGeneratorSetting_startOnHeadland_title" text="Starte auf"/> <text name="CP_vehicle_courseGeneratorSetting_startOnHeadland_tooltip" text="Startpunkt auf dem Vorgewende oder auf den Bahnen."/> <text name="CP_vehicle_courseGeneratorSetting_startOnHeadland_on_headland" text="Vorgewende"/> @@ -368,11 +368,11 @@ <text name="CP_global_setting_autoRepair_less_than_25" text="< 25%"/> <text name="CP_global_setting_autoRepair_less_than_70" text="< 70%"/> <text name="CP_global_setting_autoRepair_always" text="Instandhalten"/> - <text name="CP_global_setting_fuelThreshold_title" text="Minimum Kraftstoff"/> + <text name="CP_global_setting_fuelThreshold_title" text="Mindestfüllstand Kraftstoff"/> <text name="CP_global_setting_fuelThreshold_tooltip" text="Fahrer wird entlassen, wenn Kraftstoff unter diesem Wert liegt."/> - <text name="CP_global_setting_waitForRefueling_title" text="Wartet auf auftanken"/> + <text name="CP_global_setting_waitForRefueling_title" text="Warten auf auftanken"/> <text name="CP_global_setting_waitForRefueling_tooltip" text="Fahrzeug wartet vor Ort, um aufgefüllt zu werden."/> - <text name="CP_global_setting_brokenThreshold_title" text="Minimum Zustand"/> + <text name="CP_global_setting_brokenThreshold_title" text="Mindestzustand"/> <text name="CP_global_setting_brokenThreshold_tooltip" text="Fahrer wird entlassen, wenn der Schaden des Fahrzeugs oder Arbeitsgeräts über diesem Wert liegt. 100% ist aus."/> <text name="CP_global_setting_stopThreshingDuringRain_title" text="Drescher während Regen stoppen"/> <text name="CP_global_setting_stopThreshingDuringRain_tooltip" text="Wenn die Einstellung aktiviert ist, dann warten alle Drescher solange, bis der Regen aufhört."/> @@ -527,7 +527,7 @@ Der Kurs wird beim Schließen automatisch gespeichert und überschrieben. <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> - <text name="CP_help_subTitle" text="Modifikation für den Landwirtschafts-Simulator 22"/> + <text name="CP_help_subTitle" text="Modifikation für den Landwirtschafts-Simulator 25"/> <text name="CP_help_page_blank" text=""/> <text name="CP_help_page_General_title" text="Generelle Informationen"/> <text name="CP_help_page_GeneralBase_text" text=" @@ -543,7 +543,7 @@ Außerdem hat CP ein Interface für AutoDrive, dadurch ist es möglich Sämaschi Drescher-Abfahrer sind in der Lage, die geladene Frucht als Haufen am Feldrand abzulegen. Im Farming Simulator 25 besitzt Courseplay sein eigenes Menü, welches ihr über verschiedene Buttons im HUD erreicht. -Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS25 . +Für weitere Informationen, schaut bitte auf GitHub vorbei unter: https://github.com/Courseplay/Courseplay_FS25 . Dort könnt ihr Fragen stellen, Fehlerberichte einreichen oder Verbesserungen vorschlagen. Ein ganz großes Danke geht an unsere Übersetzer und an die Community für die zahlreichen Fehlermeldungen, Feedbacks und Ideen. @@ -555,11 +555,11 @@ Mit dieser Funktion versuchen wir neuen Courseplay-Nutzern den Einstieg zu erlei <text name="CP_help_page_extendedJobMenu_title" text="Erweitertes Helfermenü"/> <text name="CP_help_page_startJobMenuBase_text" text=" Die Einstellungen für jeden Helfer finden jetzt im Menü "Helfer Einstellungen" statt. -Das Menü funktioniert grundlegend wie das alte Helfer Menü. +Das Menü funktioniert grundlegend wie das alte Helfermenü. Wird ein Fahrzeug auf der Map ausgewählt, könnt ihr einen Job erstellen, in Abhängigkeit der angeschlossenen Geräte und des Fahrzeugstyps. -Neben der Karte und den Job Einstellungen findet ihr oben im Menü noch die Einstellungen für den Feldkurs Generator und den Reben Generator. Diese sind nur verfügbar, wenn ihr ein gültiges Fahrzeug ausgewählt habt und einen Job erstellt habt. +Neben der Karte und den Job-Einstellungen findet ihr oben im Menü noch die Kursgenerator-Einstellungen für Feldarbeit und Reben. Diese sind nur verfügbar, wenn ihr ein gültiges Fahrzeug ausgewählt habt und einen Job erstellt habt. Um einen Feldkurs generieren zu können, muss ein gültiges Fahrzeug für Feldarbeit auf der Helferkarte ausgewählt sein. -Tip: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. +Tipp: Um etwas schneller zu sein, könnt ihr das Menü über den Text im HUD "Kein Kurs" öffnen. Ihr seid dann direkt im Modus Job erstellen des Fahrzeugs in dem ihr drin sitzt. "/> <text name="CP_help_page_startJobMenuFunctions_text" text=" Um deinen ersten Job zu starten, musst du ein Fahrzeug mit einem für die Arbeit gültigen Arbeitsgerät auswählen. @@ -664,13 +664,13 @@ Bevor ihr euch mit den Einstellmöglichkeiten beschäftigt, solltet ihr euch ers - Enges Feld: Erzeugt nur an den kürzesten Feldrandseiten ein Vorgewende. Es lässt sich aktuell nicht vermeiden, dass Fahrzeuge an den langen Seiten im Vorgewende das Feld verlassen. - Vorgewende-Überlappung: Es ist ein Standardwert eingestellt, der immer verwendet wird. Dieser kann aber mit dieser Einstellung angepasst werden. Die Überlappung findet innerhalb des Feldes statt und ragt nicht über den Feldrand hinaus. - Feldgrenze: Mit dieser Einstellung lässt sich die Feldgrenze weiter nach innen oder außen verschieben und somit lässt sich der normale Feldrand verschieben. -- Feldrand für Bahnen: Ein ganz neues Feature, welches nur auf bestimmten Feldern funktioniert. Hat man ein Feld mit einem langen Bogen als Feldgrenze und setzt den Feldmarker in die Nähe dieses Feldrands, so werden die Bahnen in der Mitte den Verlauf des Feldrands annehmen. +- Bahnenform anhand der Feldkante: Ein ganz neues Feature, welches nur auf bestimmten Feldern funktioniert. Hat man ein Feld mit einem langen Bogen als Feldgrenze und setzt den Feldmarker in die Nähe dieses Feldrands, so werden die Bahnen in der Mitte den Verlauf des Feldrands annehmen. ANMERKUNG: Sollte ein Kurs nicht sauber generiert werden, funktioniert diese Funktion nicht auf dem Feld oder mit dem Feldrand. Es wird dafür auch kein Update geben! "/> - <text name="CP_help_page_courseGeneratorHeadland_title" text="Kursgenerator Vorgewende"/> - <text name="CP_help_page_courseGeneratorCenter_title" text="Kursgenerator Feldmitte"/> - <text name="CP_help_page_courseGeneratorIsland_title" text="Kursgenerator Insel"/> - <text name="CP_help_page_courseGeneratorExpert_title" text="Kursgenerator Expert"/> + <text name="CP_help_page_courseGeneratorHeadland_title" text="Kursgenerator: Vorgewende"/> + <text name="CP_help_page_courseGeneratorCenter_title" text="Kursgenerator: Feldmitte"/> + <text name="CP_help_page_courseGeneratorIsland_title" text="Kursgenerator: Feldinseln"/> + <text name="CP_help_page_courseGeneratorExpert_title" text="Kursgenerator: Experteneinstellungen"/> <text name="CP_help_page_courseManager_title" text="Kursmanager"/> <text name="CP_help_page_courseManagerBase_text" text=" Der Kursmanager erlaubt es dir, Kurse zu speichern und später wieder zu laden. @@ -712,7 +712,7 @@ A: Mit gedrückter linker Maustaste auf der Kopfzeile lässt sich das HUD auf ei B: Um schnell zu den globalen Einstellungen zu gelangen, kannst du mit der Maus auf das CP-Icon klicken. C: Hier wird der Name deines Fahrzeugs angezeigt, klickst du darauf, gelangst du direkt in die CP-Fahrzeugeinstellungen. D: Diese Symbole sind dafür da um (1) den aktuell geladenen Kurs zu löschen, (2a) die Anzeige des geladenen Kurs durch zu schalten (2b) wenn kein Kurs geladen ist, ein Aufnahme-Button um einen Feldrandkurs aufzuzeichnen, (3) den Helfer zu starten und zu stoppen. -E: Dieses Ziel Icon hat verschiedene Funktionen. Je nach dem gewähltem Modus gelangt man direkt auf die Helferkarte mit dem passenden Helferjob. Dort können dann mögliche fehlende Marker, wie die Feldposition platziert werden. Links davon wird bei der Feldarbeit die verbleibende Zeit vom Kurs angezeigt. +E: Dieses Ziel-Icon hat verschiedene Funktionen. Je nach dem gewähltem Modus gelangt man direkt auf die Helferkarte mit dem passenden Helferjob. Dort können dann mögliche fehlende Marker, wie die Feldposition platziert werden. Links davon wird bei der Feldarbeit die verbleibende Zeit vom Kurs angezeigt. F: Durch das Klicken auf diesen Text werden die erlaubten Modi je nach Fahrzeuggespann durchgeschaltet. G: Unterhalb dieser Linie befinden sich modusabhängige Einstellungen, die in den nachfolgenden Bildern erklärt werden. "/> @@ -729,7 +729,7 @@ E: Mit einem Klick auf das Symbol auf der rechten Seite wird der Kurs des Fahrze A: Da sowohl ein Drescher als auch ein Lader (z.B. ROPA Maus) gleichzeitig auf einem Feld arbeiten könnten, kann hier gewählt werden, welches Gerät abgetankt werden soll. B: Hier kannst du bestimmen, ab wie viel Prozent der Abfahrer zum Entladen fahren soll. Einstellbar zwischen 40% und 100%. Die Bedienung ist gleich wie bei der Arbeitsbreite vom Feldarbeitsmodus. C: Es kann vorkommen, dass die Position des Abfahrers nicht perfekt passt. Das kann am Anhänger oder am Abladerohr des Ernters liegen. Manchmal reicht auch ein Gefälle des Feldes aus. Hier kannst du den Abstand zum Ernter einstellen. -D: Das gleiche lässt sich auch für eine Korrektur nach vorne oder nach hinten zum Ernter einstellen. +D: Das Gleiche lässt sich auch für eine Korrektur nach vorne oder nach hinten zum Ernter einstellen. E: Ähnlich wie beim Kopieren eines Kurses, werden hier die Positionen der Marker für den Abfahrer kopiert und können dann auf weiteren Abfahrer übertragen werden. "/> <text name="CP_help_page_minihud_balecollect_title" text="Ballen sammeln/wickeln"/> @@ -745,7 +745,7 @@ B: Arbeitsbreite des Laders oder der Schaufel, genauso wie bei der Feldarbeit. C: Versatz für die Ladehöhe einer Schaufel. Kann genutzt werden falls die Schaufel nicht tief genug oder zu tief gesenkt wird. D: Wie schon beim Abfahrer, kannst du hier die benötigten Marker kopieren und auf ein weiteres Gerät einfügen. "/> - <text name="CP_help_page_minihud_siloworker_title" text="Silo Arbeiter"/> + <text name="CP_help_page_minihud_siloworker_title" text="Siloarbeiter"/> <text name="CP_help_page_minihud_siloworker_text" text=" A: Wie auch beim Silolader, ist die Schildhöhe entscheidend. Die kannst du hier anpassen. B: Diese Anzeige zeigt dir den aktuellen Fortschritt der Siloverdichtung an. Durch einen Mausklick auf den Text, kannst du umschalten, ob der Abfahrer automatisch bei 100% Siloverdichtung stoppen soll. diff --git a/translations/translation_en.xml b/translations/translation_en.xml index 5dfe6aabb..2fd161b2b 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -46,7 +46,7 @@ <text name="CP_fieldWorkJobParameters_startAt_bales" text="collecting/wrapping bales"/> <text name="CP_fieldWorkJobParameters_subTitle_laneOffset" text="Lane offset:"/> <text name="CP_fieldWorkJobParameters_laneOffset_title" text="Lane offset:"/> - <text name="CP_fieldWorkJobParameters_laneOffset_tooltip" text="Lane offset"/> + <text name="CP_fieldWorkJobParameters_laneOffset_tooltip" text="Lane offset if a multitool course has been created or loaded."/> <text name="CP_fieldWorkJobParameters_laneOffset_center" text="center"/> <text name="CP_fieldWorkJobParameters_laneOffset_left" text="left(1)"/> <text name="CP_fieldWorkJobParameters_laneOffset_right" text="right(1)"/> @@ -215,7 +215,7 @@ <text name="CP_vehicle_setting_debugActive_tooltip" text="Enables/disables debug for this vehicle."/> <text name="CP_vehicle_setting_refillOnTheField_title" text="Refill tool"/> <text name="CP_vehicle_setting_refillOnTheField_tooltip" text="Tool waits on the field to be refilled."/> - <text name="CP_vehicle_setting_refillOnTheField_waiting" text="Waiting"/> + <text name="CP_vehicle_setting_refillOnTheField_waiting" text="Wait"/> <text name="CP_vehicle_setting_refillOnTheField_active" text="Active"/> <!----> <!--Vehicle bunker silo settings--> @@ -224,9 +224,9 @@ <text name="CP_vehicle_setting_bunkerSiloWorkWidth_title" text="Work width"/> <text name="CP_vehicle_setting_bunkerSiloWorkWidth_tooltip" text="Work width. Value: 0 - 50"/> <text name="CP_vehicle_setting_levelerHeightOffset_title" text="Shield height offset"/> - <text name="CP_vehicle_setting_levelerHeightOffset_tooltip" text="Offset of the shield from the ground. Value: 0 - 1"/> + <text name="CP_vehicle_setting_levelerHeightOffset_tooltip" text="Height offset from the shield to the ground. Value: 0 - 1"/> <text name="CP_vehicle_setting_loadingShovelHeightOffset_title" text="Shovel height offset"/> - <text name="CP_vehicle_setting_loadingShovelHeightOffset_tooltip" text="Offset for the shovel loading position. Value: -1 - 1"/> + <text name="CP_vehicle_setting_loadingShovelHeightOffset_tooltip" text="Height offset for the shovel loading position. Value: -1 - 1"/> <!----> <!--Vehicle settings speed--> <!----> @@ -278,7 +278,7 @@ <text name="CP_vehicle_courseGeneratorSetting_subTitle_center" text="Settings center"/> <text name="CP_vehicle_courseGeneratorSetting_subTitle_island" text="Settings island"/> <text name="CP_vehicle_courseGeneratorSetting_workWidth_title" text="Work width"/> - <text name="CP_vehicle_courseGeneratorSetting_workWidth_tooltip" text="Work width. Value: 0 - 50"/> + <text name="CP_vehicle_courseGeneratorSetting_workWidth_tooltip" text="Working width. Value: 0 - 55"/> <text name="CP_vehicle_courseGeneratorSetting_numberOfHeadlands_title" text="Number of headlands"/> <text name="CP_vehicle_courseGeneratorSetting_numberOfHeadlands_tooltip" text="Number of headlands. Value: 0 - 40"/> <text name="CP_vehicle_courseGeneratorSetting_headlandOverlapPercent_title" text="Headland overlap"/> @@ -527,7 +527,7 @@ The course is saved automatically on closing of the editor and overrides the sel <!--Help menu--> <!----> <text name="CP_help_title" text="Courseplay"/> - <text name="CP_help_subTitle" text="A Modification for Farming Simulator 22 "/> + <text name="CP_help_subTitle" text="A Modification for Farming Simulator 25"/> <text name="CP_help_page_blank" text=""/> <text name="CP_help_page_General_title" text="General Information"/> <text name="CP_help_page_GeneralBase_text" text=" @@ -538,18 +538,18 @@ Courseplay is also able to work on rice fields and vines. Fieldwork courses can be setup in multitool mode, which allows the use of up to 5 driver working in a convoy on the same field. It's also possible to have the combine unload in a trailer on/near the field automatically. Custom field borders can be assigned for Courseplay to use, for example: in case of a meadow, which isn't recognised as a normal field. -Lastly cp has a interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. -Combine unloader are able to create a heap near the field with their loaded fruit. -In Farming Simulator 25, Courseplay got its own menu wich you can access over different buttons on the HUD. +Lastly CP has an interface for AutoDrive, which allows for refilling of a seeder at a nearby silo or unloading a forage wagon and so on. +Combine unloaders are able to create a heap near the field with their loaded fruit. +In Farming Simulator 25, Courseplay got its own menu, which you can access over different buttons on the HUD. For more information please visit our GitHub: https://github.com/Courseplay/Courseplay_FS25 . There you can get help or report any issues you've experienced. Finally, we thank every translator and our community for reporting bugs, giving use feedback and new ideas. -What is the expert Mode: -When the expert Mode is deactivated, you have only access to some settings. -The other settings are hidden and set to default values, which works in most situations. -That way, we try to help Users to get easier into Courseplay without being overwhelmed from all the settings. +What is the expert mode: +When the expert mode is deactivated, you only have access to some settings. +The other settings are hidden and set to default values, which work in most situations. +That way, we try to make it easier for new Courseplay users to get started without overwhelming them with the various settings. "/> <text name="CP_help_page_extendedJobMenu_title" text="Extended AI Menu"/> <text name="CP_help_page_startJobMenuBase_text" text=" @@ -592,7 +592,7 @@ Basics: The first few values you see here are real basic stuff. - Work width: Courseplay automatically detects the working width for most tools, so you won't have to worry about this, but if automatic detection fails, you can set it manually. This setting has an impact on the overall course. -- Number of Headlands: The best way to keep your vehicles on the field while turning at the end of each row is to add headlands. +- Number of headlands: The best way to keep your vehicles on the field while turning at the end of each row is to add headlands. The Number of the Headlands multiplied by the work width should be at least the total length of your vehicle plus the attached tool. - Start work on: As mentioned in the extended AI Menu, the field position is used to set the start or end position for the fieldwork course. You want to start working on the headland when harvesting, and work towards the middle of the field. @@ -666,7 +666,7 @@ Some settings are only visible, if you set the expert mode to active on the glob Before you play around with those settings, you should make sure you know what the basic settings do. Some expert settings only work properly under some conditions. -- Multiple Tools: This setting is used when you want more than just one vehicle to work on your course. As this gets a bit more complicated, there is a separate help topic for it. +- Multiple tools: This setting is used when you want more than just one vehicle to work on your course. As this gets a bit more complicated, there is a separate help topic for it. - Switching lanes: With this option active, vehicles switch lanes at each turn, so every vehicle's turn is of the same width. See the extra help menu "Switching lanes" for more details. - Narrow field: Creates the headland only at two short edges of the field. In that case, it is not possible to keep your vehicle on the field on the longest edges at the headland. - Headland overlap: By default, headland passes overlap each other (but never the field edge) by a few percent to avoid missing fruit in some edge cases. You can change that overlap here. @@ -674,10 +674,10 @@ Some expert settings only work properly under some conditions. - Use base line edge: With this setting you can generate curved up/down rows, running parallel with the edge of the field near the field marker. This makes sense when the field edge is not straight, the up/down rows will follow the curve of the edge. IMPORTANT NOTE: This is an experimental setting which may not work for all field shapes. If it doesn't work, try another setting. "/> - <text name="CP_help_page_courseGeneratorHeadland_title" text="Course generator headland"/> - <text name="CP_help_page_courseGeneratorCenter_title" text="Course generator center"/> - <text name="CP_help_page_courseGeneratorIsland_title" text="Course generator island"/> - <text name="CP_help_page_courseGeneratorExpert_title" text="Course generator expert"/> + <text name="CP_help_page_courseGeneratorHeadland_title" text="Course generator: headland"/> + <text name="CP_help_page_courseGeneratorCenter_title" text="Course generator: center"/> + <text name="CP_help_page_courseGeneratorIsland_title" text="Course generator: islands"/> + <text name="CP_help_page_courseGeneratorExpert_title" text="Course generator: expert settings"/> <text name="CP_help_page_courseManager_title" text="Course manager"/> <text name="CP_help_page_courseManagerBase_text" text=" The course manager allows you to save courses and enables you to load the saved course later again. @@ -871,7 +871,7 @@ The status messages are: - Driving to trailer - Ouf of money - Waiting for rain to finish -- Waits for snow to disappear +- Waiting for snow to disappear - Waiting on unloader - Left the course, stopped - Wrong bale type