diff --git a/Courseplay.lua b/Courseplay.lua
index 737948e89..2dcb6efa0 100644
--- a/Courseplay.lua
+++ b/Courseplay.lua
@@ -157,7 +157,7 @@ end
function Courseplay.drawHudMap(map)
if g_Courseplay.globalSettings.drawOntoTheHudMap:getValue() then
local vehicle = g_currentMission.controlledVehicle
- if vehicle and vehicle:getIsEntered() and not g_gui:getIsGuiVisible() and vehicle.spec_courseplaySpec and not vehicle.spec_locomotive then
+ 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
end
@@ -192,14 +192,28 @@ end
FSCareerMissionInfo.saveToXMLFile = Utils.prependedFunction(FSCareerMissionInfo.saveToXMLFile, Courseplay.saveToXMLFile)
function Courseplay:update(dt)
- g_devHelper:update()
- g_bunkerSiloManager:update(dt)
+ g_devHelper:update()
+ g_bunkerSiloManager:update(dt)
+ g_triggerManager:update(dt)
+ if not self.postInit then
+ -- Doubles the map zoom for 4x Maps. Mainly to make it easier to set targets for unload triggers.
+ self.postInit = true
+ local function setIngameMapFix(mapElement)
+ local factor = 2*mapElement.terrainSize/2048
+ mapElement.zoomMax = mapElement.zoomMax * factor
+ mapElement.zoomDefault = mapElement.zoomDefault * factor
+ mapElement.mapZoom = mapElement.zoomDefault
+ end
+ setIngameMapFix(g_currentMission.inGameMenu.pageAI.ingameMap)
+ setIngameMapFix(g_currentMission.inGameMenu.pageMapOverview.ingameMap)
+ end
end
function Courseplay:draw()
if not g_gui:getIsGuiVisible() then
g_vineScanner:draw()
g_bunkerSiloManager:draw()
+ g_triggerManager:draw()
end
g_devHelper:draw()
CpDebug:draw()
@@ -323,27 +337,19 @@ end
function Courseplay.register(typeManager)
--- TODO: make this function async.
for typeName, typeEntry in pairs(typeManager.types) do
- if CourseplaySpec.prerequisitesPresent(typeEntry.specializations) then
- typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".courseplaySpec")
- end
- if CpVehicleSettings.prerequisitesPresent(typeEntry.specializations) then
- typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".cpVehicleSettings")
- end
- if CpCourseGeneratorSettings.prerequisitesPresent(typeEntry.specializations) then
- typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".cpCourseGeneratorSettings")
- end
- if CpCourseManager.prerequisitesPresent(typeEntry.specializations) then
- typeManager:addSpecialization(typeName, Courseplay.MOD_NAME .. ".cpCourseManager")
- end
CpAIWorker.register(typeManager, typeName, typeEntry.specializations)
+ CpVehicleSettings.register(typeManager, typeName, typeEntry.specializations)
+ CpCourseGeneratorSettings.register(typeManager, typeName, typeEntry.specializations)
+ CpCourseManager.register(typeManager, typeName, typeEntry.specializations)
CpAIFieldWorker.register(typeManager, typeName, typeEntry.specializations)
CpAIBaleFinder.register(typeManager, typeName, typeEntry.specializations)
CpAICombineUnloader.register(typeManager, typeName, typeEntry.specializations)
- CpAIBunkerSiloWorker.register(typeManager, typeName, typeEntry.specializations)
CpAISiloLoaderWorker.register(typeManager, typeName, typeEntry.specializations)
+ CpAIBunkerSiloWorker.register(typeManager, typeName, typeEntry.specializations)
CpGamePadHud.register(typeManager, typeName,typeEntry.specializations)
CpHud.register(typeManager, typeName, typeEntry.specializations)
CpInfoTexts.register(typeManager, typeName, typeEntry.specializations)
+ CpShovelPositions.register(typeManager, typeName, typeEntry.specializations)
end
end
TypeManager.finalizeTypes = Utils.prependedFunction(TypeManager.finalizeTypes, Courseplay.register)
diff --git a/README.md b/README.md
index 80fe575dd..fc112f5f8 100644
--- a/README.md
+++ b/README.md
@@ -9,17 +9,32 @@ You can download [here](https://github.com/pops64/Courseplay_FS22/releases/lates
* Chopper Support
* **Multiplayer support**
-* Fieldwork and vine work with courses from the course generator.
-* Course manager for saving/loading/renaming/moving and deleting of courses.
-* Wrapping and collecting of bales with or without a course.
-* Unloading of combines with unloaders.
-* Creating heaps of sugar beets or other fruits on the field with the combine unloader.
-* Unloading silo loaders, like the ``ROPA Maus`` and letting AD or Giants unload the trailer after that.
-* Compacting of a bunker silo or pushing the chaff into the back of a silo.
-* Let the combine unload into nearby trailers (combine self unload).
-* Field work with up to 5 vehicles on the same course (multi tool course).
-* Creating custom fields by recording a course or drawing on the AI Map.
-* Course editor to edit fieldwork courses or custom field borders.
+* 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.
diff --git a/config/HelpMenu.xml b/config/HelpMenu.xml
index 15eb062bd..0be47f0d8 100644
--- a/config/HelpMenu.xml
+++ b/config/HelpMenu.xml
@@ -222,5 +222,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/MasterTranslations.xml b/config/MasterTranslations.xml
index d03637cfc..a669275e5 100644
--- a/config/MasterTranslations.xml
+++ b/config/MasterTranslations.xml
@@ -79,6 +79,10 @@
+
+
+
+
@@ -302,9 +306,21 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -601,6 +617,14 @@
+
+
+
+
+
+
+
+
@@ -2476,6 +2500,96 @@ The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml
index b58b8e625..d50e7001c 100644
--- a/config/VehicleConfigurations.xml
+++ b/config/VehicleConfigurations.xml
@@ -97,6 +97,13 @@ You can define the following custom settings:
Overrides the automatic selection of a moving tool for example for the shield controller of a snowcat.
Used to control the target tilt.
+- loadingShovelOffset: number -1 : 1
+ Offset to the loading and transport shovel position in meter for the height of the shovel.
+
+- shovelMovingToolIx: number
+ If the shovel is a high dump shovel then the moving tool index of the correct one is needed.
+ Usually this index is 1 for the first moving tool.
+
- 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".
@@ -106,8 +113,46 @@ You can define the following custom settings:
Ignore bales detected by the forward-looking proximity controller if any implement has this set to true.
This can be set for bale pushers to prevent the Courseplay driver stopping when a bale is blocking the way.
+- fixWheelLoaderDirectionNodeByMovingToolIx
+ Fixes the ai direction node for the platinum wheel loaders, as their direction is not changed based on the rotation.
+ As a fix the parent node of the arm moving tool ix is used.
+
+- articulatedAxisReverseNodeInverted: boolean
+ Is the reverse node for articulated axis vehicles rotated by 180 degree?
+
+- unloadXOffset: number
+ Offset of the discharge node or a given pipe for unloading into a trailer.
+ For pipes this overwrites the measured pipe offset.
+
+- disablePipeMovingToolCorrection: boolean
+ Disables the pipe height adjustment for trailers of a few pipe implements/vehicles.
+
-->
+
+ toolOffsetX
+ noReverse
+ turnRadius
+ workingWidth
+ balerUnloadDistance
+ directionNodeOffsetZ
+ implementWheelAlwaysOnGround
+ ignoreCollisionBoxesWhenFolded
+ baleCollectorOffset
+ disableUnfolding
+ raiseLate
+ lowerEarly
+ useVehicleSizeForMarkers
+ armMovingToolIx
+ movingToolIx
+ shovelMovingToolIx
+ loadingShovelOffset
+ ignoreBaleCollisionForward
+ fixWheelLoaderDirectionNodeByMovingToolIx
+ articulatedAxisReverseNodeInverted
+ disablePipeMovingToolCorrection
+ unloadOffsetX
+
@@ -314,6 +359,13 @@ You can define the following custom settings:
ignoreCollisionBoxesWhenFolded = "true"
/>
+
+
+
@@ -327,6 +379,22 @@ You can define the following custom settings:
+
+
+
+
+
+
+
+
+
diff --git a/config/VehicleSettingsSetup.xml b/config/VehicleSettingsSetup.xml
index aeb5cb720..adbfe7500 100644
--- a/config/VehicleSettingsSetup.xml
+++ b/config/VehicleSettingsSetup.xml
@@ -76,6 +76,9 @@
+
diff --git a/config/VineCourseGeneratorSettingsSetup.xml b/config/VineCourseGeneratorSettingsSetup.xml
index fe7f220c9..f191e4802 100644
--- a/config/VineCourseGeneratorSettingsSetup.xml
+++ b/config/VineCourseGeneratorSettingsSetup.xml
@@ -21,6 +21,6 @@
-
+
diff --git a/config/gamePadHud/SiloLoaderGamePadHudPage.xml b/config/gamePadHud/SiloLoaderGamePadHudPage.xml
index c406daddd..34fc6f86f 100644
--- a/config/gamePadHud/SiloLoaderGamePadHudPage.xml
+++ b/config/gamePadHud/SiloLoaderGamePadHudPage.xml
@@ -5,5 +5,7 @@
-->
+
+
diff --git a/config/jobParameters/CombineUnloaderJobParameterSetup.xml b/config/jobParameters/CombineUnloaderJobParameterSetup.xml
index 343ee708f..1f60669dd 100644
--- a/config/jobParameters/CombineUnloaderJobParameterSetup.xml
+++ b/config/jobParameters/CombineUnloaderJobParameterSetup.xml
@@ -29,7 +29,7 @@
-
+
diff --git a/config/jobParameters/JobParameterSetup.xml b/config/jobParameters/JobParameterSetup.xml
index fa003f020..ef081c84c 100644
--- a/config/jobParameters/JobParameterSetup.xml
+++ b/config/jobParameters/JobParameterSetup.xml
@@ -15,16 +15,18 @@
- 1
- 2
- 3
+ 1
+ 2
+ 3
4
+ 5
nearest
first
last
bunkerSilo
+ siloLoader
diff --git a/config/jobParameters/SiloLoaderJobParameterSetup.xml b/config/jobParameters/SiloLoaderJobParameterSetup.xml
index 6ec615b9c..68040f16b 100644
--- a/config/jobParameters/SiloLoaderJobParameterSetup.xml
+++ b/config/jobParameters/SiloLoaderJobParameterSetup.xml
@@ -10,6 +10,20 @@
-
+
+
+
+
+
+ 1
+ 2
+
+
+ trailer
+ unloadTrigger
+
+
+
+
diff --git a/img/helpmenu/shovelloadertrigger.dds b/img/helpmenu/shovelloadertrigger.dds
new file mode 100644
index 000000000..b0e406bec
Binary files /dev/null and b/img/helpmenu/shovelloadertrigger.dds differ
diff --git a/modDesc.xml b/modDesc.xml
index cebd38979..d58679340 100644
--- a/modDesc.xml
+++ b/modDesc.xml
@@ -2,6 +2,7 @@
7.3.0.6
Pops64
+ 7.3.1.1
CoursePlay - Chopper Support
自动作业 - Chopper Support
@@ -222,11 +223,24 @@ Changelog 7.1.0.0:
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -238,19 +252,20 @@ Changelog 7.1.0.0:
-
-
-
-
-
+
+
+
+
+
+
@@ -259,11 +274,24 @@ Changelog 7.1.0.0:
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -321,19 +349,27 @@ Changelog 7.1.0.0:
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -341,6 +377,7 @@ Changelog 7.1.0.0:
+
@@ -348,14 +385,13 @@ Changelog 7.1.0.0:
-
-
-
+
+
@@ -364,6 +400,7 @@ Changelog 7.1.0.0:
+
@@ -372,6 +409,7 @@ Changelog 7.1.0.0:
+
@@ -384,6 +422,7 @@ Changelog 7.1.0.0:
+
@@ -395,7 +434,6 @@ Changelog 7.1.0.0:
-
@@ -408,6 +446,7 @@ Changelog 7.1.0.0:
+
diff --git a/scripts/Course.lua b/scripts/Course.lua
index be962b0f2..7eacc1031 100644
--- a/scripts/Course.lua
+++ b/scripts/Course.lua
@@ -914,6 +914,7 @@ function Course:appendWaypoints(waypoints)
end
--- Append another course to the course
+---@param other Course
function Course:append(other)
self:appendWaypoints(other.waypoints)
end
diff --git a/scripts/CpSettingsUtil.lua b/scripts/CpSettingsUtil.lua
index 24d9639bc..945cf28e9 100644
--- a/scripts/CpSettingsUtil.lua
+++ b/scripts/CpSettingsUtil.lua
@@ -70,7 +70,7 @@ function CpSettingsUtil.init()
schema:register(XMLValueType.STRING, "Settings#autoUpdateGui", "Gui gets updated automatically")
local key = "Settings.SettingSubTitle(?)"
- schema:register(XMLValueType.STRING, key .."#title", "Setting sub title", nil, true)
+ schema:register(XMLValueType.STRING, key .."#title", "Setting sub title", nil)
schema:register(XMLValueType.BOOL, key .."#prefix", "Setting sub title is a prefix", true)
schema:register(XMLValueType.STRING, key.."#isDisabled", "Callback function, if the settings is disabled.") -- optional
@@ -157,7 +157,7 @@ function CpSettingsUtil.loadSettingsFromSetup(class, filePath)
class.pageTitle = setupKey .. "title"
end
xmlFile:iterate("Settings.SettingSubTitle", function (i, masterKey)
- local subTitle = xmlFile:getValue(masterKey.."#title")
+ local subTitle = xmlFile:getValue(masterKey.."#title", "...")
--- This flag can by used to simplify the translation text.
local pre = xmlFile:getValue(masterKey.."#prefix", true)
if pre then
diff --git a/scripts/CpUtil.lua b/scripts/CpUtil.lua
index 65f2436ad..aa7de4cdf 100644
--- a/scripts/CpUtil.lua
+++ b/scripts/CpUtil.lua
@@ -169,11 +169,10 @@ end
--- Debug function for implements, that calls CpUtil.debugVehicle, with the root vehicle.
---@param channel number
---@param implement table
----@param ... unknown
-function CpUtil.debugImplement(channel, implement, ...)
+function CpUtil.debugImplement(channel, implement, str, ...)
local rootVehicle = implement and implement.rootVehicle or implement
CpUtil.debugVehicle(channel, rootVehicle,
- CpUtil.getName(implement) .. ': ' .. string.format(...))
+ CpUtil.getName(implement) .. ": " .. str, ...)
end
function CpUtil.isVehicleDebugActive(vehicle)
diff --git a/scripts/ai/AIDriveStrategyCourse.lua b/scripts/ai/AIDriveStrategyCourse.lua
index 283a110fd..6012b541d 100644
--- a/scripts/ai/AIDriveStrategyCourse.lua
+++ b/scripts/ai/AIDriveStrategyCourse.lua
@@ -111,8 +111,7 @@ function AIDriveStrategyCourse:setAIVehicle(vehicle, jobParameters)
self:initializeImplementControllers(vehicle)
self.ppc = PurePursuitController(vehicle)
self.ppc:registerListeners(self, 'onWaypointPassed', 'onWaypointChange')
- -- TODO_22 properly implement this in courseplaySpec
- self.storage = vehicle.spec_courseplaySpec
+ self.storage = vehicle.spec_cpAIWorker
self.settings = vehicle:getCpSettings()
self.courseGeneratorSettings = vehicle:getCourseGeneratorSettings()
@@ -602,13 +601,13 @@ end
---------------------------------------------------------------------------------------------------------------------------
function AIDriveStrategyCourse:disableCollisionDetection()
if self.vehicle then
- CourseplaySpec.disableCollisionDetection(self.vehicle)
+ CpAIWorker.disableCollisionDetection(self.vehicle)
end
end
function AIDriveStrategyCourse:enableCollisionDetection()
if self.vehicle then
- CourseplaySpec.enableCollisionDetection(self.vehicle)
+ CpAIWorker.enableCollisionDetection(self.vehicle)
end
end
diff --git a/scripts/ai/AIDriveStrategyShovelSiloLoader.lua b/scripts/ai/AIDriveStrategyShovelSiloLoader.lua
new file mode 100644
index 000000000..1f28a76aa
--- /dev/null
+++ b/scripts/ai/AIDriveStrategyShovelSiloLoader.lua
@@ -0,0 +1,704 @@
+--[[
+This file is part of Courseplay (https://github.com/Courseplay/Courseplay_FS22)
+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
+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 .
+]]
+
+--[[
+
+This drive strategy implements:
+ - Loading from an bunker silo or a heap on a field with a wheel loader.
+ - Dumping the picked up fill level to an unload trigger oder a trailer.
+ - Automatically setting the shovel/arm Positions of the wheel loader.
+
+]]
+
+
+
+---@class AIDriveStrategyShovelSiloLoader : AIDriveStrategyCourse
+---@field shovelController ShovelController
+AIDriveStrategyShovelSiloLoader = {}
+local AIDriveStrategyShovelSiloLoader_mt = Class(AIDriveStrategyShovelSiloLoader, AIDriveStrategyCourse)
+
+----------------------------------------------------------------
+--- State properties
+----------------------------------------------------------------
+--[[
+ shovelPosition : number (1-4)
+ shovelMovingSpeed : number|nil speed while the shovel/ front loader is moving
+]]
+
+
+----------------------------------------------------------------
+--- States
+----------------------------------------------------------------
+
+AIDriveStrategyShovelSiloLoader.myStates = {
+ DRIVING_ALIGNMENT_COURSE = {shovelPosition = ShovelController.POSITIONS.TRANSPORT},
+ DRIVING_INTO_SILO = {shovelPosition = ShovelController.POSITIONS.LOADING, shovelMovingSpeed = 0},
+ DRIVING_OUT_OF_SILO = {shovelPosition = ShovelController.POSITIONS.TRANSPORT},
+ DRIVING_TEMPORARY_OUT_OF_SILO = {shovelPosition = ShovelController.POSITIONS.TRANSPORT},
+ WAITING_FOR_TRAILER = {shovelPosition = ShovelController.POSITIONS.TRANSPORT},
+ DRIVING_TO_UNLOAD_POSITION = {shovelPosition = ShovelController.POSITIONS.TRANSPORT},
+ DRIVING_TO_UNLOAD_TRAILER = {shovelPosition = ShovelController.POSITIONS.TRANSPORT},
+ DRIVING_TO_UNLOAD = {shovelPosition = ShovelController.POSITIONS.PRE_UNLOADING, shovelMovingSpeed = 0},
+ UNLOADING = {shovelPosition = ShovelController.POSITIONS.UNLOADING, shovelMovingSpeed = 0},
+ REVERSING_AWAY_FROM_UNLOAD = {shovelPosition = ShovelController.POSITIONS.PRE_UNLOADING, shovelMovingSpeed = 0},
+}
+
+AIDriveStrategyShovelSiloLoader.maxValidTrailerDistanceToSiloFront = 30
+AIDriveStrategyShovelSiloLoader.searchForTrailerDelaySec = 10
+AIDriveStrategyShovelSiloLoader.distShovelTrailerPreUnload = 7
+AIDriveStrategyShovelSiloLoader.distShovelUnloadStationPreUnload = 8
+AIDriveStrategyShovelSiloLoader.isStuckMs = 1000 * 15
+function AIDriveStrategyShovelSiloLoader.new(customMt)
+ if customMt == nil then
+ customMt = AIDriveStrategyShovelSiloLoader_mt
+ end
+ local self = AIDriveStrategyCourse.new(customMt)
+ AIDriveStrategyCourse.initStates(self, AIDriveStrategyShovelSiloLoader.myStates)
+ self.state = self.states.INITIAL
+ self.debugChannel = CpDebug.DBG_SILO
+ return self
+end
+
+function AIDriveStrategyShovelSiloLoader:delete()
+ AIDriveStrategyShovelSiloLoader:superClass().delete(self)
+ if self.siloController then
+ self.siloController:delete()
+ self.siloController = nil
+ end
+ CpUtil.destroyNode(self.heapNode)
+ CpUtil.destroyNode(self.unloadPositionNode)
+ CpUtil.destroyNode(self.siloFrontNode)
+ self.isStuckTimer:delete()
+end
+
+function AIDriveStrategyShovelSiloLoader:getGeneratedCourse(jobParameters)
+ return nil
+end
+
+---@param bunkerSilo CpBunkerSilo
+---@param heapSilo CpHeapBunkerSilo
+function AIDriveStrategyShovelSiloLoader:setSiloAndHeap(bunkerSilo, heapSilo)
+ self.bunkerSilo = bunkerSilo
+ self.heapSilo = heapSilo
+end
+
+---@param unloadTrigger CpTrigger
+---@param unloadStation table
+function AIDriveStrategyShovelSiloLoader:setUnloadTriggerAndStation(unloadTrigger, unloadStation)
+ self.unloadTrigger = unloadTrigger
+ self.unloadStation = unloadStation
+end
+
+function AIDriveStrategyShovelSiloLoader:startWithoutCourse(jobParameters)
+
+ -- 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.jobParameters = jobParameters
+ self.unloadPositionNode = CpUtil.createNode("unloadPositionNode", 0, 0, 0)
+
+ --- Is the unload target a trailer?
+ self.isUnloadingAtTrailerActive = jobParameters.unloadAt:getValue() == CpSiloLoaderJobParameters.UNLOAD_TRAILER
+ if not self.isUnloadingAtTrailerActive then
+ self:debug("Starting shovel silo to unload into unload trigger.")
+ --- Uses the exactFillRootNode from the trigger
+ --- and the direction of the unload position marker
+ --- to place the unload position node slightly in front.
+ local x, y, z = getWorldTranslation(self.unloadTrigger:getFillUnitExactFillRootNode(1))
+ setTranslation(self.unloadPositionNode, x, y, z)
+ local position = jobParameters.unloadPosition
+ local dirX, dirZ = position:getDirection()
+ setDirection(self.unloadPositionNode, dirX, 0, dirZ, 0, 0, 1)
+ local dx, dy, dz = localToWorld(self.unloadPositionNode, 0, 0, -math.max(
+ self.distShovelUnloadStationPreUnload, self.turningRadius, 1.5 * AIUtil.getLength(self.vehicle)))
+ setTranslation(self.unloadPositionNode, dx, dy, dz)
+ else
+ self:debug("Starting shovel silo to unload into trailer.")
+ end
+ if self.bunkerSilo ~= nil then
+ self:debug("Bunker silo was found.")
+ self.silo = self.bunkerSilo
+ else
+ self:debug("Heap was found.")
+ self.silo = self.heapSilo
+ end
+ --- fill level, when the driver is started
+ self.fillLevelLeftOverSinceStart = self.silo:getTotalFillLevel()
+
+ local cx, cz = self.silo:getFrontCenter()
+ local dirX, dirZ = self.silo:getLengthDirection()
+ local yRot = MathUtil.getYRotationFromDirection(dirX, dirZ)
+ self.siloFrontNode = CpUtil.createNode("siloFrontNode", cx, cz, yRot)
+ self.siloAreaToAvoid = PathfinderUtil.NodeArea(self.siloFrontNode, -self.silo:getWidth()/2 - 3,
+ -3, self.silo:getWidth() + 6, self.silo:getLength() + 6)
+
+ self.siloController = CpBunkerSiloLoaderController(self.silo, self.vehicle, self)
+
+ self.vehicle:raiseAIEvent("onAIFieldWorkerStart", "onAIImplementStart")
+end
+
+-----------------------------------------------------------------------------------------------------------------------
+--- Implement handling
+-----------------------------------------------------------------------------------------------------------------------
+function AIDriveStrategyShovelSiloLoader:initializeImplementControllers(vehicle)
+ self:addImplementController(vehicle, MotorController, Motorized, {}, nil)
+ self:addImplementController(vehicle, WearableController, Wearable, {}, nil)
+ ---@type table, ShovelController
+ self.shovelImplement, self.shovelController = self:addImplementController(vehicle, ShovelController, Shovel, {}, nil)
+
+end
+
+--- Fuel save only allowed when no trailer is there to unload into.
+function AIDriveStrategyShovelSiloLoader:isFuelSaveAllowed()
+ return self.state == self.states.WAITING_FOR_TRAILER
+end
+
+-----------------------------------------------------------------------------------------------------------------------
+--- Static parameters (won't change while driving)
+-----------------------------------------------------------------------------------------------------------------------
+function AIDriveStrategyShovelSiloLoader:setAllStaticParameters()
+ self.reverser = AIReverseDriver(self.vehicle, self.ppc)
+ self.proximityController = ProximityController(self.vehicle, self:getWorkWidth())
+ self.proximityController:registerIgnoreObjectCallback(self, self.ignoreProximityObject)
+ Markers.setMarkerNodes(self.vehicle)
+ self.frontMarkerNode, self.backMarkerNode, self.frontMarkerDistance, self.backMarkerDistance =
+ Markers.getMarkerNodes(self.vehicle)
+ self.siloEndProximitySensor = SingleForwardLookingProximitySensorPack(self.vehicle, self.shovelController:getShovelNode(), 5, 1)
+ self.heapNode = CpUtil.createNode("heapNode", 0, 0, 0, nil)
+ self.lastTrailerSearch = 0
+ self.isStuckTimer = Timer.new(self.isStuckMs)
+ self.isStuckTimer:setFinishCallback(function ()
+ if self.frozen then
+ return
+ end
+ if self.state == self.states.DRIVING_INTO_SILO then
+ self:debug("Was stuck trying to drive into the bunker silo.")
+ self:startDrivingOutOfSilo()
+ self:setNewState(self.states.DRIVING_TEMPORARY_OUT_OF_SILO)
+ end
+ end)
+end
+
+-----------------------------------------------------------------------------------------------------------------------
+--- Event listeners
+-----------------------------------------------------------------------------------------------------------------------
+function AIDriveStrategyShovelSiloLoader:onWaypointPassed(ix, course)
+ if course:isLastWaypointIx(ix) then
+ if self.state == self.states.DRIVING_ALIGNMENT_COURSE then
+ local course = self:getRememberedCourseAndIx()
+ self:startCourse(course, 1)
+ self:setNewState(self.states.DRIVING_INTO_SILO)
+ elseif self.state == self.states.DRIVING_INTO_SILO then
+ self:startDrivingOutOfSilo()
+ elseif self.state == self.states.DRIVING_OUT_OF_SILO then
+ if self.isUnloadingAtTrailerActive then
+ self:setNewState(self.states.WAITING_FOR_TRAILER)
+ else
+ self:startPathfindingToUnloadPosition()
+ end
+ elseif self.state == self.states.DRIVING_TEMPORARY_OUT_OF_SILO then
+ self:startDrivingToSilo({self.siloController:getLastTarget()})
+ elseif self.state == self.states.DRIVING_TO_UNLOAD_TRAILER then
+ self:approachTrailerForUnloading()
+ elseif self.state == self.states.DRIVING_TO_UNLOAD_POSITION then
+ self:approachUnloadStationForUnloading()
+ elseif self.state == self.states.DRIVING_TO_UNLOAD then
+ self:setNewState(self.states.UNLOADING)
+ elseif self.state == self.states.REVERSING_AWAY_FROM_UNLOAD then
+ if self.shovelController:isEmpty() then
+ self:startDrivingToSilo()
+ else
+ self:setNewState(self.states.WAITING_FOR_TRAILER)
+ end
+ end
+ end
+end
+
+--- this the part doing the actual work on the field after/before all
+--- implements are started/lowered etc.
+function AIDriveStrategyShovelSiloLoader:getDriveData(dt, vX, vY, vZ)
+ 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
+ if self.silo:getTotalFillLevel() <=0 then
+ self:debug("Stopping the driver, as the silo is empty.")
+ self.vehicle:stopCurrentAIJob(AIMessageSuccessFinishedJob.new())
+ return
+ end
+ if self.shovelController:isFull() then
+ if self.isUnloadingAtTrailerActive then
+ self:setNewState(self.states.WAITING_FOR_TRAILER)
+ else
+ self:startPathfindingToUnloadPosition()
+ end
+ else
+ self:startDrivingToSilo()
+ end
+ self:setMaxSpeed(0)
+ elseif self.state == self.states.DRIVING_ALIGNMENT_COURSE then
+ self:setMaxSpeed(self.settings.fieldSpeed:getValue())
+ elseif self.state == self.states.WAITING_FOR_PATHFINDER then
+ self:setMaxSpeed(0)
+ elseif self.state == self.states.DRIVING_INTO_SILO then
+ self:setMaxSpeed(self.settings.bunkerSiloSpeed:getValue())
+ if AIUtil.isStopped(self.vehicle) and not self.proximityController:isStopped() then
+ --- Updates the is stuck timer
+ self.isStuckTimer:startIfNotRunning()
+ end
+ local _, _, closestObject = self.siloEndProximitySensor:getClosestObjectDistanceAndRootVehicle()
+ local isEndReached, maxSpeed = self.siloController:isEndReached(self.shovelController:getShovelNode(), 0)
+ if self.silo:isTheSameSilo(closestObject) or isEndReached then
+ self:debug("End wall detected or bunker silo end is reached.")
+ self:startDrivingOutOfSilo()
+ end
+ if self.shovelController:isFull() then
+ self:debug("Shovel is full, starting to drive out of the silo.")
+ self:startDrivingOutOfSilo()
+ end
+ elseif self.state == self.states.DRIVING_OUT_OF_SILO then
+ self:setMaxSpeed(self.settings.bunkerSiloSpeed:getValue())
+ elseif self.state == self.states.DRIVING_TEMPORARY_OUT_OF_SILO then
+ self:setMaxSpeed(self.settings.bunkerSiloSpeed:getValue())
+ elseif self.state == self.states.DRIVING_TO_UNLOAD_POSITION then
+ self:setMaxSpeed(self.settings.fieldSpeed:getValue())
+ elseif self.state == self.states.DRIVING_TO_UNLOAD_TRAILER then
+ self:setMaxSpeed(self.settings.fieldSpeed:getValue())
+ elseif self.state == self.states.WAITING_FOR_TRAILER then
+ self:setMaxSpeed(0)
+ if (g_time - self.lastTrailerSearch) > self.searchForTrailerDelaySec * 1000 then
+ self:searchForTrailerToUnloadInto()
+ self.lastTrailerSearch = g_time
+ end
+ elseif self.state == self.states.DRIVING_TO_UNLOAD then
+ self:setMaxSpeed(self.settings.reverseSpeed:getValue())
+ local refNode
+ if self.isUnloadingAtTrailerActive then
+ refNode = self.targetTrailer.exactFillRootNode
+ else
+ refNode = self.unloadTrigger:getFillUnitExactFillRootNode(1)
+ end
+ if self.shovelController:isShovelOverTrailer(refNode) then
+ self:setNewState(self.states.UNLOADING)
+ self:setMaxSpeed(0)
+ end
+ if not self.isUnloadingAtTrailerActive then
+ if self.shovelController:isShovelOverTrailer(refNode, 3) and self.shovelController:canDischarge(self.unloadTrigger) then
+ self:setNewState(self.states.UNLOADING)
+ self:setMaxSpeed(0)
+ end
+ end
+ elseif self.state == self.states.UNLOADING then
+ self:setMaxSpeed(0)
+ if self:hasFinishedUnloading() then
+ self:startReversingAwayFromUnloading()
+ end
+ elseif self.state == self.states.REVERSING_AWAY_FROM_UNLOAD then
+ self:setMaxSpeed(self.settings.fieldSpeed:getValue())
+ end
+ if self.state.properties.shovelPosition then
+ if not self.frozen and self.shovelController:moveShovelToPosition(self.state.properties.shovelPosition) then
+ if self.state.properties.shovelMovingSpeed ~= nil then
+ self:setMaxSpeed(self.state.properties.shovelMovingSpeed)
+ end
+ end
+ end
+ self:limitSpeed()
+ self:checkProximitySensors(moveForwards)
+ return gx, gz, moveForwards, self.maxSpeed, 100
+end
+
+function AIDriveStrategyShovelSiloLoader:update(dt)
+ if CpDebug:isChannelActive(CpDebug.DBG_SILO, self.vehicle) then
+ if self.siloFrontNode and self.state == self.states.WAITING_FOR_TRAILER then
+ DebugUtil.drawDebugCircleAtNode(self.siloFrontNode, self.maxValidTrailerDistanceToSiloFront,
+ math.ceil(self.maxValidTrailerDistanceToSiloFront), nil, false, {0, 3, 0})
+ end
+ if self.course:isTemporary() then
+ self.course:draw()
+ elseif self.ppc:getCourse():isTemporary() then
+ self.ppc:getCourse():draw()
+ end
+ if self.silo then
+ self.silo:drawDebug()
+ self.siloAreaToAvoid:drawDebug()
+ end
+ self.siloController:draw()
+ if self.heapSilo then
+ CpUtil.drawDebugNode(self.heapNode, false, 3)
+ end
+ if self.targetTrailer then
+ CpUtil.drawDebugNode(self.targetTrailer.exactFillRootNode, false, 3, "ExactFillRootNode")
+ end
+ CpUtil.drawDebugNode(self.unloadPositionNode, false, 3)
+ end
+ self:updateImplementControllers(dt)
+ AIDriveStrategyCourse.update(self)
+end
+
+function AIDriveStrategyShovelSiloLoader:updateCpStatus(status)
+ status:setSiloLoaderStatus(self.silo:getTotalFillLevel(), self.fillLevelLeftOverSinceStart)
+end
+
+--- Ignores the bunker silo and the unload target for the proximity sensors.
+function AIDriveStrategyShovelSiloLoader:ignoreProximityObject(object, vehicle)
+ if self.silo:isTheSameSilo(object) then
+ return true
+ end
+ --- This ignores the terrain.
+ if object == nil then
+ return true
+ end
+ if object == self.unloadStation then
+ return true
+ end
+ if self.unloadTrigger and self.unloadTrigger:isTheSameObject(object) then
+ return true
+ end
+ if self.targetTrailer then
+ if object == self.targetTrailer.trailer then
+ return true
+ end
+ end
+ return false
+end
+
+function AIDriveStrategyShovelSiloLoader:getProximitySensorWidth()
+ -- a bit less as size.width always has plenty of buffer
+ return self.vehicle.size.width - 0.5
+end
+
+function AIDriveStrategyShovelSiloLoader:getWorkWidth()
+ return self.settings.bunkerSiloWorkWidth:getValue()
+end
+
+function AIDriveStrategyShovelSiloLoader:setNewState(newState)
+ self:debug("Changed State from %s to %s", self.state.name, newState.name)
+ self.state = newState
+end
+
+--- Is the trailer valid or not?
+---@param trailer table
+---@param trailerToIgnore table|nil
+---@return boolean
+---@return table|nil
+function AIDriveStrategyShovelSiloLoader:isValidTrailer(trailer, trailerToIgnore)
+ local function debug(...)
+ self:debug("%s attached to: %s => %s", CpUtil.getName(trailer),
+ trailer.rootVehicle and CpUtil.getName(trailer.rootVehicle) or "no root vehicle", string.format(...))
+ end
+ if not SpecializationUtil.hasSpecialization(Trailer, trailer.specializations) then
+ return false
+ end
+ if trailer.rootVehicle and not AIUtil.isStopped(trailer.rootVehicle) then
+ self:debug("is not stopped!", CpUtil.getName(trailer))
+ return false
+ end
+ if trailerToIgnore and table.hasElement(trailerToIgnore, trailer) then
+ debug("will be ignored!", CpUtil.getName(trailer))
+ return false
+ end
+ local canLoad, fillUnitIndex, fillType, exactFillRootNode =
+ ImplementUtil.getCanLoadTo(trailer, self.shovelImplement,
+ nil, debug)
+ if not canLoad or exactFillRootNode == nil then
+ debug("can't be used!", CpUtil.getName(trailer))
+ return false
+ end
+ return true, { fillUnitIndex = fillUnitIndex,
+ fillType = fillType,
+ exactFillRootNode = exactFillRootNode,
+ trailer = trailer }
+end
+
+--- Gets the closest trailer data with the distance
+---@param trailerToIgnore table|nil optional trailers that will be ignored.
+---@return table|nil
+---@return number
+function AIDriveStrategyShovelSiloLoader:getClosestTrailerAndDistance(trailerToIgnore)
+ local closestDistance = math.huge
+ local closestTrailerData = nil
+ for i, vehicle in pairs(g_currentMission.vehicles) do
+ local dist = calcDistanceFrom(vehicle.rootNode, self.siloFrontNode)
+ if dist < closestDistance then
+ local valid, trailerData = self:isValidTrailer(vehicle, trailerToIgnore)
+ if valid then
+ closestDistance = dist
+ closestTrailerData = trailerData
+ end
+ end
+ end
+ return closestTrailerData, closestDistance
+end
+
+--- Searches for trailer to unload into in a maximum radius relative to the silo front center.
+function AIDriveStrategyShovelSiloLoader:searchForTrailerToUnloadInto()
+ self:debug("Searching for an trailer nearby.")
+ local trailerData, dist = self:getClosestTrailerAndDistance({})
+ if not trailerData then
+ self:debug("No valid trailer found anywhere!")
+ self:setInfoText(InfoTextManager.WAITING_FOR_UNLOADER)
+ return
+ end
+ local trailer = trailerData.trailer
+ if dist > self.maxValidTrailerDistanceToSiloFront then
+ self:debug("Closest Trailer %s attached to %s with the distance %.2fm/%.2fm found is to far away!",
+ CpUtil.getName(trailer), trailer.rootVehicle and CpUtil.getName(trailer.rootVehicle) or "no root vehicle",
+ dist, self.maxValidTrailerDistanceToSiloFront)
+ self:setInfoText(InfoTextManager.WAITING_FOR_UNLOADER)
+ return
+ end
+ self:clearInfoText(InfoTextManager.WAITING_FOR_UNLOADER)
+ --- Sets the unload position node in front of the closest side of the trailer.
+ self:debug("Found a valid trailer %s within distance %.2f", CpUtil.getName(trailer), dist)
+ self.targetTrailer = trailerData
+ local _, _, distShovelDirectionNode = localToLocal(self.shovelController:getShovelNode(), self.vehicle:getAIDirectionNode(), 0, 0, 0)
+ local dirX, _, dirZ = localDirectionToWorld(trailer.rootNode, 0, 0, 1)
+ local yRot = MathUtil.getYRotationFromDirection(dirX, dirZ)
+ local dx, _, dz = localToLocal(self.shovelController:getShovelNode(), trailer.rootNode, 0, 0, 0)
+ if dx > 0 then
+ local x, y, z = localToWorld(trailer.rootNode, math.abs(distShovelDirectionNode) + self.distShovelTrailerPreUnload, 0, 0)
+ setTranslation(self.unloadPositionNode, x, y, z)
+ setRotation(self.unloadPositionNode, 0, MathUtil.getValidLimit(yRot - math.pi/2), 0)
+ else
+ local x, y, z = localToWorld(trailer.rootNode, -math.abs(distShovelDirectionNode) - self.distShovelTrailerPreUnload, 0, 0)
+ setTranslation(self.unloadPositionNode, x, y, z)
+ setRotation(self.unloadPositionNode, 0, MathUtil.getValidLimit(yRot + math.pi/2), 0)
+ end
+ self:startPathfindingToTrailer()
+end
+
+----------------------------------------------------------------
+--- Pathfinding
+----------------------------------------------------------------
+
+--- Find an alignment path to the silo lane course.
+---@param course table silo lane course
+function AIDriveStrategyShovelSiloLoader:startPathfindingToStart(course)
+ if not self.pathfinder or not self.pathfinder:isActive() then
+ self:setNewState(self.states.WAITING_FOR_PATHFINDER)
+ self:rememberCourse(course, 1)
+ local done, path
+ local fm = self:getFrontAndBackMarkers()
+ self.pathfinder, done, path = PathfinderUtil.startPathfindingFromVehicleToWaypoint(
+ self.vehicle, course, 1, 0, -(fm + 4),
+ true, nil, nil,
+ nil, 0, self.siloAreaToAvoid)
+ if done then
+ return self:onPathfindingDoneToStart(path)
+ else
+ self:setPathfindingDoneCallback(self, self.onPathfindingDoneToStart)
+ end
+ else
+ self:debug('Pathfinder already active')
+ end
+ return true
+end
+
+function AIDriveStrategyShovelSiloLoader:onPathfindingDoneToStart(path)
+ if path and #path > 2 then
+ self:debug("Found alignment path to the course for the silo.")
+ local alignmentCourse = Course(self.vehicle, CourseGenerator.pointsToXzInPlace(path), true)
+ alignmentCourse:adjustForTowedImplements(2)
+ self:startCourse(alignmentCourse, 1)
+ self:setNewState(self.states.DRIVING_ALIGNMENT_COURSE)
+ else
+ self:debug("No path to the silo found!")
+ self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new())
+ --- TODO: Might need to consider a retry to another silo lane
+ end
+end
+
+--- Starts Pathfinding to the position node in front of a unload trigger.
+function AIDriveStrategyShovelSiloLoader:startPathfindingToUnloadPosition()
+ if not self.pathfinder or not self.pathfinder:isActive() then
+ self:setNewState(self.states.WAITING_FOR_PATHFINDER)
+ local done, path, goalNodeInvalid
+ self.pathfinder, done, path, goalNodeInvalid = PathfinderUtil.startPathfindingFromVehicleToNode(
+ self.vehicle, self.unloadPositionNode,
+ 0, 0, true,
+ nil, {}, nil,
+ 0, self.siloAreaToAvoid, false
+ )
+ if done then
+ return self:onPathfindingDoneToUnloadPosition(path, goalNodeInvalid)
+ else
+ self:setPathfindingDoneCallback(self, self.onPathfindingDoneToUnloadPosition)
+ end
+ else
+ self:debug('Pathfinder already active')
+ end
+ return true
+end
+
+function AIDriveStrategyShovelSiloLoader:onPathfindingDoneToUnloadPosition(path, goalNodeInvalid)
+ if path and #path > 2 then
+ self:debug("Found path to unloading station.")
+ local course = Course(self.vehicle, CourseGenerator.pointsToXzInPlace(path), true)
+ course:adjustForTowedImplements(2)
+ self:startCourse(course, 1)
+ self:setNewState(self.states.DRIVING_TO_UNLOAD_POSITION)
+ else
+ self:debug("Failed to drive close to unload position.")
+ self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new())
+ end
+end
+
+--- Starts Pathfinding to the position node in front of the trailer side.
+function AIDriveStrategyShovelSiloLoader:startPathfindingToTrailer()
+ if not self.pathfinder or not self.pathfinder:isActive() then
+ self:setNewState(self.states.WAITING_FOR_PATHFINDER)
+ local done, path, goalNodeInvalid
+ self.pathfinder, done, path, goalNodeInvalid = PathfinderUtil.startPathfindingFromVehicleToNode(
+ self.vehicle, self.unloadPositionNode,
+ 0, 0, true,
+ nil, {}, nil,
+ 0, self.siloAreaToAvoid, false
+ )
+ if done then
+ return self:onPathfindingDoneToTrailer(path, goalNodeInvalid)
+ else
+ self:setPathfindingDoneCallback(self, self.onPathfindingDoneToTrailer)
+ end
+ else
+ self:debug('Pathfinder already active')
+ end
+ return true
+end
+
+function AIDriveStrategyShovelSiloLoader:onPathfindingDoneToTrailer(path, goalNodeInvalid)
+ if path and #path > 2 then
+ self:debug("Found path to trailer %s.", CpUtil.getName(self.targetTrailer.trailer))
+ local course = Course(self.vehicle, CourseGenerator.pointsToXzInPlace(path), true)
+ self:startCourse(course, 1)
+ self:setNewState(self.states.DRIVING_TO_UNLOAD_TRAILER)
+ else
+ self:debug("Failed to find path to trailer!")
+ ---self:setNewState(self.states.WAITING_FOR_TRAILER)
+ --- Later on we might try another approach?
+ self.vehicle:stopCurrentAIJob(AIMessageCpErrorNoPathFound.new())
+ end
+end
+----------------------------------------------------------------
+--- Silo work
+----------------------------------------------------------------
+
+--- Starts driving into the silo lane
+function AIDriveStrategyShovelSiloLoader:startDrivingToSilo(target)
+ --- Creates a straight course in the silo.
+ local startPos, endPos
+ if target then
+ startPos, endPos = unpack(target)
+ else
+ startPos, endPos = self.siloController:getTarget(self:getWorkWidth())
+ end
+ local x, z = unpack(startPos)
+ local dx, dz = unpack(endPos)
+ local siloCourse = Course.createFromTwoWorldPositions(self.vehicle, x, z, dx, dz,
+ 0, 0, 3, 3, false)
+ local distance = siloCourse:getDistanceBetweenVehicleAndWaypoint(self.vehicle, 1)
+ if distance > self.turningRadius then
+ self:debug("Start driving to silo with pathfinder.")
+ self:startPathfindingToStart(siloCourse)
+ else
+ self:debug("Start driving into the silo directly.")
+ self:startCourse(siloCourse, 1)
+ self:setNewState(self.states.DRIVING_INTO_SILO)
+ end
+end
+
+function AIDriveStrategyShovelSiloLoader:startDrivingOutOfSilo()
+ --- Creates the straight reverse course.
+ local startPos, endPos = self.siloController:getLastTarget()
+ local x, z = unpack(endPos)
+ local dx, dz = unpack(startPos)
+ local reverseCourse = Course.createFromTwoWorldPositions(self.vehicle, x, z, dx, dz,
+ 0, 0, 6, 3, true)
+ local ix = reverseCourse:getNextRevWaypointIxFromVehiclePosition(1, self.vehicle:getAIDirectionNode(), 10)
+ if ix == 1 then
+ ix = reverseCourse:getNumberOfWaypoints()
+ end
+ self:startCourse(reverseCourse, ix)
+ self:setNewState(self.states.DRIVING_OUT_OF_SILO)
+end
+
+--- Drives from the position node in front of the trailer to the trailer, so the unloading can begin after that.
+function AIDriveStrategyShovelSiloLoader:approachTrailerForUnloading()
+ local dx, _, dz = getWorldTranslation(self.targetTrailer.exactFillRootNode)
+ local x, _, z = getWorldTranslation(self.vehicle:getAIDirectionNode())
+ local course = Course.createFromTwoWorldPositions(self.vehicle, x, z, dx, dz,
+ 0, -3, 0, 3, false)
+ local firstWpIx = course:getNearestWaypoints(self.vehicle:getAIDirectionNode())
+ self:startCourse(course, firstWpIx)
+ self:setNewState(self.states.DRIVING_TO_UNLOAD)
+ self.shovelController:calculateMinimalUnloadingHeight(self.targetTrailer.exactFillRootNode)
+end
+
+--- Drives from the position node in front of the trigger to the unload trigger, so the unloading can begin after that.
+function AIDriveStrategyShovelSiloLoader:approachUnloadStationForUnloading()
+ local dx, _, dz = getWorldTranslation(self.unloadTrigger:getFillUnitExactFillRootNode())
+ local x, _, z = getWorldTranslation(self.vehicle:getAIDirectionNode())
+ local course = Course.createFromTwoWorldPositions(self.vehicle, x, z, dx, dz,
+ 0, -3, 3, 2, false)
+ local firstWpIx = course:getNearestWaypoints(self.vehicle:getAIDirectionNode())
+ self:startCourse(course, firstWpIx)
+ self:setNewState(self.states.DRIVING_TO_UNLOAD)
+ self.shovelController:calculateMinimalUnloadingHeight(self.unloadTrigger:getFillUnitExactFillRootNode())
+end
+
+----------------------------------------------------------------
+--- Unloading
+----------------------------------------------------------------
+
+--- Is the unloading finished?
+---@return boolean
+function AIDriveStrategyShovelSiloLoader:hasFinishedUnloading()
+ if self.shovelController:isEmpty() then
+ self:debug("Finished unloading, as the shovel is empty.")
+ return true
+ end
+ if self.isUnloadingAtTrailerActive then
+ if self.targetTrailer.trailer:getFillUnitFreeCapacity(self.targetTrailer.fillUnitIndex) <= 0 then
+ self:debug("Trailer is full, abort unloading into trailer %s.", CpUtil.getName(self.targetTrailer.trailer))
+ self.targetTrailer = nil
+ return true
+ end
+ else
+ if self.unloadTrigger:getFillUnitFreeCapacity(1, self.shovelController:getDischargeFillType(), self.vehicle:getOwnerFarmId()) <= 0 then
+ self:debugSparse("Unload Trigger is full.")
+ end
+ end
+ return false
+end
+
+--- Starts reverse straight to make some space to the trailer or unload trigger.
+function AIDriveStrategyShovelSiloLoader:startReversingAwayFromUnloading()
+ local _, _, spaceToTrailer = localToLocal(self.shovelController:getShovelNode(), self.vehicle:getAIDirectionNode(), 0, 0, 0)
+ local course = Course.createStraightReverseCourse(self.vehicle, 2*spaceToTrailer,
+ 0, self.vehicle:getAIDirectionNode() )
+ self:startCourse(course, 1)
+ self:setNewState(self.states.REVERSING_AWAY_FROM_UNLOAD)
+end
diff --git a/scripts/ai/AIReverseDriver.lua b/scripts/ai/AIReverseDriver.lua
index a8c4eb3da..249767095 100644
--- a/scripts/ai/AIReverseDriver.lua
+++ b/scripts/ai/AIReverseDriver.lua
@@ -276,4 +276,4 @@ function AIReverseDriver:calculateHitchCorrectionAngle(crossTrackError, orientat
end
return correctionAngle
-end
+end
\ No newline at end of file
diff --git a/scripts/ai/ImplementUtil.lua b/scripts/ai/ImplementUtil.lua
index 8536cd752..82defb206 100644
--- a/scripts/ai/ImplementUtil.lua
+++ b/scripts/ai/ImplementUtil.lua
@@ -366,18 +366,18 @@ end
---@param tool table moving tool
---@param dt number
---@param rotTarget number target rotation in radiant
+---@return boolean
function ImplementUtil.moveMovingToolToRotation(implement, tool, dt, rotTarget)
if tool.rotSpeed == nil then
- return
+ 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 rotSpeed = MathUtil.clamp(diff * tool.rotSpeed, tool.rotSpeed/3, 0.5)
- if diff < 0 then
- rotSpeed=rotSpeed*(-1)
- end
+ local dir = MathUtil.sign(diff)
+ local rotSpeed = MathUtil.clamp( math.abs(diff) * math.abs(tool.rotSpeed), math.abs(tool.rotSpeed)/3, 0.5 )
+ rotSpeed = dir * rotSpeed
if math.abs(diff) < 0.03 or rotSpeed == 0 then
ImplementUtil.stopMovingTool(implement, tool)
return false
@@ -404,9 +404,16 @@ function ImplementUtil.stopMovingTool(implement, tool)
end
function ImplementUtil.getLevelerNode(object)
- return object.spec_leveler and object.spec_leveler.nodes and object.spec_leveler.nodes[1] and object.spec_leveler.nodes[1]
+ 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
@@ -417,4 +424,88 @@ function ImplementUtil.showBaleCollectorOffset(vehicle, offset)
local dx, dy, dz = localToWorld(vehicle:getAIDirectionNode(), -offset, 3, 2)
DebugUtil.drawDebugLine(x, y, z, dx, dy, dz, 1, 0, 0)
end
-end
\ No newline at end of file
+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
+ return true, fillUnitIndex, loadTargetImplement:getFillUnitExactFillRootNode(fillUnitIndex)
+ 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
+
diff --git a/scripts/ai/Markers.lua b/scripts/ai/Markers.lua
index c02916442..a266c2a07 100644
--- a/scripts/ai/Markers.lua
+++ b/scripts/ai/Markers.lua
@@ -15,6 +15,13 @@
Markers = {}
+function Markers.registerConsoleCommands()
+ g_devHelper.consoleCommands:registerConsoleCommand("cpFrontAndBackerMarkerCalculate",
+ "Calculates the front and back markers", "consoleCommandReload", Markers)
+ g_devHelper.consoleCommands:registerConsoleCommand("cpFrontAndBackerMarkerPrintDebug",
+ "Print Marker data", "consoleCommandPrintDebug", Markers)
+end
+Markers.registerConsoleCommands()
-- a global table with the vehicle as the key to persist the marker nodes we don't want to leak through jobs
-- and also don't want to deal with keeping track when to delete them
g_vehicleMarkers = {}
@@ -36,16 +43,16 @@ local function setBackMarkerNode(vehicle, measuredBackDistance)
if AIUtil.hasImplementsOnTheBack(vehicle) then
local lastImplement
lastImplement, backMarkerOffset = AIUtil.getLastAttachedImplement(vehicle)
- referenceNode = AIUtil.getDirectionNode(vehicle)
+ referenceNode = AIUtil.getDirectionNode(vehicle)
CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'Using the last implement\'s rear distance for the back marker node, %d m from root node', backMarkerOffset)
elseif measuredBackDistance then
- referenceNode = AIUtil.getDirectionNode(vehicle)
+ referenceNode = AIUtil.getDirectionNode(vehicle)
backMarkerOffset = -measuredBackDistance
CpUtil.debugVehicle(CpDebug.DBG_IMPLEMENTS, vehicle, 'back marker node on measured back distance %.1f', measuredBackDistance)
elseif reverserNode then
-- if there is a reverser node, use that, mainly because that most likely will turn with an implement
-- or with the back component of an articulated vehicle. Just need to find out the distance correctly
- local dx, _, dz = localToLocal(reverserNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0)
+ local dx, _, dz = localToLocal(reverserNode, AIUtil.getDirectionNode(vehicle), 0, 0, 0)
local dBetweenRootAndReverserNode = MathUtil.vector2Length(dx, dz)
backMarkerOffset = dBetweenRootAndReverserNode - vehicle.size.length / 2 - vehicle.size.lengthOffset
referenceNode = reverserNode
@@ -123,3 +130,31 @@ function Markers.getMarkerNodes(vehicle)
local backMarker = Markers.getBackMarkerNode(vehicle)
return frontMarker, backMarker, g_vehicleMarkers[vehicle].frontMarkerOffset, g_vehicleMarkers[vehicle].backMarkerOffset
end
+
+--------------------------------------------
+--- Console Commands
+--------------------------------------------
+
+function Markers:consoleCommandReload(backDistance)
+ local vehicle = g_currentMission.controlledVehicle
+ if not vehicle then
+ CpUtil.info("No valid vehicle entered!")
+ return
+ end
+ if backDistance then
+ backDistance = tonumber(backDistance)
+ end
+ Markers.setMarkerNodes(vehicle, backDistance)
+ Markers:consoleCommandPrintDebug()
+end
+
+function Markers:consoleCommandPrintDebug()
+ local vehicle = g_currentMission.controlledVehicle
+ if not vehicle then
+ CpUtil.info("No valid vehicle entered!")
+ return
+ end
+ local _, frontMarkerDistance = Markers.getFrontMarkerNode(vehicle)
+ local _, backMarkerDistance = Markers.getBackMarkerNode(vehicle)
+ CpUtil.infoVehicle(vehicle, "Front distance: %.2f, back distance: %.2f", frontMarkerDistance, backMarkerDistance)
+end
diff --git a/scripts/ai/ProximityController.lua b/scripts/ai/ProximityController.lua
index 4045723dc..3cad859df 100644
--- a/scripts/ai/ProximityController.lua
+++ b/scripts/ai/ProximityController.lua
@@ -161,10 +161,6 @@ end
---@return boolean|nil direction is forwards if true or nil
---@return number maximum speed adjusted to slow down (or 0 to stop) when obstacles are ahead, otherwise maxSpeed
function ProximityController:getDriveData(maxSpeed, moveForwards)
-
- --- Resets the traffic info text.
- self.vehicle:resetCpActiveInfoText(InfoTextManager.BLOCKED_BY_OBJECT)
-
local d, vehicle, object, range, deg, dAvg, hitTerrain = math.huge, nil, nil, 10, 0, 0, false
local pack = moveForwards and self.forwardLookingProximitySensorPack or self.backwardLookingProximitySensorPack
if pack then
@@ -173,12 +169,14 @@ function ProximityController:getDriveData(maxSpeed, moveForwards)
end
if self:ignoreObject(object, vehicle, moveForwards, hitTerrain) then
self:setState(self.states.NO_OBSTACLE, 'No obstacle')
+ self.vehicle:resetCpActiveInfoText(InfoTextManager.BLOCKED_BY_OBJECT)
return nil, nil, nil, maxSpeed
end
if object then
--- Makes sure the detected object is not an implement or the root vehicle.
for _, childVehicle in pairs(self.vehicle:getChildVehicles()) do
if object == childVehicle then
+ self.vehicle:resetCpActiveInfoText(InfoTextManager.BLOCKED_BY_OBJECT)
return nil, nil, nil, maxSpeed
end
end
@@ -228,8 +226,9 @@ function ProximityController:getDriveData(maxSpeed, moveForwards)
end
if self.showBlockedByObjectMessageTimer:get() then
self.vehicle:setCpInfoTextActive(InfoTextManager.BLOCKED_BY_OBJECT)
+ else
+ self.vehicle:resetCpActiveInfoText(InfoTextManager.BLOCKED_BY_OBJECT)
end
-
return nil, nil, nil, maxSpeed
end
diff --git a/scripts/ai/controllers/PipeController.lua b/scripts/ai/controllers/PipeController.lua
index 46ce63346..0f5e8f512 100644
--- a/scripts/ai/controllers/PipeController.lua
+++ b/scripts/ai/controllers/PipeController.lua
@@ -293,7 +293,11 @@ function PipeController:measurePipeProperties()
currentPipeState, targetPipeState, pipeAnimTime)
self:printFoldableDebug()
self:printPipeDebug()
-
+ local configOffsetX = g_vehicleConfigurations:get(self.implement, "unloadOffsetX" )
+ if configOffsetX ~= nil then
+ self:debug("Setting pipe x offset to configured %.2f x offset", configOffsetX)
+ self.pipeOffsetX = configOffsetX
+ end
end
function PipeController:getPipesBaseNode()
@@ -431,6 +435,13 @@ end
function PipeController:setupMoveablePipe()
self.validMovingTools = {}
+ self.hasPipeMovingTools = false
+ self.tempBaseNode = CpUtil.createNode("tempBaseNode", 0, 0, 0)
+ self.tempDependedNode = CpUtil.createNode("tempDependedNode", 0, 0, 0)
+ if g_vehicleConfigurations:get(self.implement, "disablePipeMovingToolCorrection") then
+ --- Moveable pipe will not be adjusted based on a nearby trailer.
+ return
+ end
if self.cylinderedSpec and self.pipeSpec.numAutoAimingStates <= 0 then
for i, m in ipairs(self.cylinderedSpec.movingTools) do
-- Gets only the pipe moving tools.
@@ -465,9 +476,6 @@ function PipeController:setupMoveablePipe()
end
end
- self.tempBaseNode = CpUtil.createNode("tempBaseNode", 0, 0, 0)
- self.tempDependedNode = CpUtil.createNode("tempDependedNode", 0, 0, 0)
-
self:debug("Number of moveable pipe elements found: %d", #self.validMovingTools)
end
diff --git a/scripts/ai/controllers/ShovelController.lua b/scripts/ai/controllers/ShovelController.lua
index 7a840dd6e..22d4ca547 100644
--- a/scripts/ai/controllers/ShovelController.lua
+++ b/scripts/ai/controllers/ShovelController.lua
@@ -1,14 +1,94 @@
---@class ShovelController : ImplementController
ShovelController = CpObject(ImplementController)
-function ShovelController:init(vehicle, implement)
+ShovelController.POSITIONS = {
+ DEACTIVATED = 0,
+ LOADING = 1,
+ TRANSPORT = 2,
+ PRE_UNLOADING = 3,
+ UNLOADING = 4,
+}
+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
+
+function ShovelController:init(vehicle, implement, isConsoleCommand)
ImplementController.init(self, vehicle, implement)
self.shovelSpec = self.implement.spec_shovel
- self.shovelNode = self.shovelSpec.shovelNodes[1]
+ self.shovelNode = ImplementUtil.getShovelNode(implement)
+ self.turnOnSpec = self.implement.spec_turnOnVehicle
+ self.isConsoleCommand = isConsoleCommand
+ --- Sugar can unlading is still WIP
+ self.isSugarCaneTrailer = self.implement.spec_trailer ~= nil
+ self.sugarCaneTrailer = {
+ isDischargeActive = false,
+ isDischargingTimer = CpTemporaryObject(false),
+ movingTool = nil,
+ isMovingToolDirty = false,
+ isDischargingToGround = false
+ }
+ if self.isSugarCaneTrailer then
+ --- Find the moving tool for the sugar cane trailer
+ for i, tool in pairs(implement.cylindered.movingTools) do
+ if tool.axis then
+ self.sugarCaneTrailer.movingTool = tool
+ end
+ end
+ end
+end
+
+function ShovelController:getDriveData()
+ local maxSpeed
+ if self.isSugarCaneTrailer then
+ --- Sugar cane trailer discharge
+ if self.sugarCaneTrailer.isDischargeActive then
+ if self.sugarCaneTrailer.isDischargingTimer:get() then
+ --- Waiting until the discharging stopped or
+ --- the trailer is empty
+ maxSpeed = 0
+ self:debugSparse("Waiting for unloading!")
+ end
+
+ -- if self.trailerSpec.tipState == Trailer.TIPSTATE_OPENING then
+ -- --- Trailer not yet ready to unload.
+ -- maxSpeed = 0
+ -- self:debugSparse("Waiting for trailer animation opening!")
+ -- end
+ if self:isEmpty() then
+ --- Waiting for the trailer animation to finish.
+ maxSpeed = 0
+ self:debugSparse("Waiting for trailer animation closing!")
+ end
+ else
+ -- ImplementUtil.moveMovingToolToRotation(self.implement,
+ -- self.sugarCaneTrailerMovingTool, dt , )
+ end
+ end
+ return nil, nil, nil, maxSpeed
end
-function ShovelController:update()
-
+function ShovelController:update(dt)
+ if self.isSugarCaneTrailer then
+ --- Sugar cane trailer discharge
+ if self.sugarCaneTrailer.isDischargeActive then
+ if self:isEmpty() then
+ self:finishedSugarCaneTrailerDischarge()
+ end
+ if self.implement:getCanDischargeToGround(self.dischargeData.dischargeNode) then
+ --- Update discharge timer
+ self.sugarCaneTrailer.isDischargingTimer:set(true, 500)
+ if not self:isDischarging() then
+ -- self.implement:setDischargeState(Dischargeable.DISCHARGE_STATE_GROUND)
+ end
+ end
+ -- ImplementUtil.moveMovingToolToRotation(self.implement,
+ -- self.sugarCaneTrailerMovingTool, dt , )
+ else
+ -- ImplementUtil.moveMovingToolToRotation(self.implement,
+ -- self.sugarCaneTrailerMovingTool, dt , )
+ end
+ end
end
function ShovelController:getShovelNode()
@@ -16,11 +96,11 @@ function ShovelController:getShovelNode()
end
function ShovelController:isFull()
- return self:getFillLevelPercentage() >= 0.98
+ return self:getFillLevelPercentage() >= 99
end
function ShovelController:isEmpty()
- return self:getFillLevelPercentage() <= 0.01
+ return self:getFillLevelPercentage() <= 1
end
function ShovelController:getFillLevelPercentage()
@@ -40,6 +120,249 @@ function ShovelController:getShovelFillType()
return self.shovelSpec.loadingFillType
end
-function ShovelController:isReadyToLoad()
- return self:getShovelFillType() == FillType.UNKNOWN and self:getFillLevelPercentage() < 0.5
+function ShovelController:getDischargeFillType()
+ return self.implement:getDischargeFillType(self:getDischargeNode())
+end
+
+function ShovelController:getDischargeNode()
+ return self.implement:getCurrentDischargeNode()
+end
+
+--- Checks if the shovel raycast has found an unload target.
+---@param targetTrigger CpTrigger|nil
+---@return boolean
+function ShovelController:canDischarge(targetTrigger)
+ local dischargeNode = self:getDischargeNode()
+ local spec = self.implement.spec_dischargeable
+ if not spec.isAsyncRaycastActive then
+ local oldNode = dischargeNode.raycast.node
+ dischargeNode.raycast.node = self.implement.spec_attachable.attacherJoint.node
+ self.implement:updateRaycast(dischargeNode)
+ dischargeNode.raycast.node = oldNode
+ end
+ if targetTrigger and targetTrigger:getTrigger() ~= self.implement:getDischargeTargetObject(dischargeNode) then
+ return false
+ end
+ return dischargeNode.dischargeHit
+end
+
+--- Is the shovel node over the trailer?
+---@param refNode number
+---@param margin number|nil
+---@return boolean
+function ShovelController:isShovelOverTrailer(refNode, margin)
+ local node = self:getShovelNode()
+ local _, _, distShovelToRoot = localToLocal(node, self.implement.rootVehicle:getAIDirectionNode(), 0, 0, 0)
+ local _, _, distTrailerToRoot = localToLocal(refNode, self.implement.rootVehicle:getAIDirectionNode(), 0, 0, 0)
+ margin = margin or 0
+ if self:isHighDumpShovel() then
+ margin = margin + 1
+ end
+ return ( distTrailerToRoot - distShovelToRoot ) < margin
+end
+
+function ShovelController:isHighDumpShovel()
+ return g_vehicleConfigurations:get(self.implement, "shovelMovingToolIx") ~= nil
+end
+
+--- Calculates the minimal unloading height for the trigger.
+---@param triggerNode number|nil
+---@return boolean
+function ShovelController:calculateMinimalUnloadingHeight(triggerNode)
+ local sx, sy, sz = getWorldTranslation(self.vehicle:getAIDirectionNode())
+ local tx, ty, tz
+ if triggerNode then
+ tx, ty, tz = getWorldTranslation(triggerNode)
+ else
+ local dirX, _, dirZ = localDirectionToWorld(self.vehicle:getAIDirectionNode(), 0, 0, 1)
+ local _, frontMarkerDistance = Markers.getFrontMarkerNode(self.vehicle)
+ tx, ty, tz = sx + dirX * (frontMarkerDistance + 4), sy, sz + dirZ * (frontMarkerDistance + 4)
+ end
+ local length = MathUtil.vector2Length(tx - sx, tz - sz) + 0.25
+ local dx, dy, dz = tx - sx, ty - sy, tz -sz
+ local _, terrainHeight, _ = getWorldTranslation(self.vehicle.rootNode)
+ 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,
+ self.TRIGGER_HEIGHT_RAYCAST_COLLISION_MASK)
+ if self.objectWasHit then
+ maxHeightObjectHit = i
+ end
+ end
+ if maxHeightObjectHit > 0 then
+ self:debug("Finished raycast with minimal height: %.2f", maxHeightObjectHit)
+ self.implement:setCpShovelMinimalUnloadHeight(maxHeightObjectHit + 0.5)
+ return true
+ end
+ self:debug("Could not find a valid minimal height, so we use the maximum: %.2f", self.MAX_TRIGGER_HEIGHT)
+ self.implement:setCpShovelMinimalUnloadHeight(self.MAX_TRIGGER_HEIGHT)
+ return false
+end
+
+--- Callback checks if an object was hit.
+function ShovelController:calculateMinimalUnloadingHeightRaycastCallback(hitObjectId, x, y, z, distance, nx, ny, nz, subShapeIndex, shapeId, isLast)
+ if hitObjectId then
+ local object = g_currentMission.nodeToObject[hitObjectId]
+ if object then
+ if object ~= self.vehicle and object ~= self.implement then
+ self:debug("Object: %s was hit!", CpUtil.getName(object))
+ self.objectWasHit = true
+ return true
+ end
+ else
+ self:debug("Shape was hit!")
+ self.objectWasHit = true
+ return true
+ end
+ return false
+ end
+ return false
+end
+
+function ShovelController:delete()
+ if self.implement.cpResetShovelState then
+ self.implement:cpResetShovelState()
+ end
+end
+
+--- Applies the given shovel position and
+--- enables shovels that need an activation for unloading.
+---@param pos number shovel position 1-4
+---@return boolean reached?
+function ShovelController:moveShovelToPosition(pos)
+ if self.turnOnSpec then
+ if pos == ShovelController.POSITIONS.UNLOADING then
+ if not self.implement:getIsTurnedOn() and self.implement:getCanBeTurnedOn() then
+ self.implement:setIsTurnedOn(true)
+ self:debug("Turning on the shovel.")
+ end
+ if not self.implement:getDischargeState() ~= Dischargeable.DISCHARGE_STATE_OBJECT then
+ self.implement:setDischargeState(Dischargeable.DISCHARGE_STATE_OBJECT)
+ end
+ return false
+ else
+ if self.implement:getIsTurnedOn() then
+ self.implement:setIsTurnedOn(false)
+ self:debug("turning off the shovel.")
+ end
+ end
+ end
+ if self.implement.cpSetShovelState == nil then
+ return false
+ end
+ self.implement:cpSetShovelState(pos)
+ return self.implement:areCpShovelPositionsDirty()
+end
+
+--------------------------------------------
+--- WIP! Sugar cane trailer functions
+--------------------------------------------
+
+--- Gets the dischargeNode and offset from a selected tip side.
+---@param tipSideID number
+---@param isTippingToGroundNeeded boolean
+---@return table|nil dischargeNodeIndex
+---@return table|nil dischargeNode
+---@return number|nil xOffset
+function ShovelController:getDischargeNodeAndOffsetForTipSide(tipSideID, isTippingToGroundNeeded)
+ local dischargeNode = self:getDischargeNode()
+ return dischargeNode.index, dischargeNode, self:getDischargeXOffset(dischargeNode)
+end
+
+--- Gets the x offset of the discharge node relative to the implement root.
+function ShovelController:getDischargeXOffset(dischargeNode)
+ local node = dischargeNode.node
+ local xOffset, _ ,_ = localToLocal(node, self.implement.rootNode, 0, 0, 0)
+ return xOffset
+end
+
+--- Starts AI Discharge to an object/trailer.
+---@param dischargeNode table discharge node to use.
+---@return boolean success
+function ShovelController:startDischarge(dischargeNode)
+ self.sugarCaneTrailer.isDischargeActive = true
+ return true
+end
+
+--- Starts discharging to the ground if possible.
+function ShovelController:startDischargeToGround(dischargeNode)
+ self.sugarCaneTrailer.isDischargeActive = true
+ self.sugarCaneTrailer.isDischargingToGround = true
+ -- self.isDischargingToGround = true
+ -- self.dischargeData = {
+ -- dischargeNode = dischargeNode,
+ -- }
+ -- local tipSide = self.trailerSpec.dischargeNodeIndexToTipSide[dischargeNode.index]
+ -- if tipSide ~= nil then
+ -- self.implement:setPreferedTipSide(tipSide.index)
+ -- end
+ return true
+end
+
+--- Callback for the drive strategy, when the unloading finished.
+function ShovelController:setFinishDischargeCallback(finishDischargeCallback)
+ self.sugarCaneTrailer.finishDischargeCallback = finishDischargeCallback
+end
+
+--- Callback for ai discharge.
+function ShovelController:finishedSugarCaneTrailerDischarge()
+ self:debug("Finished unloading.")
+ if self.sugarCaneTrailer.finishDischargeCallback then
+ self.sugarCaneTrailer.finishDischargeCallback(self.driveStrategy, self)
+ end
+ self.sugarCaneTrailer.isDischargeActive = false
+ self.sugarCaneTrailer.isDischargingToGround = false
+end
+
+function ShovelController:prepareForUnload()
+ return true
+end
+
+function ShovelController:isDischarging()
+ return self.implement:getDischargeState() ~= Dischargeable.DISCHARGE_STATE_OFF
+end
+
+--- Gets the discharge node z offset relative to the root vehicle direction node.
+function ShovelController:getUnloadOffsetZ(dischargeNode)
+ local node = dischargeNode.node
+ local dist = ImplementUtil.getDistanceToImplementNode(self.vehicle:getAIDirectionNode(),
+ self.implement, node)
+ return dist
+end
+
+--------------------------------------------
+--- Debug functions
+--------------------------------------------
+
+function ShovelController:printShovelDebug()
+ self:debug("--Shovel Debug--")
+ if self.shovelNode then
+ self:debug("Fill unit index: %d, max pickup angle: %.2f, width: %.2f",
+ self.shovelNode.fillUnitIndex,
+ math.deg(self.shovelNode.maxPickupAngle),
+ self.shovelNode.width)
+ if self.shovelNode.movingToolActivation then
+ self:debug("Has moving tool activation => open factor: %.2f, inverted: %s",
+ self.shovelNode.movingToolActivation.openFactor,
+ tostring(self.shovelNode.movingToolActivation.isInverted))
+ end
+ if self.shovelSpec.shovelDischargeInfo.node then
+ self:debug("min angle: %.2f, max angle: %.2f",
+ math.deg(self.shovelSpec.shovelDischargeInfo.minSpeedAngle),
+ math.deg(self.shovelSpec.shovelDischargeInfo.maxSpeedAngle))
+ end
+ end
+ self:debug("--Shovel Debug finished--")
+end
+
+function ShovelController:debug(...)
+ if self.isConsoleCommand then
+ --- Ignore vehicle debug setting, if the pipe controller was created by a console command.
+ self:info(...)
+ return
+ end
+ ImplementController.debug(self, ...)
end
\ No newline at end of file
diff --git a/scripts/ai/controllers/TrailerController.lua b/scripts/ai/controllers/TrailerController.lua
index d667e53a1..88e5c70a7 100644
--- a/scripts/ai/controllers/TrailerController.lua
+++ b/scripts/ai/controllers/TrailerController.lua
@@ -59,9 +59,9 @@ end
--- Gets the dischargeNode and offset from a selected tip side.
---@param tipSideID number
---@param isTippingToGroundNeeded boolean
----@return table dischargeNodeIndex
----@return table dischargeNode
----@return number xOffset
+---@return table|nil dischargeNodeIndex
+---@return table|nil dischargeNode
+---@return number|nil xOffset
function TrailerController:getDischargeNodeAndOffsetForTipSide(tipSideID, isTippingToGroundNeeded)
local tipSide = self.trailerSpec.tipSides[tipSideID]
if not tipSide then
diff --git a/scripts/ai/jobs/CpAIJob.lua b/scripts/ai/jobs/CpAIJob.lua
index 9d36dfb35..4212b4ace 100644
--- a/scripts/ai/jobs/CpAIJob.lua
+++ b/scripts/ai/jobs/CpAIJob.lua
@@ -364,8 +364,8 @@ function CpAIJob.registerJob(AIJobTypeManager)
AIJobTypeManager:registerJobType(CpAIJobBaleFinder.name, CpAIJobBaleFinder.jobName, CpAIJobBaleFinder)
AIJobTypeManager:registerJobType(CpAIJobFieldWork.name, CpAIJobFieldWork.jobName, CpAIJobFieldWork)
AIJobTypeManager:registerJobType(CpAIJobCombineUnloader.name, CpAIJobCombineUnloader.jobName, CpAIJobCombineUnloader)
- AIJobTypeManager:registerJobType(CpAIJobBunkerSilo.name, CpAIJobBunkerSilo.jobName, CpAIJobBunkerSilo)
AIJobTypeManager:registerJobType(CpAIJobSiloLoader.name, CpAIJobSiloLoader.jobName, CpAIJobSiloLoader)
+ AIJobTypeManager:registerJobType(CpAIJobBunkerSilo.name, CpAIJobBunkerSilo.jobName, CpAIJobBunkerSilo)
end
diff --git a/scripts/ai/jobs/CpAIJobSiloLoader.lua b/scripts/ai/jobs/CpAIJobSiloLoader.lua
index 60499cae4..dc3fdc8ca 100644
--- a/scripts/ai/jobs/CpAIJobSiloLoader.lua
+++ b/scripts/ai/jobs/CpAIJobSiloLoader.lua
@@ -1,5 +1,5 @@
---- Job for stationary loader.
----@class CpAIJobSiloLoader : CpAIJobFieldWork
+--- AI Job for silo loader like the ropa maus or wheel loaders.
+---@class CpAIJobSiloLoader : CpAIJob
---@field heapPlot HeapPlot
---@field heapNode number
CpAIJobSiloLoader = {
@@ -36,6 +36,7 @@ function CpAIJobSiloLoader:setupJobParameters()
CpAIJobSiloLoader:superClass().setupJobParameters(self)
self:setupCpJobParameters(CpSiloLoaderJobParameters(self))
self.cpJobParameters.loadPosition:setSnappingAngle(math.pi/8) -- AI menu snapping angle of 22.5 degree.
+ self.cpJobParameters.unloadPosition:setSnappingAngle(math.pi/8) -- AI menu snapping angle of 22.5 degree.
end
function CpAIJobSiloLoader:getIsAvailableForVehicle(vehicle)
@@ -46,8 +47,8 @@ function CpAIJobSiloLoader:getCanStartJob()
return self.hasValidPosition
end
----@param vehicle Vehicle
----@param mission Mission
+---@param vehicle table
+---@param mission table
---@param farmId number
---@param isDirectStart boolean disables the drive to by giants
---@param isStartPositionInvalid boolean resets the drive to target position by giants and the field position to the vehicle position.
@@ -68,8 +69,15 @@ function CpAIJobSiloLoader:applyCurrentState(vehicle, mission, farmId, isDirectS
self.cpJobParameters.loadPosition:setPosition(x, z)
self.cpJobParameters.loadPosition:setAngle(angle)
end
-end
+ local x, z = self.cpJobParameters.unloadPosition:getPosition()
+
+ -- no unload position use the vehicle's current position
+ if x == nil or z == nil then
+ x, _, z = getWorldTranslation(vehicle.rootNode)
+ self.cpJobParameters.unloadPosition:setPosition(x, z)
+ end
+end
function CpAIJobSiloLoader:setValues()
CpAIJob.setValues(self)
@@ -95,7 +103,10 @@ function CpAIJobSiloLoader:validate(farmId)
self.heapPlot:setVisible(false)
self.heap = nil
self.bunkerSilo = nil
+ self.unloadStation = nil
+ self.unloadTrigger = nil
self.hasValidPosition = false
+ self:getCpJobParameters().unloadStation:setValue(-1)
local isValid, errorMessage = CpAIJob.validate(self, farmId)
if not isValid then
return isValid, errorMessage
@@ -120,7 +131,33 @@ function CpAIJobSiloLoader:validate(farmId)
else
return false, g_i18n:getText("CP_error_no_heap_found")
end
-
+ if not AIUtil.hasChildVehicleWithSpecialization(vehicle, ConveyorBelt) then
+ if self.cpJobParameters.unloadAt:getValue() == CpSiloLoaderJobParameters.UNLOAD_TRIGGER then
+ --- Validates the unload trigger setup
+ local found, unloadTrigger, unloadStation = self:getUnloadTriggerAt(self.cpJobParameters.unloadPosition)
+ if found then
+ self.unloadStation = unloadStation
+ self.unloadTrigger = unloadTrigger
+ if unloadStation == nil then
+ return false, g_i18n:getText("CP_error_no_unload_trigger_found")
+ end
+ local id = NetworkUtil.getObjectId(unloadStation)
+ if id ~= nil then
+ CpUtil.debugVehicle(CpDebug.DBG_SILO, vehicle,
+ "Found a valid unload trigger: %s for %s",
+ CpUtil.getName(unloadTrigger), CpUtil.getName(unloadStation))
+ self:getCpJobParameters().unloadStation:setValue(id)
+ self:getCpJobParameters().unloadStation:validateUnloadingStation()
+ end
+ else
+ return false, g_i18n:getText("CP_error_no_unload_trigger_found")
+ end
+ local unloadPosition = self:getCpJobParameters().unloadPosition
+ if unloadPosition.x == nil or unloadPosition.angle == nil then
+ return false, g_i18n:getText("CP_error_no_unload_trigger_found")
+ end
+ end
+ end
return isValid, errorMessage
end
@@ -147,7 +184,80 @@ function CpAIJobSiloLoader:getBunkerSiloOrHeap(loadPosition, node)
return found, nil, heapSilo
end
+--- Gets the unload trigger at the unload position.
+---@param unloadPosition CpAIParameterPositionAngle
+---@return boolean found?
+---@return table|nil Trigger
+---@return table|nil unloadStation
+function CpAIJobSiloLoader:getUnloadTriggerAt(unloadPosition)
+ local x, z = unloadPosition:getPosition()
+ local dirX, dirZ = unloadPosition:getDirection()
+ if x == nil or dirX == nil then
+ return false
+ end
+ local fillType
+ local silo = self.heap or self.bunkerSilo
+ if silo then
+ fillType = silo:getFillType()
+ end
+ local found, trigger, station = g_triggerManager:getDischargeableUnloadTriggerAt( x, z, dirX, dirZ, 5, 25)
+ if found and fillType ~= nil then
+ --- Additional check if the fill type of the silo
+ --- matches with the fill type of the unload target.
+ if not trigger:getIsFillTypeAllowed(fillType) then
+ --- Fill type is not supported by the trigger.
+ found = false
+ local convertedOutputFillTypes = self:getConvertedFillTypes()
+ for _, convertedFillType in ipairs(convertedOutputFillTypes) do
+ --- Checks possible found fill type conversions
+ if trigger:getIsFillTypeAllowed(convertedFillType) then
+ found = true
+ break
+ end
+ end
+ end
+ end
+ return found, trigger, station
+end
+
function CpAIJobSiloLoader:drawSilos(map)
self.heapPlot:draw(map)
g_bunkerSiloManager:drawSilos(map, self.bunkerSilo)
+ if self.cpJobParameters.unloadAt:getValue() == CpSiloLoaderJobParameters.UNLOAD_TRIGGER then
+ local fillTypes = self:getConvertedFillTypes()
+ local silo = self.heap or self.bunkerSilo
+ if silo then
+ table.insert(fillTypes, silo:getFillType())
+ end
+ g_triggerManager:drawDischargeableTriggers(map, self.unloadTrigger, fillTypes)
+ end
+end
+
+
+--- Gets all the unloading stations.
+function CpAIJobSiloLoader:getUnloadingStations()
+ local unloadingStations = {}
+ for _, unloadingStation in pairs(g_currentMission.storageSystem:getUnloadingStations()) do
+ --- TODO: Maybe a few stations need to be ignored?
+ --- For example stations that have no possible correct fill type
+ table.insert(unloadingStations, unloadingStation)
+ end
+ return unloadingStations
+end
+
+--- Gets converted fill types if there are any.
+---@return table
+function CpAIJobSiloLoader:getConvertedFillTypes()
+ local fillTypes = {}
+ local vehicle = self:getVehicle()
+ if vehicle then
+ local shovels, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Shovel)
+ local spec = found and shovels[1].spec_turnOnVehicle
+ if spec and spec.activateableDischargeNode and spec.activateableDischargeNode.fillTypeConverter then
+ for _, data in pairs(spec.activateableDischargeNode.fillTypeConverter) do
+ table.insert(fillTypes, data.targetFillTypeIndex)
+ end
+ end
+ end
+ return fillTypes
end
\ No newline at end of file
diff --git a/scripts/ai/jobs/CpJobParameters.lua b/scripts/ai/jobs/CpJobParameters.lua
index 5c06ff3c7..b5987bfaf 100644
--- a/scripts/ai/jobs/CpJobParameters.lua
+++ b/scripts/ai/jobs/CpJobParameters.lua
@@ -127,6 +127,28 @@ function CpJobParameters:isBunkerSiloHudModeDisabled()
return self:isAIMenuJob()
end
+function CpJobParameters:isSiloLoadingHudModeDisabled()
+ local vehicle = self.job:getVehicle()
+ if vehicle then
+ if not vehicle:getCanStartCpSiloLoaderWorker() then
+ return true
+ end
+ end
+ return self:isAIMenuJob()
+end
+
+function CpJobParameters:isFieldWorkHudModeDisabled()
+ local vehicle = self.job:getVehicle()
+ if vehicle then
+ if (AIUtil.hasChildVehicleWithSpecialization(vehicle, Leveler) and
+ not AIUtil.hasChildVehicleWithSpecialization(vehicle, Shovel)) or
+ AIUtil.hasChildVehicleWithSpecialization(vehicle, ConveyorBelt) then
+ return true
+ end
+ end
+ return false
+end
+
--- Callback raised by a setting and executed as an vehicle event.
---@param callbackStr string event to be raised
---@param setting AIParameterSettingList setting that raised the callback.
@@ -137,7 +159,6 @@ function CpJobParameters:raiseCallback(callbackStr, setting, ...)
end
end
-
function CpJobParameters:__tostring()
return tostring(self.settings)
end
@@ -205,8 +226,8 @@ function CpCombineUnloaderJobParameters:isFieldUnloadDisabled()
return self.useGiantsUnload:getValue()
end
-function CpCombineUnloaderJobParameters:isUnloadStationSelectorDisabled()
- return self:isGiantsUnloadDisabled() or not self.useGiantsUnload:getValue()
+function CpCombineUnloaderJobParameters:isUnloadStationSelectorVisible()
+ return not self:isGiantsUnloadDisabled() and self.useGiantsUnload:getValue()
end
function CpCombineUnloaderJobParameters:isFieldUnloadPositionSelectorDisabled()
@@ -218,7 +239,6 @@ function CpCombineUnloaderJobParameters:isFieldUnloadTipSideDisabled()
return self:isFieldUnloadDisabled() or self:hasPipe() or not self.useFieldUnload:getValue()
end
-
function CpCombineUnloaderJobParameters:hasPipe()
local vehicle = self.job:getVehicle()
if vehicle then
@@ -311,6 +331,9 @@ end
--- AI parameters for the bunker silo job.
---@class CpSiloLoaderJobParameters : CpJobParameters
+---@field unloadAt AIParameterSettingList
+---@field UNLOAD_TRAILER number
+---@field UNLOAD_TRIGGER number
CpSiloLoaderJobParameters = CpObject(CpJobParameters)
function CpSiloLoaderJobParameters:init(job)
@@ -326,3 +349,34 @@ end
function CpSiloLoaderJobParameters.getSettings(vehicle)
return vehicle.spec_cpAISiloLoaderWorker.cpJob:getCpJobParameters()
end
+
+function CpSiloLoaderJobParameters:isShovelSiloLoadDisabled()
+ local vehicle = self.job:getVehicle()
+ if vehicle then
+ return AIUtil.hasChildVehicleWithSpecialization(vehicle, ConveyorBelt)
+ end
+ return false
+end
+
+function CpSiloLoaderJobParameters:isUnloadPositionDisabled()
+ return self:isShovelSiloLoadDisabled() or self.unloadAt:getValue() == CpSiloLoaderJobParameters.UNLOAD_TRAILER
+end
+
+function CpSiloLoaderJobParameters:isUnloadStationDisabled()
+ return true
+end
+
+function CpSiloLoaderJobParameters:generateUnloadingStations(setting, oldIx)
+ local unloadingStationIds = {}
+ local texts = {}
+ table.insert(unloadingStationIds, -1)
+ table.insert(texts, "---")
+ if self.job then
+ for i, unloadingStation in ipairs(self.job:getUnloadingStations()) do
+ local id = NetworkUtil.getObjectId(unloadingStation)
+ table.insert(unloadingStationIds, id)
+ table.insert(texts, unloadingStation:getName() or "")
+ end
+ end
+ return unloadingStationIds, texts, oldIx
+end
\ No newline at end of file
diff --git a/scripts/ai/parameters/CpAIParameterPositionAngle.lua b/scripts/ai/parameters/CpAIParameterPositionAngle.lua
index 46ccc8b02..8561c3698 100644
--- a/scripts/ai/parameters/CpAIParameterPositionAngle.lua
+++ b/scripts/ai/parameters/CpAIParameterPositionAngle.lua
@@ -109,7 +109,7 @@ end
function CpAIParameterPosition:isAlmostEqualTo(otherPosition)
local x, z = otherPosition:getPosition()
if x ~= nil and self.x ~= nil then
- return MathUtil.vector2Length(self.x - x, self.z - z) <= 1
+ return MathUtil.vector2Length(self.x - x, self.z - z) <= 1
end
return false
end
@@ -121,7 +121,8 @@ CpAIParameterPositionAngle = CpObject(CpAIParameterPosition)
CpAIParameterPositionAngle.POSITION_TYPES = {
DRIVE_TO = 0, --- with angle
FIELD_OR_SILO = 1, --- without angle
- UNLOAD = 2 --- with angle
+ UNLOAD = 2, --- with angle
+ LOAD = 3 --- with angle
}
---@param data table
---@param vehicle table
diff --git a/scripts/ai/parameters/CpAIParameterUnloadingStation.lua b/scripts/ai/parameters/CpAIParameterUnloadingStation.lua
index 1afe6ecd4..22c39d61c 100644
--- a/scripts/ai/parameters/CpAIParameterUnloadingStation.lua
+++ b/scripts/ai/parameters/CpAIParameterUnloadingStation.lua
@@ -41,7 +41,7 @@ end
--- Applies the current position to the map hotspot.
function CpAIParameterUnloadingStation:applyToMapHotspot(mapHotspot)
- if not self:getCanBeChanged() then
+ if not self:getIsVisible() then
return false
end
local unloadingStation = self:getUnloadingStation()
diff --git a/scripts/ai/tasks/CpAITaskSiloLoader.lua b/scripts/ai/tasks/CpAITaskSiloLoader.lua
index b4a6feb0c..ddfc5333a 100644
--- a/scripts/ai/tasks/CpAITaskSiloLoader.lua
+++ b/scripts/ai/tasks/CpAITaskSiloLoader.lua
@@ -1,5 +1,6 @@
--- Bunker silo task
---@class CpAITaskSiloLoader
+---@field job table
CpAITaskSiloLoader = {}
local CpAITaskSiloLoader_mt = Class(CpAITaskSiloLoader, AITask)
@@ -32,7 +33,8 @@ end
function CpAITaskSiloLoader:start()
if self.isServer then
- self.vehicle:startCpSiloLoaderWorker(self.job:getCpJobParameters(), self.silo, self.heap)
+ local _, unloadTrigger, unloadStation = self.job:getUnloadTriggerAt(self.job:getCpJobParameters().unloadPosition)
+ self.vehicle:startCpSiloLoaderWorker(self.job:getCpJobParameters(), self.silo, self.heap, unloadTrigger, unloadStation)
end
CpAITaskSiloLoader:superClass().start(self)
diff --git a/scripts/ai/turns/AITurn.lua b/scripts/ai/turns/AITurn.lua
index 2118efaee..012c5b62b 100644
--- a/scripts/ai/turns/AITurn.lua
+++ b/scripts/ai/turns/AITurn.lua
@@ -730,8 +730,8 @@ function CourseTurn:onPathfindingDone(path)
self.turnCourse:setUseTightTurnOffsetForLastWaypoints(15)
local endingTurnLength = self.turnContext:appendEndingTurnCourse(self.turnCourse, nil, true)
local x = AIUtil.getDirectionNodeToReverserNodeOffset(self.vehicle)
- self:debug('Extending course at direction switch for reversing: %.1f m', -x )
- self.turnCourse:adjustForReversing(math.max(0, -x))
+ self:debug('Extending course at direction switch for reversing to %.1f m (or at least 1m)', -x )
+ self.turnCourse:adjustForReversing(math.max(1, -x))
TurnManeuver.setLowerImplements(self.turnCourse, endingTurnLength, true)
else
self:debug('No path found in %d ms, falling back to normal turn course generator', g_currentMission.time - (self.pathfindingStartedAt or 0))
diff --git a/scripts/config/VehicleConfigurations.lua b/scripts/config/VehicleConfigurations.lua
index fb8a8509b..cef9e48fe 100644
--- a/scripts/config/VehicleConfigurations.lua
+++ b/scripts/config/VehicleConfigurations.lua
@@ -27,32 +27,15 @@ along with this program. If not, see .
---
---@class VehicleConfigurations : CpObject
VehicleConfigurations = CpObject()
-VehicleConfigurations.XML_KEY = "VehicleConfigurations.Vehicle"
+VehicleConfigurations.BASE_KEY = "VehicleConfigurations"
+VehicleConfigurations.XML_KEY = VehicleConfigurations.BASE_KEY .. ".Vehicle"
+VehicleConfigurations.XML_CONFIGURATION_KEY = VehicleConfigurations.BASE_KEY .. ".Configurations.Configuration"
VehicleConfigurations.MOD_NAME = g_currentModName
---- All attributes and the data type.
-VehicleConfigurations.attributes = {
- toolOffsetX = XMLValueType.FLOAT,
- noReverse = XMLValueType.BOOL,
- turnRadius = XMLValueType.FLOAT,
- workingWidth = XMLValueType.FLOAT,
- balerUnloadDistance = XMLValueType.FLOAT,
- directionNodeOffsetZ = XMLValueType.FLOAT,
- implementWheelAlwaysOnGround = XMLValueType.BOOL,
- ignoreCollisionBoxesWhenFolded = XMLValueType.BOOL,
- baleCollectorOffset = XMLValueType.FLOAT,
- disableUnfolding = XMLValueType.BOOL,
- raiseLate = XMLValueType.BOOL,
- lowerEarly = XMLValueType.BOOL,
- useVehicleSizeForMarkers = XMLValueType.BOOL,
- armMovingToolIx = XMLValueType.INT,
- movingToolIx = XMLValueType.INT,
- ignoreBaleCollisionForward = XMLValueType.BOOL,
-}
-
function VehicleConfigurations:init()
self.vehicleConfigurations = {}
self.modVehicleConfigurations = {}
+ self.attributes = {}
if g_currentMission then
self:loadFromXml()
end
@@ -63,17 +46,18 @@ function VehicleConfigurations:registerXmlSchema()
self.xmlSchema = XMLSchema.new("vehicleConfigurations")
self.xmlSchema:register(XMLValueType.STRING,self.XML_KEY.."(?)#name","Configuration name")
self.xmlSchema:register(XMLValueType.STRING,self.XML_KEY.."(?)#modName","Mod name") --- Optional to avoid conflict for xml files with the same name.
- for name,xmlType in pairs(VehicleConfigurations.attributes) do
- self.xmlSchema:register(xmlType,self.XML_KEY.."(?)#"..name,"Configuration value")
- end
+ self.xmlSchema:register(XMLValueType.STRING,self.XML_CONFIGURATION_KEY.."(?)#type","Configuration value type")
+ self.xmlSchema:register(XMLValueType.STRING,self.XML_CONFIGURATION_KEY.."(?)","Configuration name")
end
function VehicleConfigurations:loadFromXml()
- self:registerXmlSchema()
+ self.vehicleConfigurations = {}
+ self.modVehicleConfigurations = {}
+ self.attributes = {}
self.xmlFileName = Utils.getFilename('config/VehicleConfigurations.xml', Courseplay.BASE_DIRECTORY)
- self.xmlFile = self:loadXmlFile(self.xmlFileName)
+ self:registerXmlSchema()
+ self.xmlFile = self:loadXmlFile(self.xmlFileName, true)
self.userXmlFileName = getUserProfileAppPath() .. 'modSettings/'..VehicleConfigurations.MOD_NAME..'/vehicleConfigurations.xml'
-
self.userXmlFile = self:loadXmlFile(self.userXmlFileName)
end
@@ -112,19 +96,39 @@ function VehicleConfigurations:readVehicle(xmlFile, vehicleElement)
end
end
-function VehicleConfigurations:loadXmlFile(fileName)
+
+function VehicleConfigurations:loadXmlFile(fileName, loadConfig)
CpUtil.info('Loading vehicle configuration from %s ...', fileName)
local xmlFile = XMLFile.loadIfExists("vehicleConfigurationsXmlFile",fileName, self.xmlSchema)
if xmlFile then
- xmlFile:iterate(self.XML_KEY, function (ix, key)
- self:readVehicle(xmlFile, key)
- end)
+ if not loadConfig or self:loadConfigurations(xmlFile) then
+ xmlFile:iterate(self.XML_KEY, function (ix, key)
+ self:readVehicle(xmlFile, key)
+ end)
+ end
xmlFile:delete()
else
CpUtil.info('Vehicle configuration file %s does not exist.', fileName)
end
end
+function VehicleConfigurations:loadConfigurations(xmlFile)
+ self.attributes = {}
+ xmlFile:iterate(self.XML_CONFIGURATION_KEY, function(ix, key)
+ local type = xmlFile:getValue(key .. "#type"):upper()
+ local name = xmlFile:getValue(key)
+ self.attributes[name] = XMLValueType[type]
+ if self.attributes[name] == nil then
+ CpUtil.info("Vehicle configuration %s has no valid type for %s!", name, type)
+ end
+ end)
+ for name, xmlType in pairs(self.attributes) do
+ CpUtil.info("Registered %s", name)
+ self.xmlSchema:register(xmlType, self.XML_KEY.."(?)#"..name, "Configuration value")
+ end
+ return true
+end
+
--- Get a custom configuration value for a single vehicle/implement
--- @param object table vehicle or implement object. This function uses the object's configFileName to uniquely
--- identify the vehicle/implement.
@@ -132,20 +136,23 @@ end
--- @return any|nil the value of the configuration attribute or nil if there's no custom config for it
function VehicleConfigurations:get(object, attribute)
if not self:isValidAttribute(attribute) then
- CpUtil.infoImplement(object, "The given attribute name: %s is not valid!", attribute)
+ CpUtil.infoImplement(object, "The given vehicle config attribute name: %s is not valid!", attribute)
return
end
+ local function getConfigName(data)
+ if data[object.configFileNameClean] then
+ return data[object.configFileNameClean][attribute]
+ elseif data[object.configFileNameClean..".xml"] then
+ return data[object.configFileNameClean..".xml"][attribute]
+ end
+ end
if object and object.configFileNameClean then
local modName = object.customEnvironment
- if self.modVehicleConfigurations[modName] then
+ if modName and self.modVehicleConfigurations[modName] then
--- If a mod name was given, then also check the xml filename.
- if self.modVehicleConfigurations[modName][object.configFileNameClean] then
- return self.modVehicleConfigurations[modName][object.configFileNameClean][attribute]
- end
- elseif self.vehicleConfigurations[object.configFileNameClean] then
- return self.vehicleConfigurations[object.configFileNameClean][attribute]
- elseif self.vehicleConfigurations[object.configFileNameClean..".xml"] then
- return self.vehicleConfigurations[object.configFileNameClean..".xml"][attribute]
+ return getConfigName(self.modVehicleConfigurations[modName])
+ else
+ return getConfigName(self.vehicleConfigurations)
end
end
end
@@ -221,6 +228,12 @@ function VehicleConfigurations:registerConsoleCommands()
g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsListAttributes",
"Prints all valid attribute names",
"consoleCommandPrintAttributeNames", self)
+ g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintAttributes",
+ "Prints all normal attributes",
+ "consoleCommandPrintAllNormalVehicleConfigurations", self)
+ g_devHelper.consoleCommands:registerConsoleCommand("cpVehicleConfigurationsPrintModAttributes",
+ "Prints all mod name attributes",
+ "consoleCommandPrintAllModNameVehicleConfigurations", self)
end
function VehicleConfigurations:consoleCommandReload()
@@ -253,7 +266,10 @@ function VehicleConfigurations:consoleCommandPrintSingleAttributeValuesForVehicl
local values = self:queryAttributeValues(vehicle, attribute)
for _, data in pairs(values) do
if data.found then
- CpUtil.infoVehicle(data.implement, "%s", tostring(data.value))
+ CpUtil.infoVehicle(data.implement, "(%s => Mod: %s) %s",
+ tostring(data.implement.configFileNameClean),
+ tostring(data.implement.customEnvironment ~= nil and data.implement.customEnvironment or false),
+ tostring(data.value))
else
CpUtil.infoVehicle(data.implement, "not found")
end
@@ -272,16 +288,40 @@ function VehicleConfigurations:consoleCommandPrintAllAttributeValuesForVehicleAn
CpUtil.info("Found the following for %s: ....", attribute)
for _, data in pairs(values) do
if data.found then
- CpUtil.infoVehicle(data.implement, "%s", tostring(data.value))
+ CpUtil.infoVehicle(data.implement, "(%s => Mod: %s) %s",
+ tostring(data.implement.configFileNameClean),
+ tostring(data.implement.customEnvironment ~= nil and data.implement.customEnvironment or false),
+ tostring(data.value))
end
end
end
end
end
+function VehicleConfigurations:consoleCommandPrintAllModNameVehicleConfigurations()
+ for modName, configurations in pairs(self.modVehicleConfigurations) do
+ for name, configs in pairs(configurations) do
+ for n, v in pairs(configs) do
+ CpUtil.info("(%s => Mod: %s) %s => %s",
+ name, modName, n, v)
+ end
+ end
+ end
+end
+
+function VehicleConfigurations:consoleCommandPrintAllNormalVehicleConfigurations()
+ for name, configs in pairs(self.vehicleConfigurations) do
+ for n, v in pairs(configs) do
+ CpUtil.info("(%s) %s => %s",
+ name, n, v)
+ end
+ end
+end
+
function VehicleConfigurations:consoleCommandPrintAttributeNames()
for attribute, xmlValueType in pairs(self.attributes) do
- CpUtil.info("Attribute: %s => %s", attribute, XMLValueType.TYPES[xmlValueType].name)
+ CpUtil.info("Attribute: %s => %s",
+ attribute, XMLValueType.TYPES[xmlValueType].name)
end
end
diff --git a/scripts/dev/DevHelper.lua b/scripts/dev/DevHelper.lua
index 00c8fcc7a..e21336e55 100644
--- a/scripts/dev/DevHelper.lua
+++ b/scripts/dev/DevHelper.lua
@@ -39,15 +39,26 @@ function DevHelper:debug(...)
CpUtil.info(string.format(...))
end
+--- Makes sure deleting of the selected vehicle can be detected
+function DevHelper:removedSelectedVehicle()
+ self.vehicle = nil
+end
+
function DevHelper:update()
if not self.isEnabled then return end
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_aiVehicle then
-
+ if g_currentMission.controlledVehicle and g_currentMission.controlledVehicle.spec_cpAIWorker then
+ if self.vehicle ~= g_currentMission.controlledVehicle then
+ if self.vehicle then
+ self.vehicle:removeDeleteListener(self, "removedSelectedVehicle")
+ end
+ --self.vehicleData = PathfinderUtil.VehicleData(g_currentMission.controlledVehicle, true)
+ end
self.vehicle = g_currentMission.controlledVehicle
+ self.vehicle:addDeleteListener(self, "removedSelectedVehicle")
self.node = g_currentMission.controlledVehicle:getAIDirectionNode()
lx, _, lz = localDirectionToWorld(self.node, 0, 0, 1)
@@ -262,10 +273,22 @@ function DevHelper:showAIMarkers()
CpUtil.drawDebugNode(frontMarker, false, 3)
CpUtil.drawDebugNode(backMarker, false, 3)
- if self.vehicle:getAIDirectionNode() then
- CpUtil.drawDebugNode(self.vehicle:getAIDirectionNode(), false , 3, "AiDirectionNode")
+ local directionNode = self.vehicle:getAIDirectionNode()
+ if directionNode then
+ CpUtil.drawDebugNode(self.vehicle:getAIDirectionNode(), false , 4, "AiDirectionNode")
end
-
+ local reverseNode = self.vehicle:getAIReverserNode()
+ if reverseNode then
+ CpUtil.drawDebugNode(reverseNode, false , 4.5, "AiReverseNode")
+ end
+ local steeringNode = self.vehicle:getAISteeringNode()
+ if steeringNode then
+ CpUtil.drawDebugNode(steeringNode, false , 5, "AiSteeringNode")
+ end
+ local articulatedAxisReverseNode = AIUtil.getArticulatedAxisVehicleReverserNode(self.vehicle)
+ if articulatedAxisReverseNode then
+ CpUtil.drawDebugNode(articulatedAxisReverseNode, false , 5.5, "AiArticulatedAxisReverseNode")
+ end
end
function DevHelper:togglePpcControlledNode()
diff --git a/scripts/editor/CourseEditor.lua b/scripts/editor/CourseEditor.lua
index e11782857..a2e34485e 100644
--- a/scripts/editor/CourseEditor.lua
+++ b/scripts/editor/CourseEditor.lua
@@ -114,7 +114,9 @@ function CourseEditor:showYesNoDialog(title, callbackFunc)
end
function CourseEditor:delete()
- self.courseDisplay:delete()
+ if self.courseDisplay then
+ self.courseDisplay:delete()
+ end
end
--- Updates the course display, when a waypoint change happened.
diff --git a/scripts/gui/CoursePlot.lua b/scripts/gui/CoursePlot.lua
index 8b9edded8..6069f97f0 100644
--- a/scripts/gui/CoursePlot.lua
+++ b/scripts/gui/CoursePlot.lua
@@ -79,45 +79,6 @@ function CoursePlot:setStopPosition( x, z )
self.stopPosition.x, self.stopPosition.z = x, z
end
-function CoursePlot:worldToScreen(map, worldX, worldZ, isHudMap)
- local objectX = (worldX + map.worldCenterOffsetX) / map.worldSizeX * 0.5 + 0.25
- local objectZ = (worldZ + map.worldCenterOffsetZ) / map.worldSizeZ * 0.5 + 0.25
- local x, y, _, _ = map.fullScreenLayout:getMapObjectPosition(objectX, objectZ, 0, 0, 0, true)
- local rot = 0
- local visible = true
- if isHudMap then
- --- The plot is displayed in the hud.
- objectX = (worldX + map.worldCenterOffsetX) / map.worldSizeX * map.mapExtensionScaleFactor + map.mapExtensionOffsetX
- objectZ = (worldZ + map.worldCenterOffsetZ) / map.worldSizeZ * map.mapExtensionScaleFactor + map.mapExtensionOffsetZ
-
- x, y, rot, visible = map.layout:getMapObjectPosition(objectX, objectZ, 0, 0, 0, false)
- if map.state == IngameMap.STATE_MINIMAP_ROUND and map.layout.rotateWithMap then
- x, y, rot, visible = self:getMapObjectPositionCircleLayoutFix(map.layout, objectX, objectZ, 0, 0, 0, false)
- end
- end
- return x, y, rot, visible
-end
-
---- Giants was not so kind, as to allow getting the positions even, if the object is outside the map range ...
---- This is a custom version for: IngameMapLayoutCircle:getMapObjectPosition(...)
-function CoursePlot:getMapObjectPositionCircleLayoutFix(layout, objectU, objectV, width, height, rot, persistent)
- local mapWidth, mapHeight = layout:getMapSize()
- local mapX, mapY = layout:getMapPosition()
- local objectX = objectU * mapWidth + mapX
- local objectY = (1 - objectV) * mapHeight + mapY
- objectX, objectY, rot = layout:rotateWithMap(objectX, objectY, rot, persistent)
- objectX = objectX - width * 0.5
- objectY = objectY - height * 0.5
-
- return objectX, objectY, rot, true
-end
-
-function CoursePlot:screenToWorld( x, y )
- local worldX = ((x - self.x) / self.scaleX) - self.worldOffsetX
- local worldZ = ((y - self.y - self.height) / -self.scaleZ) - self.worldOffsetZ
- return worldX, worldZ
-end
-
-- Draw the waypoints in the screen area defined in new(), the bottom left corner
-- is at worldX/worldZ coordinates, the size shown is worldWidth wide (and high)
function CoursePlot:drawPoints(map, points, isHudMap)
@@ -129,14 +90,14 @@ function CoursePlot:drawPoints(map, points, isHudMap)
if points and #points > 1 then
-- I know this is in helpers.lua already but that code has too many dependencies
-- on global variables and vehicle.cp.
- local wp, np, startX, startY, endX, endY, dx, dz, dx2D, dy2D, width, rotation, r, g, b, sv, ev
+ local wp, np, startX, startY, endX, endY, dx, dz, dx2D, dy2D, width, rotation, r, g, b, sv, ev, _
-- render a line between subsequent waypoints
for i = 1, #points - 1 do
wp = points[ i ]
np = points[ i + 1 ]
- startX, startY, _, sv = self:worldToScreen(map, wp.x, wp.z, isHudMap)
- endX, endY, _, ev = self:worldToScreen(map, np.x, np.z, isHudMap)
+ startX, startY, _, sv = CpGuiUtil.worldToScreen(map, wp.x, wp.z, isHudMap)
+ endX, endY, _, ev = CpGuiUtil.worldToScreen(map, np.x, np.z, isHudMap)
-- render only if it is on the plot area
if startX and startY and endX and endY then
@@ -176,7 +137,7 @@ function CoursePlot:draw(map, isHudMap)
-- render a sign marking the end of the course
if self.stopPosition.x and self.stopPosition.z then
- local x, y, rotation = self:worldToScreen( map,self.stopPosition.x, self.stopPosition.z, isHudMap)
+ local x, y, rotation = CpGuiUtil.worldToScreen( map,self.stopPosition.x, self.stopPosition.z, isHudMap)
if x and y then
setOverlayColor( self.stopSignOverlayId, 1, 1, 1, 1 )
renderOverlay( self.stopSignOverlayId,
@@ -188,7 +149,7 @@ function CoursePlot:draw(map, isHudMap)
-- render a sign marking the current position used as a starting location for the course
if self.startPosition.x and self.startPosition.z then
- local x, y, rotation = self:worldToScreen(map, self.startPosition.x, self.startPosition.z, isHudMap)
+ local x, y, rotation = CpGuiUtil.worldToScreen(map, self.startPosition.x, self.startPosition.z, isHudMap)
if x and y then
setOverlayColor( self.startSignOverlayId, 1, 1, 1, 0.8 )
renderOverlay( self.startSignOverlayId,
diff --git a/scripts/gui/CpAIFrameExtended.lua b/scripts/gui/CpAIFrameExtended.lua
index 99f2d296d..c3d48e1e1 100644
--- a/scripts/gui/CpAIFrameExtended.lua
+++ b/scripts/gui/CpAIFrameExtended.lua
@@ -14,19 +14,33 @@ CpInGameMenuAIFrameExtended.curDrawPositions={}
CpInGameMenuAIFrameExtended.drawDelay = g_updateLoopIndex
CpInGameMenuAIFrameExtended.DELAY = 1
CpInGameMenuAIFrameExtended.hotspotFilterState = {}
+--- Hotspots visible, while drawing a custom field border.
CpInGameMenuAIFrameExtended.validCustomFieldCreationHotspots = {
- --- Hotspots visible, while drawing a custom field border.
- MapHotspot.CATEGORY_FIELD,
--- MapHotspot.CATEGORY_UNLOADING,
--- MapHotspot.CATEGORY_LOADING,
--- MapHotspot.CATEGORY_PRODUCTION,
- MapHotspot.CATEGORY_AI,
- MapHotspot.CATEGORY_COMBINE,
- MapHotspot.CATEGORY_STEERABLE,
- MapHotspot.CATEGORY_PLAYER,
--- MapHotspot.CATEGORY_SHOP,
--- MapHotspot.CATEGORY_OTHER
- CustomFieldHotspot.CATEGORY
+ [MapHotspot.CATEGORY_FIELD] = true,
+-- [MapHotspot.CATEGORY_UNLOADING] = true,
+-- [MapHotspot.CATEGORY_LOADING] = true,
+-- [MapHotspot.CATEGORY_PRODUCTION] = true,
+ [MapHotspot.CATEGORY_AI] = true,
+ [MapHotspot.CATEGORY_COMBINE] = true,
+ [MapHotspot.CATEGORY_STEERABLE] = true,
+ [MapHotspot.CATEGORY_PLAYER] = true,
+-- MapHotspot.CATEGORY_SHOP] = true,
+-- MapHotspot.CATEGORY_OTHER] = true,
+ [CustomFieldHotspot.CATEGORY] = true
+}
+
+--- Hotspots visible, while picking a loading position.
+CpInGameMenuAIFrameExtended.validPickingLoadingPositionHotspots = {
+ [MapHotspot.CATEGORY_FIELD] = true,
+-- [MapHotspot.CATEGORY_UNLOADING] = true,
+-- [MapHotspot.CATEGORY_LOADING] = true,
+-- [MapHotspot.CATEGORY_PRODUCTION] = true,
+ [MapHotspot.CATEGORY_AI] = true,
+ [MapHotspot.CATEGORY_COMBINE] = true,
+ [MapHotspot.CATEGORY_STEERABLE] = true,
+ [MapHotspot.CATEGORY_PLAYER] = true,
+-- MapHotspot.CATEGORY_SHOP] = true,
+-- MapHotspot.CATEGORY_OTHER] = true,
}
function CpInGameMenuAIFrameExtended:onAIFrameLoadMapFinished()
@@ -101,7 +115,7 @@ function CpInGameMenuAIFrameExtended:onAIFrameLoadMapFinished()
g_messageCenter:subscribe(MessageType.GUI_AFTER_CLOSE, onCloseInGameMenu, g_currentMission.inGameMenu)
g_messageCenter:subscribe(MessageType.GUI_BEFORE_OPEN, onOpenInGameMenu, g_currentMission.inGameMenu)
--- Closes the course generator settings with the back button.
- local function onClickBack(pageAI,superFunc)
+ local function onClickBack(pageAI,superFunc)
if pageAI.mode == CpInGameMenuAIFrameExtended.MODE_COURSE_GENERATOR then
pageAI:onClickOpenCloseCourseGenerator()
return
@@ -111,16 +125,26 @@ function CpInGameMenuAIFrameExtended:onAIFrameLoadMapFinished()
return
end
CpInGameMenuAIFrameExtended.resetHotspots(self)
- return superFunc(pageAI)
+ superFunc(pageAI)
+ if pageAI:getIsPicking() then
+ self:updateParameterValueTexts()
+ end
+
end
- self.buttonBack.onClickCallback = Utils.overwrittenFunction(self.buttonBack.onClickCallback,onClickBack)
- self.ingameMapBase.drawHotspotsOnly = Utils.appendedFunction(self.ingameMapBase.drawHotspotsOnly , CpInGameMenuAIFrameExtended.draw)
+ self.buttonBack.onClickCallback = Utils.overwrittenFunction(
+ self.buttonBack.onClickCallback,onClickBack)
- --- Adds a second map hotspot for field position.
+ self.ingameMapBase.drawHotspotsOnly = Utils.appendedFunction(
+ self.ingameMapBase.drawHotspotsOnly , CpInGameMenuAIFrameExtended.draw)
+
+ --- Adds the ai target hotspot.
+ self.driveToAiTargetMapHotspot = AITargetHotspot.new()
self.fieldSiloAiTargetMapHotspot = AITargetHotspot.new()
self.fieldSiloAiTargetMapHotspot.icon:setUVs(CpInGameMenuAIFrameExtended.positionUvs) --- Without angle
-
self.unloadAiTargetMapHotspot = AITargetHotspot.new()
+ self.loadAiTargetMapHotspot = AITargetHotspot.new()
+
+ self.rawAiTargetMapHotspot = self.aiTargetMapHotspot
self.ingameMap.onClickHotspotCallback = Utils.appendedFunction(self.ingameMap.onClickHotspotCallback,
CpInGameMenuAIFrameExtended.onClickHotspot)
@@ -131,6 +155,7 @@ end
InGameMenuAIFrame.onLoadMapFinished = Utils.appendedFunction(InGameMenuAIFrame.onLoadMapFinished,
CpInGameMenuAIFrameExtended.onAIFrameLoadMapFinished)
+--- Creates alternative buttons, which are put into the button layout.
function CpInGameMenuAIFrameExtended:setupButtons()
local function createBtn(prefab, text, callback)
local btn = prefab:clone(prefab.parent)
@@ -195,6 +220,18 @@ end
InGameMenuAIFrame.updateContextInputBarVisibility = Utils.appendedFunction(InGameMenuAIFrame.updateContextInputBarVisibility,CpInGameMenuAIFrameExtended.updateContextInputBarVisibility)
+function CpInGameMenuAIFrameExtended:setJobMenuVisible(visible)
+ if not visible then
+ --- Removes the map hotspot, if the job menu of the vehicle is closed.
+ g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.aiTargetMapHotspot)
+ end
+end
+InGameMenuAIFrame.setJobMenuVisible = Utils.appendedFunction(InGameMenuAIFrame.setJobMenuVisible, CpInGameMenuAIFrameExtended.setJobMenuVisible)
+
function CpInGameMenuAIFrameExtended:isCreateFieldBorderBtnVisible()
local visible = self.mode == CpInGameMenuAIFrameExtended.MODE_DRAW_FIELD_BORDER or
self.mode == InGameMenuAIFrame.MODE_OVERVIEW and self.currentHotspot == nil
@@ -254,6 +291,7 @@ function InGameMenuAIFrame:onClickOpenCloseCourseGenerator()
end
end
+--- Generates the correct course generator layout and binds the settings to the gui elements.
function CpInGameMenuAIFrameExtended:bindCourseGeneratorSettings()
local vehicle = InGameMenuMapUtil.getHotspotVehicle(self.currentHotspot)
if vehicle ~=nil and vehicle.getCourseGeneratorSettings then
@@ -319,59 +357,64 @@ end
--- Updates the visibility of the vehicle settings on select/unselect of a vehicle in the ai menu page.
--- Also updates the field position map hotspot.
function CpInGameMenuAIFrameExtended:setMapSelectionItem(hotspot)
+ g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.aiTargetMapHotspot)
- if hotspot ~= nil then
- local vehicle = InGameMenuMapUtil.getHotspotVehicle(hotspot)
- self.lastVehicle = vehicle
- self.hudVehicle = nil
- if vehicle then
- if vehicle.getJob ~= nil then
- local job = vehicle:getJob()
-
- if job ~= nil then
- if job.getCpJobParameters ~= nil then
- local parameters = job:getCpJobParameters():getAiTargetMapHotspotParameters()
- for i, param in pairs(parameters) do
- if param:is_a(CpAIParameterPosition) then
- if param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO then
- if param:applyToMapHotspot(self.aiTargetMapHotspot) then
- g_currentMission:addMapHotspot(self.aiTargetMapHotspot)
- end
- elseif param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then
- if param:applyToMapHotspot(self.fieldSiloAiTargetMapHotspot) then
- g_currentMission:addMapHotspot(self.fieldSiloAiTargetMapHotspot)
- end
- elseif param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then
- if param:applyToMapHotspot(self.unloadAiTargetMapHotspot) then
- g_currentMission:addMapHotspot(self.unloadAiTargetMapHotspot)
- end
- end
- elseif param:is_a(CpAIParameterUnloadingStation) then
- g_currentMission:removeMapHotspot(self.aiUnloadingMarkerHotspot)
- if param:applyToMapHotspot(self.aiUnloadingMarkerHotspot) then
- g_currentMission:addMapHotspot(self.aiUnloadingMarkerHotspot)
- end
- end
- end
+ if hotspot == nil then
+ return
+ end
+ local vehicle = InGameMenuMapUtil.getHotspotVehicle(hotspot)
+ self.lastVehicle = vehicle
+ self.hudVehicle = nil
+ if vehicle == nil or vehicle.getJob == nil then
+ return
+ end
+ local job = vehicle:getJob()
+ if job == nil or job.getCpJobParameters == nil then
+ return
+ end
+ if vehicle:getIsCpActive() then
+ local parameters = job:getCpJobParameters():getAiTargetMapHotspotParameters()
+ for i, param in pairs(parameters) do
+ if param:is_a(CpAIParameterPosition) then
+ if param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO then
+ if param:applyToMapHotspot(self.driveToAiTargetMapHotspot) then
+ g_currentMission:addMapHotspot(self.driveToAiTargetMapHotspot)
end
- if job.getTarget ~= nil then
- local x, z, rot = job:getTarget()
-
- self.aiTargetMapHotspot:setWorldPosition(x, z)
-
- if rot ~= nil then
- self.aiTargetMapHotspot:setWorldRotation(rot + math.pi)
- end
-
- g_currentMission:addMapHotspot(self.aiTargetMapHotspot)
+ elseif param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then
+ if param:applyToMapHotspot(self.fieldSiloAiTargetMapHotspot) then
+ g_currentMission:addMapHotspot(self.fieldSiloAiTargetMapHotspot)
+ end
+ elseif param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.LOAD then
+ if param:applyToMapHotspot(self.loadAiTargetMapHotspot) then
+ g_currentMission:addMapHotspot(self.loadAiTargetMapHotspot)
+ end
+ elseif param:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then
+ if param:applyToMapHotspot(self.unloadAiTargetMapHotspot) then
+ g_currentMission:addMapHotspot(self.unloadAiTargetMapHotspot)
end
end
-
+ elseif param:is_a(CpAIParameterUnloadingStation) then
+ g_currentMission:removeMapHotspot(self.aiUnloadingMarkerHotspot)
+ if param:applyToMapHotspot(self.aiUnloadingMarkerHotspot) then
+ g_currentMission:addMapHotspot(self.aiUnloadingMarkerHotspot)
+ end
end
end
end
+ if job.getTarget ~= nil then
+ local x, z, rot = job:getTarget()
+
+ self.aiTargetMapHotspot:setWorldPosition(x, z)
+
+ if rot ~= nil then
+ self.aiTargetMapHotspot:setWorldRotation(rot + math.pi)
+ end
+
+ g_currentMission:addMapHotspot(self.aiTargetMapHotspot)
+ end
g_currentMission.inGameMenu:updatePages()
end
InGameMenuAIFrame.setMapSelectionItem = Utils.appendedFunction(InGameMenuAIFrame.setMapSelectionItem, CpInGameMenuAIFrameExtended.setMapSelectionItem)
@@ -399,8 +442,10 @@ function CpInGameMenuAIFrameExtended:onAIFrameClose()
self.courseGeneratorLayout:setVisible(false)
self.contextBox:setVisible(true)
self.lastHotspot = self.currentHotspot
+ g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot)
CpInGameMenuAIFrameExtended.unbindCourseGeneratorSettings(self)
g_currentMission.inGameMenu:updatePages()
end
@@ -462,72 +507,94 @@ function CpInGameMenuAIFrameExtended:draw()
end
function CpInGameMenuAIFrameExtended:delete()
+ if self.driveToAiTargetMapHotspot then
+ self.driveToAiTargetMapHotspot:delete()
+ self.driveToAiTargetMapHotspot = nil
+ end
if self.fieldSiloAiTargetMapHotspot ~= nil then
self.fieldSiloAiTargetMapHotspot:delete()
-
self.fieldSiloAiTargetMapHotspot = nil
end
-
if self.unloadAiTargetMapHotspot ~= nil then
self.unloadAiTargetMapHotspot:delete()
-
self.unloadAiTargetMapHotspot = nil
end
+ if self.loadAiTargetMapHotspot ~= nil then
+ self.loadAiTargetMapHotspot:delete()
+ self.loadAiTargetMapHotspot = nil
+ end
end
InGameMenuAIFrame.delete = Utils.appendedFunction(InGameMenuAIFrame.delete,CpInGameMenuAIFrameExtended.delete)
+
+function CpInGameMenuAIFrameExtended:onClickPositionParameter(superFunc, element, ...)
+ local parameter = element.aiParameter
+ if parameter:getCanBeChanged() then
+ --- Checks if the position setting is not disabled
+ superFunc(self, element, ...)
+ end
+end
+InGameMenuAIFrame.onClickPositionParameter = Utils.overwrittenFunction(
+ InGameMenuAIFrame.onClickPositionParameter, CpInGameMenuAIFrameExtended.onClickPositionParameter)
+InGameMenuAIFrame.onClickPositionRotationParameter = Utils.overwrittenFunction(
+ InGameMenuAIFrame.onClickPositionRotationParameter, CpInGameMenuAIFrameExtended.onClickPositionParameter)
+
+
--- Ugly hack to swap the main AI hotspot with the field position hotspot,
--- as only the main hotspot can be moved by the player.
function CpInGameMenuAIFrameExtended:startPickingPosition(superFunc, parameter, callback, ...)
if parameter and parameter.getPositionType then
CpInGameMenuAIFrameExtended.resetHotspots(self)
- if parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then
- local mapHotspot = self.aiTargetMapHotspot
+ if parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO then
+ self.aiTargetMapHotspot = self.driveToAiTargetMapHotspot
+ self.currentPickingMapHotspotType = CpAIParameterPositionAngle.POSITION_TYPES.DRIVE_TO
+ elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then
self.aiTargetMapHotspot = self.fieldSiloAiTargetMapHotspot
- self.fieldSiloAiTargetMapHotspot = mapHotspot
self.currentPickingMapHotspotType = CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO
elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then
- local mapHotspot = self.aiTargetMapHotspot
self.aiTargetMapHotspot = self.unloadAiTargetMapHotspot
- self.unloadAiTargetMapHotspot = mapHotspot
self.currentPickingMapHotspotType = CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD
+ CpInGameMenuAIFrameExtended.hotspotFilterState = {}
+ CpGuiUtil.saveAndDisableHotspotFilters(self.ingameMapBase, CpInGameMenuAIFrameExtended.hotspotFilterState)
+ CpGuiUtil.applyHotspotFilters(self.ingameMapBase, CpInGameMenuAIFrameExtended.validPickingLoadingPositionHotspots)
+ elseif parameter:getPositionType() == CpAIParameterPositionAngle.POSITION_TYPES.LOAD then
+ self.aiTargetMapHotspot = self.loadAiTargetMapHotspot
+ self.currentPickingMapHotspotType = CpAIParameterPositionAngle.POSITION_TYPES.LOAD
end
end
callback = Utils.appendedFunction(callback,function (finished, x, z)
CpInGameMenuAIFrameExtended.resetHotspots(self)
+ self:updateParameterValueTexts()
end)
-
+ g_currentMission:removeMapHotspot(self.aiTargetMapHotspot)
superFunc(self, parameter, callback, ...)
end
-InGameMenuAIFrame.startPickPosition = Utils.overwrittenFunction(InGameMenuAIFrame.startPickPosition,CpInGameMenuAIFrameExtended.startPickingPosition)
-InGameMenuAIFrame.startPickPositionAndRotation = Utils.overwrittenFunction(InGameMenuAIFrame.startPickPositionAndRotation,CpInGameMenuAIFrameExtended.startPickingPosition)
+InGameMenuAIFrame.startPickPosition = Utils.overwrittenFunction(InGameMenuAIFrame.startPickPosition,
+ CpInGameMenuAIFrameExtended.startPickingPosition)
+
+InGameMenuAIFrame.startPickPositionAndRotation = Utils.overwrittenFunction(InGameMenuAIFrame.startPickPositionAndRotation,
+ CpInGameMenuAIFrameExtended.startPickingPosition)
function CpInGameMenuAIFrameExtended:resetHotspots()
- if self.currentPickingMapHotspotType == CpAIParameterPositionAngle.POSITION_TYPES.FIELD_OR_SILO then
- local mapHotspot = self.aiTargetMapHotspot
- self.aiTargetMapHotspot = self.fieldSiloAiTargetMapHotspot
- self.fieldSiloAiTargetMapHotspot = mapHotspot
- self.currentPickingMapHotspotType = nil
- elseif self.currentPickingMapHotspotType == CpAIParameterPositionAngle.POSITION_TYPES.UNLOAD then
- local mapHotspot = self.aiTargetMapHotspot
- self.aiTargetMapHotspot = self.unloadAiTargetMapHotspot
- self.unloadAiTargetMapHotspot = mapHotspot
- self.currentPickingMapHotspotType = nil
+ self.aiTargetMapHotspot = self.rawAiTargetMapHotspot
+ self.currentPickingMapHotspotType = nil
+ if CpInGameMenuAIFrameExtended.hotspotFilterState then
+ CpGuiUtil.applyHotspotFilters(self.ingameMapBase, CpInGameMenuAIFrameExtended.hotspotFilterState)
end
+ CpInGameMenuAIFrameExtended.hotspotFilterState = nil
end
-
--- Added support for the cp field target position.
function CpInGameMenuAIFrameExtended:updateParameterValueTexts(superFunc, ...)
if self.currentJobElements == nil then
return
end
g_currentMission:removeMapHotspot(self.aiTargetMapHotspot)
- g_currentMission:removeMapHotspot(self.aiLoadingMarkerHotspot)
- g_currentMission:removeMapHotspot(self.aiUnloadingMarkerHotspot)
+ g_currentMission:removeMapHotspot(self.driveToAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.fieldSiloAiTargetMapHotspot)
g_currentMission:removeMapHotspot(self.unloadAiTargetMapHotspot)
+ g_currentMission:removeMapHotspot(self.loadAiTargetMapHotspot)
for _, element in ipairs(self.currentJobElements) do
local parameter = element.aiParameter
local parameterType = parameter:getType()
@@ -539,8 +606,8 @@ function CpInGameMenuAIFrameExtended:updateParameterValueTexts(superFunc, ...)
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.aiTargetMapHotspot) then
- g_currentMission:addMapHotspot(self.aiTargetMapHotspot)
+ 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
@@ -550,72 +617,93 @@ function CpInGameMenuAIFrameExtended:updateParameterValueTexts(superFunc, ...)
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 parameterType == AIParameterType.POSITION or parameterType == AIParameterType.POSITION_ANGLE then
element:setText(parameter:getString())
- g_currentMission:addMapHotspot(self.aiTargetMapHotspot)
+ g_currentMission:addMapHotspot(self.rawAiTargetMapHotspot)
local x, z = parameter:getPosition()
- self.aiTargetMapHotspot:setWorldPosition(x, z)
+ self.rawAiTargetMapHotspot:setWorldPosition(x, z)
if parameterType == AIParameterType.POSITION_ANGLE then
local angle = parameter:getAngle() + math.pi
- self.aiTargetMapHotspot:setWorldRotation(angle)
+ self.rawAiTargetMapHotspot:setWorldRotation(angle)
end
- else
- element:updateTitle()
+ end
+ end
+end
+InGameMenuAIFrame.updateParameterValueTexts = Utils.overwrittenFunction(InGameMenuAIFrame.updateParameterValueTexts,
+ CpInGameMenuAIFrameExtended.updateParameterValueTexts)
- if parameterType == AIParameterType.UNLOADING_STATION then
- if parameter.applyToMapHotspot then
- if parameter:applyToMapHotspot(self.aiUnloadingMarkerHotspot) then
- g_currentMission:addMapHotspot(self.aiUnloadingMarkerHotspot)
- end
- else
- local unloadingStation = parameter:getUnloadingStation()
+function CpInGameMenuAIFrameExtended:updateWarnings()
+ g_currentMission:removeMapHotspot(self.aiLoadingMarkerHotspot)
+ g_currentMission:removeMapHotspot(self.aiUnloadingMarkerHotspot)
+ for _, element in ipairs(self.currentJobElements) do
+ local parameter = element.aiParameter
+ local parameterType = parameter:getType()
+ if parameterType == AIParameterType.TEXT then
+ local title = element:getDescendantByName("title")
- if unloadingStation ~= nil then
- local placeable = unloadingStation.owningPlaceable
+ title:setText(parameter:getString())
+ elseif parameterType == AIParameterType.UNLOADING_STATION then
+ element:updateTitle()
+ if parameter.applyToMapHotspot then
+ if parameter:applyToMapHotspot(self.aiUnloadingMarkerHotspot) then
+ g_currentMission:addMapHotspot(self.aiUnloadingMarkerHotspot)
+ end
+ else
+ local unloadingStation = parameter:getUnloadingStation()
- if placeable ~= nil and placeable.getHotspot ~= nil then
- local hotspot = placeable:getHotspot(1)
+ if unloadingStation ~= nil then
+ local placeable = unloadingStation.owningPlaceable
- if hotspot ~= nil then
- local x, z = hotspot:getWorldPosition()
+ if placeable ~= nil and placeable.getHotspot ~= nil then
+ local hotspot = placeable:getHotspot(1)
- self.aiUnloadingMarkerHotspot:setWorldPosition(x, z)
- g_currentMission:addMapHotspot(self.aiUnloadingMarkerHotspot)
- end
+ if hotspot ~= nil then
+ local x, z = hotspot:getWorldPosition()
+ self.aiUnloadingMarkerHotspot:setWorldPosition(x, z)
+ g_currentMission:addMapHotspot(self.aiUnloadingMarkerHotspot)
end
end
end
- elseif parameterType == AIParameterType.LOADING_STATION then
- local loadingStation = parameter:getLoadingStation()
+ end
+ elseif parameterType == AIParameterType.UNLOADING_STATION then
+ element:updateTitle()
+ local loadingStation = parameter:getLoadingStation()
- if loadingStation ~= nil and parameter:getCanBeChanged() then
- local placeable = loadingStation.owningPlaceable
+ if loadingStation ~= nil and parameter:getCanBeChanged() then
+ local placeable = loadingStation.owningPlaceable
- if placeable ~= nil and placeable.getHotspot ~= nil then
- local hotspot = placeable:getHotspot(1)
+ if placeable ~= nil and placeable.getHotspot ~= nil then
+ local hotspot = placeable:getHotspot(1)
- if hotspot ~= nil then
- local x, z = hotspot:getWorldPosition()
+ if hotspot ~= nil then
+ local x, z = hotspot:getWorldPosition()
- self.aiLoadingMarkerHotspot:setWorldPosition(x, z)
- g_currentMission:addMapHotspot(self.aiLoadingMarkerHotspot)
- end
+ self.aiLoadingMarkerHotspot:setWorldPosition(x, z)
+ g_currentMission:addMapHotspot(self.aiLoadingMarkerHotspot)
end
end
end
end
-
element:setDisabled(not parameter:getCanBeChanged())
end
end
-InGameMenuAIFrame.updateParameterValueTexts = Utils.overwrittenFunction(InGameMenuAIFrame.updateParameterValueTexts,
- CpInGameMenuAIFrameExtended.updateParameterValueTexts)
+
+InGameMenuAIFrame.updateWarnings = Utils.appendedFunction(InGameMenuAIFrame.updateWarnings,
+ CpInGameMenuAIFrameExtended.updateWarnings)
+
+--------------------------------------------
+--- Custom fields
+--------------------------------------------
--- Enables clickable field hotspots.
function CpInGameMenuAIFrameExtended:onClickHotspot(element,hotspot)
@@ -657,24 +745,18 @@ function InGameMenuAIFrame:onClickCreateFieldBorder()
g_customFieldManager:addField(CpInGameMenuAIFrameExtended.curDrawPositions)
CpInGameMenuAIFrameExtended.curDrawPositions = {}
--- Restore hotspot filters here:
- for k, v in pairs(self.ingameMapBase.filter) do
- self.ingameMapBase:setHotspotFilter(k, CpInGameMenuAIFrameExtended.hotspotFilterState[k])
+ if CpInGameMenuAIFrameExtended.hotspotFilterState then
+ CpGuiUtil.applyHotspotFilters(self.ingameMapBase, CpInGameMenuAIFrameExtended.hotspotFilterState)
end
-
+ CpInGameMenuAIFrameExtended.hotspotFilterState = nil
else
CpInGameMenuAIFrameExtended.curDrawPositions = {}
self.drawingCustomFieldHeader:setVisible(true)
self.mode = CpInGameMenuAIFrameExtended.MODE_DRAW_FIELD_BORDER
CpInGameMenuAIFrameExtended.hotspotFilterState = {}
--- Change the hotspot filter here:
- for k, v in pairs(self.ingameMapBase.filter) do
- CpInGameMenuAIFrameExtended.hotspotFilterState[k] = v
-
- self.ingameMapBase:setHotspotFilter(k, false)
- end
- for _,v in ipairs(CpInGameMenuAIFrameExtended.validCustomFieldCreationHotspots) do
- self.ingameMapBase:setHotspotFilter(v, true)
- end
+ CpGuiUtil.saveAndDisableHotspotFilters(self.ingameMapBase, CpInGameMenuAIFrameExtended.hotspotFilterState)
+ CpGuiUtil.applyHotspotFilters(self.ingameMapBase, CpInGameMenuAIFrameExtended.validCustomFieldCreationHotspots)
end
end
diff --git a/scripts/gui/CpGamePadHudScreen.lua b/scripts/gui/CpGamePadHudScreen.lua
index 276724dfe..836c11611 100644
--- a/scripts/gui/CpGamePadHudScreen.lua
+++ b/scripts/gui/CpGamePadHudScreen.lua
@@ -192,6 +192,9 @@ function CpGamePadHudFieldWorkScreen:update(dt, ...)
if self.vehicle:getCanStartCpBunkerSiloWorker() and self.vehicle:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_BUNKER_SILO
and not AIUtil.hasChildVehicleWithSpecialization(self.vehicle, Leveler) then
self.vehicle:reopenCpGamePadHud()
+ elseif self.vehicle:getCanStartCpSiloLoaderWorker() and self.vehicle:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_SILO_LOADING
+ and not AIUtil.hasChildVehicleWithSpecialization(self.vehicle, ConveyorBelt) then
+ self.vehicle:reopenCpGamePadHud()
end
end
@@ -260,7 +263,9 @@ end
function CpGamePadHudSiloLoaderScreen:update(dt, ...)
CpGamePadHudSiloLoaderScreen:superClass().update(self, dt, ...)
-
+ if not self.vehicle:getCanStartCpSiloLoaderWorker() or self.vehicle:getCpStartingPointSetting():getValue() ~= CpJobParameters.START_AT_SILO_LOADING then
+ self.vehicle:reopenCpGamePadHud()
+ end
end
function CpGamePadHudSiloLoaderScreen:drawWorkWidth()
diff --git a/scripts/gui/CpGuiUtil.lua b/scripts/gui/CpGuiUtil.lua
index 4a3fd484b..489cadcd7 100644
--- a/scripts/gui/CpGuiUtil.lua
+++ b/scripts/gui/CpGuiUtil.lua
@@ -169,6 +169,15 @@ function CpGuiUtil.getGenericSubTitleElementFromLayout(rootElement)
return CpGuiUtil.getFirstElementWithProfileName(rootElement, "settingsMenuSubtitle")
end
+--- Gets the rgb color in 0-1 from 0-255.
+---@param r number
+---@param g number
+---@param b number
+---@param alpha number|nil
+---@return number
+---@return number
+---@return number
+---@return number
function CpGuiUtil.getNormalizedRgb(r, g, b, alpha)
return r / 255, g / 255, b / 255, alpha
end
@@ -205,6 +214,83 @@ function CpGuiUtil.setTarget(element, target)
element.targetName = target.name
end
+--- Apply the defined filter to the map.
+---@param map table ingame menu map
+---@param hotspots table
+function CpGuiUtil.applyHotspotFilters(map, hotspots)
+ for k, v in pairs(hotspots) do
+ map:setHotspotFilter(k, v)
+ end
+end
+
+--- Saves the hotspot filters and disables these on the map.
+---@param map table
+---@param hotspots table
+function CpGuiUtil.saveAndDisableHotspotFilters(map, hotspots)
+ for k, v in pairs(map.filter) do
+ map:setHotspotFilter(k, false)
+ hotspots[k] = v
+ end
+end
+
+------------------------------------------------
+--- Plots
+------------------------------------------------
+
+--- Translates the world coordinates to screen coordinates.
+---@param map table
+---@param worldX number
+---@param worldZ number
+---@param isHudMap boolean|nil If the hud minimap is used.
+---@return number
+---@return number
+---@return number
+---@return boolean
+function CpGuiUtil.worldToScreen(map, worldX, worldZ, isHudMap)
+ local objectX = (worldX + map.worldCenterOffsetX) / map.worldSizeX * 0.5 + 0.25
+ local objectZ = (worldZ + map.worldCenterOffsetZ) / map.worldSizeZ * 0.5 + 0.25
+ local x, y, _, _ = map.fullScreenLayout:getMapObjectPosition(objectX, objectZ, 0, 0, 0, true)
+ local rot = 0
+ local visible = true
+ if isHudMap then
+ --- The plot is displayed in the hud.
+ objectX = (worldX + map.worldCenterOffsetX) / map.worldSizeX * map.mapExtensionScaleFactor + map.mapExtensionOffsetX
+ objectZ = (worldZ + map.worldCenterOffsetZ) / map.worldSizeZ * map.mapExtensionScaleFactor + map.mapExtensionOffsetZ
+
+ x, y, rot, visible = map.layout:getMapObjectPosition(objectX, objectZ, 0, 0, 0, false)
+ if map.state == IngameMap.STATE_MINIMAP_ROUND and map.layout.rotateWithMap then
+ x, y, rot, visible = CpGuiUtil.getMapObjectPositionCircleLayoutFix(map.layout, objectX, objectZ, 0, 0, 0, false)
+ end
+ end
+ return x, y, rot, visible
+end
+
+--- Giants was not so kind, as to allow getting the positions even, if the object is outside the map range ...
+--- This is a custom version for: IngameMapLayoutCircle:getMapObjectPosition(...)
+---@param layout table
+---@param objectU number
+---@param objectV number
+---@param width number
+---@param height number
+---@param rot number
+---@param persistent boolean
+---@return number
+---@return number
+---@return number
+---@return boolean
+function CpGuiUtil.getMapObjectPositionCircleLayoutFix(layout, objectU, objectV, width, height, rot, persistent)
+ local mapWidth, mapHeight = layout:getMapSize()
+ local mapX, mapY = layout:getMapPosition()
+ local objectX = objectU * mapWidth + mapX
+ local objectY = (1 - objectV) * mapHeight + mapY
+ objectX, objectY, rot = layout:rotateWithMap(objectX, objectY, rot, persistent)
+ objectX = objectX - width * 0.5
+ objectY = objectY - height * 0.5
+
+ return objectX, objectY, rot, true
+end
+
+
------------------------------------------------
--- Hud
------------------------------------------------
@@ -426,3 +512,23 @@ function CpGuiUtil.openGlobalSettingsGui(vehicle)
local inGameMenu = CpGuiUtil.preOpeningInGameMenu(vehicle)
inGameMenu:goToPage(inGameMenu.pageCpGlobalSettings)
end
+
+CpGuiUtil.UNIT_EXTENSIONS = {
+ "k",
+ "M",
+ "G",
+ "T"
+}
+--- Converts the number into kilo, mega, giga or tera units with the correct symbol.
+---@param num number
+---@return number
+---@return string
+function CpGuiUtil.getFixedUnitValueWithUnitSymbol(num)
+ for i=4, 1, -1 do
+ local delta = math.pow(10, 3 * i)
+ if num >= delta then
+ return num / delta, CpGuiUtil.UNIT_EXTENSIONS[i]
+ end
+ end
+ return num, ""
+end
\ No newline at end of file
diff --git a/scripts/gui/CpStatus.lua b/scripts/gui/CpStatus.lua
index e756d37e6..ce261d9a8 100644
--- a/scripts/gui/CpStatus.lua
+++ b/scripts/gui/CpStatus.lua
@@ -132,11 +132,9 @@ function CpStatus:getCompactionText(withoutPercentageSymbol)
end
function CpStatus:getSiloFillLevelPercentageLeftOver(withoutPercentageSymbol)
- if self.isActive and self.fillLevelPercentageLeftOver ~=nil then
- if withoutPercentageSymbol then
- return tostring(self.fillLevelPercentageLeftOver)
- end
- return string.format('%d%%', self.fillLevelPercentageLeftOver)
+ if self.isActive and self.fillLevelLeftOver ~= nil then
+ local value, unitExtension = CpGuiUtil.getFixedUnitValueWithUnitSymbol(self.fillLevelLeftOver)
+ return string.format("%.1f %s%s", value, unitExtension, g_i18n:getText("unit_literShort"))
end
return '--'
end
diff --git a/scripts/gui/FieldPlot.lua b/scripts/gui/FieldPlot.lua
index fa8f3a415..ed7f3694e 100644
--- a/scripts/gui/FieldPlot.lua
+++ b/scripts/gui/FieldPlot.lua
@@ -48,7 +48,7 @@ function FieldPlot:draw(map)
local lastWp = self.waypoints and #self.waypoints>0 and self.waypoints[#self.waypoints]
if self.drawLastWp and lastWp then
- local x, y = self:worldToScreen(map, lastWp.x, lastWp.z )
+ local x, y = CpGuiUtil.worldToScreen(map, lastWp.x, lastWp.z )
local signSizeMeters = 1.5* self.lineThickness
local zoom = map.fullScreenLayout:getIconZoom()
local width, height = signSizeMeters * map.uiScale * zoom, signSizeMeters * map.uiScale * zoom * g_screenAspectRatio
diff --git a/scripts/gui/UnloadingTriggerPlot.lua b/scripts/gui/UnloadingTriggerPlot.lua
new file mode 100644
index 000000000..18e69421d
--- /dev/null
+++ b/scripts/gui/UnloadingTriggerPlot.lua
@@ -0,0 +1,53 @@
+--- Draws an unloading triggers as a X Symbol on the in game menu map.
+---@class UnloadingTriggerPlot
+UnloadingTriggerPlot = CpObject()
+
+function UnloadingTriggerPlot:init(node)
+ self.courseOverlayId = createImageOverlay('dataS/scripts/shared/graph_pixel.dds')
+ self.isVisible = false
+ -- the normal FS22 blue -- 0.9900 0.4640 0.0010 1
+ --self.color = {CpGuiUtil.getNormalizedRgb(42, 193, 237)}
+ self.color = {CpGuiUtil.getNormalizedRgb(255, 128, 0)}
+ -- a lighter shade of the same color
+ self.lightColor = {CpGuiUtil.getNormalizedRgb(213, 255, 0)}
+ -- a darker shade of the same color
+ self.darkColor = {CpGuiUtil.getNormalizedRgb(19, 87, 107)}
+ self.lineThickness = 4 / g_screenHeight -- 4 pixels
+ self.isHighlighted = false
+ local _
+ self.x, _, self.z = getWorldTranslation(node)
+end
+
+function UnloadingTriggerPlot:draw(map)
+ local r, g, b = unpack(self.color)
+ if self.isHighlighted then
+ r, g, b = unpack(self.lightColor)
+ end
+ setOverlayColor( self.courseOverlayId, r, g, b, 0.8 )
+ -- local x, y = CpGuiUtil.worldToScreen(map, self.x, self.z, false)
+
+ local s1x, s1y = CpGuiUtil.worldToScreen(map, self.x - 5, self.z - 5, false)
+ local e1x, e1y = CpGuiUtil.worldToScreen(map, self.x + 5, self.z + 5, false)
+ local s2x, s2y = CpGuiUtil.worldToScreen(map, self.x + 5, self.z - 5, false)
+ local e2x, e2y = CpGuiUtil.worldToScreen(map, self.x - 5, self.z + 5, false)
+
+ --- Create a coss through the node position
+ local mapRotation = map.layout:getMapRotation()
+ local dx2D = e1x - s1x
+ local dy2D = ( e1y - s1y ) / g_screenAspectRatio
+ local width = MathUtil.vector2Length(dx2D, dy2D)
+ local rotation = MathUtil.getYRotationFromDirection(10, 10) - math.pi * 0.5 + mapRotation
+ setOverlayRotation( self.courseOverlayId, rotation, 0, 0 )
+ renderOverlay( self.courseOverlayId, s1x, s1y, width, self.lineThickness )
+
+ dx2D = e2x - s2x
+ dy2D = ( e2y - s2y ) / g_screenAspectRatio
+ width = MathUtil.vector2Length(dx2D, dy2D)
+ rotation = MathUtil.getYRotationFromDirection(-10, 10) - math.pi * 0.5 + mapRotation
+ setOverlayRotation( self.courseOverlayId, rotation, 0, 0 )
+ renderOverlay( self.courseOverlayId, s2x, s2y, width, self.lineThickness )
+end
+
+function UnloadingTriggerPlot:setHighlighted(highlighted)
+ self.isHighlighted = highlighted
+end
\ No newline at end of file
diff --git a/scripts/gui/hud/CpBaseHud.lua b/scripts/gui/hud/CpBaseHud.lua
index 9e3df27a4..5c1e2284b 100644
--- a/scripts/gui/hud/CpBaseHud.lua
+++ b/scripts/gui/hud/CpBaseHud.lua
@@ -424,11 +424,13 @@ function CpBaseHud:getActiveHudPage(vehicle)
return self.combineUnloaderLayout
elseif vehicle:getCanStartCpBaleFinder() and not vehicle:hasCpCourse() then
return self.baleFinderLayout
- elseif vehicle:getCanStartCpBunkerSiloWorker() and vehicle:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_BUNKER_SILO
- or AIUtil.hasChildVehicleWithSpecialization(vehicle, Leveler) then
- return self.bunkerSiloWorkerLayout
- elseif vehicle:getCanStartCpSiloLoaderWorker() then
+ elseif vehicle:getCanStartCpSiloLoaderWorker() and (vehicle:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_SILO_LOADING
+ or AIUtil.hasChildVehicleWithSpecialization(vehicle, ConveyorBelt)) then
return self.siloLoaderWorkerLayout
+ elseif vehicle:getCanStartCpBunkerSiloWorker() and (vehicle:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_BUNKER_SILO or
+ (AIUtil.hasChildVehicleWithSpecialization(vehicle, Leveler)
+ and not AIUtil.hasChildVehicleWithSpecialization(vehicle, Shovel))) then
+ return self.bunkerSiloWorkerLayout
else
return self.fieldworkLayout
end
diff --git a/scripts/gui/hud/CpBunkerSiloWorkerHudPage.lua b/scripts/gui/hud/CpBunkerSiloWorkerHudPage.lua
index b61ee59c4..60e60011a 100644
--- a/scripts/gui/hud/CpBunkerSiloWorkerHudPage.lua
+++ b/scripts/gui/hud/CpBunkerSiloWorkerHudPage.lua
@@ -101,5 +101,13 @@ function CpBunkerSiloWorkerHudPageElement:updateContent(vehicle, status)
end
function CpBunkerSiloWorkerHudPageElement:isStartingPointBtnDisabled(vehicle)
- return AIUtil.hasAIImplementWithSpecialization(vehicle, Leveler)
+ return AIUtil.hasChildVehicleWithSpecialization(vehicle, Leveler)
+ and not AIUtil.hasChildVehicleWithSpecialization(vehicle, Shovel)
end
+
+function CpBunkerSiloWorkerHudPageElement:getStartingPointBtnText(vehicle)
+ if self:isStartingPointBtnDisabled(vehicle) then
+ return vehicle:getCpStartText()
+ end
+ return vehicle:getCpStartingPointSetting():getString()
+end
\ No newline at end of file
diff --git a/scripts/gui/hud/CpSiloLoaderWorkerHudPage.lua b/scripts/gui/hud/CpSiloLoaderWorkerHudPage.lua
index d6af9cdbd..4d5068109 100644
--- a/scripts/gui/hud/CpSiloLoaderWorkerHudPage.lua
+++ b/scripts/gui/hud/CpSiloLoaderWorkerHudPage.lua
@@ -35,14 +35,17 @@ function CpSiloLoaderWorkerHudPageElement:setupElements(baseHud, vehicle, lines,
end)
- --- Progress of fill level removed since the start of the driver.
+ --- Displays the fill level of current worked on heap.
local x, y = unpack(lines[4].left)
self.fillLevelProgressLabel = CpTextHudElement.new(self , x , y, CpBaseHud.defaultFontSize)
self.fillLevelProgressLabel:setTextDetails(g_i18n:getText("CP_siloLoader_fillLevelProgress"))
- --- Progress of fill level removed since the start of the driver.
+ --- Displays the fill level of current worked on heap.
local x, y = unpack(lines[4].right)
self.fillLevelProgressText = CpTextHudElement.new(self, x, y, CpBaseHud.defaultFontSize, RenderText.ALIGN_RIGHT)
+ --- Shovel loading height offset.
+ self.loadingShovelHeightOffsetBtn = baseHud:addLineTextButton(self, 2, CpBaseHud.defaultFontSize,
+ vehicle:getCpSettings().loadingShovelHeightOffset)
CpGuiUtil.addCopyAndPasteButtons(self, baseHud,
vehicle, lines, wMargin, hMargin, 1)
@@ -85,6 +88,11 @@ function CpSiloLoaderWorkerHudPageElement:updateContent(vehicle, status)
self.workWidthBtn:setTextDetails(workWidth:getTitle(), workWidth:getString())
self.workWidthBtn:setVisible(workWidth:getIsVisible())
+ local loadingHeightOffset = vehicle:getCpSettings().loadingShovelHeightOffset
+ self.loadingShovelHeightOffsetBtn:setTextDetails(loadingHeightOffset:getTitle(), loadingHeightOffset:getString())
+ self.loadingShovelHeightOffsetBtn:setVisible(loadingHeightOffset:getIsVisible())
+ self.loadingShovelHeightOffsetBtn:setDisabled(loadingHeightOffset:getIsDisabled())
+
self.fillLevelProgressText:setTextDetails(status:getSiloFillLevelPercentageLeftOver())
--- Update copy and paste buttons
@@ -145,4 +153,15 @@ function CpSiloLoaderWorkerHudPageElement:arePositionEqual(parameters, otherPara
return false
end
return true
+end
+
+function CpSiloLoaderWorkerHudPageElement:isStartingPointBtnDisabled(vehicle)
+ return AIUtil.hasChildVehicleWithSpecialization(vehicle, ConveyorBelt) or vehicle:getIsCpActive()
+end
+
+function CpSiloLoaderWorkerHudPageElement:getStartingPointBtnText(vehicle)
+ if self:isStartingPointBtnDisabled(vehicle) then
+ return vehicle:getCpStartText()
+ end
+ return vehicle:getCpStartingPointSetting():getString()
end
\ No newline at end of file
diff --git a/scripts/pathfinder/PathfinderUtil.lua b/scripts/pathfinder/PathfinderUtil.lua
index 29a5c062d..32b19e645 100644
--- a/scripts/pathfinder/PathfinderUtil.lua
+++ b/scripts/pathfinder/PathfinderUtil.lua
@@ -663,8 +663,21 @@ function PathfinderUtil.initializeTrailerHeading(start, vehicleData)
end
end
----@param start State3D
+---@param vehicle table
---@param goal State3D
+---@param allowReverse boolean
+---@param fieldNum number|nil
+---@param vehiclesToIgnore table|nil
+---@param objectsToIgnore table|nil
+---@param maxFruitPercent number|nil
+---@param offFieldPenalty number|nil
+---@param areaToAvoid PathfinderUtil.Area|nil
+---@param mustBeAccurate boolean|nil
+---@param areaToIgnoreFruit PathfinderUtil.Area|nil
+---@return PathfinderInterface pathfinder
+---@return boolean done finished pathfinding?
+---@return table|nil path that was found?
+---@return boolean|nil goalNodeInvalid
function PathfinderUtil.startPathfindingFromVehicleToGoal(vehicle, goal,
allowReverse, fieldNum,
vehiclesToIgnore, objectsToIgnore,
@@ -789,6 +802,10 @@ end
--- in front of the vehicle when it stops working on that row before the turn starts. Negative values mean the
--- vehicle is towing the implements and is past the end of the row when the implement reaches the end of the row.
---@param turnOnField boolean is turn on field allowed?
+---@return PathfinderInterface pathfinder
+---@return boolean done finished pathfinding?
+---@return table|nil path that was found?
+---@return boolean|nil goalNodeInvalid
function PathfinderUtil.findPathForTurn(vehicle, startOffset, goalReferenceNode, goalOffset, turnRadius, allowReverse,
courseWithHeadland, workingWidth, backMarkerDistance, turnOnField)
local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(vehicle:getAIDirectionNode(), 0, startOffset or 0)
@@ -839,6 +856,8 @@ end
---@param zOffset number offset in meters relative to the goal node (forward positive, backward negative)
--- Together with the goalReferenceNode defines the goal
---@param turnRadius number vehicle turning radius
+---@return table|nil path
+---@return number length
function PathfinderUtil.findAnalyticPath(solver, vehicleDirectionNode, startOffset, goalReferenceNode,
xOffset, zOffset, turnRadius)
local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(vehicleDirectionNode, 0, startOffset or 0)
@@ -871,8 +890,8 @@ function PathfinderUtil.getNodePositionAndDirection(node, xOffset, zOffset)
end
---@param vehicle table
----@param xOffset|nil
----@param zOffset|nil
+---@param xOffset number|nil
+---@param zOffset number|nil
---@return State3D position/heading of vehicle
function PathfinderUtil.getVehiclePositionAsState3D(vehicle, xOffset, zOffset)
local x, z, yRot = PathfinderUtil.getNodePositionAndDirection( AIUtil.getDirectionNode(vehicle), xOffset, zOffset)
@@ -895,13 +914,17 @@ end
---@param xOffset number side offset of the goal from the goalWaypoint
---@param zOffset number length offset of the goal from the goalWaypoint
---@param allowReverse boolean allow reverse driving
----@param fieldNum number if > 0, the pathfinding is restricted to the given field and its vicinity. Otherwise the
+---@param fieldNum number|nil if > 0, the pathfinding is restricted to the given field and its vicinity. Otherwise the
--- pathfinding considers any collision-free path valid, also outside of the field.
----@param vehiclesToIgnore table[] list of vehicles to ignore for the collision detection (optional)
----@param maxFruitPercent number maximum percentage of fruit present before a node is marked as invalid (optional)
----@param offFieldPenalty number penalty to apply to nodes off the field
----@param areaToAvoid PathfinderUtil.NodeArea nodes in this area will be penalized so the path will most likely avoid it
----@param areaToIgnoreFruit PathfinderUtil.Area area to ignore fruit
+---@param vehiclesToIgnore table[]|nil list of vehicles to ignore for the collision detection (optional)
+---@param maxFruitPercent number|nil maximum percentage of fruit present before a node is marked as invalid (optional)
+---@param offFieldPenalty number|nil penalty to apply to nodes off the field
+---@param areaToAvoid PathfinderUtil.NodeArea|nil nodes in this area will be penalized so the path will most likely avoid it
+---@param areaToIgnoreFruit PathfinderUtil.Area|nil area to ignore fruit
+---@return PathfinderInterface pathfinder
+---@return boolean done finished pathfinding?
+---@return table|nil path that was found?
+---@return boolean|nil goalNodeInvalid
function PathfinderUtil.startPathfindingFromVehicleToWaypoint(vehicle, course, goalWaypointIx,
xOffset, zOffset, allowReverse,
fieldNum, vehiclesToIgnore, maxFruitPercent,
@@ -927,6 +950,10 @@ end
---@param offFieldPenalty number|nil penalty to apply to nodes off the field
---@param areaToAvoid PathfinderUtil.NodeArea|nil nodes in this area will be penalized so the path will most likely avoid it
---@param mustBeAccurate boolean|nil must be accurately find the goal position/angle (optional)
+---@return PathfinderInterface pathfinder
+---@return boolean done finished pathfinding?
+---@return table|nil path that was found?
+---@return boolean|nil goalNodeInvalid
function PathfinderUtil.startPathfindingFromVehicleToNode(vehicle, goalNode,
xOffset, zOffset, allowReverse,
fieldNum, vehiclesToIgnore, maxFruitPercent,
@@ -948,6 +975,10 @@ end
---@param fieldNum number if other than 0 or nil the pathfinding is restricted to the given field and its vicinity
---@param vehiclesToIgnore table[] list of vehicles to ignore for the collision detection (optional)
---@param maxFruitPercent number maximum percentage of fruit present before a node is marked as invalid (optional)
+---@return PathfinderInterface pathfinder
+---@return boolean done finished pathfinding?
+---@return table|nil path that was found?
+---@return boolean|nil goalNodeInvalid
function PathfinderUtil.startAStarPathfindingFromVehicleToNode(vehicle, goalNode,
xOffset, zOffset,
fieldNum, vehiclesToIgnore, maxFruitPercent)
diff --git a/scripts/silo/BunkerSiloVehicleController.lua b/scripts/silo/BunkerSiloVehicleController.lua
index caf5ac979..ff013ec0f 100644
--- a/scripts/silo/BunkerSiloVehicleController.lua
+++ b/scripts/silo/BunkerSiloVehicleController.lua
@@ -29,8 +29,6 @@ function CpBunkerSiloVehicleController:init(silo, vehicle, driveStrategy, direct
^
|
]]
-
-
if dsz > dhz then
self:debug("Silo needs to be inverted.")
self.isInverted = true
@@ -44,8 +42,6 @@ function CpBunkerSiloVehicleController:init(silo, vehicle, driveStrategy, direct
| v |
wx sx
]]
-
-
self.isInverted = true
elseif dsz < 0 and dhz > 0 then
self:debug("Start distance: dsz: %.2f, dhz: %.2f", dsz, dhz)
@@ -317,6 +313,24 @@ CpBunkerSiloLoaderController = CpObject(CpBunkerSiloVehicleController)
function CpBunkerSiloLoaderController:init(silo, vehicle, driveStrategy)
CpBunkerSiloVehicleController.init(self, silo, vehicle,
driveStrategy, vehicle:getAIDirectionNode())
+
+
+ local sx, sz = self.silo:getStartPosition()
+ local hx, hz = self.silo:getHeightPosition()
+ local dx, _, dz = getWorldTranslation(vehicle:getAIDirectionNode())
+ self.isInverted = false
+
+ if MathUtil.vector2Length(sx-dx, sz-dz) < MathUtil.vector2Length(hx-dx, hz-dz) then
+ if self.silo.siloMode == CpBunkerSilo.SIDE_MODES.ONE_SIDED_INVERTED then
+ self.isInverted = true
+ end
+ else
+ self.isInverted = true
+ if self.silo.siloMode == CpBunkerSilo.SIDE_MODES.ONE_SIDED then
+ self.isInverted = false
+ end
+ end
+
end
--- Gets the next line with the most fill level.
diff --git a/scripts/silo/BunkerSiloWrapper.lua b/scripts/silo/BunkerSiloWrapper.lua
index 6c6062985..2ddfe1a4f 100644
--- a/scripts/silo/BunkerSiloWrapper.lua
+++ b/scripts/silo/BunkerSiloWrapper.lua
@@ -65,68 +65,98 @@ function CpSilo:init(sx, sz, wx, wz, hx, hz)
end
+---@return number sx
+---@return number sz
function CpSilo:getStartPosition()
return self.sx, self.sz
end
+---@return number wx
+---@return number wz
function CpSilo:getWidthPosition()
return self.wx, self.wz
end
+---@return number hx
+---@return number hz
function CpSilo:getHeightPosition()
return self.hx, self.hz
end
+---@return number width
function CpSilo:getWidth()
return self.width
end
+---@return number length
function CpSilo:getLength()
return self.length
end
+---@return number dirX
+---@return number dirZ
function CpSilo:getLengthDirection()
return self.dirXLength, self.dirZLength
end
+---@return number dirX
+---@return number dirZ
function CpSilo:getWidthDirection()
return self.dirXWidth, self.dirZWidth
end
+---@return number cx
+---@return number cz
function CpSilo:getCenter()
local cx, cz = self:getFrontCenter()
return cx + self.dirXLength * self.length/2, cz + self.dirZLength * self.length/2
end
+---@return number fcx
+---@return number fcz
function CpSilo:getFrontCenter()
local width = self:getWidth()
return self.sx + self.dirXWidth * width/2, self.sz + self.dirZWidth * width/2
end
+---@return number bcx
+---@return number bcz
function CpSilo:getBackCenter()
local length = self:getLength()
local fcx, fcz = self:getFrontCenter()
return fcx + self.dirXLength * length/2, fcz + self.dirZLength * length/2
end
---- Is the point directly in the silo area.
+--- Is the point directly in the silo area?
+---@param x number
+---@param z number
+---@return boolean
function CpSilo:isPointInSilo(x, z)
return self:isPointInArea(x, z, self.area)
end
+---@param node number
+---@return boolean
function CpSilo:isNodeInSilo(node)
local x, _, z = getWorldTranslation(node)
return self:isPointInArea(x, z, self.area)
end
+---@param vehicle table
+---@return boolean
function CpSilo:isVehicleInSilo(vehicle)
return self:isNodeInSilo(vehicle.rootNode)
end
+---@param x number
+---@param z number
+---@param area table
+---@return boolean
function CpSilo:isPointInArea(x, z, area)
return CpMathUtil.isPointInPolygon(area, x, z)
end
+---@return table area
function CpSilo:getArea()
return self.area
end
@@ -175,6 +205,10 @@ function CpSilo:getTotalFillLevel()
return 0
end
+function CpSilo:isTheSameSilo()
+ --- override
+end
+
--- Heap Bunker Silo
--- Wrapper for a heap.
---@class CpHeapBunkerSilo :CpSilo
@@ -388,6 +422,14 @@ function CpBunkerSilo:getNode()
return self.silo.interactionTriggerNode
end
+function CpBunkerSilo:getFillType()
+ return self.silo.outputFillType
+end
+
+function CpBunkerSilo:getTotalFillLevel()
+ return self.silo.fillLevel
+end
+
function CpBunkerSilo:delete()
for _, controller in pairs(self.controllers) do
controller:setBunkerSiloInvalid()
@@ -666,11 +708,3 @@ function CpBunkerSilo:getDebugData()
end
return data
end
-
-function CpBunkerSilo:getFillType()
- return self.silo.outputFillType
-end
-
-function CpBunkerSilo:getTotalFillLevel()
- return self.silo.fillLevel
-end
\ No newline at end of file
diff --git a/scripts/specializations/CourseplaySpec.lua b/scripts/specializations/CourseplaySpec.lua
deleted file mode 100644
index 8894d1472..000000000
--- a/scripts/specializations/CourseplaySpec.lua
+++ /dev/null
@@ -1,109 +0,0 @@
---- Cp ai driver spec
-
----@class CourseplaySpec
-CourseplaySpec = {}
-
-CourseplaySpec.MOD_NAME = g_currentModName
-
-CourseplaySpec.KEY = "."..CourseplaySpec.MOD_NAME..".courseplaySpec."
-
-function CourseplaySpec.initSpecialization()
- local schema = Vehicle.xmlSchemaSavegame
-end
-
-function CourseplaySpec.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(AIFieldWorker, specializations)
-end
-
-function CourseplaySpec.registerEventListeners(vehicleType)
- SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", CourseplaySpec)
- SpecializationUtil.registerEventListener(vehicleType, "onLoad", CourseplaySpec)
- SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", CourseplaySpec)
- SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", CourseplaySpec)
- SpecializationUtil.registerEventListener(vehicleType, "onEnterVehicle", CourseplaySpec)
- SpecializationUtil.registerEventListener(vehicleType, "onLeaveVehicle", CourseplaySpec)
- SpecializationUtil.registerEventListener(vehicleType, "onDraw", CourseplaySpec)
-end
-
-function CourseplaySpec.registerEvents(vehicleType)
- SpecializationUtil.registerEvent(vehicleType, "onCpUnitChanged")
- SpecializationUtil.registerEvent(vehicleType, "onCpDrawHudMap")
-end
-
-function CourseplaySpec.registerOverwrittenFunctions(vehicleType)
-
-end
-
-
-function CourseplaySpec:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
-
-end
-
-
-
-------------------------------------------------------------------------------------------------------------------------
---- Event listeners
----------------------------------------------------------------------------------------------------------------------------
-function CourseplaySpec:onLoad(savegame)
- --- Register the spec: spec_courseplaySpec
- local specName = CourseplaySpec.MOD_NAME .. ".courseplaySpec"
- self.spec_courseplaySpec = self["spec_" .. specName]
- g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.USE_MILES], CourseplaySpec.onUnitChanged, self)
- g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.USE_ACRE], CourseplaySpec.onUnitChanged, self)
- g_messageCenter:subscribe(MessageType.CP_DISTANCE_UNIT_CHANGED, CourseplaySpec.onUnitChanged, self)
-end
-
-function CourseplaySpec:onUnitChanged()
- SpecializationUtil.raiseEvent(self,"onCpUnitChanged")
-end
-
-function CourseplaySpec:onPostLoad(savegame)
-
-end
-
-function CourseplaySpec:saveToXMLFile(xmlFile, baseKey, usedModNames)
-
-end
-
-function CourseplaySpec:onEnterVehicle(isControlling)
-
-end
-
-function CourseplaySpec:onLeaveVehicle(wasEntered)
-
-end
-
-function CourseplaySpec:isCollisionDetectionEnabled()
- return self.collisionDetectionEnabled
-end
-
-function CourseplaySpec:enableCollisionDetection()
- self.collisionDetectionEnabled = true
-end
-
-function CourseplaySpec:disableCollisionDetection()
- self.collisionDetectionEnabled = false
-end
-
---- This is to be able to disable the built-in AIDriveStrategyCollision check from our drive strategies
-function CourseplaySpec:getCollisionCheckActive(superFunc,...)
- if self.collisionDetectionEnabled then
- return superFunc(self,...)
- else
- return false
- end
-end
-
---- Enriches the status data for the hud here.
-function CourseplaySpec:onUpdateTick()
-
-end
-
-function CourseplaySpec:onDraw()
-
-end
-
-
-AIDriveStrategyCollision.getCollisionCheckActive = Utils.overwrittenFunction(
- AIDriveStrategyCollision.getCollisionCheckActive, CourseplaySpec.getCollisionCheckActive
-)
diff --git a/scripts/specializations/CpAIBaleFinder.lua b/scripts/specializations/CpAIBaleFinder.lua
index 535e1197e..51adf5f96 100644
--- a/scripts/specializations/CpAIBaleFinder.lua
+++ b/scripts/specializations/CpAIBaleFinder.lua
@@ -19,7 +19,7 @@ function CpAIBaleFinder.initSpecialization()
end
function CpAIBaleFinder.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(CpAIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpAIBaleFinder.register(typeManager,typeName,specializations)
@@ -135,9 +135,9 @@ function CpAIBaleFinder:getCanStartCp(superFunc)
end
--- Only use the bale finder, if the cp field work job is not possible.
-function CpAIBaleFinder:getCpStartableJob(superFunc)
+function CpAIBaleFinder:getCpStartableJob(superFunc, ...)
local spec = self.spec_cpAIBaleFinder
- return superFunc(self) or self:getCanStartCpBaleFinder() and spec.cpJob
+ return superFunc(self, ...) or self:getCanStartCpBaleFinder() and spec.cpJob
end
function CpAIBaleFinder:getCpStartText(superFunc)
diff --git a/scripts/specializations/CpAIBunkerSiloWorker.lua b/scripts/specializations/CpAIBunkerSiloWorker.lua
index 6b73042db..1132a81f2 100644
--- a/scripts/specializations/CpAIBunkerSiloWorker.lua
+++ b/scripts/specializations/CpAIBunkerSiloWorker.lua
@@ -18,7 +18,7 @@ function CpAIBunkerSiloWorker.initSpecialization()
end
function CpAIBunkerSiloWorker.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(CpAIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpAIBunkerSiloWorker.register(typeManager,typeName,specializations)
@@ -95,11 +95,13 @@ end
--- Is the bunker silo allowed?
function CpAIBunkerSiloWorker:getCanStartCpBunkerSiloWorker()
+ if AIUtil.hasChildVehicleWithSpecialization(self, Shovel) then
+ return false
+ end
return not self:getCanStartCpFieldWork()
and not self:getCanStartCpBaleFinder()
and not self:getCanStartCpCombineUnloader()
and not self:getCanStartCpSiloLoaderWorker()
- and (not self:hasCpCourse() or AIUtil.hasChildVehicleWithSpecialization(self, Leveler, nil))
end
function CpAIBunkerSiloWorker:getCanStartCp(superFunc)
@@ -112,7 +114,7 @@ function CpAIBunkerSiloWorker:getCpStartableJob(superFunc, isStartedByHud)
if isStartedByHud and not AIUtil.hasChildVehicleWithSpecialization(self, Leveler) then
job = self:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_BUNKER_SILO and job
end
- return superFunc(self) or job
+ return superFunc(self, isStartedByHud) or job
end
function CpAIBunkerSiloWorker:getCpStartText(superFunc)
diff --git a/scripts/specializations/CpAICombineUnloader.lua b/scripts/specializations/CpAICombineUnloader.lua
index 69f1ef223..9a7b85712 100644
--- a/scripts/specializations/CpAICombineUnloader.lua
+++ b/scripts/specializations/CpAICombineUnloader.lua
@@ -215,9 +215,9 @@ function CpAICombineUnloader:getCanStartCp(superFunc)
return superFunc(self) or self:getCanStartCpCombineUnloader() and not self:getIsCpCourseRecorderActive()
end
-function CpAICombineUnloader:getCpStartableJob(superFunc)
+function CpAICombineUnloader:getCpStartableJob(superFunc, ...)
local spec = self.spec_cpAICombineUnloader
- return superFunc(self) or self:getCanStartCpCombineUnloader() and spec.cpJob
+ return superFunc(self, ...) or self:getCanStartCpCombineUnloader() and spec.cpJob
end
function CpAICombineUnloader:getCpStartText(superFunc)
diff --git a/scripts/specializations/CpAIFieldWorker.lua b/scripts/specializations/CpAIFieldWorker.lua
index fb0168958..42e125966 100644
--- a/scripts/specializations/CpAIFieldWorker.lua
+++ b/scripts/specializations/CpAIFieldWorker.lua
@@ -39,8 +39,7 @@ function CpAIFieldWorker.registerEventListeners(vehicleType)
SpecializationUtil.registerEventListener(vehicleType, "onCpFull", CpAIFieldWorker)
SpecializationUtil.registerEventListener(vehicleType, "onCpFinished", CpAIFieldWorker)
- SpecializationUtil.registerEventListener(vehicleType, "onPostDetachImplement", CpAIFieldWorker)
- SpecializationUtil.registerEventListener(vehicleType, "onPostAttachImplement", CpAIFieldWorker)
+ SpecializationUtil.registerEventListener(vehicleType, "onStateChange", CpAIFieldWorker)
SpecializationUtil.registerEventListener(vehicleType, 'onCpCourseChange', CpAIFieldWorker)
SpecializationUtil.registerEventListener(vehicleType, 'onCpADStartedByPlayer', CpAIFieldWorker)
@@ -107,22 +106,20 @@ function CpAIFieldWorker:saveToXMLFile(xmlFile, baseKey, usedModNames)
spec.cpJobStartAtLastWp:getCpJobParameters():saveToXMLFile(xmlFile, baseKey.. ".cpJobStartAtLastWp")
end
-function CpAIFieldWorker:onCpCourseChange()
- local spec = self.spec_cpAIFieldWorker
- spec.cpJob:getCpJobParameters():validateSettings()
-end
-
-function CpAIFieldWorker:onPostDetachImplement()
+function CpAIFieldWorker:onStateChange(state, data)
local spec = self.spec_cpAIFieldWorker
- spec.cpJob:getCpJobParameters():validateSettings()
+ if state == Vehicle.STATE_CHANGE_ATTACH then
+ spec.cpJob:getCpJobParameters():validateSettings()
+ elseif state == Vehicle.STATE_CHANGE_DETACH then
+ spec.cpJob:getCpJobParameters():validateSettings()
+ end
end
-function CpAIFieldWorker:onPostAttachImplement()
+function CpAIFieldWorker:onCpCourseChange()
local spec = self.spec_cpAIFieldWorker
spec.cpJob:getCpJobParameters():validateSettings()
end
-
function CpAIFieldWorker:getCpStartingPointSetting()
local spec = self.spec_cpAIFieldWorker
return spec.cpJob:getCpJobParameters().startAt
@@ -299,9 +296,9 @@ function CpAIFieldWorker:getCanStartCp(superFunc)
end
--- Gets the field work job for the hud or start action event.
-function CpAIFieldWorker:getCpStartableJob(superFunc)
+function CpAIFieldWorker:getCpStartableJob(superFunc, ...)
local spec = self.spec_cpAIFieldWorker
- return self:getCanStartCpFieldWork() and self:hasCpCourse() and spec.cpJob or superFunc(self)
+ return self:getCanStartCpFieldWork() and self:hasCpCourse() and spec.cpJob or superFunc(self, ...)
end
function CpAIFieldWorker:getCpStartText(superFunc)
diff --git a/scripts/specializations/CpAISiloLoaderWorker.lua b/scripts/specializations/CpAISiloLoaderWorker.lua
index 972bf1174..4ec9f4486 100644
--- a/scripts/specializations/CpAISiloLoaderWorker.lua
+++ b/scripts/specializations/CpAISiloLoaderWorker.lua
@@ -1,6 +1,8 @@
local modName = CpAISiloLoaderWorker and CpAISiloLoaderWorker.MOD_NAME -- for reload
+--- Specialization for the silo loader job
+--- Used for shovel loader and big silo loader, like the Ropa NarwaRo.
---@class CpAISiloLoaderWorker
CpAISiloLoaderWorker = {}
@@ -92,11 +94,11 @@ function CpAISiloLoaderWorker:onUpdate(dt)
local spec = self.spec_cpAISiloLoaderWorker
end
---- Is the bunker silo allowed?
function CpAISiloLoaderWorker:getCanStartCpSiloLoaderWorker()
- return not self:getCanStartCpFieldWork() and not self:getCanStartCpBaleFinder() and not self:hasCpCourse()
- and not self:getCanStartCpCombineUnloader() and AIUtil.hasChildVehicleWithSpecialization(self, Shovel)
- and AIUtil.hasChildVehicleWithSpecialization(self, ConveyorBelt)
+ return not self:getCanStartCpFieldWork()
+ and not self:getCanStartCpBaleFinder()
+ and not self:getCanStartCpCombineUnloader()
+ and AIUtil.hasChildVehicleWithSpecialization(self, Shovel)
end
function CpAISiloLoaderWorker:getCanStartCp(superFunc)
@@ -105,7 +107,11 @@ end
function CpAISiloLoaderWorker:getCpStartableJob(superFunc, isStartedByHud)
local spec = self.spec_cpAISiloLoaderWorker
- return superFunc(self) or self:getCanStartCpSiloLoaderWorker() and spec.cpJob
+ local job = self:getCanStartCpSiloLoaderWorker() and spec.cpJob
+ if isStartedByHud and not AIUtil.hasChildVehicleWithSpecialization(self, ConveyorBelt) then
+ job = self:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_SILO_LOADING and job
+ end
+ return superFunc(self, isStartedByHud) or job
end
function CpAISiloLoaderWorker:getCpStartText(superFunc)
@@ -166,17 +172,23 @@ function CpAISiloLoaderWorker:startCpAtLastWp(superFunc, ...)
end
end
-function CpAISiloLoaderWorker:startCpSiloLoaderWorker(jobParameters, bunkerSilo, heap)
+function CpAISiloLoaderWorker:startCpSiloLoaderWorker(jobParameters, bunkerSilo, heap, unloadTrigger, unloadStation)
if self.isServer then
- local strategy = AIDriveStrategySiloLoader.new()
- -- this also starts the strategy
+ local strategy
+ if SpecializationUtil.hasSpecialization(ConveyorBelt, self.specializations) then
+ CpUtil.debugVehicle(CpDebug.DBG_SILO, self, "Starting a silo loader strategy.")
+ strategy = AIDriveStrategySiloLoader.new()
+ else
+ CpUtil.debugVehicle(CpDebug.DBG_SILO, self, "Starting a shovel silo loader strategy.")
+ strategy = AIDriveStrategyShovelSiloLoader.new()
+ strategy:setUnloadTriggerAndStation(unloadTrigger, unloadStation)
+ end
strategy:setSiloAndHeap(bunkerSilo, heap)
strategy:setAIVehicle(self, jobParameters)
- CpUtil.debugVehicle(CpDebug.DBG_SILO, self, "Starting silo worker job.")
self:startCpWithStrategy(strategy)
end
end
function CpAISiloLoaderWorker:stopCpSiloLoaderWorker()
self:stopCpDriver()
-end
\ No newline at end of file
+end
diff --git a/scripts/specializations/CpAIWorker.lua b/scripts/specializations/CpAIWorker.lua
index 2034afe03..5e39040fc 100644
--- a/scripts/specializations/CpAIWorker.lua
+++ b/scripts/specializations/CpAIWorker.lua
@@ -28,6 +28,9 @@ function CpAIWorker.register(typeManager, typeName, specializations)
end
function CpAIWorker.registerEvents(vehicleType)
+ SpecializationUtil.registerEvent(vehicleType, "onCpUnitChanged")
+ SpecializationUtil.registerEvent(vehicleType, "onCpDrawHudMap")
+
SpecializationUtil.registerEvent(vehicleType, "onCpFinished")
SpecializationUtil.registerEvent(vehicleType, "onCpEmpty")
SpecializationUtil.registerEvent(vehicleType, "onCpFull")
@@ -42,11 +45,10 @@ function CpAIWorker.registerEventListeners(vehicleType)
SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", CpAIWorker)
SpecializationUtil.registerEventListener(vehicleType, "onLoad", CpAIWorker)
SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", CpAIWorker)
- SpecializationUtil.registerEventListener(vehicleType, "onPreDetachImplement", CpAIWorker)
- SpecializationUtil.registerEventListener(vehicleType, "onPostAttachImplement", CpAIWorker)
SpecializationUtil.registerEventListener(vehicleType, "onUpdate", CpAIWorker)
SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", CpAIWorker)
SpecializationUtil.registerEventListener(vehicleType, "onLeaveVehicle", CpAIWorker)
+ SpecializationUtil.registerEventListener(vehicleType, "onPreDelete", CpAIWorker)
--- Autodrive events
SpecializationUtil.registerEventListener(vehicleType, "onStopAutoDrive", CpAIWorker)
SpecializationUtil.registerEventListener(vehicleType, "onStartAutoDrive", CpAIWorker)
@@ -76,10 +78,14 @@ function CpAIWorker.registerOverwrittenFunctions(vehicleType)
SpecializationUtil.registerOverwrittenFunction(vehicleType, 'stopCurrentAIJob', CpAIWorker.stopCurrentAIJob)
SpecializationUtil.registerOverwrittenFunction(vehicleType, 'getCanMotorRun', CpAIWorker.getCanMotorRun)
SpecializationUtil.registerOverwrittenFunction(vehicleType, 'stopFieldWorker', CpAIWorker.stopFieldWorker)
+ SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAIReverserNode", CpAIWorker.getAIReverserNode)
+ SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAIDirectionNode", CpAIWorker.getAIDirectionNode)
end
-------------------------------------------------------------------------------------------------------------------------
+
+---------------------------------------------------
--- Event listeners
----------------------------------------------------------------------------------------------------------------------------
+---------------------------------------------------
+
function CpAIWorker:onLoad(savegame)
--- Register the spec: spec_CpAIWorker
self.spec_cpAIWorker = self["spec_" .. CpAIWorker.SPEC_NAME]
@@ -87,22 +93,19 @@ function CpAIWorker:onLoad(savegame)
--- Flag to make sure the motor isn't being turned on again by giants code, when we want it turned off.
spec.motorDisabled = false
spec.driveStrategy = nil
+ g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.USE_MILES], CpAIWorker.onUnitChanged, self)
+ g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.USE_ACRE], CpAIWorker.onUnitChanged, self)
+ g_messageCenter:subscribe(MessageType.CP_DISTANCE_UNIT_CHANGED, CpAIWorker.onUnitChanged, self)
end
-function CpAIWorker:onLoadFinished()
-
+function CpAIWorker:onUnitChanged()
+ SpecializationUtil.raiseEvent(self,"onCpUnitChanged")
end
-function CpAIWorker:onPreDetachImplement(implement)
- local spec = self.spec_cpAIWorker
-end
-
-function CpAIWorker:onPostAttachImplement(object)
- local spec = self.spec_cpAIWorker
-
+function CpAIWorker:onLoadFinished()
+
end
-
function CpAIWorker:onLeaveVehicle(wasEntered)
if wasEntered then
CpJobSyncOnLeaveEvent.sendEvent(self)
@@ -148,6 +151,18 @@ function CpAIWorker:onRegisterActionEvents(isActiveForInput, isActiveForInputIgn
end
end
+function CpAIWorker:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
+ CpAIWorker.updateActionEvents(self)
+end
+
+function CpAIWorker:onPreDelete()
+
+end
+
+-----------------------------------------------
+--- Action input events
+-----------------------------------------------
+
--- Updates the action event visibility and text.
function CpAIWorker:updateActionEvents()
local spec = self.spec_cpAIWorker
@@ -185,72 +200,6 @@ function CpAIWorker:updateActionEvents()
end
end
-function CpAIWorker:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
- CpAIWorker.updateActionEvents(self)
-end
-
-
---- Used to enable/disable release of the helper
---- and handles post release functionality with for example auto drive.
---- TODO: This function is a mess and desperately needs a better solution!
-function CpAIWorker:stopCurrentAIJob(superFunc, message, ...)
- if message then
- CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "stop message: %s", message:getMessage())
- else
- CpUtil.infoVehicle(self, "no stop message was given.")
- return superFunc(self, message, ...)
- end
- local releaseMessage, hasFinished, event, isOnlyShownOnPlayerStart = g_infoTextManager:getInfoTextDataByAIMessage(message)
-
- CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "finished: %s, event: %s",
- tostring(hasFinished), tostring(event))
-
- local wasCpActive = self:getIsCpActive()
- if wasCpActive then
- local driveStrategy = self:getCpDriveStrategy()
- if driveStrategy then
- -- TODO: this isn't needed if we do not return a 0 < maxSpeed < 0.5, should either be exactly 0 or greater than 0.5
- local maxSpeed = driveStrategy and driveStrategy:getMaxSpeed()
- if message:isa(AIMessageErrorBlockedByObject) then
- if self.spec_aiFieldWorker.didNotMoveTimer and self.spec_aiFieldWorker.didNotMoveTimer < 0 then
- if maxSpeed and maxSpeed < 1 then
- -- disable the Giants timeout which dismisses the AI worker if it does not move for 5 seconds
- -- since we often stop for instance in convoy mode when waiting for another vehicle to turn
- -- (when we do this, we set our maxSpeed to 0). So we also check our maxSpeed, this way the Giants timer will
- -- fire if we are blocked (thus have a maxSpeed > 0 but not moving)
- CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, 'Overriding the Giants did not move timer, with speed: %.2f', maxSpeed)
- return
- else
- CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, 'Giants did not move timer triggered, with speed: %.2f!', maxSpeed)
- end
- end
- end
- driveStrategy:onFinished()
- end
- end
- self:resetCpAllActiveInfoTexts()
- --- Only add the info text, if it's available and nobody is in the vehicle.
- if not self:getIsControlled() and releaseMessage and not isOnlyShownOnPlayerStart then
- self:setCpInfoTextActive(releaseMessage)
- end
- superFunc(self, message,...)
- if wasCpActive then
- if event then
- SpecializationUtil.raiseEvent(self, event)
- end
- if hasFinished and self:getCpSettings().foldImplementAtEnd:getValue() then
- --- Folds implements at the end if the setting is active.
- self:prepareForAIDriving()
- end
-
- end
-end
-
-
------------------------------------------------
---- Action input events
------------------------------------------------
-
function CpAIWorker:changeStartingPoint()
local startingPointSetting = self:getCpStartingPointSetting()
startingPointSetting:setNextItem()
@@ -265,7 +214,7 @@ function CpAIWorker:changeCourseVisibility()
end
function CpAIWorker:startStopCpActionEvent()
- self:cpStartStopDriver()
+ self:cpStartStopDriver(true)
end
--- Directly starts a cp job or stops a currently active job.
@@ -301,6 +250,10 @@ function CpAIWorker:cpStartStopDriver(isStartedByHud)
end
end
+-----------------------------------------------
+--- Status getter functions
+-----------------------------------------------
+
--- Is a cp worker active ?
--- Every cp job should be an instance of type CpAIJob.
function CpAIWorker:getIsCpActive()
@@ -309,23 +262,24 @@ end
--- Is cp drive to field work active
function CpAIWorker:getIsCpDriveToFieldWorkActive()
- return self:getIsCpActive() and self.driveToFieldWorkStartStrategy ~= nil
+ local spec = self.spec_cpAIWorker
+ return self:getIsCpActive() and spec.driveToTask ~=nil
end
--- Is a cp job ready to be started?
function CpAIWorker:getCanStartCp()
- return false
+ --- override
end
--- Gets the job to be started by the hud or the keybinding.
function CpAIWorker:getCpStartableJob()
-
+ --- override
end
--- Gets the additional action event start text,
--- for example the starting point.
function CpAIWorker:getCpStartText()
- return ""
+ --- override
end
--- Makes sure giants isn't turning the motor back on, when we have turned it off.
@@ -336,6 +290,66 @@ function CpAIWorker:getCanMotorRun(superFunc, ...)
return superFunc(self, ...)
end
+-----------------------------------------------
+--- Strategy handling
+-----------------------------------------------
+
+
+--- Used to enable/disable release of the helper
+--- and handles post release functionality with for example auto drive.
+--- TODO: this might by only called on the client, so
+--- server depended code has to be moved to stopJob or similar code.
+function CpAIWorker:stopCurrentAIJob(superFunc, message, ...)
+ if message then
+ CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "stop message: %s", message:getMessage())
+ else
+ CpUtil.infoVehicle(self, "no stop message was given.")
+ return superFunc(self, message, ...)
+ end
+ local releaseMessage, hasFinished, event, isOnlyShownOnPlayerStart = g_infoTextManager:getInfoTextDataByAIMessage(message)
+
+ CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, "finished: %s, event: %s",
+ tostring(hasFinished), tostring(event))
+ local wasCpActive = self:getIsCpActive()
+ if wasCpActive then
+ local driveStrategy = self:getCpDriveStrategy()
+ if driveStrategy then
+ -- TODO: this isn't needed if we do not return a 0 < maxSpeed < 0.5, should either be exactly 0 or greater than 0.5
+ local maxSpeed = driveStrategy and driveStrategy:getMaxSpeed()
+ if message:isa(AIMessageErrorBlockedByObject) then
+ if self.spec_aiFieldWorker.didNotMoveTimer and self.spec_aiFieldWorker.didNotMoveTimer < 0 then
+ if maxSpeed and maxSpeed < 1 then
+ -- disable the Giants timeout which dismisses the AI worker if it does not move for 5 seconds
+ -- since we often stop for instance in convoy mode when waiting for another vehicle to turn
+ -- (when we do this, we set our maxSpeed to 0). So we also check our maxSpeed, this way the Giants timer will
+ -- fire if we are blocked (thus have a maxSpeed > 0 but not moving)
+ CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, 'Overriding the Giants did not move timer, with speed: %.2f', maxSpeed)
+ return
+ else
+ CpUtil.debugVehicle(CpDebug.DBG_FIELDWORK, self, 'Giants did not move timer triggered, with speed: %.2f!', maxSpeed)
+ end
+ end
+ end
+ driveStrategy:onFinished()
+ end
+ end
+ self:resetCpAllActiveInfoTexts()
+ --- Only add the info text, if it's available and nobody is in the vehicle.
+ if not self:getIsControlled() and releaseMessage and not isOnlyShownOnPlayerStart then
+ self:setCpInfoTextActive(releaseMessage)
+ end
+ superFunc(self, message,...)
+ if wasCpActive then
+ if event then
+ SpecializationUtil.raiseEvent(self, event)
+ end
+ if hasFinished and self:getCpSettings().foldImplementAtEnd:getValue() then
+ --- Folds implements at the end if the setting is active.
+ self:prepareForAIDriving()
+ end
+ end
+end
+
function CpAIWorker:startCpDriveTo(task, jobParameters)
local spec = self.spec_cpAIWorker
spec.driveToTask = task
@@ -454,6 +468,7 @@ function CpAIWorker:cpHold(ms)
end
end
+---@param strategy AIDriveStrategyCourse
function CpAIWorker:startCpWithStrategy(strategy)
local spec = self.spec_cpAIWorker
spec.driveStrategy = strategy
@@ -464,12 +479,10 @@ function CpAIWorker:stopCpDriver()
--- Reset the flag.
local spec = self.spec_cpAIWorker
spec.motorDisabled = false
-
if spec.driveStrategy then
spec.driveStrategy:delete()
spec.driveStrategy = nil
end
-
if self.isServer then
WheelsUtil.updateWheelsPhysics(self, 0, 0, 0, true, true)
end
@@ -484,9 +497,7 @@ function CpAIWorker:stopCpDriver()
if actionController ~= nil then
actionController:resetCurrentState()
end
-
self:raiseAIEvent("onAIFieldWorkerEnd", "onAIImplementEnd")
-
end
function CpAIWorker:getCpDriveStrategy()
@@ -494,6 +505,69 @@ function CpAIWorker:getCpDriveStrategy()
return spec.driveStrategy
end
+--- Fixes the ai reverse node rotation for articulated axis vehicles,
+--- if the node is pointing backwards and not forwards.
+--- TODO: Consider consolidation with AIUtil.getArticulatedAxisVehicleReverserNode
+function CpAIWorker:getAIReverserNode(superFunc)
+ local spec = self.spec_cpAIWorker
+ if not self:getIsCpActive() then return superFunc(self) end
+ if self.spec_articulatedAxis and self.spec_articulatedAxis.aiRevereserNode then
+ if g_vehicleConfigurations:get(self, "articulatedAxisReverseNodeInverted") then
+ if not spec.articulatedAxisReverseNode then
+ spec.articulatedAxisReverseNode = CpUtil.createNode(
+ "cpAiRevereserNode", 0, 0, 0,
+ getParent(self.spec_articulatedAxis.aiRevereserNode))
+ end
+ return spec.articulatedAxisReverseNode
+ end
+ end
+ return superFunc(self)
+end
+
+--- Fixes the Direction for the platinum wheel loader, as
+--- their direction is not updated base on the rotation.
+--- So we use the parent node of the arm tool node.
+---@param superFunc any
+function CpAIWorker:getAIDirectionNode(superFunc)
+ if not self:getIsCpActive() then return superFunc(self) end
+ local movingToolIx = g_vehicleConfigurations:get(self, "fixWheelLoaderDirectionNodeByMovingToolIx")
+ if movingToolIx ~= nil then
+ return getParent(self.spec_cylindered.movingTools[movingToolIx].node)
+ end
+ return superFunc(self)
+end
+
+
+
+--- TODO: Do we really need the AIDriveStrategyCollision from giants, as this one is only active for fieldwork?
+--- Maybe there is already a unique cp logic implemented, that catches the use cases.
+function CpAIWorker:isCollisionDetectionEnabled()
+ local spec = self.spec_cpAIWorker
+ return spec.collisionDetectionEnabled
+end
+
+function CpAIWorker:enableCollisionDetection()
+ local spec = self.spec_cpAIWorker
+ spec.collisionDetectionEnabled = true
+end
+
+function CpAIWorker:disableCollisionDetection()
+ local spec = self.spec_cpAIWorker
+ spec.collisionDetectionEnabled = false
+end
+
+function CpAIWorker:getCollisionCheckActive(superFunc,...)
+ local spec = self.vehicle.spec_cpAIWorker
+ if spec.collisionDetectionEnabled then
+ return superFunc(self,...)
+ else
+ return false
+ end
+end
+AIDriveStrategyCollision.getCollisionCheckActive = Utils.overwrittenFunction(
+ AIDriveStrategyCollision.getCollisionCheckActive, CpAIWorker.getCollisionCheckActive
+)
+
function CpAIWorker:stopFieldWorker(superFunc, ...)
--- Reset the flag.
self.spec_cpAIWorker.motorDisabled = false
diff --git a/scripts/specializations/CpCourseGeneratorSettings.lua b/scripts/specializations/CpCourseGeneratorSettings.lua
index b443bfc22..7b23a4b9c 100644
--- a/scripts/specializations/CpCourseGeneratorSettings.lua
+++ b/scripts/specializations/CpCourseGeneratorSettings.lua
@@ -6,9 +6,12 @@
CpCourseGeneratorSettings = {}
CpCourseGeneratorSettings.MOD_NAME = g_currentModName
-CpCourseGeneratorSettings.KEY = "."..CpCourseGeneratorSettings.MOD_NAME..".cpCourseGeneratorSettings"
CpCourseGeneratorSettings.SETTINGS_KEY = ".settings"
CpCourseGeneratorSettings.VINE_SETTINGS_KEY = ".vineSettings"
+CpCourseGeneratorSettings.NAME = ".cpCourseGeneratorSettings"
+CpCourseGeneratorSettings.SPEC_NAME = CpCourseGeneratorSettings.MOD_NAME .. CpCourseGeneratorSettings.NAME
+CpCourseGeneratorSettings.KEY = "." .. CpCourseGeneratorSettings.MOD_NAME .. CpCourseGeneratorSettings.NAME
+
function CpCourseGeneratorSettings.initSpecialization()
local schema = Vehicle.xmlSchemaSavegame
--- Old save format
@@ -27,9 +30,14 @@ function CpCourseGeneratorSettings.initSpecialization()
CpCourseGeneratorSettings.registerConsoleCommands()
end
+function CpCourseGeneratorSettings.register(typeManager,typeName,specializations)
+ if CpCourseGeneratorSettings.prerequisitesPresent(specializations) then
+ typeManager:addSpecialization(typeName, CpCourseGeneratorSettings.SPEC_NAME)
+ end
+end
function CpCourseGeneratorSettings.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(AIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpCourseGeneratorSettings.registerEvents(vehicleType)
@@ -79,8 +87,7 @@ end
function CpCourseGeneratorSettings:onLoad(savegame)
--- Register the spec: spec_cpCourseGeneratorSettings
- local specName = CpCourseGeneratorSettings.MOD_NAME .. ".cpCourseGeneratorSettings"
- self.spec_cpCourseGeneratorSettings = self["spec_" .. specName]
+ self.spec_cpCourseGeneratorSettings = self["spec_" .. CpCourseGeneratorSettings.SPEC_NAME]
local spec = self.spec_cpCourseGeneratorSettings
spec.gui = g_currentMission.inGameMenu.pageAI
--- Clones the generic settings to create different settings containers for each vehicle.
diff --git a/scripts/specializations/CpCourseManager.lua b/scripts/specializations/CpCourseManager.lua
index 25ede6744..1a1ef23bf 100644
--- a/scripts/specializations/CpCourseManager.lua
+++ b/scripts/specializations/CpCourseManager.lua
@@ -5,8 +5,9 @@
CpCourseManager = {}
CpCourseManager.MOD_NAME = g_currentModName
-
-CpCourseManager.KEY = "."..CpCourseManager.MOD_NAME..".cpCourseManager"
+CpCourseManager.NAME = ".cpCourseManager"
+CpCourseManager.SPEC_NAME = CpCourseManager.MOD_NAME .. CpCourseManager.NAME
+CpCourseManager.KEY = "." .. CpCourseManager.MOD_NAME .. CpCourseManager.NAME
CpCourseManager.xmlKey = "Course"
CpCourseManager.rootKey = "AssignedCourses"
CpCourseManager.rootKeyFileManager = "Courses"
@@ -43,8 +44,14 @@ function CpCourseManager.initSpecialization()
schema:register(XMLValueType.INT, key .. "#assignedCoursesID", "Assigned Courses id.")
end
+function CpCourseManager.register(typeManager,typeName,specializations)
+ if CpCourseManager.prerequisitesPresent(specializations) then
+ typeManager:addSpecialization(typeName, CpCourseManager.SPEC_NAME)
+ end
+end
+
function CpCourseManager.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(AIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpCourseManager.registerEventListeners(vehicleType)
@@ -113,8 +120,7 @@ end
function CpCourseManager:onLoad(savegame)
--- Register the spec: spec_cpCourseManager
- local specName = CpCourseManager.MOD_NAME .. ".cpCourseManager"
- self.spec_cpCourseManager = self["spec_" .. specName]
+ self.spec_cpCourseManager = self["spec_" .. CpCourseManager.SPEC_NAME]
local spec = self.spec_cpCourseManager
spec.coursePlot = CoursePlot(g_currentMission.inGameMenu.ingameMap)
diff --git a/scripts/specializations/CpGamePadHud.lua b/scripts/specializations/CpGamePadHud.lua
index 1afa7e2ec..c9fdc8994 100644
--- a/scripts/specializations/CpGamePadHud.lua
+++ b/scripts/specializations/CpGamePadHud.lua
@@ -44,7 +44,7 @@ function CpGamePadHud.initSpecialization()
end
function CpGamePadHud.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(AIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpGamePadHud.register(typeManager, typeName, specializations)
@@ -194,11 +194,13 @@ function CpGamePadHud:actionEventOpenCloseDisplay()
page = CpGamePadHud.UNLOADER_PAGE
elseif self:getCanStartCpBaleFinder() then
page = CpGamePadHud.BALE_LOADER_PAGE
- elseif self:getCanStartCpBunkerSiloWorker() and self:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_BUNKER_SILO
- or AIUtil.hasChildVehicleWithSpecialization(self, Leveler) then
- page = CpGamePadHud.BUNKER_SILO_PAGE
- elseif self:getCanStartCpSiloLoaderWorker() then
+ elseif self:getCanStartCpSiloLoaderWorker() and (self:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_SILO_LOADING or
+ AIUtil.hasChildVehicleWithSpecialization(self, ConveyorBelt)) then
page = CpGamePadHud.SILO_LOADER_PAGE
+ elseif self:getCanStartCpBunkerSiloWorker() and (self:getCpStartingPointSetting():getValue() == CpJobParameters.START_AT_BUNKER_SILO
+ or (AIUtil.hasChildVehicleWithSpecialization(self, Leveler)
+ and not AIUtil.hasChildVehicleWithSpecialization(self, Shovel))) then
+ page = CpGamePadHud.BUNKER_SILO_PAGE
else
page = CpGamePadHud.FIELDWORK_PAGE
end
diff --git a/scripts/specializations/CpHud.lua b/scripts/specializations/CpHud.lua
index fbef606ef..23f7b63df 100644
--- a/scripts/specializations/CpHud.lua
+++ b/scripts/specializations/CpHud.lua
@@ -16,7 +16,7 @@ function CpHud.initSpecialization()
end
function CpHud.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(CpAIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpHud.register(typeManager,typeName,specializations)
diff --git a/scripts/specializations/CpInfoTexts.lua b/scripts/specializations/CpInfoTexts.lua
index 9aaf790e9..8b78daecc 100644
--- a/scripts/specializations/CpInfoTexts.lua
+++ b/scripts/specializations/CpInfoTexts.lua
@@ -19,7 +19,7 @@ end
function CpInfoTexts.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(CpAIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpInfoTexts.registerEventListeners(vehicleType)
diff --git a/scripts/specializations/CpShovelPositions.lua b/scripts/specializations/CpShovelPositions.lua
new file mode 100644
index 000000000..0ac058084
--- /dev/null
+++ b/scripts/specializations/CpShovelPositions.lua
@@ -0,0 +1,713 @@
+--[[
+ This specialization is used to control the shovel position into 4 stages:
+ - Loading position 0.2m above the ground.
+ - Transport position
+ - Pre unloading position
+ - Unloading position
+
+ TODO:
+ - Fine tuning
+ - Testing from different front loaders ...
+ - Add Telescopic handlers support.
+]]--
+
+---@class CpShovelPositions
+CpShovelPositions = {
+ DEACTIVATED = 0,
+ LOADING = 1,
+ TRANSPORT = 2,
+ PRE_UNLOAD = 3,
+ UNLOADING = 4,
+ NUM_STATES = 4,
+ LOADING_POSITION = {
+ ARM_LIMITS = {
+ 0,
+ 0.1
+ },
+ SHOVEL_LIMITS = {
+ 88,
+ 92
+ },
+ },
+ TRANSPORT_POSITION = {
+ ARM_LIMITS = {
+ 0.1,
+ 0.20
+ },
+ SHOVEL_LIMITS = {
+ 53,
+ 57
+ },
+ },
+ PRE_UNLOAD_POSITION = {
+ ARM_LIMITS = {
+ 2.7,
+ 2.8
+ },
+ SHOVEL_LIMITS = {
+ 43,
+ 47
+ },
+ },
+ DEBUG = true
+}
+CpShovelPositions.MOD_NAME = g_currentModName
+CpShovelPositions.NAME = ".cpShovelPositions"
+CpShovelPositions.SPEC_NAME = CpShovelPositions.MOD_NAME .. CpShovelPositions.NAME
+CpShovelPositions.KEY = "." .. CpShovelPositions.SPEC_NAME
+
+function CpShovelPositions.initSpecialization()
+ local schema = Vehicle.xmlSchemaSavegame
+ CpShovelPositions.initConsoleCommands()
+end
+
+function CpShovelPositions.prerequisitesPresent(specializations)
+ return SpecializationUtil.hasSpecialization(Shovel, specializations) and not
+ SpecializationUtil.hasSpecialization(Trailer, specializations) and not
+ SpecializationUtil.hasSpecialization(ConveyorBelt, specializations)
+end
+
+function CpShovelPositions.register(typeManager, typeName, specializations)
+ if CpShovelPositions.prerequisitesPresent(specializations) then
+ typeManager:addSpecialization(typeName, CpShovelPositions.SPEC_NAME)
+ end
+end
+
+function CpShovelPositions.registerEventListeners(vehicleType)
+ SpecializationUtil.registerEventListener(vehicleType, "onLoad", CpShovelPositions)
+ SpecializationUtil.registerEventListener(vehicleType, "onDraw", CpShovelPositions)
+ SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", CpShovelPositions)
+ SpecializationUtil.registerEventListener(vehicleType, "onPostAttach", CpShovelPositions)
+end
+
+function CpShovelPositions.registerFunctions(vehicleType)
+ SpecializationUtil.registerFunction(vehicleType, "cpSetShovelState", CpShovelPositions.cpSetShovelState)
+ SpecializationUtil.registerFunction(vehicleType, "cpSetTemporaryShovelState", CpShovelPositions.cpSetTemporaryShovelState)
+ SpecializationUtil.registerFunction(vehicleType, "cpSetTemporaryLoadingShovelState", CpShovelPositions.cpSetTemporaryLoadingShovelState)
+ SpecializationUtil.registerFunction(vehicleType, "cpResetShovelState", CpShovelPositions.cpResetShovelState)
+ SpecializationUtil.registerFunction(vehicleType, "cpSetupShovelPositions", CpShovelPositions.cpSetupShovelPositions)
+ SpecializationUtil.registerFunction(vehicleType, "areCpShovelPositionsDirty", CpShovelPositions.areCpShovelPositionsDirty)
+ SpecializationUtil.registerFunction(vehicleType, "setCpShovelMinimalUnloadHeight", CpShovelPositions.setCpShovelMinimalUnloadHeight)
+end
+
+--------------------------------------------
+--- Event Listener
+--------------------------------------------
+
+function CpShovelPositions:onLoad(savegame)
+ --- Register the spec: spec_ShovelPositions
+ self.spec_cpShovelPositions = self["spec_" .. CpShovelPositions.SPEC_NAME]
+ local spec = self.spec_cpShovelPositions
+ --- Current shovel state.
+ spec.state = CpShovelPositions.DEACTIVATED
+ spec.isDirty = false
+ spec.minimalShovelUnloadHeight = 4
+ spec.highDumpMovingToolIx = g_vehicleConfigurations:get(self, "shovelMovingToolIx")
+ spec.isHighDumpShovel = spec.highDumpMovingToolIx ~= nil
+end
+
+function CpShovelPositions:onPostAttach()
+ if self.spec_cpShovelPositions then
+ CpShovelPositions.cpSetupShovelPositions(self)
+ end
+end
+
+function CpShovelPositions:onDraw()
+
+end
+
+function CpShovelPositions:onUpdateTick(dt)
+ local spec = self.spec_cpShovelPositions
+ if spec.shovelToolIx == nil or spec.armToolIx == nil or self.rootVehicle == nil then
+ return
+ end
+ if spec.state == CpShovelPositions.LOADING then
+ CpUtil.try(CpShovelPositions.updateLoadingPosition, self, dt)
+ elseif spec.state == CpShovelPositions.TRANSPORT then
+ CpUtil.try(CpShovelPositions.updateTransportPosition, self, dt)
+ elseif spec.state == CpShovelPositions.PRE_UNLOAD then
+ CpUtil.try(CpShovelPositions.updatePreUnloadPosition, self, dt)
+ elseif spec.state == CpShovelPositions.UNLOADING then
+ CpUtil.try(CpShovelPositions.updateUnloadingPosition, self, dt)
+ end
+ if spec.resetStateWhenReached and not self:areCpShovelPositionsDirty() then
+ self:cpSetShovelState(CpShovelPositions.DEACTIVATED)
+ spec.resetStateWhenReached = false
+ end
+end
+
+--- Changes the current shovel state position.
+function CpShovelPositions:cpSetShovelState(state)
+ if not self.isServer then return end
+ local spec = self.spec_cpShovelPositions
+ spec.resetWhenReached = false
+ if spec.state ~= state then
+ spec.state = state
+ if state == CpShovelPositions.DEACTIVATED then
+ ImplementUtil.stopMovingTool(spec.armVehicle, spec.armTool)
+ ImplementUtil.stopMovingTool(spec.shovelVehicle, spec.shovelTool)
+ end
+ end
+end
+
+function CpShovelPositions:cpSetTemporaryShovelState(state)
+ if not self.isServer then return end
+ local spec = self.spec_cpShovelPositions
+ self:cpSetShovelState(state)
+ spec.resetStateWhenReached = true
+end
+
+function CpShovelPositions:cpSetTemporaryLoadingShovelState()
+ self:cpSetTemporaryShovelState(CpShovelPositions.LOADING)
+end
+
+--- Deactivates the shovel position control.
+function CpShovelPositions:cpResetShovelState()
+ if not self.isServer then return end
+ CpShovelPositions.debug(self, "Reset shovelPositionState.")
+ local spec = self.spec_cpShovelPositions
+ spec.state = CpShovelPositions.DEACTIVATED
+ ImplementUtil.stopMovingTool(spec.armVehicle, spec.armTool)
+ ImplementUtil.stopMovingTool(spec.shovelVehicle, spec.shovelTool)
+end
+
+--- Is the current target shovel position not yet reached?
+function CpShovelPositions:areCpShovelPositionsDirty()
+ local spec = self.spec_cpShovelPositions
+ return spec.isDirty
+end
+
+--- Sets the relevant moving tools.
+function CpShovelPositions:cpSetupShovelPositions()
+ local spec = self.spec_cpShovelPositions
+ spec.shovelToolIx = nil
+ spec.armToolIx = nil
+ spec.shovelTool = nil
+ spec.armTool = nil
+ local rootVehicle = self:getRootVehicle()
+ local childVehicles = rootVehicle:getChildVehicles()
+ for _, vehicle in ipairs(childVehicles) do
+ if vehicle.spec_cylindered then
+ for i, tool in pairs(vehicle.spec_cylindered.movingTools) do
+ if tool.controlGroupIndex ~= nil then
+ if tool.axis == "AXIS_FRONTLOADER_ARM" then
+ spec.armToolIx = i
+ spec.armTool = tool
+ spec.armVehicle = vehicle
+ spec.armProjectionNode = CpUtil.createNode("CpShovelArmProjectionNode",
+ 0, 0, 0, vehicle.rootNode)
+ spec.armToolRefNode = CpUtil.createNode("CpShovelArmToolRefNode",
+ 0, 0, 0, vehicle.rootNode)
+ elseif tool.axis == "AXIS_FRONTLOADER_TOOL" then
+ spec.shovelToolIx = i
+ spec.shovelTool = tool
+ spec.shovelVehicle = vehicle
+ elseif tool.axis == "AXIS_FRONTLOADER_ARM2" then
+ --- WIP for telescope loaders
+ spec.armExtendToolIx = i
+ spec.armExtendTool = tool
+ spec.armExtendVehicle = vehicle
+ end
+ end
+ end
+ end
+ end
+end
+
+--- Controls the shovel rotation.
+--- Also opens/closes the shovel if needed.
+---@param dt number
+---@param targetAngle number
+---@return boolean isDirty
+function CpShovelPositions:controlShovelPosition(dt, targetAngle)
+ local spec = self.spec_cpShovelPositions
+ local shovelData = ImplementUtil.getShovelNode(self)
+ local isDirty = false
+ if shovelData.movingToolActivation and not spec.isHighDumpShovel then
+ --- The shovel has a moving tool for grabbing.
+ --- So it needs to be opened for loading and unloading.
+ for i, tool in pairs(self.spec_cylindered.movingTools) do
+ if tool.axis and tool.rotMax ~= nil and tool.rotMin ~= nil then
+ if spec.state == CpShovelPositions.LOADING or spec.state == CpShovelPositions.UNLOADING then
+ --- Opens the shovel for loading and unloading
+ isDirty = ImplementUtil.moveMovingToolToRotation(self, tool, dt,
+ tool.invertAxis and tool.rotMin or tool.rotMax)
+ else
+ --- Closes the shovel after loading or unloading
+ isDirty = ImplementUtil.moveMovingToolToRotation(self, tool, dt,
+ tool.invertAxis and tool.rotMax or tool.rotMin)
+ end
+ break
+ end
+ end
+ end
+ 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)
+ return ImplementUtil.moveMovingToolToRotation(spec.shovelVehicle,
+ spec.shovelTool, dt, goalAngle) or isDirty
+end
+
+--- Performs the unloading with a high dump shovel.
+--- This kind of a shovel has a moving tool for unloading.
+---@param dt number
+---@return boolean isDirty
+---@return number angle
+---@return number targetAngle
+function CpShovelPositions:unfoldHighDumpShovel(dt)
+ local highDumpShovelTool = self.spec_cylindered.movingTools[self.spec_cpShovelPositions.highDumpMovingToolIx]
+ local _, dy, _ = localDirectionToWorld(getParent(highDumpShovelTool.node), 0, 0, 1)
+ local angle = math.acos(dy) or 0
+ local targetAngle = math.pi/2 - math.pi/6
+ if CpShovelPositions.controlShovelPosition(self, dt, targetAngle - angle) then
+ return true, angle, targetAngle
+ end
+ local isDirty = ImplementUtil.moveMovingToolToRotation(self, highDumpShovelTool, dt,
+ highDumpShovelTool.invertAxis and highDumpShovelTool.rotMin or highDumpShovelTool.rotMax)
+ return isDirty, angle, targetAngle
+end
+
+--- Folds the high dump shovel back to the normal position.
+---@param dt number
+---@return boolean isDirty
+function CpShovelPositions:foldHighDumpShovel(dt)
+ local highDumpShovelTool = self.spec_cylindered.movingTools[self.spec_cpShovelPositions.highDumpMovingToolIx]
+ return ImplementUtil.moveMovingToolToRotation(self, highDumpShovelTool, dt,
+ highDumpShovelTool.invertAxis and highDumpShovelTool.rotMax or highDumpShovelTool.rotMin)
+end
+
+--- Sets the current shovel position values, like the arm and shovel rotations.
+---@param dt number
+---@param shovelLimits table
+---@param armLimits table
+---@param heightOffset number|nil
+---@return boolean
+function CpShovelPositions:setShovelPosition(dt, shovelLimits, armLimits, heightOffset)
+ heightOffset = heightOffset or 0
+ local spec = self.spec_cpShovelPositions
+ local min, max = unpack(shovelLimits)
+ --- Target angle of the shovel node, which is at the end of the shovel.
+ local targetAngle = math.rad(min) + math.rad(max - min)/2
+ min, max = unpack(armLimits)
+ --- Target height of the arm.
+ --- This is relative to the attacher joint of the shovel.
+ local targetHeight = min + (max - min)/2
+ local shovelTool = spec.shovelTool
+ local armTool = spec.armTool
+ local shovelVehicle = spec.shovelVehicle
+ local armVehicle = spec.armVehicle
+ local minimalTargetHeight = spec.minimalShovelUnloadHeight
+ local curRot = {}
+ curRot[1], curRot[2], curRot[3] = getRotation(shovelTool.node)
+ local oldShovelRot = curRot[shovelTool.rotationAxis]
+
+ curRot = {}
+ curRot[1], curRot[2], curRot[3] = getRotation(armTool.node)
+ local oldArmRot = curRot[armTool.rotationAxis]
+
+ local armProjectionNode = spec.armProjectionNode
+ local armToolRefNode = spec.armToolRefNode
+
+ local radiusArmToolToShovelTool = calcDistanceFrom(shovelTool.node, armTool.node)
+
+ local attacherJointNode = self.spec_attachable.attacherJoint.node
+ local angle, shovelNode = CpShovelPositions.getShovelData(self)
+ --local _, shovelY, _ = localToLocal(self.rootNode, attacherJointNode, 0, 0, 0)
+ local _, shovelY, _ = localToLocal(armVehicle.rootNode, shovelVehicle.rootNode, 0, 0, 0)
+
+ --- All values will be calculated in the coordinate system from the vehicle root node.
+
+ local _, ty, tz = localToLocal(getChildAt(armTool.node, 0), armVehicle.rootNode, 0, 0, 0)
+ local ax, ay, az = localToLocal(armTool.node, armVehicle.rootNode, 0, 0, 0)
+ local wx, _, wz = getWorldTranslation(armVehicle.rootNode)
+
+ local by = shovelY
+ if self.spec_foliageBending and self.spec_foliageBending.bendingNodes[1] then
+ --- The foliage bending area can be used to get relatively exact dimensions of the shovel.
+ local bending = self.spec_foliageBending.bendingNodes[1]
+ if bending.id ~= nil and bending.node ~= nil then
+ --- Trying to find the lowest point of shovel.
+ --- This might be front tip or near the attacher.
+ local sx, _, sz = localToWorld(shovelTool.node, 0, 0, 0)
+ local bx1, by1, bz1 = localToWorld(bending.node, 0, 0, bending.minZ)
+ local bx2, by2, bz2 = localToWorld(bending.node, 0, 0, bending.maxZ)
+ DebugUtil.drawDebugLine(bx1, by1, bz1, bx2, by2, bz2, 0, 0, 1)
+ if by1 < by2 then
+ _, by, _ = worldToLocal(shovelTool.node, sx, by1, sz)
+ DebugUtil.drawDebugLine(sx, by1, sz, sx, by1 - by, sz, 0, 0, 1)
+ else
+ _, by, _ = worldToLocal(shovelTool.node, sx, by2, sz)
+ DebugUtil.drawDebugLine(sx, by2, sz, sx, by2 - by, sz, 0, 0, 1)
+ end
+ end
+ else
+ local bx1, by1, bz1 = localToWorld(self.rootNode, 0, 0, self.size.lengthOffset + self.size.length/2)
+ local bx2, by2, bz2 = localToWorld(self.rootNode, 0, 0, self.size.lengthOffset - self.size.length/2)
+ DebugUtil.drawDebugLine(bx1, by1, bz1, bx2, by2, bz2, 0, 0, 1)
+ local sx, _, sz = localToWorld(shovelTool.node, 0, 0, 0)
+ if by1 < by2 then
+ _, by, _ = worldToLocal(shovelTool.node, sx, by1, sz)
+ else
+ _, by, _ = worldToLocal(shovelTool.node, sx, by2, sz)
+ end
+ end
+ local deltaY = 0
+ if spec.state == CpShovelPositions.PRE_UNLOAD or spec.state == CpShovelPositions.UNLOADING then
+ targetHeight = minimalTargetHeight
+ end
+ local sx, sy, sz = 0, -by + targetHeight + heightOffset, 0
+ local ex, ey, ez = 0, sy, 20
+ local wsx, wsy, wsz = localToWorld(armVehicle.rootNode, sx, sy, sz)
+ local wex, wey, wez = localToWorld(armVehicle.rootNode, ex, ey, ez)
+ local _, terrainHeight, _ = getWorldTranslation(self.rootVehicle.rootNode, 0, 0, 0)
+
+ local yMax = ay + radiusArmToolToShovelTool
+ local yMin = ay - radiusArmToolToShovelTool
+ if sy > yMax then
+ --- Makes sure the target height is still reachable
+ sy = yMax - 0.01
+ ey = yMax - 0.01
+ end
+ if sy < yMin then
+ --- Makes sure the target height is still reachable
+ sy = yMin + 0.01
+ ey = yMin + 0.01
+ end
+ local hasIntersection, i1z, i1y, i2z, i2y = MathUtil.getCircleLineIntersection(
+ az, ay, radiusArmToolToShovelTool,
+ sz, sy, ez, ey)
+
+ local isDirty, skipArm
+ if spec.state == CpShovelPositions.UNLOADING or spec.state == CpShovelPositions.PRE_UNLOAD then
+ if spec.isHighDumpShovel then
+ if spec.state == CpShovelPositions.UNLOADING then
+ isDirty, angle, targetAngle = CpShovelPositions.unfoldHighDumpShovel(self, dt)
+ end
+ else
+ isDirty = CpShovelPositions.controlShovelPosition(self, dt, targetAngle - angle)
+ end
+ if spec.state == CpShovelPositions.UNLOADING and isDirty then
+ skipArm = true
+ end
+ end
+ if spec.state ~= CpShovelPositions.UNLOADING then
+ if spec.isHighDumpShovel then
+ isDirty = CpShovelPositions.foldHighDumpShovel(self, dt) or isDirty
+ if isDirty then
+ skipArm = true
+ end
+ end
+ end
+
+ local alpha, oldRotRelativeArmRot = 0, 0
+ if hasIntersection then
+ --- Controls the arm height
+ setTranslation(armProjectionNode, 0, i1y, i1z)
+ setTranslation(armToolRefNode, ax, ay, az)
+ local _, shy, shz = localToLocal(shovelTool.node, armVehicle.rootNode, 0, 0, 0)
+ local dirZ, dirY = MathUtil.vector2Normalize(shz - az, shy - ay)
+ oldRotRelativeArmRot = MathUtil.getYRotationFromDirection(-dirZ, dirY) + math.pi/2
+
+ alpha = math.atan2(i1y - ay, i1z - az)
+ local beta = -math.atan2(i2y - ay, i2z - az)
+ local a = MathUtil.clamp(oldArmRot - MathUtil.getAngleDifference(
+ alpha, oldRotRelativeArmRot), armTool.rotMin, armTool.rotMax)
+ if not skipArm then
+ isDirty = ImplementUtil.moveMovingToolToRotation(
+ armVehicle, armTool, dt, a) or isDirty
+ end
+ end
+
+ if spec.state ~= CpShovelPositions.UNLOADING then
+ isDirty = isDirty or CpShovelPositions.controlShovelPosition(self, dt, targetAngle - angle)
+ end
+
+ --- Debug information
+ if g_currentMission.controlledVehicle == 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,
+ wex, terrainHeight + minimalTargetHeight, wez, 0, 0, 1)
+ DebugUtil.drawDebugCircleAtNode(armVehicle.rootNode, radiusArmToolToShovelTool,
+ 30, nil, true, {ax, ay, az})
+ CpUtil.drawDebugNode(armVehicle.rootNode)
+ CpUtil.drawDebugNode(armTool.node)
+ CpUtil.drawDebugNode(shovelTool.node)
+ CpUtil.drawDebugNode(armProjectionNode)
+ CpUtil.drawDebugNode(armToolRefNode)
+
+ local debugData = {}
+ if hasIntersection then
+ table.insert(debugData, {
+ value = "",
+ name = "Arm Rotation:" })
+ table.insert(debugData, {
+ name = "alpha", value = math.deg(alpha) })
+ table.insert(debugData, {
+ name = "deltaAlphaOld", value = math.deg(MathUtil.getAngleDifference(alpha, oldArmRot)) })
+ table.insert(debugData, {
+ name = "old", value = math.deg(oldArmRot) })
+ table.insert(debugData, {
+ name = "deltaAlpha", value = math.deg(MathUtil.getAngleDifference(alpha, oldRotRelativeArmRot)) })
+ table.insert(debugData, {
+ name = "deltaY", value = deltaY})
+ table.insert(debugData, {
+ name = "shovelY", value = shovelY})
+ table.insert(debugData, {
+ name = "dirRot", value = math.deg(oldRotRelativeArmRot) })
+ table.insert(debugData, {
+ name = "distAlpha", value = MathUtil.vector2Length(i1z - tz, i1y - ty) })
+
+ table.insert(debugData, {
+ value = "",
+ name = "",
+ columnOffset = 0.12
+ })
+ end
+ table.insert(debugData, {
+ value = "",
+ name = "Shovel Rotation:" })
+ table.insert(debugData, {
+ name = "angle", value = math.deg(angle) })
+ table.insert(debugData, {
+ name = "deltaAngle", value = math.deg(targetAngle - angle) })
+ table.insert(debugData, {
+ name = "targetAngle", value = math.deg(targetAngle) })
+ table.insert(debugData, {
+ value = "",
+ name = "Diff:" })
+ table.insert(debugData, {
+ name = "unload height", value = minimalTargetHeight })
+
+ DebugUtil.renderTable(0.4, 0.4, 0.018,
+ debugData, 0)
+
+ if self.spec_foliageBending ~= nil then
+ local offset = 0.25
+
+ for _, bendingNode in ipairs(self.spec_foliageBending.bendingNodes) do
+ if bendingNode.id ~= nil then
+ DebugUtil.drawDebugRectangle(bendingNode.node, bendingNode.minX, bendingNode.maxX, bendingNode.minZ, bendingNode.maxZ, bendingNode.yOffset, 1, 0, 0)
+ DebugUtil.drawDebugRectangle(bendingNode.node, bendingNode.minX - offset, bendingNode.maxX + offset, bendingNode.minZ - offset, bendingNode.maxZ + offset, bendingNode.yOffset, 0, 1, 0)
+ DebugUtil.drawDebugNode(bendingNode.node, "Bending node")
+ end
+ end
+ end
+
+
+ end
+ return isDirty
+end
+
+function CpShovelPositions:updateLoadingPosition(dt)
+ local spec = self.spec_cpShovelPositions
+ local angle = CpShovelPositions.getShovelData(self)
+ local heightOffset = self.rootVehicle.getCpSettings and self.rootVehicle:getCpSettings().loadingShovelHeightOffset:getValue()
+ local isDirty
+ if angle then
+ isDirty = CpShovelPositions.setShovelPosition(self, dt,
+ CpShovelPositions.LOADING_POSITION.SHOVEL_LIMITS,
+ CpShovelPositions.LOADING_POSITION.ARM_LIMITS, heightOffset)
+ end
+ spec.isDirty = isDirty
+end
+
+function CpShovelPositions:updateTransportPosition(dt)
+ local spec = self.spec_cpShovelPositions
+ local angle = CpShovelPositions.getShovelData(self)
+ local heightOffset = self.rootVehicle.getCpSettings and self.rootVehicle:getCpSettings().loadingShovelHeightOffset:getValue()
+ local isDirty
+ if angle then
+ isDirty = CpShovelPositions.setShovelPosition(self, dt,
+ CpShovelPositions.TRANSPORT_POSITION.SHOVEL_LIMITS,
+ CpShovelPositions.TRANSPORT_POSITION.ARM_LIMITS, heightOffset)
+ end
+ spec.isDirty = isDirty
+end
+
+function CpShovelPositions:updatePreUnloadPosition(dt)
+ local spec = self.spec_cpShovelPositions
+ local angle = CpShovelPositions.getShovelData(self)
+ local isDirty
+ if angle then
+ isDirty = CpShovelPositions.setShovelPosition(self, dt,
+ CpShovelPositions.PRE_UNLOAD_POSITION.SHOVEL_LIMITS,
+ CpShovelPositions.PRE_UNLOAD_POSITION.ARM_LIMITS, nil)
+ end
+ spec.isDirty = isDirty
+end
+
+function CpShovelPositions:updateUnloadingPosition(dt)
+ local spec = self.spec_cpShovelPositions
+ local angle, _, maxAngle = CpShovelPositions.getShovelData(self)
+ local isDirty
+ if angle and maxAngle then
+ isDirty = CpShovelPositions.setShovelPosition(self, dt,
+ {math.deg(maxAngle), math.deg(maxAngle) + 2},
+ CpShovelPositions.PRE_UNLOAD_POSITION.ARM_LIMITS, nil)
+ end
+ spec.isDirty = isDirty
+end
+
+--- Sets the unload height target of the lowest part of the shovel.
+---@param height number
+function CpShovelPositions:setCpShovelMinimalUnloadHeight(height)
+ local spec = self.spec_cpShovelPositions
+ spec.minimalShovelUnloadHeight = height
+end
+
+--- Gets all relevant shovel data.
+function CpShovelPositions:getShovelData()
+ local shovelSpec = self.spec_shovel
+ local info = shovelSpec.shovelDischargeInfo
+ if info == nil or info.node == nil then
+ CpShovelPositions.debug(self, "Info or node not found!")
+ return
+ end
+ if info.maxSpeedAngle == nil or info.minSpeedAngle == nil then
+ CpShovelPositions.debug(self, "maxSpeedAngle or minSpeedAngle not found!")
+ return
+ end
+ if shovelSpec.shovelNodes[1] == nil then
+ CpShovelPositions.debug(self, "Shovel nodes index 0 not found!")
+ return
+ end
+ local _, dy, _ = localDirectionToWorld(info.node, 0, 0, 1)
+ local angle = math.acos(dy)
+ local factor = math.max(0, math.min(1, (angle - info.minSpeedAngle) / (info.maxSpeedAngle - info.minSpeedAngle)))
+ return angle, shovelSpec.shovelNodes[1].node, info.maxSpeedAngle, info.minSpeedAngle, factor
+end
+
+function CpShovelPositions.debug(implement, ...)
+ if CpShovelPositions.DEBUG then
+ CpUtil.debugImplement(CpDebug.DBG_SILO, implement, ...)
+ end
+end
+
+
+--------------------------------------------
+--- Console Commands
+--------------------------------------------
+
+function CpShovelPositions.initConsoleCommands()
+ g_devHelper.consoleCommands:registerConsoleCommand("cpShovelPositionsPrintShovelDebug",
+ "Prints debug information for the shovel",
+ "consoleCommandPrintShovelDebug", CpShovelPositions)
+ g_devHelper.consoleCommands:registerConsoleCommand("cpShovelPositionsSetState",
+ "Set's the current shovel state",
+ "consoleCommandSetShovelState", CpShovelPositions)
+ g_devHelper.consoleCommands:registerConsoleCommand("cpShovelPositionsSetArmLimit",
+ "Set's the arm max limit",
+ "consoleCommandSetPreUnloadArmLimit", CpShovelPositions)
+ g_devHelper.consoleCommands:registerConsoleCommand('cpShovelPositionsSetMinimalUnloadHeight',
+ 'Sets the minimal unload height to a fixed value',
+ 'consoleCommandSetMinimalUnloadHeight', CpShovelPositions)
+ g_devHelper.consoleCommands:registerConsoleCommand('cpShovelPositionsMeasureAndSetMinUnloadHeight',
+ 'Measures and sets the minimal unload height',
+ 'consoleCommandMeasureAndSetMinimalUnloadHeight', CpShovelPositions)
+end
+
+local function executeConsoleCommand(func, ...)
+ local vehicle = g_currentMission.controlledVehicle
+ if not vehicle then
+ CpUtil.info("Not entered a valid vehicle!")
+ return false
+ end
+ -- if vehicle:getIsAIActive() then
+ -- CpUtil.infoVehicle(vehicle, "Error, AI is active!")
+ -- return false
+ -- end
+ local shovels, found = AIUtil.getAllChildVehiclesWithSpecialization(vehicle, Shovel)
+ if not found then
+ CpUtil.infoVehicle(vehicle, "No shovel implement found!")
+ return false
+ end
+ return func(shovels[1], ...)
+end
+
+function CpShovelPositions:consoleCommandSetShovelState(state)
+ return executeConsoleCommand(function(shovelImplement, state)
+ state = tonumber(state)
+ if state == nil or state < 0 or state > CpShovelPositions.NUM_STATES then
+ CpUtil.infoVehicle(shovelImplement, "No valid state(0 - %d) was given!", CpShovelPositions.NUM_STATES)
+ return false
+ end
+ shovelImplement:cpSetShovelState(state)
+ end, state)
+end
+
+function CpShovelPositions:consoleCommandSetPreUnloadArmLimit(min, max)
+ return executeConsoleCommand(function(shovelImplement, min, max)
+ min = tonumber(min)
+ max = tonumber(max)
+ if min == nil or max == nil then
+ CpUtil.infoVehicle(shovelImplement, "No valid limits given! min: %s, max: %s", tostring(min), tostring(max))
+ return false
+ end
+ CpShovelPositions.PRE_UNLOAD_POSITION.ARM_LIMITS = { min, max }
+ end, min, max)
+end
+
+function CpShovelPositions:consoleCommandPrintShovelDebug(cylinderedDepth)
+ return executeConsoleCommand(function(shovelImplement)
+ --- Position debug
+ CpUtil.infoImplement(shovelImplement, "-- Position debug --")
+ local spec = shovelImplement.spec_cpShovelPositions
+ if spec then
+ CpUtil.infoImplement(shovelImplement, " arm tool %s -> %s",
+ CpUtil.getName(spec.armVehicle), tostring(spec.armToolIx))
+ CpUtil.infoImplement(shovelImplement, " shovel tool %s -> %s",
+ CpUtil.getName(spec.shovelVehicle), tostring(spec.shovelToolIx))
+ local highDumpShovelIx = g_vehicleConfigurations:get(shovelImplement, "shovelMovingToolIx")
+ CpUtil.infoImplement(shovelImplement, " shovel high dump %s -> %s",
+ CpUtil.getName(shovelImplement), tostring(highDumpShovelIx))
+ end
+
+ CpUtil.infoImplement(shovelImplement, "-- Position debug --")
+ --- Shovel debug
+ local controller = ShovelController(shovelImplement.rootVehicle, shovelImplement, true)
+ controller:printShovelDebug()
+ controller:delete()
+ --- Cylindered debug here
+ CpUtil.infoImplement(shovelImplement, "-- Cylindered debug --")
+ cylinderedDepth = cylinderedDepth and tonumber(cylinderedDepth) or 0
+
+ local childVehicles = shovelImplement.rootVehicle:getChildVehicles()
+ for _, vehicle in ipairs(childVehicles) do
+ if vehicle.spec_cylindered then
+ for ix, tool in pairs(vehicle.spec_cylindered.movingTools) do
+ CpUtil.infoImplement(shovelImplement, " %s => ix: %d ",
+ CpUtil.getName(vehicle), ix)
+ if cylinderedDepth > 0 then
+ CpUtil.infoImplement(shovelImplement, " %s",
+ DebugUtil.debugTableToString(tool, " ", 0, cylinderedDepth))
+ end
+ CpUtil.infoImplement(shovelImplement, " %s => ix: %d finished",
+ CpUtil.getName(vehicle), ix)
+ end
+ end
+ end
+ CpUtil.infoImplement(shovelImplement, "-- Cylindered debug finished --")
+ end)
+end
+
+function CpShovelPositions:consoleCommandSetMinimalUnloadHeight(height)
+ return executeConsoleCommand(function(shovelImplement, height)
+ height = tonumber(height)
+ if height == nil then
+ CpUtil.infoVehicle(shovelImplement, "No valid height given! height: %s", tostring(height))
+ return false
+ end
+ local spec = shovelImplement.spec_cpShovelPositions
+ spec.minimalShovelUnloadHeight = height
+ end, height)
+end
+
+function CpShovelPositions:consoleCommandMeasureAndSetMinimalUnloadHeight()
+ return executeConsoleCommand(function(shovelImplement)
+ local controller = ShovelController(shovelImplement.rootVehicle, shovelImplement, true)
+ controller:calculateMinimalUnloadingHeight()
+ controller:delete()
+ end)
+end
\ No newline at end of file
diff --git a/scripts/specializations/CpVehicleSettings.lua b/scripts/specializations/CpVehicleSettings.lua
index 8ee13db07..7d2e397c8 100644
--- a/scripts/specializations/CpVehicleSettings.lua
+++ b/scripts/specializations/CpVehicleSettings.lua
@@ -6,9 +6,12 @@
CpVehicleSettings = {}
CpVehicleSettings.MOD_NAME = g_currentModName
-CpVehicleSettings.KEY = "."..CpVehicleSettings.MOD_NAME..".cpVehicleSettings"
CpVehicleSettings.SETTINGS_KEY = ".settings"
CpVehicleSettings.USER_KEY = ".users"
+CpVehicleSettings.NAME = ".cpVehicleSettings"
+CpVehicleSettings.SPEC_NAME = CpVehicleSettings.MOD_NAME .. CpVehicleSettings.NAME
+CpVehicleSettings.KEY = "." .. CpVehicleSettings.MOD_NAME .. CpVehicleSettings.NAME
+
function CpVehicleSettings.initSpecialization()
local schema = Vehicle.xmlSchemaSavegame
--- Old xml schema for settings
@@ -30,13 +33,19 @@ function CpVehicleSettings.initSpecialization()
CpVehicleSettings.registerConsoleCommands()
end
+function CpVehicleSettings.register(typeManager,typeName,specializations)
+ if CpVehicleSettings.prerequisitesPresent(specializations) then
+ typeManager:addSpecialization(typeName, CpVehicleSettings.SPEC_NAME)
+ end
+end
function CpVehicleSettings.prerequisitesPresent(specializations)
- return SpecializationUtil.hasSpecialization(AIFieldWorker, specializations)
+ return SpecializationUtil.hasSpecialization(CpAIWorker, specializations)
end
function CpVehicleSettings.registerEvents(vehicleType)
SpecializationUtil.registerEvent(vehicleType, 'onCpUserSettingChanged')
+ SpecializationUtil.registerEvent(vehicleType, 'onCpLoadingShovelOffsetSettingChanged')
end
@@ -44,13 +53,12 @@ function CpVehicleSettings.registerEventListeners(vehicleType)
-- SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onLoad", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onLoadFinished", CpVehicleSettings)
- SpecializationUtil.registerEventListener(vehicleType, "onPreDetachImplement", CpVehicleSettings)
- SpecializationUtil.registerEventListener(vehicleType, "onPostAttachImplement", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onCpUnitChanged", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onReadStream", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onStateChange", CpVehicleSettings)
SpecializationUtil.registerEventListener(vehicleType, "onCpUserSettingChanged", CpVehicleSettings)
+ SpecializationUtil.registerEventListener(vehicleType, 'onCpLoadingShovelOffsetSettingChanged', CpVehicleSettings)
end
function CpVehicleSettings.registerFunctions(vehicleType)
@@ -74,8 +82,7 @@ end
function CpVehicleSettings:onLoad(savegame)
--- Register the spec: spec_CpVehicleSettings
- local specName = CpVehicleSettings.MOD_NAME .. ".cpVehicleSettings"
- self.spec_cpVehicleSettings = self["spec_" .. specName]
+ self.spec_cpVehicleSettings = self["spec_" .. CpVehicleSettings.SPEC_NAME]
local spec = self.spec_cpVehicleSettings
--- Clones the generic settings to create different settings containers for each vehicle.
@@ -90,43 +97,11 @@ function CpVehicleSettings:onLoadFinished()
spec.wasLoaded = nil
end
---- TODO: These are only applied on a implement an not on a single vehicle.
---- This means self driving vehicle are not getting these vehicle configuration values.
-function CpVehicleSettings:onPostAttachImplement(object)
- --- Only apply these values, if were are not loading from a savegame.
- local spec = self.spec_cpVehicleSettings
- if spec.wasLoaded then
- return
- end
-
- CpVehicleSettings.setAutomaticWorkWidthAndOffset(self)
- CpVehicleSettings.setAutomaticBunkerSiloWorkWidth(self)
- CpVehicleSettings.setAutomaticBaleCollectorOffset(self)
-
- CpVehicleSettings.setFromVehicleConfiguration(self, object, spec.raiseImplementLate, 'raiseLate')
- CpVehicleSettings.setFromVehicleConfiguration(self, object, spec.lowerImplementEarly, 'lowerEarly')
- CpVehicleSettings.setFromVehicleConfiguration(self, object, spec.bunkerSiloWorkWidth, 'workingWidth')
- CpVehicleSettings.validateSettings(self)
-end
-
-function CpVehicleSettings:onPreDetachImplement(implement)
- --- Only apply these values, if were are not loading from a savegame.
- local spec = self.spec_cpVehicleSettings
- if spec.wasLoaded then
- return
- end
-
- CpVehicleSettings.setAutomaticWorkWidthAndOffset(self, implement.object)
- CpVehicleSettings.setAutomaticBunkerSiloWorkWidth(self, implement.object)
-
- CpVehicleSettings.resetToDefault(self, implement.object, spec.raiseImplementLate, 'raiseLate', false)
- CpVehicleSettings.resetToDefault(self, implement.object, spec.lowerImplementEarly, 'lowerEarly', false)
- CpVehicleSettings.validateSettings(self)
-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)
+ local spec = self.spec_cpVehicleSettings
if state == Vehicle.STATE_CHANGE_FILLTYPE_CHANGE and self:getIsSynchronized() then
local _, hasSprayer = AIUtil.getAllChildVehiclesWithSpecialization(self, Sprayer, nil)
if hasSprayer then
@@ -137,6 +112,29 @@ function CpVehicleSettings:onStateChange(state, data)
self:getCourseGeneratorSettings().workWidth:setFloatValue(width)
end
end
+ elseif state == Vehicle.STATE_CHANGE_ATTACH then
+ CpVehicleSettings.setAutomaticWorkWidthAndOffset(self)
+ CpVehicleSettings.setAutomaticBunkerSiloWorkWidth(self)
+ CpVehicleSettings.setAutomaticBaleCollectorOffset(self)
+ CpVehicleSettings.setFromVehicleConfiguration(self, data.attachedVehicle,
+ spec.raiseImplementLate, 'raiseLate')
+ CpVehicleSettings.setFromVehicleConfiguration(self, data.attachedVehicle,
+ spec.lowerImplementEarly, 'lowerEarly')
+ CpVehicleSettings.setFromVehicleConfiguration(self, data.attachedVehicle,
+ spec.bunkerSiloWorkWidth, 'workingWidth')
+ CpVehicleSettings.setFromVehicleConfiguration(self, data.attachedVehicle,
+ spec.loadingShovelHeightOffset, 'loadingShovelOffset')
+
+ CpVehicleSettings.validateSettings(self)
+ elseif state == Vehicle.STATE_CHANGE_DETACH then
+ CpVehicleSettings.setAutomaticWorkWidthAndOffset(self, data.attachedVehicle)
+ CpVehicleSettings.setAutomaticBunkerSiloWorkWidth(self, data.attachedVehicle)
+
+ CpVehicleSettings.resetToDefault(self, data.attachedVehicle, spec.raiseImplementLate,
+ 'raiseLate', false)
+ CpVehicleSettings.resetToDefault(self, data.attachedVehicle, spec.lowerImplementEarly,
+ 'lowerEarly', false)
+ CpVehicleSettings.validateSettings(self)
end
end
@@ -369,7 +367,7 @@ function CpVehicleSettings:areCourseSettingsVisible()
end
function CpVehicleSettings:areBunkerSiloSettingsVisible()
- return self:getCanStartCpBunkerSiloWorker()
+ return self:getCanStartCpBunkerSiloWorker() or self:getCanStartCpSiloLoaderWorker()
end
function CpVehicleSettings:areCombineUnloaderSettingsVisible()
@@ -394,6 +392,22 @@ function CpVehicleSettings:setAutomaticBaleCollectorOffset()
spec.baleCollectorOffset:setFloatValue(offset)
end
+function CpVehicleSettings:isLoadingShovelOffsetSettingVisible()
+ return self:getCanStartCpSiloLoaderWorker() and not AIUtil.hasChildVehicleWithSpecialization(self, ConveyorBelt)
+end
+
+function CpVehicleSettings:isLoadingShovelOffsetSettingDisabled()
+ return not AIUtil.isStopped(self)
+end
+
+function CpVehicleSettings:onCpLoadingShovelOffsetSettingChanged()
+ local shovels, found = AIUtil.getAllChildVehiclesWithSpecialization(self, Shovel)
+ if not found then
+ return false
+ end
+ shovels[1]:cpSetTemporaryLoadingShovelState()
+end
+
--- Saves the user value changed on the server.
function CpVehicleSettings:onCpUserSettingChanged(setting)
if not self.isServer then
diff --git a/scripts/trigger/TriggerManager.lua b/scripts/trigger/TriggerManager.lua
new file mode 100644
index 000000000..20386277f
--- /dev/null
+++ b/scripts/trigger/TriggerManager.lua
@@ -0,0 +1,167 @@
+--- Stores all the relevant giants triggers expect bunker silos.
+--- For now only unload triggers are supported.
+---@class TriggerManager
+TriggerManager = CpObject()
+TriggerManager.DEBUG = false
+function TriggerManager:init()
+ ---@type table
+ self.unloadTriggers = {}
+ ---@type table
+ self.dischargeableUnloadTriggers = {}
+end
+
+--- Adds an unload trigger.
+---@param silo table UnloadTrigger
+function TriggerManager:addUnloadingSilo(silo)
+ if silo.exactFillRootNode ~= nil then
+ self.unloadTriggers[silo.exactFillRootNode] = CpTrigger(silo, silo.exactFillRootNode)
+ if silo:getIsToolTypeAllowed(ToolType.DISCHARGEABLE) then
+ self.dischargeableUnloadTriggers[silo.exactFillRootNode] = self.unloadTriggers[silo.exactFillRootNode]
+ end
+ end
+end
+
+--- Removes the unload trigger, as it got removed for example sold.
+---@param silo table UnloadTrigger
+function TriggerManager:removeUnloadingSilo(silo)
+ if silo.exactFillRootNode ~= nil then
+ if self.unloadTriggers[silo.exactFillRootNode] then
+ self.unloadTriggers[silo.exactFillRootNode]:delete()
+ self.unloadTriggers[silo.exactFillRootNode] = nil
+ self.dischargeableUnloadTriggers[silo.exactFillRootNode] = nil
+ end
+ end
+end
+
+--- Gets the unload trigger from the exactFillRootNode.
+---@param node number exactFillRootNode
+---@return CpTrigger
+function TriggerManager:getUnloadTriggerForNode(node)
+ return self.unloadTriggers[node]
+end
+
+--- Gets the first trigger found in the defined area.
+---@param triggers table
+---@param x number
+---@param z number
+---@param dirX number
+---@param dirZ number
+---@param width number
+---@param length number
+---@return boolean
+---@return CpTrigger|nil
+---@return table|nil
+function TriggerManager:getTriggerAt(triggers, x, z, dirX, dirZ, width, length)
+ local angle = MathUtil.getYRotationFromDirection(dirX, dirZ)
+ local dirWX, dirWZ = MathUtil.getDirectionFromYRotation(angle + math.pi/2)
+ local sx, sz = x + dirWX * width/2, z + dirWZ * width/2
+
+ local area = {
+ {
+ x = sx,
+ z = sz
+ },
+ {
+ x = sx - dirWX * width,
+ z = sz - dirWZ * width
+ },
+ {
+ x = sx - dirWX * width + dirX * length,
+ z = sz - dirWZ * width + dirZ * length
+ },
+ {
+ x = sx + dirX * length,
+ z = sz + dirZ * length
+ },
+ {
+ x = sx,
+ z = sz
+ },
+ }
+ local dx, _, dz
+ for node, trigger in pairs(triggers) do
+ dx, _, dz = getWorldTranslation(node)
+ if CpMathUtil.isPointInPolygon(area, dx, dz) then
+ return true, trigger, trigger:getTarget()
+ end
+ end
+ return false
+end
+
+--- Gets the first unload trigger found in the defined area.
+---@param x number
+---@param z number
+---@param dirX number
+---@param dirZ number
+---@param width number
+---@param length number
+---@return boolean
+---@return CpTrigger|nil
+---@return table|nil
+function TriggerManager:getUnloadTriggerAt(x, z, dirX, dirZ, width, length)
+ return self:getTriggerAt(self.unloadTriggers, x, z, dirX, dirZ, width, length)
+end
+
+--- Gets the first dischargeable unload trigger found in the defined area.
+---@param x number
+---@param z number
+---@param dirX number
+---@param dirZ number
+---@param width number
+---@param length number
+---@return boolean found?
+---@return CpTrigger|nil unload trigger
+---@return table|nil unload station/placeable
+function TriggerManager:getDischargeableUnloadTriggerAt(x, z, dirX, dirZ, width, length)
+ return self:getTriggerAt(self.dischargeableUnloadTriggers, x, z, dirX, dirZ, width, length)
+end
+
+function TriggerManager:update(dt)
+
+end
+
+--- Draws all bunker silos onto the ai map.
+---@param map table map to draw to.
+---@param selected CpTrigger silo that gets highlighted.
+function TriggerManager:drawUnloadTriggers(map, selected)
+ for _, trigger in pairs(self.unloadTriggers) do
+ trigger:drawPlot(map, selected)
+ end
+end
+
+--- Draws all bunker silos onto the ai map.
+---@param map table map to draw to.
+---@param selected CpTrigger silo that gets highlighted.
+---@param fillTypes table|nil fill type that needs to be supported.
+function TriggerManager:drawDischargeableTriggers(map, selected, fillTypes)
+ for _, trigger in pairs(self.dischargeableUnloadTriggers) do
+ trigger:drawPlot(map, selected, fillTypes)
+ end
+end
+
+function TriggerManager:draw()
+ for node, trigger in pairs(self.unloadTriggers) do
+ if self.DEBUG then
+ local text = string.format("%s:\n %d", getName(node), node )
+ CpUtil.drawDebugNode(node, false, 2, text)
+ end
+ end
+end
+
+g_triggerManager = TriggerManager()
+
+local function addUnloadingSilo(silo, superFunc, ...)
+ local ret = superFunc(silo, ...)
+ g_triggerManager:addUnloadingSilo(silo)
+ return ret
+end
+
+UnloadTrigger.load = Utils.overwrittenFunction(UnloadTrigger.load, addUnloadingSilo)
+
+
+local function removeUnloadingSilo(silo, ...)
+ g_triggerManager:removeUnloadingSilo(silo)
+end
+
+UnloadTrigger.delete = Utils.prependedFunction(UnloadTrigger.delete, removeUnloadingSilo)
+
diff --git a/scripts/trigger/TriggerWrapper.lua b/scripts/trigger/TriggerWrapper.lua
new file mode 100644
index 000000000..549db2ab9
--- /dev/null
+++ b/scripts/trigger/TriggerWrapper.lua
@@ -0,0 +1,69 @@
+
+--- Wrapper for a trigger.
+---@class CpTrigger
+CpTrigger = CpObject()
+
+function CpTrigger:init(trigger, node)
+ self.trigger = trigger
+ self.node = node
+ self.plot = UnloadingTriggerPlot(self.node)
+end
+
+function CpTrigger:delete()
+
+end
+
+function CpTrigger:getNode()
+ return self.node
+end
+
+function CpTrigger:getTrigger()
+ return self.trigger
+end
+
+function CpTrigger:getTarget()
+ return self.trigger:getTarget()
+end
+
+---@param fillUnitIndex number
+---@return number
+function CpTrigger:getFillUnitExactFillRootNode(fillUnitIndex)
+ return self.trigger:getFillUnitExactFillRootNode(fillUnitIndex)
+end
+
+function CpTrigger:getFillUnitFreeCapacity(fillUnitIndex, fillTypeIndex, farmId)
+ return self.trigger:getFillUnitFreeCapacity(fillUnitIndex, fillTypeIndex, farmId)
+end
+
+--- Is the fill type allowed ?
+---@param fillType any
+---@return boolean
+function CpTrigger:getIsFillTypeAllowed(fillType)
+ return self.trigger:getIsFillTypeAllowed(fillType)
+end
+
+---@param map table
+---@param selectedTrigger CpTrigger
+---@param fillTypes table|nil
+function CpTrigger:drawPlot(map, selectedTrigger, fillTypes)
+ if fillTypes then
+ local found = false
+ for i, fillType in pairs(fillTypes) do
+ if self.trigger:getIsFillTypeAllowed(fillType) then
+ found = true
+ end
+ end
+ if not found then
+ return
+ end
+ end
+ self.plot:setHighlighted(self == selectedTrigger)
+ self.plot:draw(map)
+end
+
+--- Is the trigger part of the given object?
+function CpTrigger:isTheSameObject(otherObject)
+ if self.trigger:getTarget().owningPlaceable == otherObject then
+ return true
+ end
+end
diff --git a/translations/translation_br.xml b/translations/translation_br.xml
index 6f6efed29..af21ffaf4 100644
--- a/translations/translation_br.xml
+++ b/translations/translation_br.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -817,6 +823,16 @@ A carreta pode ser descarregado usando Courseplay
Para iniciar, o marcador de carregamento no menu AI precisa ser definido.
O marcador detecta montes ou silos e os destaca no mapa.
Agora é possível iniciar o auxiliar.
+"/>
+
+
+
+
diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml
index 4b52f01ad..b108f2dcc 100644
--- a/translations/translation_cs.xml
+++ b/translations/translation_cs.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -801,6 +807,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_ct.xml b/translations/translation_ct.xml
index f260f53a4..f9ecd704d 100644
--- a/translations/translation_ct.xml
+++ b/translations/translation_ct.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -800,6 +806,16 @@ Courseplay能夠在青貯筒倉中壓實或分佈糠秕,或在楔形筒倉中
要啟動AI選單上的裝載標記,需要進行設定。
這個標記可以檢測到堆或筒倉,並在地圖上突出顯示它們。
那麼現在可以啟用小幫手了。
+"/>
+
+
+
+
diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml
index 09674798e..2f9cefff1 100644
--- a/translations/translation_cz.xml
+++ b/translations/translation_cz.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -322,7 +328,7 @@
-
+
@@ -798,6 +804,16 @@ Nakladač může být vyložen pomocí CP.
Chcete-li spustit nakládací značku v nabídce AI, je třeba ji nastavit.
Značka detekuje hromady nebo silážní jámy a zvýrazňuje je na mapě.
Nyní je možné spustit pracovníka.
+"/>
+
+
+
+
diff --git a/translations/translation_da.xml b/translations/translation_da.xml
index ba4b5edca..9c8a1c52c 100644
--- a/translations/translation_da.xml
+++ b/translations/translation_da.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -811,6 +817,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_de.xml b/translations/translation_de.xml
index a901c8288..410e8860e 100644
--- a/translations/translation_de.xml
+++ b/translations/translation_de.xml
@@ -25,6 +25,7 @@
+
@@ -93,7 +94,10 @@
-
+
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -823,6 +829,41 @@ Bedenkt dabei, dass das automatische Abtanken mit CP in Fahrtrichtung des Laders
Als erstes muss auf der Helferkarte der Lade-Marker gesetzt werden.
Dieses erkennt vorhandende Haufen oder Silos und markiert den ausgewählten.
Anschließend kann der Helfer gestartet werden.
+"/>
+
+
+
+
diff --git a/translations/translation_ea.xml b/translations/translation_ea.xml
index 5e94095f8..4c57c90f5 100644
--- a/translations/translation_ea.xml
+++ b/translations/translation_ea.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_en.xml b/translations/translation_en.xml
index 17c62d658..82f77c139 100644
--- a/translations/translation_en.xml
+++ b/translations/translation_en.xml
@@ -25,6 +25,7 @@
+
@@ -93,7 +94,10 @@
-
+
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -823,6 +829,43 @@ Keep in mind that the automatic unloading with CP goes in the same direction as
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_es.xml b/translations/translation_es.xml
index d0e538305..839a9aab4 100644
--- a/translations/translation_es.xml
+++ b/translations/translation_es.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -809,6 +815,16 @@ El cargador se puede descargar con un descargador de Courseplay.
Para iniciar el marcador de carga, en el menú AI debe configurarse.
El marcador detecta montones o silos y los resalta en el mapa.
Ahora es posible iniciar el ayudante.
+"/>
+
+
+
+
diff --git a/translations/translation_fc.xml b/translations/translation_fc.xml
index 513372322..f05343ef0 100644
--- a/translations/translation_fc.xml
+++ b/translations/translation_fc.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_fi.xml b/translations/translation_fi.xml
index c1aab329b..ae2266a62 100644
--- a/translations/translation_fi.xml
+++ b/translations/translation_fi.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml
index 1dade8980..b571e8006 100644
--- a/translations/translation_fr.xml
+++ b/translations/translation_fr.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -789,6 +795,16 @@ La chargeuse peut être vidée par une tâche de déchargement Courseplay.
+
+
+
+
diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml
index ba963b5a2..13002836c 100644
--- a/translations/translation_hu.xml
+++ b/translations/translation_hu.xml
@@ -18,27 +18,28 @@
-
+
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
@@ -58,50 +59,52 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
-
-
+
+
@@ -111,9 +114,9 @@
-
-
-
+
+
+
@@ -142,8 +145,8 @@
-
-
+
+
@@ -158,12 +161,12 @@
-
-
+
+
-
-
+
+
@@ -176,8 +179,10 @@
-
-
+
+
+
+
@@ -189,8 +194,8 @@
-
-
+
+
@@ -203,8 +208,8 @@
-
-
+
+
@@ -300,12 +305,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -366,7 +371,7 @@ Az útvonal automatikusan mentésre kerül a szerkesztő bezárásakor és felü
-
+
@@ -413,13 +418,13 @@ Az útvonal automatikusan mentésre kerül a szerkesztő bezárásakor és felü
-
+
-
-
-
+
+
+
@@ -427,15 +432,15 @@ Az útvonal automatikusan mentésre kerül a szerkesztő bezárásakor és felü
-
+
-
-
+
+
@@ -807,6 +812,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_it.xml b/translations/translation_it.xml
index 037b2c050..10c9b2f18 100644
--- a/translations/translation_it.xml
+++ b/translations/translation_it.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -813,6 +819,16 @@ Il caricatore può essere scaricato con uno scaricatore Courseplay.
Per avviare l'indicatore di caricamento nel menu AI è necessario impostare.
Il marcatore rileva cumuli o silos e li evidenzia sulla mappa.
Ora è possibile avviare l'helper.
+"/>
+
+
+
+
diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml
index 0c6ca5560..a03ff17b0 100644
--- a/translations/translation_jp.xml
+++ b/translations/translation_jp.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -807,6 +813,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_kr.xml b/translations/translation_kr.xml
index 57c0a3b3b..c140ab75f 100644
--- a/translations/translation_kr.xml
+++ b/translations/translation_kr.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml
index 1ece0e1c3..336ebc710 100644
--- a/translations/translation_nl.xml
+++ b/translations/translation_nl.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -807,6 +813,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_no.xml b/translations/translation_no.xml
index adb13ff92..19491c6fe 100644
--- a/translations/translation_no.xml
+++ b/translations/translation_no.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml
index ed0172fc2..7e6a75d8d 100644
--- a/translations/translation_pl.xml
+++ b/translations/translation_pl.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -805,6 +811,16 @@ Załadunkowy możne zostać rozładowany poprzez rozładunkowego Courseplay.
Aby rozpocząć załadunek, punkt załadunkowy musi zostać ustawiony w menu SI.
Znacznik wykrywa pryzmę lub silos i podświetla to na mapie.
Terze możesz rozpocząć pracę pomocnika.
+"/>
+
+
+
+
diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml
index 8010bca72..d9be37c23 100644
--- a/translations/translation_pt.xml
+++ b/translations/translation_pt.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -802,6 +808,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_ro.xml b/translations/translation_ro.xml
index d7acebfbc..9dadefaa9 100644
--- a/translations/translation_ro.xml
+++ b/translations/translation_ro.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+
diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml
index f674767a3..cdcbb3c45 100644
--- a/translations/translation_ru.xml
+++ b/translations/translation_ru.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -810,6 +816,16 @@ Courseplay способен прессовать или распределять
Чтобы начать необходимо установить погрузочный маркер в меню ИИ.
Маркер обнаруживает кучи или бункеры и выделяет их на карте.
Теперь можно запускать помощника.
+"/>
+
+
+
+
diff --git a/translations/translation_sv.xml b/translations/translation_sv.xml
index f8f5e745b..800370f66 100644
--- a/translations/translation_sv.xml
+++ b/translations/translation_sv.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -805,6 +811,16 @@ Lastaren kan lossas med en Courseplay-avlastare.
För att starta laddningsmarkören på AI-menyn måste ställas in.
Markören upptäcker högar eller silos och markerar dem på kartan.
Nu är det möjligt att starta hjälparen.
+"/>
+
+
+
+
diff --git a/translations/translation_tr.xml b/translations/translation_tr.xml
index 9c0a59b6d..ea6aa0325 100644
--- a/translations/translation_tr.xml
+++ b/translations/translation_tr.xml
@@ -25,6 +25,7 @@
+
@@ -94,6 +95,9 @@
+
+
+
@@ -178,6 +182,8 @@
+
+
@@ -807,6 +813,16 @@ The loader can be unloaded with an Courseplay unloader.
To start the loading-marker on the AI menu needs to be set.
The marker detects heaps or silos and highlights them on the map.
Now it's possible to start the helper.
+"/>
+
+
+
+