From 7ae1ce88fed0d612538170678d504013335ebf3c Mon Sep 17 00:00:00 2001 From: schwiti6190 <58079399+schwiti6190@users.noreply.github.com> Date: Tue, 12 Oct 2021 21:10:08 +0200 Subject: [PATCH] Reworked old code and some MP fixes. - Moved all the old cp.workwidth to the Work width setting. - Moved cp.laneNumber to the multi tools setting. - Created a field edge path setting instead of cp.fieldEdge. - Removed old cp.laneOffset - Small MP toolOffsetX fix. - Fixes custom fields in MP. - Small work width fixe for mods. - Enables automatic work width calculation for shovels. --- AITurn.lua | 8 +- BalerAIDriver.lua | 3 +- BunkerSiloAIDriver.lua | 2 +- CombineAIDriver.lua | 19 +- CombineUnloadAIDriver.lua | 2 +- CombineUnloadManager.lua | 4 +- CpManager.lua | 1 + Events/CustomFieldEvent.lua | 110 ++++++++ Events/SettingEvent.lua | 199 +++++++++---- FieldworkAIDriver.lua | 89 +++--- FillableFieldworkAIDriver.lua | 7 +- GlobalSettings.lua | 9 +- PlowAIDriver.lua | 8 + ShieldAIDriver.lua | 2 +- base.lua | 120 +------- config/VehicleConfigurations.xml | 4 + course-generator/CourseGeneratorSettings.lua | 226 ++++++++++++++- course-generator/cp.lua | 2 - course_management.lua | 2 - courseplay.lua | 26 +- courseplay_event.lua | 60 +--- fields.lua | 91 ++++++ gui/CourseGeneratorScreen.lua | 4 +- hud.lua | 238 ++++++++-------- settings.lua | 279 +++---------------- start_stop.lua | 6 - toolManager.lua | 191 +++++++++++-- translations/translation_br.xml | 2 + translations/translation_cs.xml | 2 + translations/translation_cz.xml | 2 + translations/translation_de.xml | 2 + translations/translation_en.xml | 2 + translations/translation_es.xml | 2 + translations/translation_fr.xml | 2 + translations/translation_hu.xml | 2 + translations/translation_it.xml | 2 + translations/translation_jp.xml | 2 + translations/translation_nl.xml | 2 + translations/translation_pl.xml | 2 + translations/translation_pt.xml | 2 + translations/translation_ru.xml | 2 + translations/translation_sl.xml | 2 + turn.lua | 12 +- 43 files changed, 1029 insertions(+), 725 deletions(-) create mode 100644 Events/CustomFieldEvent.lua diff --git a/AITurn.lua b/AITurn.lua index 73587ecb7..4c19a71a1 100644 --- a/AITurn.lua +++ b/AITurn.lua @@ -133,7 +133,8 @@ end ---@return boolean, number True if there's enough space to make a forward turn on the field. Also return the ---distance to reverse in order to be able to just make the turn on the field function AITurn.canTurnOnField(turnContext, vehicle) - local spaceNeededOnFieldForTurn = AIDriverUtil.getTurningRadius(vehicle) + vehicle.cp.workWidth / 2 + local workWidth = vehicle.cp.courseGeneratorSettings.workWidth:get() + local spaceNeededOnFieldForTurn = AIDriverUtil.getTurningRadius(vehicle) + workWidth / 2 local distanceToFieldEdge = turnContext:getDistanceToFieldEdge(turnContext.vehicleAtTurnStartNode) courseplay.debugVehicle(AITurn.debugChannel, vehicle, 'Space needed to turn on field %.1f m', spaceNeededOnFieldForTurn) if distanceToFieldEdge then @@ -635,9 +636,10 @@ function CombinePocketHeadlandTurn:generatePocketHeadlandTurn(turnContext) local turnDiameter = self.vehicle.cp.settings.turnDiameter:get() local turnRadius = turnDiameter / 2 -- this is how far we have to cut into the next headland (the position where the header will be after the turn) - local offset = math.min(turnRadius + turnContext.frontMarkerDistance, self.vehicle.cp.workWidth) + local workWidth = vehicle.cp.courseGeneratorSettings.workWidth:get() + local offset = math.min(turnRadius + turnContext.frontMarkerDistance, workWidth) local corner = turnContext:createCorner(self.vehicle, turnRadius) - local d = -self.vehicle.cp.workWidth / 2 + turnContext.frontMarkerDistance + local d = -workWidth / 2 + turnContext.frontMarkerDistance local wp = corner:getPointAtDistanceFromCornerStart(d + 2) wp.speed = self.vehicle.cp.speeds.turn * 0.75 table.insert(cornerWaypoints, wp) diff --git a/BalerAIDriver.lua b/BalerAIDriver.lua index 9e41423ab..6989af814 100644 --- a/BalerAIDriver.lua +++ b/BalerAIDriver.lua @@ -61,7 +61,8 @@ function BalerAIDriver:startTurn(ix) if self.isCombine then self:debug('This vehicle is also a harvester, check check for special headland turns.') self:setMarkers() - self.turnContext = TurnContext(self.course, ix, self.aiDriverData, self.vehicle.cp.workWidth, + + self.turnContext = TurnContext(self.course, ix, self.aiDriverData, self:getWorkWidth(), self.frontMarkerDistance, self.backMarkerDistance, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) diff --git a/BunkerSiloAIDriver.lua b/BunkerSiloAIDriver.lua index f4f3cd58b..0cf9149f3 100644 --- a/BunkerSiloAIDriver.lua +++ b/BunkerSiloAIDriver.lua @@ -206,7 +206,7 @@ function BunkerSiloAIDriver:isHeapSearchAllowed() end function BunkerSiloAIDriver:getWorkWidth() - return self.vehicle.cp.workWidth + return self.courseGeneratorSettings.workWidth:get() end --- If true then the drive into silo course is reverse and diff --git a/CombineAIDriver.lua b/CombineAIDriver.lua index 48f3f375d..3a243c7b3 100644 --- a/CombineAIDriver.lua +++ b/CombineAIDriver.lua @@ -88,7 +88,7 @@ function CombineAIDriver:init(vehicle) self:checkMarkers() -- distance to keep to the right (>0) or left (<0) when pulling back to make room for the tractor - self.pullBackRightSideOffset = math.abs(self.pipeOffsetX) - self.vehicle.cp.workWidth / 2 + 5 + self.pullBackRightSideOffset = math.abs(self.pipeOffsetX) - self:getWorkWidth() / 2 + 5 self.pullBackRightSideOffset = self.pipeOnLeftSide and self.pullBackRightSideOffset or -self.pullBackRightSideOffset -- should be at pullBackRightSideOffset to the right or left at pullBackDistanceStart self.pullBackDistanceStart = self.settings.turnDiameter:get() --* 0.7 @@ -200,7 +200,7 @@ function CombineAIDriver:start(startingPoint) -- we work with the traffic conflict detector and the proximity sensors instead self:disableCollisionDetection() self:fixMaxRotationLimit() - local total, pipeInFruit = self.fieldworkCourse:setPipeInFruitMap(self.pipeOffsetX, self.vehicle.cp.workWidth) + local total, pipeInFruit = self.fieldworkCourse:setPipeInFruitMap(self.pipeOffsetX, self:getWorkWidth()) local ix = self.fieldworkCourse:getStartingWaypointIx(AIDriverUtil.getDirectionNode(self.vehicle), startingPoint) self:shouldStrawSwathBeOn(ix) self.fillLevelFullPercentage = self.normalFillLevelFullPercentage @@ -580,9 +580,10 @@ function CombineAIDriver:checkFruit() else self.fruitLeft, self.fruitRight = AIVehicleUtil.getValidityOfTurnDirections(self.vehicle) end - local x, _, z = localToWorld(self:getDirectionNode(), self.vehicle.cp.workWidth, 0, 0) + local workWidth = self:getWorkWidth() + local x, _, z = localToWorld(self:getDirectionNode(), workWidth, 0, 0) self.fieldOnLeft = courseplay:isField(x, z, 1, 1) - x, _, z = localToWorld(self:getDirectionNode(), -self.vehicle.cp.workWidth, 0, 0) + x, _, z = localToWorld(self:getDirectionNode(), -workWidth, 0, 0) self.fieldOnRight = courseplay:isField(x, z, 1, 1) self:debug('Fruit left: %.2f right %.2f, field on left %s, right %s', self.fruitLeft, self.fruitRight, tostring(self.fieldOnLeft), tostring(self.fieldOnRight)) @@ -904,7 +905,7 @@ end --- from node. function CombineAIDriver:getAreaToAvoid() if self:isWaitingForUnloadAfterPulledBack() then - local xOffset = self.vehicle.cp.workWidth / 2 + local xOffset = self:getWorkWidth() / 2 local zOffset = 0 local length = self.pullBackDistanceEnd local width = self.pullBackRightSideOffset @@ -1028,7 +1029,7 @@ function CombineAIDriver:startTurn(ix) self:debug('Starting a combine turn.') self:setMarkers() - self.turnContext = TurnContext(self.course, ix, self.aiDriverData, self.vehicle.cp.workWidth, + self.turnContext = TurnContext(self.course, ix, self.aiDriverData, self:getWorkWidth(), self.frontMarkerDistance, self.backMarkerDistance, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) @@ -1085,10 +1086,6 @@ function CombineAIDriver:getFieldworkCourse() return self.fieldworkCourse end -function CombineAIDriver:getWorkWidth() - return self.vehicle.cp.workWidth -end - function CombineAIDriver:isChopper() return self.combine:getFillUnitCapacity(self.combine.fillUnitIndex) > 10000000 end @@ -1759,7 +1756,7 @@ end function CombineAIDriver:addForwardProximitySensor() self:setFrontMarkerNode(self.vehicle) self.forwardLookingProximitySensorPack = WideForwardLookingProximitySensorPack( - self.vehicle, self.ppc, self:getFrontMarkerNode(self.vehicle), self.proximitySensorRange, 1, self.vehicle.cp.workWidth) + self.vehicle, self.ppc, self:getFrontMarkerNode(self.vehicle), self.proximitySensorRange, 1, self:getWorkWidth()) end --- Check the vehicle in the proximity sensor's range. If it is player driven, don't slow them down when hitting this diff --git a/CombineUnloadAIDriver.lua b/CombineUnloadAIDriver.lua index c44d61b82..263d70b52 100644 --- a/CombineUnloadAIDriver.lua +++ b/CombineUnloadAIDriver.lua @@ -1909,7 +1909,7 @@ function CombineUnloadAIDriver:startChopperTurn(ix) self:setNewOnFieldState(self.states.HANDLE_CHOPPER_HEADLAND_TURN) else self.turnContext = TurnContext(self.followCourse, ix, self.aiDriverData, - self.combineToUnload.cp.workWidth, self.frontMarkerDistance, self.backMarkerDistance, 0, 0) + self.combineToUnload.cp.driver:getWorkWidth(), self.frontMarkerDistance, self.backMarkerDistance, 0, 0) local finishingRowCourse = self.turnContext:createFinishingRowCourse(self.vehicle) self:startCourse(finishingRowCourse, 1) self:setNewOnFieldState(self.states.HANDLE_CHOPPER_180_TURN) diff --git a/CombineUnloadManager.lua b/CombineUnloadManager.lua index cdbb8c9f8..0f1915840 100644 --- a/CombineUnloadManager.lua +++ b/CombineUnloadManager.lua @@ -455,7 +455,7 @@ end function CombineUnloadManager:getPipeOffset(combine) if self:getIsChopper(combine) then - return (combine.cp.workWidth / 2) + 3 + return (combine.cp.driver:getWorkWidth() / 2) + 3 elseif self:getIsCombine(combine) then local pipeOffsetX, _ = combine.cp.driver:getPipeOffset() return pipeOffsetX @@ -531,7 +531,7 @@ function CombineUnloadManager:getOnFieldSituation(combine) local rightDirX,_,rightDirZ = localDirectionToWorld(node, -1, 0, 0); --set measurements of the box to check local boxWidth = 3; - local boxLength = 6 + combine.cp.workWidth/2; + local boxLength = 6 + combine.cp.driver:getWorkWidth()/2; --to get the box centered divide the measurements by 2 local boxWidthCenter = boxWidth/2 diff --git a/CpManager.lua b/CpManager.lua index 16d2316b5..59ae99b9e 100644 --- a/CpManager.lua +++ b/CpManager.lua @@ -235,6 +235,7 @@ function CpManager:deleteMap() -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- delete fields data and overlays + ---@class courseplay.fields.fieldData courseplay.fields.fieldData = {}; courseplay.fields.curFieldScanIndex = 0; courseplay.fields.allFieldsScanned = false; diff --git a/Events/CustomFieldEvent.lua b/Events/CustomFieldEvent.lua new file mode 100644 index 000000000..5972b50a3 --- /dev/null +++ b/Events/CustomFieldEvent.lua @@ -0,0 +1,110 @@ +--- This event is used to synchronize custom field, +--- either from the server to a newly joined player +--- or after a custom field was created to all user and server. +--- +CustomFieldEvent = {} +local CustomFieldEvent_mt = Class(CustomFieldEvent, Event) + +InitEventClass(CustomFieldEvent, "CustomFieldEvent") + +function CustomFieldEvent:emptyNew() + local self = Event:new(CustomFieldEvent_mt) + self.className = "CustomFieldEvent" + return self +end + +--- Creates a new Event +---@param field table +function CustomFieldEvent:new(field) + self.field = field + self.debug("CustomFieldEvent:new()") + return self +end + +--- Reads the serialized data on the receiving end of the event. +function CustomFieldEvent:readStream(streamId, connection) -- wird aufgerufen wenn mich ein Event erreicht + self.field = CustomFieldEvent.readField(streamId) + self.debug("CustomFieldEvent:readStream()") + self.debug("Field name: %s, numPoints = %s ",tostring(self.field.name), tostring(self.field.numPoints)) + + self:run(connection); +end + +--- Writes the serialized data from the sender. +function CustomFieldEvent:writeStream(streamId, connection) -- Wird aufgrufen wenn ich ein event verschicke (merke: reihenfolge der Daten muss mit der bei readStream uebereinstimmen + self.debug("CustomFieldEvent:writeStream()") + self.debug("Field name: %s, numPoints = %s ",tostring(self.field.name), tostring(self.field.numPoints)) + CustomFieldEvent.writeField(self.field,streamId) +end + +--- Runs the event on the receiving end of the event. +function CustomFieldEvent:run(connection) -- wir fuehren das empfangene event aus + self.debug("CustomFieldEvent:run()") + CpFieldUtil.saveFieldFromNetwork(self.field) + --- If the receiver was the client make sure every clients gets also updated. + if not connection:getIsServer() then + self.debug("Send CustomFieldEvent to clients") + g_server:broadcastEvent(CustomFieldEvent:new(self.field), nil, connection) + end +end + +--- Sends an event to sync a custom created field. +function CustomFieldEvent.sendEvent(field) + --- Only sync custom fields + if field.isCustom then + if g_server ~= nil then + CustomFieldEvent.debug("Send CustomFieldEvent to clients") + CustomFieldEvent.debug("Field name: %s",tostring(field.name)) + g_server:broadcastEvent(CustomFieldEvent:new(field)) + else + CustomFieldEvent.debug("Send CustomFieldEvent to server") + CustomFieldEvent.debug("Field name: %s",tostring(field.name)) + g_client:getServerConnection():sendEvent(CustomFieldEvent:new(field)) + end; + end +end + +function CustomFieldEvent.debug(...) + courseplay.debugFormat(courseplay.DBG_MULTIPLAYER,...) +end + +--- Writes a single custom field. +function CustomFieldEvent.writeField(field,streamId) + local numPoints = field.numPoints or #field.points + streamDebugWriteString(streamId, field.name) + streamDebugWriteInt32(streamId, numPoints) + streamDebugWriteInt32(streamId, field.fieldNum) + streamDebugWriteInt32(streamId, field.dimensions.minX) + streamDebugWriteInt32(streamId, field.dimensions.maxX) + streamDebugWriteInt32(streamId, field.dimensions.minZ) + streamDebugWriteInt32(streamId, field.dimensions.maxZ) + for p = 1, numPoints do + streamDebugWriteFloat32(streamId, field.points[p].cx) + streamDebugWriteFloat32(streamId, field.points[p].cy) + streamDebugWriteFloat32(streamId, field.points[p].cz) + end + +end + +--- Reads a single custom field. +function CustomFieldEvent.readField(streamId) + local field = { + dimensions = {}, + points = {}, + isCustom = true + } + field.name = streamDebugReadString(streamId) + field.numPoints = streamDebugReadInt32(streamId) + field.fieldNum = streamDebugReadInt32(streamId) + field.dimensions.minX = streamDebugReadInt32(streamId) + field.dimensions.maxX = streamDebugReadInt32(streamId) + field.dimensions.minZ = streamDebugReadInt32(streamId) + field.dimensions.maxZ = streamDebugReadInt32(streamId) + for p = 1, field.numPoints do + field.points[p] = {} + field.points[p].cx = streamDebugReadFloat32(streamId) + field.points[p].cy = streamDebugReadFloat32(streamId) + field.points[p].cz = streamDebugReadFloat32(streamId) + end + return field +end \ No newline at end of file diff --git a/Events/SettingEvent.lua b/Events/SettingEvent.lua index 19e8d9079..d32c26677 100644 --- a/Events/SettingEvent.lua +++ b/Events/SettingEvent.lua @@ -1,124 +1,218 @@ ---- This event is used to synchronize Setting changes on run time. +--- This event is used to synchronize global setting changes on run time. --- Every setting event requires: --- - a container name (parentName) --- - a setting name --- - an event index for the setting --- - the value --- -SettingEvent = {} -local SettingEvent_mt = Class(SettingEvent, Event) +GlobalSettingEvent = {} +local GlobalSettingEvent_mt = Class(GlobalSettingEvent, Event) -InitEventClass(SettingEvent, "SettingEvent") +InitEventClass(GlobalSettingEvent, "GlobalSettingEvent") -function SettingEvent:emptyNew() - local self = Event:new(SettingEvent_mt) - self.className = "SettingEvent" +function GlobalSettingEvent:emptyNew() + local self = Event:new(GlobalSettingEvent_mt) + self.className = "GlobalSettingEvent" return self end --- Creates a new Event ----@param vehicle table ---@param setting Setting ---@param eventData table ---@param value any -function SettingEvent:new(vehicle,setting,eventData,value) - self.vehicle = vehicle +function GlobalSettingEvent:new(setting,eventData,value) self.value = value self.parentName,self.name,self.eventIx,self.writeFunc = self.decodeEventData(setting,eventData) - self.debug("SettingEvent:new()") + self.debug("GlobalSettingEvent:new()") return self end --- Reads the serialized data on the receiving end of the event. -function SettingEvent:readStream(streamId, connection) -- wird aufgerufen wenn mich ein Event erreicht +function GlobalSettingEvent:readStream(streamId, connection) -- wird aufgerufen wenn mich ein Event erreicht self.parentName = streamReadString(streamId) self.name = streamReadString(streamId) self.eventIx = streamReadUInt8(streamId) self.debug("Parent name: %s, Setting name: %s, eventIx: %d",self.parentName, self.name,self.eventIx) - self.vehicle = nil - if streamReadBool(streamId) then - self.vehicle = NetworkUtil.getObject(streamReadInt32(streamId)) - self.debugVehicle(self.vehicle,"Vehicle setting") - self.setting,self.eventData = self.encodeEventData(self.vehicle,self.parentName,self.name,self.eventIx) - else - self.debug("Global setting") - self.setting,self.eventData = self.encodeEventData(nil,self.parentName,self.name,self.eventIx) - end - + self.setting,self.eventData = self.encodeEventData(self.parentName,self.name,self.eventIx) if self.eventData.readFunc then self.value = self.eventData.readFunc(streamId) end - self.debug("SettingEvent:readStream()") + self.debug("GlobalSettingEvent:readStream()") self.debug("Parent name: %s, Setting name: %s, value: %s, eventIx: %d",self.parentName, self.name, tostring(self.value),self.eventIx) self:run(connection); end --- Writes the serialized data from the sender. -function SettingEvent:writeStream(streamId, connection) -- Wird aufgrufen wenn ich ein event verschicke (merke: reihenfolge der Daten muss mit der bei readStream uebereinstimmen - self.debug("SettingEvent:writeStream()") +function GlobalSettingEvent:writeStream(streamId, connection) -- Wird aufgrufen wenn ich ein event verschicke (merke: reihenfolge der Daten muss mit der bei readStream uebereinstimmen + self.debug("GlobalSettingEvent:writeStream()") streamWriteString(streamId, self.parentName) streamWriteString(streamId, self.name) streamWriteUInt8(streamId,self.eventIx) self.debug("Parent name: %s, Setting name: %s, value: %s, eventIx: %d",self.parentName, self.name, tostring(self.value),self.eventIx) - if self.vehicle ~= nil then - self.debugVehicle(self.vehicle,"Vehicle setting") - streamWriteBool(streamId, true) - streamWriteInt32(streamId, NetworkUtil.getObjectId(self.vehicle)) - else - self.debug("Global setting") - streamWriteBool(streamId, false) - end if self.writeFunc then self.writeFunc(streamId, self.value) end end --- Runs the event on the receiving end of the event. -function SettingEvent:run(connection) -- wir fuehren das empfangene event aus - self.debug("SettingEvent:run()") +function GlobalSettingEvent:run(connection) -- wir fuehren das empfangene event aus + self.debug("GlobalSettingEvent:run()") self.eventData.eventFunc(self.setting,self.value) --- If the receiver was the client make sure every clients gets also updated. if not connection:getIsServer() then - self.debug("Send SettingEvent to clients") - g_server:broadcastEvent(SettingEvent:new(self.vehicle,self.setting,self.eventData,self.value), nil, connection, self.vehicle) + self.debug("Send GlobalSettingEvent to clients") + g_server:broadcastEvent(GlobalSettingEvent:new(self.setting,self.eventData,self.value), nil, connection) end end --- Sends an Event either: --- - from the server to all clients or --- - from the client to the server ----@param vehicle table ---@param setting Setting ---@param eventData table an event registers on the setting ---@param value any -function SettingEvent.sendEvent(vehicle,setting,eventData,value) +function GlobalSettingEvent.sendEvent(setting,eventData,value) if g_server ~= nil then - SettingEvent.debug("Send SettingEvent to clients") - SettingEvent.debug("Setting name: %s",setting:getName()) - g_server:broadcastEvent(SettingEvent:new(vehicle,setting,eventData,value), nil, nil, vehicle) + GlobalSettingEvent.debug("Send GlobalSettingEvent to clients") + GlobalSettingEvent.debug("Setting name: %s",setting:getName()) + g_server:broadcastEvent(GlobalSettingEvent:new(setting,eventData,value)) else - SettingEvent.debug("Send SettingEvent to server") - SettingEvent.debug("Setting name: %s",setting:getName()) - g_client:getServerConnection():sendEvent(SettingEvent:new(vehicle,setting,eventData,value)) + GlobalSettingEvent.debug("Send GlobalSettingEvent to server") + GlobalSettingEvent.debug("Setting name: %s",setting:getName()) + g_client:getServerConnection():sendEvent(GlobalSettingEvent:new(setting,eventData,value)) end; end -function SettingEvent.debug(...) +function GlobalSettingEvent.debug(...) courseplay.debugFormat(courseplay.DBG_MULTIPLAYER,...) end -function SettingEvent.debugVehicle(vehicle,...) +--- Gets all relevant values from the setting event to send the event. +---@param setting Setting +---@param eventData table +function GlobalSettingEvent.decodeEventData(setting,eventData) + local parentName = setting:getParentName() + local settingName = setting:getName() + local eventIx = eventData.ix + local writeFunc = eventData.writeFunc + return parentName,settingName,eventIx,writeFunc +end + +--- Gets the setting and event back from all received values. +---@param parentName table Name of the setting container +---@param settingName string Name of the setting +---@param eventIx number Event number received +function GlobalSettingEvent.encodeEventData(parentName,settingName,eventIx) + local setting = courseplay[parentName][settingName] + return setting,setting:getEvent(eventIx) +end + + +--- This event is used to synchronize Setting changes on run time. +--- Every setting event requires: +--- - a container name (parentName) +--- - a setting name +--- - an event index for the setting +--- - the value +--- +VehicleSettingEvent = {} +local VehicleSettingEvent_mt = Class(VehicleSettingEvent, Event) + +InitEventClass(VehicleSettingEvent, "VehicleSettingEvent") + +function VehicleSettingEvent:emptyNew() + local self = Event:new(VehicleSettingEvent_mt) + self.className = "VehicleSettingEvent" + return self +end + +--- Creates a new Event +---@param vehicle table +---@param setting Setting +---@param eventData table +---@param value any +function VehicleSettingEvent:new(vehicle,setting,eventData,value) + self.vehicle = vehicle + self.value = value + self.parentName,self.name,self.eventIx,self.writeFunc = self.decodeEventData(setting,eventData) + self.debug(self.vehicle,"VehicleSettingEvent:new()") + return self +end + +--- Reads the serialized data on the receiving end of the event. +function VehicleSettingEvent:readStream(streamId, connection) -- wird aufgerufen wenn mich ein Event erreicht + self.vehicle = NetworkUtil.getObject(streamReadInt32(streamId)) + self.parentName = streamReadString(streamId) + self.name = streamReadString(streamId) + self.eventIx = streamReadUInt8(streamId) + self.debug(self.vehicle,"Parent name: %s, Setting name: %s, eventIx: %d",self.parentName, self.name,self.eventIx) + self.setting,self.eventData = self.encodeEventData(self.vehicle,self.parentName,self.name,self.eventIx) + + if self.eventData.readFunc then + self.value = self.eventData.readFunc(streamId) + end + + self.debug(self.vehicle,"VehicleSettingEvent:readStream()") + self.debug(self.vehicle,"Parent name: %s, Setting name: %s, value: %s, eventIx: %d",self.parentName, self.name, tostring(self.value),self.eventIx) + + self:run(connection); +end + +--- Writes the serialized data from the sender. +function VehicleSettingEvent:writeStream(streamId, connection) -- Wird aufgrufen wenn ich ein event verschicke (merke: reihenfolge der Daten muss mit der bei readStream uebereinstimmen + streamWriteInt32(streamId, NetworkUtil.getObjectId(self.vehicle)) + self.debug(self.vehicle,"VehicleSettingEvent:writeStream()") + streamWriteString(streamId, self.parentName) + streamWriteString(streamId, self.name) + streamWriteUInt8(streamId,self.eventIx) + self.debug(self.vehicle,"Parent name: %s, Setting name: %s, value: %s, eventIx: %d",self.parentName, self.name, tostring(self.value),self.eventIx) + if self.writeFunc then + self.writeFunc(streamId, self.value) + end +end + +--- Runs the event on the receiving end of the event. +function VehicleSettingEvent:run(connection) -- wir fuehren das empfangene event aus + self.debug(self.vehicle,"VehicleSettingEvent:run()") + self.eventData.eventFunc(self.setting,self.value) + + --- If the receiver was the client make sure every clients gets also updated. + if not connection:getIsServer() then + self.debug(self.vehicle,"Send VehicleSettingEvent to clients") + g_server:broadcastEvent(VehicleSettingEvent:new(self.vehicle,self.setting,self.eventData,self.value), nil, connection, self.vehicle) + end +end + +--- Sends an Event either: +--- - from the server to all clients or +--- - from the client to the server +---@param vehicle table +---@param setting Setting +---@param eventData table an event registers on the setting +---@param value any +function VehicleSettingEvent.sendEvent(vehicle,setting,eventData,value) + if g_server ~= nil then + VehicleSettingEvent.debug(vehicle,"Send VehicleSettingEvent to clients") + VehicleSettingEvent.debug(vehicle,"Setting name: %s",setting:getName()) + g_server:broadcastEvent(VehicleSettingEvent:new(vehicle,setting,eventData,value), nil, nil, vehicle) + else + VehicleSettingEvent.debug(vehicle,"Send VehicleSettingEvent to server") + VehicleSettingEvent.debug(vehicle,"Setting name: %s",setting:getName()) + g_client:getServerConnection():sendEvent(VehicleSettingEvent:new(vehicle,setting,eventData,value)) + end; +end +function VehicleSettingEvent.debug(vehicle,...) courseplay.debugVehicle(courseplay.DBG_MULTIPLAYER,vehicle,...) end --- Gets all relevant values from the setting event to send the event. ---@param setting Setting ---@param eventData table -function SettingEvent.decodeEventData(setting,eventData) +function VehicleSettingEvent.decodeEventData(setting,eventData) local parentName = setting:getParentName() local settingName = setting:getName() local eventIx = eventData.ix @@ -131,12 +225,7 @@ end ---@param parentName table Name of the setting container ---@param settingName string Name of the setting ---@param eventIx number Event number received -function SettingEvent.encodeEventData(vehicle,parentName,settingName,eventIx) - local setting - if vehicle ~= nil then - setting = vehicle.cp[parentName][settingName] - else - setting = courseplay[parentName][settingName] - end +function VehicleSettingEvent.encodeEventData(vehicle,parentName,settingName,eventIx) + local setting = vehicle.cp[parentName][settingName] return setting,setting:getEvent(eventIx) end diff --git a/FieldworkAIDriver.lua b/FieldworkAIDriver.lua index e1a37bb4a..3332741cf 100644 --- a/FieldworkAIDriver.lua +++ b/FieldworkAIDriver.lua @@ -323,6 +323,14 @@ function FieldworkAIDriver:writeUpdateStream(streamId, connection, dirtyMask) else streamWriteBool(streamId,false) end + if self.timeRemaining ~= nil and self.timeRemaining ~= self.timeRemainingSend then + streamWriteBool(streamId,true) + streamWriteFloat32(streamId,self.timeRemaining) + self.timeRemainingSend = self.timeRemaining + else + streamWriteBool(streamId,false) + end + AIDriver.writeUpdateStream(self,streamId, connection, dirtyMask) end @@ -336,6 +344,10 @@ function FieldworkAIDriver:readUpdateStream(streamId, timestamp, connection) local nameState = streamReadString(streamId) self.fieldworkState = self.states[nameState] end + if streamReadBool(streamId) then + self.timeRemaining = streamReadFloat32(streamId) + end + AIDriver.readUpdateStream(self,streamId, timestamp, connection) end @@ -346,6 +358,12 @@ function FieldworkAIDriver:onWriteStream(streamId) else streamWriteBool(streamId,false) end + if self.timeRemaining ~= nil then + streamWriteBool(streamId,true) + streamWriteFloat32(streamId,self.timeRemaining) + else + streamWriteBool(streamId,false) + end AIDriver.onWriteStream(self,streamId) end @@ -354,6 +372,9 @@ function FieldworkAIDriver:onReadStream(streamId) local nameState = streamReadString(streamId) self.fieldworkState = self.states[nameState] end + if streamReadBool(streamId) then + self.timeRemaining = streamReadFloat32(streamId) + end AIDriver.onReadStream(self,streamId) end @@ -815,11 +836,12 @@ function FieldworkAIDriver:setUpCourses() self.fieldworkCourse:setOffset(self.vehicle.cp.settings.toolOffsetX:get(), self.vehicle.cp.settings.toolOffsetZ:get()) -- TODO: consolidate the working width calculation and usage, this is currently an ugly mess self.fieldworkCourse:setWorkWidth(self.vehicle.cp.courseWorkWidth or self.courseGeneratorSettings.workWidth:getAutoWorkWidth()) + local laneNumber = self.courseGeneratorSettings.multiTools.laneNumber:get() if self.vehicle.cp.courseGeneratorSettings.multiTools:get() > 1 then - self:debug('Calculating offset course for position %d of %d', self.vehicle.cp.laneNumber, + self:debug('Calculating offset course for position %d of %d', laneNumber, self.vehicle.cp.courseGeneratorSettings.multiTools:get()) self.fieldworkCourse = self.fieldworkCourse:calculateOffsetCourse( - self.vehicle.cp.courseGeneratorSettings.multiTools:get(), self.vehicle.cp.laneNumber, + self.vehicle.cp.courseGeneratorSettings.multiTools:get(), laneNumber, self.fieldworkCourse.workWidth / self.vehicle.cp.courseGeneratorSettings.multiTools:get(), self.vehicle.cp.settings.symmetricLaneChange:is(true)) end @@ -939,15 +961,19 @@ function FieldworkAIDriver:foldImplements() end function FieldworkAIDriver:clearRemainingTime() - self.vehicle.cp.timeRemaining = nil + self.timeRemaining = nil +end + +function FieldworkAIDriver:getRemainingTime() + return self.timeRemaining end function FieldworkAIDriver:updateRemainingTime(ix) if self.state == self.states.ON_FIELDWORK_COURSE then local dist, turns = self.course:getRemainingDistanceAndTurnsFrom(ix) local turnTime = turns * self.turnDurationMs / 1000 - self.vehicle.cp.timeRemaining = math.max(0, dist / (self:getWorkSpeed() / 3.6) + turnTime) - self:debug('Distance to go: %.1f; Turns left: %d; Time left: %ds', dist, turns, self.vehicle.cp.timeRemaining) + self.timeRemaining = math.max(0, dist / (self:getWorkSpeed() / 3.6) + turnTime) + self:debug('Distance to go: %.1f; Turns left: %d; Time left: %ds', dist, turns, self.timeRemaining) else self:clearRemainingTime() end @@ -1123,7 +1149,7 @@ end function FieldworkAIDriver:finishRow(ix) self:setMarkers() - self.turnContext = RowFinishingContext(self.course, ix, self.aiDriverData, self.vehicle.cp.workWidth, + self.turnContext = RowFinishingContext(self.course, ix, self.aiDriverData, self:getWorkWidth(), self.frontMarkerDistance, self.backMarkerDistance, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) self.aiTurn = FinishRowOnly(self.vehicle, self, self.turnContext) @@ -1136,7 +1162,7 @@ function FieldworkAIDriver:startTurn(ix) -- this should help returning to the course faster. self.ppc:setShortLookaheadDistance() self:setMarkers() - self.turnContext = TurnContext(self.course, ix, self.aiDriverData, self.vehicle.cp.workWidth, + self.turnContext = TurnContext(self.course, ix, self.aiDriverData, self:getWorkWidth(), self.frontMarkerDistance, self.backMarkerDistance, self:getTurnEndSideOffset(), self:getTurnEndForwardOffset()) if self.vehicle.cp.settings.useAITurns:is(true) then @@ -1204,27 +1230,7 @@ function FieldworkAIDriver:getMarkers() end function FieldworkAIDriver:getAIMarkers(object, suppressLog) - local aiLeftMarker, aiRightMarker, aiBackMarker - if object.getAIMarkers then - aiLeftMarker, aiRightMarker, aiBackMarker = object:getAIMarkers() - end - if not aiLeftMarker or not aiRightMarker or not aiBackMarker then - -- use the root node if there are no AI markers - if not suppressLog then - self:debug('%s has no AI markers, try work areas', nameNum(object)) - end - aiLeftMarker, aiRightMarker, aiBackMarker = self:getAIMarkersFromWorkAreas(object) - if not aiLeftMarker or not aiRightMarker or not aiLeftMarker then - if not suppressLog then - self:debug('%s has no work areas, giving up', nameNum(object)) - end - return nil, nil, nil - else - return aiLeftMarker, aiRightMarker, aiBackMarker - end - else - return aiLeftMarker, aiRightMarker, aiBackMarker - end + return WorkWidthUtil.getAIMarkers(object) end --- When finishing a turn, is it time to lower all implements here? @@ -1379,27 +1385,6 @@ function FieldworkAIDriver:onDraw() AIDriver.onDraw(self) end -function FieldworkAIDriver:isValidWorkArea(area) - return area.start and area.height and area.width and - area.type ~= WorkAreaType.RIDGEMARKER and - area.type ~= WorkAreaType.COMBINESWATH and - area.type ~= WorkAreaType.COMBINECHOPPER -end - ---- Calculate the front and back marker nodes of a work area -function FieldworkAIDriver:getAIMarkersFromWorkAreas(object) - -- work areas are defined by three nodes: start, width and height. These nodes - -- define a rectangular work area which you can make visible with the - -- gsVehicleDebugAttributes console command and then pressing F5 - for _, area in courseplay:workAreaIterator(object) do - if self:isValidWorkArea(area) then - -- for now, just use the first valid work area we find - self:debug('%s: Using %s work area markers as AIMarkers', nameNum(object), g_workAreaTypeManager.workAreaTypes[area.type].name) - return area.start, area.width, area.height - end - end -end - function FieldworkAIDriver:getAllAIImplements(object, implements) if not implements then implements = {} end for _, implement in ipairs(object:getAttachedImplements()) do @@ -1414,7 +1399,7 @@ end -- Is this and implement we should consider when deciding when to lift/raise implements at the end/start of a row? function FieldworkAIDriver:isValidAIImplement(object) - if courseplay:hasWorkAreas(object) then + if WorkWidthUtil.hasWorkAreas(object) then -- has work areas, good. return true else @@ -1503,3 +1488,7 @@ end function FieldworkAIDriver:getAllFillLevels(object, fillLevelInfo) AIDriverUtil.getAllFillLevels(object, fillLevelInfo, self) end + +function FieldworkAIDriver:getWorkWidth() + return self.courseGeneratorSettings.workWidth:get() +end \ No newline at end of file diff --git a/FillableFieldworkAIDriver.lua b/FillableFieldworkAIDriver.lua index 7c3b5d509..843a4ddd9 100644 --- a/FillableFieldworkAIDriver.lua +++ b/FillableFieldworkAIDriver.lua @@ -293,14 +293,15 @@ end function FillableFieldworkAIDriver:getTurnEndForwardOffset() -- TODO: do other implements need this? + local workWidth = self:getWorkWidth() if SpecializationUtil.hasSpecialization(Sprayer, self.vehicle.specializations) - and self.vehicle.cp.workWidth > self.settings.turnDiameter:get() then + and workWidth> self.settings.turnDiameter:get() then -- compensate for very wide implements like sprayer booms where the tip of the implement -- on the inner side of the turn may be very far forward of the vehicle's root and miss -- parts of the inside corner. - local forwardOffset = - (self.vehicle.cp.workWidth - self.settings.turnDiameter:get()) / 2.5 + local forwardOffset = - (workWidth - self.settings.turnDiameter:get()) / 2.5 self:debug('sprayer working width %.1f > turn diameter %.1f, applying forward offset %.1f to turn end', - self.vehicle.cp.workWidth, self.settings.turnDiameter:get(), forwardOffset) + workWidth, self.settings.turnDiameter:get(), forwardOffset) return forwardOffset else return 0 diff --git a/GlobalSettings.lua b/GlobalSettings.lua index 6e6f1ea1c..244e63e74 100644 --- a/GlobalSettings.lua +++ b/GlobalSettings.lua @@ -183,6 +183,7 @@ function DebugChannelsSetting:load(xmlFilePath) self.numChannels = 0 self.toolTips = {} if xmlFile and hasXMLProperty(xmlFile, baseKey) then + print("Loading debug channel setup!") local i = 0 while true do local key = string.format("%s.%s(%d)",baseKey,"DebugChannel",i) @@ -211,19 +212,23 @@ function DebugChannelsSetting:load(xmlFilePath) end self.numChannels = i delete(xmlFile) + else + print("Couldn't load debug channel setup!") end courseplay.debugChannels = self.channels --- Old code end function DebugChannelsSetting:onWriteStream(streamID) + streamWriteUInt8(streamID,self.numChannels) for ix,value in ipairs(self.channels) do streamWriteBool(streamID,value) end end function DebugChannelsSetting:onReadStream(streamID) - for ix,_ in ipairs(self.channels) do - self.channels[ix] = streamReadBool(streamID) + self.numChannels = streamReadUInt8(streamID) + for i= 1, self.numChannels do + self.channels[i] = streamReadBool(streamID) end end diff --git a/PlowAIDriver.lua b/PlowAIDriver.lua index b3c32fd7a..34bde084e 100644 --- a/PlowAIDriver.lua +++ b/PlowAIDriver.lua @@ -172,3 +172,11 @@ function PlowAIDriver:getTurnEndSideOffset() return 0 end end + +function PlowAIDriver:stop(msg) + --- Make sure after the driver has finished. + --- Clients and server values are synced, + --- as the server updates the value locally during driving. + self.settings.toolOffsetX:set(self.settings.toolOffsetX:get()) + FieldworkAIDriver.stop(self,msg) +end \ No newline at end of file diff --git a/ShieldAIDriver.lua b/ShieldAIDriver.lua index e0198bedb..08fa9e913 100644 --- a/ShieldAIDriver.lua +++ b/ShieldAIDriver.lua @@ -167,7 +167,7 @@ function ShieldAIDriver:getTargetNode() end function ShieldAIDriver:getWorkWidth() - return math.max(self.vehicle.cp.workWidth,3) + return math.max(CompactingAIDriver.getWorkWidth(self),3) end --- Overrides the player shield controls, while a cp driver is driving. diff --git a/base.lua b/base.lua index d9495560f..87eb28534 100644 --- a/base.lua +++ b/base.lua @@ -224,37 +224,17 @@ function courseplay:onLoad(savegame) --Offset - self.cp.laneOffset = 0; self.cp.totalOffsetX = 0; self.cp.skipOffsetX = false; - self.cp.workWidth = 3 - --Copy course self.cp.hasFoundCopyDriver = false; self.cp.copyCourseFromDriver = nil; self.cp.selectedDriverNumber = 0; - self.cp.laneNumber = 0; - --Course generation self.cp.hasGeneratedCourse = false; - self.cp.fieldEdge = { - selectedField = { - fieldNum = 0; - numPoints = 0; - buttonsCreated = false; - }; - customField = { - points = nil; - numPoints = 0; - isCreated = false; - show = false; - fieldNum = 0; - selectedFieldNumExists = false; - }; - }; self.cp.mouseCursorActive = false; @@ -268,7 +248,7 @@ function courseplay:onLoad(savegame) ---@type SettingsContainer self.cp.settings = SettingsContainer.createVehicleSpecificSettings(self) - ---@type SettingsContainer + ---@type CourseGeneratorSettingsContainer self.cp.courseGeneratorSettings = SettingsContainer.createCourseGeneratorSettings(self) -- HUD @@ -399,38 +379,11 @@ function courseplay:renderHud(isDriving) end function courseplay:showWorkWidth(vehicle) - local offsX, offsZ = vehicle.cp.settings.toolOffsetX:get() or 0, vehicle.cp.settings.toolOffsetZ:get() or 0; - - local left = (vehicle.cp.workWidth * 0.5) + offsX; - local right = (vehicle.cp.workWidth * -0.5) + offsX; - - -- TODO: refactor this, move showWorkWidth into the AIDriver? - if vehicle.cp.directionNode and vehicle.cp.driver.getMarkers then - local f, b = vehicle.cp.driver:getMarkers() - local p1x, p1y, p1z = localToWorld(vehicle.cp.directionNode, left, 1.6, b - offsZ); - local p2x, p2y, p2z = localToWorld(vehicle.cp.directionNode, right, 1.6, b - offsZ); - local p3x, p3y, p3z = localToWorld(vehicle.cp.directionNode, right, 1.6, f - offsZ); - local p4x, p4y, p4z = localToWorld(vehicle.cp.directionNode, left, 1.6, f - offsZ); - - cpDebug:drawPoint(p1x, p1y, p1z, 1, 1, 0); - cpDebug:drawPoint(p2x, p2y, p2z, 1, 1, 0); - cpDebug:drawPoint(p3x, p3y, p3z, 1, 1, 0); - cpDebug:drawPoint(p4x, p4y, p4z, 1, 1, 0); - - cpDebug:drawLine(p1x, p1y, p1z, 1, 0, 0, p2x, p2y, p2z); - cpDebug:drawLine(p2x, p2y, p2z, 1, 0, 0, p3x, p3y, p3z); - cpDebug:drawLine(p3x, p3y, p3z, 1, 0, 0, p4x, p4y, p4z); - cpDebug:drawLine(p4x, p4y, p4z, 1, 0, 0, p1x, p1y, p1z); - else - local lX, lY, lZ = localToWorld(vehicle.rootNode, left, 1.6, -6 - offsZ); - local rX, rY, rZ = localToWorld(vehicle.rootNode, right, 1.6, -6 - offsZ); - - cpDebug:drawPoint(lX, lY, lZ, 1, 1, 0); - cpDebug:drawPoint(rX, rY, rZ, 1, 1, 0); - - cpDebug:drawLine(lX, lY, lZ, 1, 0, 0, rX, rY, rZ); - end; -end; + local offsX, offsZ = vehicle.cp.settings.toolOffsetX:get() or 0, vehicle.cp.settings.toolOffsetZ:get() or 0 + local workWidth = vehicle.cp.courseGeneratorSettings.workWidth:get() + + WorkWidthUtil.showWorkWidth(vehicle,workWidth,offsX,offsZ) +end function courseplay:onUpdate(dt) --if the vehicle is attached to another vehicle, disable cp @@ -487,11 +440,6 @@ function courseplay:onUpdate(dt) if self.cp.collidingVehicleId ~= nil and g_currentMission.nodeToObject[self.cp.collidingVehicleId] ~= nil and g_currentMission.nodeToObject[self.cp.collidingVehicleId].isCpPathvehicle then courseplay:setPathVehiclesSpeed(self,dt) end - - --reset selected field num, when field doesn't exist anymone (contracts) - if courseplay.fields.fieldData[self.cp.fieldEdge.selectedField.fieldNum] == nil then - self.cp.fieldEdge.selectedField.fieldNum = 0; - end -- this really should be only done in one place. self.cp.curSpeed = self.lastSpeedReal * 3600; @@ -512,9 +460,6 @@ function courseplay:onUpdateTick(dt) if not courseplay.isEnabled(self) then return end - if not self.cp.fieldEdge.selectedField.buttonsCreated and courseplay.fields.numAvailableFields > 0 then - courseplay:createFieldEdgeButtons(self); - end; if self.cp.toolsDirty then courseplay:updateOnAttachOrDetach(self) @@ -712,9 +657,6 @@ function courseplay:onReadStream(streamId, connection) self.cp.course2dUpdateDrawData = true; - if streamReadBool(streamId) then - self.cp.timeRemaining = streamReadFloat32(streamId) - end if streamReadBool(streamId) then self.cp.infoText = streamReadString(streamId) @@ -765,13 +707,6 @@ function courseplay:onWriteStream(streamId, connection) end - if self.cp.timeRemaining then - streamWriteBool(streamId,true) - streamWriteFloat32(streamId,self.cp.timeRemaining) - else - streamWriteBool(streamId,false) - end - if self.cp.infoText then streamWriteBool(streamId,true) streamWriteString(streamId,self.cp.infoText) @@ -810,13 +745,6 @@ function courseplay:onReadUpdateStream(streamId, timestamp, connection) else self.cp.currentCourseName = nil end - if streamReadBool(streamId) then -- is timeRemaining~=nil ? - if streamReadBool(streamId) then -- has timeRemaining changed - self.cp.timeRemaining = streamReadFloat32(streamId) - end - else - self.cp.timeRemaining = nil - end --gitAdditionalText ? end end @@ -857,19 +785,6 @@ function courseplay:onWriteUpdateStream(streamId, connection, dirtyMask) else streamWriteBool(streamId,false) end - if self.cp.timeRemaining then -- is timeRemaining~=nil ? - streamWriteBool(streamId,true) - if self.cp.timeRemaining~=self.cp.timeRemainingSend then -- has timeRemaining changed - streamWriteBool(streamId,true) - streamWriteFloat32(streamId,self.cp.timeRemaining) - self.cp.timeRemainingSend = self.cp.timeRemaining - else - streamWriteBool(streamId,false) - end - else - streamWriteBool(streamId,false) - end - --gitAdditionalText ? end end end @@ -889,21 +804,6 @@ function courseplay:loadVehicleCPSettings(xmlFile, key, resetVehicles) self.cp.hud.show = Utils.getNoNil( getXMLBool(xmlFile, curKey .. '#showHud'), false); - -- MODES 4 / 6 - curKey = key .. '.courseplay.fieldWork'; - self.cp.workWidth = Utils.getNoNil(getXMLFloat(xmlFile, curKey .. '#workWidth'), 3); - self.cp.manualWorkWidth = Utils.getNoNil(getXMLFloat(xmlFile, curKey .. '#manualWorkWidth'), 0); - if self.cp.manualWorkWidth ~= 0 then - self.cp.workWidth = self.cp.manualWorkWidth - else - self.cp.manualWorkWidth = nil - end; - - local offsetData = Utils.getNoNil(getXMLString(xmlFile, curKey .. '#offsetData'), '0;0;0;false;0;0;0'); -- 1=laneOffset, 2=toolOffsetX, 3=toolOffsetZ, 4=symmetricalLaneChange - offsetData = StringUtil.splitString(';', offsetData); - courseplay:changeLaneOffset(self, nil, tonumber(offsetData[1])); - - if offsetData[7] ~= nil then self.cp.laneNumber = tonumber(offsetData[7]) end; self.cp.settings:loadFromXML(xmlFile, key .. '.courseplay') @@ -943,14 +843,6 @@ function courseplay:saveToXMLFile(xmlFile, key, usedModNames) setXMLBool(xmlFile, newKey..".HUD #showHud", self.cp.hud.show) - - --field work settings - local offsetData = string.format('%.1f;%.1f;%.1f;%s;%.1f;%.1f;%d', self.cp.laneOffset, 0, 0, 0, 0, 0, self.cp.laneNumber); - setXMLString(xmlFile, newKey..".fieldWork #workWidth", string.format("%.1f",self.cp.workWidth)) - setXMLString(xmlFile, newKey..".fieldWork #offsetData", offsetData) - setXMLString(xmlFile, newKey..".fieldWork #manualWorkWidth", string.format("%.1f",Utils.getNoNil(self.cp.manualWorkWidth,0))) - - self.cp.settings:saveToXML(xmlFile, newKey) self.cp.courseGeneratorSettings:saveToXML(xmlFile, newKey) diff --git a/config/VehicleConfigurations.xml b/config/VehicleConfigurations.xml index 8ea2c4366..a9f039ead 100644 --- a/config/VehicleConfigurations.xml +++ b/config/VehicleConfigurations.xml @@ -101,6 +101,10 @@ You can define the following custom settings: + + 0 then + self:setNext() + else + self:setPrevious() + end + self:onChange() +end + +function WorkWidthSetting:onChange() + courseplay:setCustomTimer(self.vehicle, 'showWorkWidth', 2); +end + --- Course gen center mode setting ---@class CenterModeSetting : SettingList CenterModeSetting = CpObject(SettingList) @@ -377,28 +392,96 @@ end ---@class MultiToolsSetting : SettingList MultiToolsSetting = CpObject(SettingList) - +MultiToolsSetting.MAX_DRIVERS = 8 function MultiToolsSetting:init(vehicle) self.values = {} self.texts = {} - for i = 1, 8 do + for i = 1, self.MAX_DRIVERS do table.insert(self.values, i) table.insert(self.texts, i) end SettingList.init(self, 'multiTools', 'COURSEPLAY_MULTI_TOOLS', 'COURSEPLAY_MULTI_TOOLS', vehicle, self.values, self.texts) + self.laneNumber = LaneNumberSetting(vehicle,self) + self.LANE_NUMBER_EVENT = self:registerIntEvent(self.setLaneNumberFromNetwork) +end + +function MultiToolsSetting:loadFromXml(xml,parentKey) + SettingList.loadFromXml(self,xml,parentKey) + self.laneNumber:loadFromXml(xml,parentKey) +end +function MultiToolsSetting:saveToXml(xml,parentKey) + SettingList.saveToXml(self,xml,parentKey) + self.laneNumber:saveToXml(xml,parentKey) +end + +function MultiToolsSetting:onReadStream(streamId) + SettingList.onReadStream(self,streamId) + self.laneNumber:onReadStream(streamId) + +end +function MultiToolsSetting:onWriteStream(streamId) + SettingList.onWriteStream(self,streamId) + self.laneNumber:onWriteStream(streamId) +end + +function MultiToolsSetting:setLaneNumberFromNetwork(value) + self.laneNumber:set(value) +end + +function MultiToolsSetting:changeLaneNumber(changeBy,noEventSend) + self.laneNumber:changeByX(changeBy) + if noEventSend == nil or noEventSend == false then + self:raiseEvent(self.LANE_NUMBER_EVENT,self.laneNumber:get()) + end end function MultiToolsSetting:onChange() + self.laneNumber:refresh(self:get()) -- TODO: consolidate the (poorly named) laneNumber and laneOffset and this into a single setting as they -- can only change together (instead of having logic all over the place according to the good old CP practices) if self:get() % 2 == 0 then - courseplay:changeLaneNumber(self.vehicle, 1) + self.laneNumber:set(1) else - courseplay:changeLaneNumber(self.vehicle, 0, true) + self.laneNumber:set(0) + end +end + +---@class LaneNumberSetting : SettingList +LaneNumberSetting = CpObject(SettingList) +function LaneNumberSetting:init(vehicle,multiToolsSetting) + self.multiToolsSetting = multiToolsSetting + self:refresh(multiToolsSetting:get()) + SettingList.init(self, 'laneNumber', 'COURSEPLAY_LANE_OFFSET', 'COURSEPLAY_LANE_OFFSET', + vehicle, self.values, self.texts) +end + +function LaneNumberSetting:refresh(numDrivers) + local evenNumOfDrivers = numDrivers % 2 == 0 + local maxLeft,maxRight = self:getMaxValues(numDrivers) + self.texts = {} + self.values = {} + for i = maxLeft, maxRight do + if not evenNumOfDrivers or i ~= 0 and evenNumOfDrivers then + table.insert(self.values, i) + local text = "" + if i>0 then + text = string.format("%d %s",i,courseplay:loc('COURSEPLAY_RIGHT')) + elseif i<0 then + text = string.format("%d %s",i,courseplay:loc('COURSEPLAY_LEFT')) + else + text = courseplay:loc('COURSEPLAY_CENTER') + end + table.insert(self.texts, text) + end end end +function LaneNumberSetting:getMaxValues(numDrivers) + local maxRight = math.floor(numDrivers/2) + return -maxRight,maxRight +end + ---@class IslandBypassModeSetting : SettingList IslandBypassModeSetting = CpObject(SettingList) @@ -507,6 +590,138 @@ function HeadlandPassesSetting:init(vehicle) vehicle, self.values, self.texts) end +---@class FieldEdgePathSetting : IntSetting +FieldEdgePathSetting = CpObject(IntSetting) +FieldEdgePathSetting.EMPTY = 0 +FieldEdgePathSetting.MAX_FIELDS = 250 +function FieldEdgePathSetting:init(vehicle,selectedFieldSetting) + self.selectedFieldSetting = selectedFieldSetting + IntSetting.init(self,"fieldEdgePath","","",vehicle,0,self.MAX_FIELDS,self.EMPTY) + self.visible = false + self.customFieldPath = nil + self.currentFieldNumExists = false + -- All the labels, tooltips. + self.labels = { + ["create"] = courseplay:loc('COURSEPLAY_SCAN_CURRENT_FIELD_EDGES'), + ["currentNr"] = courseplay:loc('COURSEPLAY_CURRENT_FIELD_EDGE_PATH_NUMBER'), + ["overwritePath"] = courseplay:loc('COURSEPLAY_OVERWRITE_CUSTOM_FIELD_EDGE_PATH_IN_LIST'), + ["addNewPath"] = courseplay:loc('COURSEPLAY_ADD_CUSTOM_FIELD_EDGE_PATH_TO_LIST'), + ["clear"] = courseplay:loc('COURSEPLAY_CLEAR_CUSTOM_FIELD_EDGE_PATH'), + ["visibility"] = courseplay:loc('COURSEPLAY_CHANGE_VISIBILITY_CUSTOM_FIELD_EDGE_PATH'), + } +end + +--- After a change, check if the current selected field is already existing. +function FieldEdgePathSetting:onChange() + local value = self:get() + if value > 0 then + self.currentFieldNumExists = CpFieldUtil.isFieldNumberValid(value) + end +end + +--- Generates a new field edge path. +function FieldEdgePathSetting:createPath() + if not self.customFieldPath then + local customField = CpFieldUtil.generateCustomFieldEdgePath(self.vehicle) + if customField.numPoints > 0 then + self.customFieldPath = customField + self.visible = true + end + end +end + +--- Converts a generated field edge path to a field and saves it. +function FieldEdgePathSetting:savePath() + if self.customFieldPath ~= nil then + local field = CpFieldUtil.saveCustomFieldEdgePathAsField(self.customFieldPath,self:get()) + self:sendEvent(field) + self:clearPath() + end +end + +---@param field table +function FieldEdgePathSetting:sendEvent(field) + CustomFieldEvent.sendEvent(field) +end + +--- Clears the generated field edge path- +function FieldEdgePathSetting:clearPath() + self.customFieldPath = nil + self.visible = false + self.currentFieldNumExists = false + self:set(self.EMPTY,true) +end + +--- Changes the visibility of a generated field edge path. +function FieldEdgePathSetting:toggleVisibility() + if self.customFieldPath then + self.visible = not self.visible + end +end + +--- Is a field edge path generated ? +function FieldEdgePathSetting:hasUnsavedCustomFieldPath() + return self.customFieldPath~=nil +end + +--- Is the field number ~=0 ? +function FieldEdgePathSetting:isValidNumber() + return self:get() ~= self.EMPTY +end + +--- Is the generated field edge visible ? +function FieldEdgePathSetting:isUnsavedCustomFieldPathVisible() + return self:hasUnsavedCustomFieldPath() and self.visible +end + +--- Displays the generated field edge path. +function FieldEdgePathSetting:showCustomFieldEdgePath() + if self:isUnsavedCustomFieldPathVisible() then + CpFieldUtil.showFieldEdgePath(self.vehicle,self.customFieldPath) + end +end + +function FieldEdgePathSetting:isDisabled() + return self.vehicle:getIsCourseplayDriving() + or self.vehicle.cp.canDrive + or self.vehicle.cp.isRecording + or self.vehicle.cp.recordingIsPaused +end + +function FieldEdgePathSetting:getCreateLabel() + return self.labels.create +end + +function FieldEdgePathSetting:getCurrentNrLabel() + return self.labels.currentNr +end + +function FieldEdgePathSetting:getClearLabel() + return self.labels.clear +end + +function FieldEdgePathSetting:getVisibilityLabel() + return self.labels.visibility +end + +--- This is a tooltip for the save button. +--- The text changes if the field is already generated or not. +function FieldEdgePathSetting:getSaveLabel() + local str = self.currentFieldNumExists and self.labels.overwritePath or self.labels.addNewPath + return self:isValidNumber() and string.format(str,self:get()) or "" +end + +function FieldEdgePathSetting:isSyncAllowed() + return false +end + +function FieldEdgePathSetting:getText() + if not self:isValidNumber() then + return "---" + end + return CpFieldUtil.getFieldName(self:get()) or IntSetting.getText(self) +end + --- Global course generator settings (read from the XML, may be added to the UI later when needed): --- --- Minimum radius in meters where a lane change on the headland is allowed. This is to ensure that @@ -573,6 +788,7 @@ function SettingsContainer.createCourseGeneratorSettings(vehicle) container:addSetting(IslandBypassModeSetting, vehicle) container:addSetting(HeadlandOverlapPercent, vehicle) container:addSetting(ShowSeedCalculatorSetting, vehicle) + container:addSetting(FieldEdgePathSetting,vehicle,container.selectedField) return container end diff --git a/course-generator/cp.lua b/course-generator/cp.lua index 8294e3893..5a0ded1ea 100644 --- a/course-generator/cp.lua +++ b/course-generator/cp.lua @@ -218,8 +218,6 @@ function courseGenerator.generate(vehicle) if CpManager.isMP then CourseEvent.sendEvent(vehicle, vehicle.Waypoints) CourseplayEvent.sendEvent(vehicle, "self.cp.courseWorkWidth", vehicle.cp.courseWorkWidth) -- need a setting for this one - CourseplayEvent.sendEvent(vehicle, "self.cp.workWidth", vehicle.cp.workWidth) -- need a setting for this one - CourseplayEvent.sendEvent(vehicle, "self.cp.laneNumber", vehicle.cp.laneNumber) -- need a setting for this one --setMultiTools end diff --git a/course_management.lua b/course_management.lua index 273fd607c..0082161c5 100644 --- a/course_management.lua +++ b/course_management.lua @@ -352,9 +352,7 @@ function courseplay:copyCourse(vehicle) --MultiTools if src.cp.courseGeneratorSettings.multiTools:get() > 1 then - vehicle.cp.workWidth = src.cp.workWidth vehicle.cp.courseWorkWidth = src.cp.courseWorkWidth - vehicle.cp.manualWorkWidth = src.cp.manualWorkWidth vehicle.cp.courseGeneratorSettings.multiTools:set(src.cp.courseGeneratorSettings.multiTools:get()) else vehicle.cp.courseGeneratorSettings.multiTools:set(1) diff --git a/courseplay.lua b/courseplay.lua index d3c4ae2f2..119823477 100644 --- a/courseplay.lua +++ b/courseplay.lua @@ -32,6 +32,7 @@ courseplay.courses = {}; courseplay.settings = {}; courseplay.hud = {}; courseplay.buttons = {}; +---@class courseplay.fields courseplay.fields = {}; courseplay.generation = {}; courseplay.lights = {}; @@ -138,6 +139,7 @@ local function initialize() 'Events/CourseEvent', 'Events/InfoTextEvent', 'Events/CommandEvents', + 'Events/CustomFieldEvent', 'Generic/LinkedList' }; @@ -229,23 +231,19 @@ local function setGlobalData() [1]={name='self.cp.canDrive',dataFormat='Bool'}, [2]={name='self.cp.drivingDirReverse',dataFormat='Bool'}, - [3]={name='self.cp.fieldEdge.customField.isCreated',dataFormat='Bool'}, - [4]={name='self.cp.fieldEdge.customField.fieldNum',dataFormat='Int'}, - [5]={name='self.cp.fieldEdge.customField.selectedFieldNumExists',dataFormat='Bool'}, - [6]={name='self.cp.fieldEdge.selectedField.fieldNum',dataFormat='Int'}, - [7]={name='self.cp.isDriving',dataFormat='Bool'}, - [8]={name='self.cp.hud.openWithMouse',dataFormat='Bool'}, - [9]={name='self.cp.workWidth',dataFormat='Float'}, - [10]={name='self.cp.coursePlayerNum',dataFormat='Int'}, --?? - [11]={name='self.cp.laneOffset',dataFormat='Float'}, - [12]={name='self.cp.hud.currentPage',dataFormat='Int'}, - [13]={name='self.cp.waypointIndex',dataFormat='Int'}, - [14]={name='self.cp.isRecording',dataFormat='Bool'}, - [15]={name='self.cp.recordingIsPaused',dataFormat='Bool'}, + [3]={name='self.cp.isDriving',dataFormat='Bool'}, + [4]={name='self.cp.hud.openWithMouse',dataFormat='Bool'}, + [5]={name='self.cp.coursePlayerNum',dataFormat='Int'}, --?? + [6]={name='self.cp.hud.currentPage',dataFormat='Int'}, + [7]={name='self.cp.waypointIndex',dataFormat='Int'}, + [8]={name='self.cp.isRecording',dataFormat='Bool'}, + [9]={name='self.cp.recordingIsPaused',dataFormat='Bool'}, } - + ---@type SettingsContainer courseplay.globalSettings = SettingsContainer.createGlobalSettings() + ---@type SettingsContainer courseplay.globalCourseGeneratorSettings = SettingsContainer.createGlobalCourseGeneratorSettings() + ---@type SettingsContainer courseplay.globalPathfinderSettings = SettingsContainer.createGlobalPathfinderSettings() end; diff --git a/courseplay_event.lua b/courseplay_event.lua index 240c7dc84..0eaa36e85 100644 --- a/courseplay_event.lua +++ b/courseplay_event.lua @@ -235,32 +235,7 @@ function CourseplayJoinFixEvent:writeStream(streamId, connection) print(string.format("\t### CourseplayMultiplayer: writing %d custom fields ", fieldsCount)) for id, field in pairs(courseplay.fields.fieldData) do if field.isCustom then - streamDebugWriteString(streamId, field.name) - self:debugWrite(field.name,"field name") - streamDebugWriteInt32(streamId, field.numPoints) - self:debugWrite(field.numPoints,"field numPoints") - streamDebugWriteBool(streamId, field.isCustom) - self:debugWrite(field.isCustom,"field isCustom") - streamDebugWriteInt32(streamId, field.fieldNum) - self:debugWrite(field.fieldNum,"field fieldNum") - streamDebugWriteInt32(streamId, field.dimensions.minX) - self:debugWrite(field.dimensions.min,"field minX") - streamDebugWriteInt32(streamId, field.dimensions.maxX) - self:debugWrite(field.dimensions.maxX,"field maxX") - streamDebugWriteInt32(streamId, field.dimensions.minZ) - self:debugWrite(field.dimensions.minZ,"field minZ") - streamDebugWriteInt32(streamId, field.dimensions.maxZ) - self:debugWrite(field.dimensions.maxZ,"field maxZ") - streamDebugWriteInt32(streamId, #(field.points)) - self:debugWrite(field.points,"field points") - for p = 1, #(field.points) do - streamDebugWriteFloat32(streamId, field.points[p].cx) - self:debugWrite(field.points[p].cx,"field cx") - streamDebugWriteFloat32(streamId, field.points[p].cy) - self:debugWrite(field.points[p].cy,"field cy") - streamDebugWriteFloat32(streamId, field.points[p].cz) - self:debugWrite(field.points[p].cz,"field cz") - end + CustomFieldEvent.writeField(field,streamId) end end end; @@ -327,37 +302,8 @@ function CourseplayJoinFixEvent:readStream(streamId, connection) print(string.format("\t### CourseplayMultiplayer: reading %d custom fields ", fieldsCount)) courseplay.fields.fieldData = {} for i = 1, fieldsCount do - local name = streamReadString(streamId) - self:debugRead(name,"field name") - local numPoints = streamReadInt32(streamId) - self:debugRead(numPoints,"field numPoints") - local isCustom = streamReadBool(streamId) - self:debugRead(isCustom,"field isCustom") - local fieldNum = streamReadInt32(streamId) - self:debugRead(fieldNum,"field fieldNum") - local minX = streamReadInt32(streamId) - self:debugRead(minX,"field minX") - local maxX = streamReadInt32(streamId) - self:debugRead(maxX,"field maxX") - local minZ = streamReadInt32(streamId) - self:debugRead(minZ,"field minZ") - local maxZ = streamReadInt32(streamId) - self:debugRead(maxZ,"field maxZ") - local ammountPoints = streamReadInt32(streamId) - self:debugRead(ammountPoints,"field numPoints") - local waypoints = {} - for w = 1, ammountPoints do - local cx = streamReadFloat32(streamId) - self:debugRead(cx,"field cx") - local cy = streamReadFloat32(streamId) - self:debugRead(cy,"field cy") - local cz = streamReadFloat32(streamId) - self:debugRead(cz,"field cz") - local wp = { cx = cx, cy = cy, cz = cz} - table.insert(waypoints, wp) - end - local field = { name = name, numPoints = numPoints, isCustom = isCustom, fieldNum = fieldNum, points = waypoints, dimensions = {minX = minX, maxX = maxX, minZ = minZ, maxZ = maxZ}} - courseplay.fields.fieldData[fieldNum] = field + local field = CustomFieldEvent.readField(streamId) + courseplay.fields.fieldData[field.fieldNum] = field end print("\t### CourseplayMultiplayer: courses/folders reading end") end; diff --git a/fields.lua b/fields.lua index b3dbd1c09..34f4fad1c 100644 --- a/fields.lua +++ b/fields.lua @@ -614,3 +614,94 @@ function courseplay.fields:getClosestDistanceToFieldEdge(fieldNum, x, z) end return closestDistance end + + +CpFieldUtil = {} + +--- Generates a custom field edge path, relative to the current vehicle position. +---@param vehicle table +---@return table data custom field edge path. +function CpFieldUtil.generateCustomFieldEdgePath(vehicle) + local field = {} + local x,y,z = getWorldTranslation(vehicle.rootNode) + local isField = x and z and courseplay:isField(x, z, 0, 0) --TODO: use width/height of 0.1 ? + courseplay.fields:dbg(string.format("Custom field scan: x,z=%.1f,%.1f, isField=%s", x, z, tostring(isField)), 'customLoad') + if isField then + local edgePoints = courseplay.fields:setSingleFieldEdgePath(vehicle.rootNode, x, z, courseplay.fields.scanStep, 2000, 10, nil, true, 'customLoad') + field.points = edgePoints + field.numPoints = edgePoints ~= nil and #edgePoints or 0 + end; + return field +end; + +--- Saves a custom field edge path as custom field. +---@param data table field edge path data. +---@param ix number field number to save as. +---@return table customField that was added. +function CpFieldUtil.saveCustomFieldEdgePathAsField(data,ix) + data.fieldNum = ix + data.name = string.format("%s %d (%s)", courseplay:loc('COURSEPLAY_FIELD'), data.fieldNum, courseplay:loc('COURSEPLAY_USER')) + data.isCustom = true + local area, _, dimensions = courseplay.fields:getPolygonData(data.points, nil, nil, true) + data.areaSqm = area + data.areaHa = area / 10000 + data.dimensions = dimensions + courseplay.fields.fieldData[data.fieldNum] = data + courseplay.fields.numAvailableFields = table.maxn(courseplay.fields.fieldData) + return data +end + +--- Displays a custom field edge path. +---@param vehicle table +---@param fieldData table field edge path data. +function CpFieldUtil.showFieldEdgePath(vehicle,fieldData) + + local function show(points,numPoints) + if numPoints > 0 then + local pointHeight = 3; + for i,point in pairs(points) do + if i < numPoints then + local nextPoint = points[i + 1] + cpDebug:drawLine(point.cx,point.cy+pointHeight,point.cz, 0,0,1, nextPoint.cx,nextPoint.cy+pointHeight,nextPoint.cz) + + if i == 1 then + cpDebug:drawPoint(point.cx, point.cy + pointHeight, point.cz, 0,1,0) + else + cpDebug:drawPoint(point.cx, point.cy + pointHeight, point.cz, 1,1,0) + end + else + cpDebug:drawPoint(point.cx, point.cy + pointHeight, point.cz, 1,0,0) + end + end + end + end + + if fieldData then + show(fieldData.points,fieldData.numPoints) + else + local points,numPoints = nil,0 + points = courseplay.fields.fieldData[vehicle.cp.courseGeneratorSettings.selectedField:get()].points + numPoints = courseplay.fields.fieldData[vehicle.cp.courseGeneratorSettings.selectedField:get()].numPoints + show(points,numPoints) + end +end + +--- Does a field with the number exist ? +---@param num number +---@return boolean +function CpFieldUtil.isFieldNumberValid(num) + return courseplay.fields.fieldData[num] ~= nil +end + +--- Saves a field send from the server. +---@param field courseplay.fields.fieldData +function CpFieldUtil.saveFieldFromNetwork(field) + courseplay.fields.fieldData[field.fieldNum] = field +end + +function CpFieldUtil.getFieldName(ix) + local field = courseplay.fields.fieldData[ix] + if field then + return field.isCustom and string.format("%d(%s)",ix,courseplay:loc("COURSEPLAY_USER")) or string.format("%d",ix) + end +end \ No newline at end of file diff --git a/gui/CourseGeneratorScreen.lua b/gui/CourseGeneratorScreen.lua index d7c31c7a4..c1289ea9d 100644 --- a/gui/CourseGeneratorScreen.lua +++ b/gui/CourseGeneratorScreen.lua @@ -33,6 +33,7 @@ function CourseGeneratorScreen:new(vehicle) self.returnScreenName = ""; self.state = CourseGeneratorScreen.SHOW_NOTHING self.vehicle = vehicle + ---@type CourseGeneratorSettingsContainer self.settings = vehicle.cp.courseGeneratorSettings self.directions = {} -- map to look up gui element state from angle @@ -88,7 +89,6 @@ end function CourseGeneratorScreen:onOpen() g_currentMission.isPlayerFrozen = true - self.settings.selectedField:refresh() -- work width not set if self.settings.workWidth:is(0) then @@ -447,6 +447,8 @@ end function courseplay:openAdvancedCourseGeneratorSettings( vehicle ) --- Prevent Dialog from locking up mouse and keyboard when closing it. courseplay:lockContext(false); + --- This settings needs to be refreshed here, as there are problems in MP otherwise. + vehicle.cp.courseGeneratorSettings.selectedField:refresh() g_courseGeneratorScreen = CourseGeneratorScreen:new(vehicle) g_gui:loadProfiles( self.path .. "gui/guiProfiles.xml" ) g_gui:loadGui( self.path .. "gui/CourseGeneratorScreen.xml", "CourseGeneratorScreen", g_courseGeneratorScreen) diff --git a/hud.lua b/hud.lua index 82c69908e..3a6182dba 100644 --- a/hud.lua +++ b/hud.lua @@ -464,8 +464,9 @@ function courseplay.hud:setContent(vehicle) end; --setup bottomInfo texts - if vehicle.cp.timeRemaining ~= nil then - local timeRemaining = courseplay:sekToTimeFormat(vehicle.cp.timeRemaining) + local timeRemaining = vehicle.cp.driver.getRemainingTime and vehicle.cp.driver:getRemainingTime() + if timeRemaining ~= nil then + timeRemaining = courseplay:sekToTimeFormat(timeRemaining) vehicle.cp.hud.content.bottomInfo.timeRemainingText = ('%02.f:%02.f:%02.f'):format(timeRemaining.nHours,timeRemaining.nMins,timeRemaining.nSecs) else vehicle.cp.hud.content.bottomInfo.timeRemainingText = nil @@ -606,10 +607,11 @@ function courseplay.hud:renderHud(vehicle) courseplay:setFontSettings('white', false); end; - --field edge path - if vehicle:getIsActive() and not vehicle.cp.canDrive and vehicle.cp.fieldEdge.customField.show and vehicle.cp.fieldEdge.customField.points ~= nil then - courseplay:showFieldEdgePath(vehicle, "customField"); - end; + --- Displays a custom field path, if it was generated. + local setting = vehicle.cp.courseGeneratorSettings.fieldEdgePath + if vehicle:getIsActive() and setting:isUnsavedCustomFieldPathVisible() then + setting:showCustomFieldEdgePath() + end end; @@ -714,14 +716,11 @@ function courseplay.hud:updatePageContent(vehicle, page) --ReturnToFirstPointSetting if not vehicle.cp.settings.returnToFirstPoint:isDisabled() then self:enableButtonWithFunction(vehicle,page, 'changeByX',vehicle.cp.settings.returnToFirstPoint) - self:disableButtonWithFunction(vehicle,page, 'setCustomSingleFieldEdge') vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.returnToFirstPoint:getLabel() vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.returnToFirstPoint:getText() else self:disableButtonWithFunction(vehicle,page, 'changeByX',vehicle.cp.settings.returnToFirstPoint) forceUpdate = true -- force reload of this page if functionToCall changed - entry.functionToCall = 'setCustomSingleFieldEdge' - self:enableButtonWithFunction(vehicle,page, 'setCustomSingleFieldEdge') courseplay.hud:setReloadPageOrder(vehicle, page, true); end @@ -828,96 +827,32 @@ function courseplay.hud:updatePageContent(vehicle, page) local setting = vehicle.cp.settings.turnDiameter vehicle.cp.hud.content.pages[page][line][1].text = setting:getLabel() vehicle.cp.hud.content.pages[page][line][2].text = setting:getText() - elseif entry.functionToCall == 'changeWorkWidth' then - vehicle.cp.hud.content.pages[page][line][1].text = courseplay:loc('COURSEPLAY_WORK_WIDTH'); - if vehicle.cp.manualWorkWidth then - vehicle.cp.hud.content.pages[page][line][2].text = string.format('%.1fm (mnl)', vehicle.cp.workWidth); - else - vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.workWidth ~= nil and string.format('%.1fm', vehicle.cp.workWidth) or '---'; - end - - elseif entry.functionToCall == 'changeLaneNumber' then - vehicle.cp.hud.content.pages[page][line][1].text = courseplay:loc('COURSEPLAY_LANE_OFFSET'); - if vehicle.cp.courseGeneratorSettings.multiTools:get() > 1 then - self:enableButtonWithFunction(vehicle,page, 'changeLaneNumber') - if vehicle.cp.laneNumber == 0 then - vehicle.cp.hud.content.pages[page][line][2].text = ('%s'):format(courseplay:loc('COURSEPLAY_CENTER')); - else - vehicle.cp.hud.content.pages[page][line][2].text = ('%d %s'):format(abs(vehicle.cp.laneNumber), courseplay:loc(vehicle.cp.laneNumber > 0 and 'COURSEPLAY_RIGHT' or 'COURSEPLAY_LEFT')); - end - else - self:disableButtonWithFunction(vehicle,page, 'changeLaneNumber') - end - - elseif entry.functionToCall == 'changeLaneOffset' then - vehicle.cp.hud.content.pages[page][line][1].text = courseplay:loc('COURSEPLAY_LANE_OFFSET'); - if vehicle.cp.courseGeneratorSettings.multiTools:get() == 1 then - self:enableButtonWithFunction(vehicle,page, 'changeLaneOffset') - if vehicle.cp.laneOffset and vehicle.cp.laneOffset ~= 0 then - vehicle.cp.hud.content.pages[page][line][2].text = ('%.1f%s (%s)'):format(abs(vehicle.cp.laneOffset), courseplay:loc('COURSEPLAY_UNIT_METER'), courseplay:loc(vehicle.cp.laneOffset > 0 and 'COURSEPLAY_RIGHT' or 'COURSEPLAY_LEFT')); - else - vehicle.cp.hud.content.pages[page][line][2].text = '---'; - end + elseif entry.functionToCall == 'workWidth:changeByX' then + local setting = vehicle.cp.courseGeneratorSettings.workWidth + vehicle.cp.hud.content.pages[page][line][1].text = setting:getLabel() + vehicle.cp.hud.content.pages[page][line][2].text = setting:getText() + elseif entry.functionToCall == 'multiTools:changeLaneNumber' then + local setting = vehicle.cp.courseGeneratorSettings.multiTools + vehicle.cp.hud.content.pages[page][line][1].text = setting.laneNumber:getLabel() + if setting:get() > 1 then + self:enableButtonWithFunction(vehicle,page, 'changeLaneNumber',setting) + vehicle.cp.hud.content.pages[page][line][2].text = setting.laneNumber:getText() else - self:disableButtonWithFunction(vehicle,page, 'changeLaneOffset') + self:disableButtonWithFunction(vehicle,page, 'changeLaneNumber',setting) end + elseif entry.functionToCall == 'turnOnField:toggle' then --TurnOnFieldSetting vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.turnOnField:getLabel() vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.turnOnField:getText() - elseif entry.functionToCall == 'setCustomSingleFieldEdge' then - if not vehicle.cp.fieldEdge.customField.isCreated - and not vehicle:getIsCourseplayDriving() - and not vehicle.cp.canDrive - and not vehicle.cp.isRecording - and not vehicle.cp.recordingIsPaused then - self:enableButtonWithFunction(vehicle,page, 'setCustomSingleFieldEdge') - self:disableButtonWithFunction(vehicle,page, 'changeByX',vehicle.cp.settings.returnToFirstPoint) - vehicle.cp.hud.content.pages[page][line][1].text = courseplay:loc('COURSEPLAY_SCAN_CURRENT_FIELD_EDGES'); - else - self:disableButtonWithFunction(vehicle,page, 'setCustomSingleFieldEdge') - forceUpdate = true -- force reload of this page if functionToCall changed - entry.functionToCall = 'returnToFirstPoint:changeByX' - self:enableButtonWithFunction(vehicle, page, 'changeByX',vehicle.cp.settings.returnToFirstPoint) - courseplay.hud:setReloadPageOrder(vehicle, page, true); - end - elseif entry.functionToCall == 'setCustomFieldEdgePathNumber' then - if vehicle.cp.fieldEdge.customField.isCreated and not vehicle.cp.canDrive then - self:enableButtonWithFunction(vehicle,page, 'setCustomFieldEdgePathNumber') - vehicle.cp.hud.clearCustomFieldEdgeButton:setShow(true) - vehicle.cp.hud.toggleCustomFieldEdgePathShowButton:setShow(true) - vehicle.cp.hud.content.pages[page][line][1].text = courseplay:loc('COURSEPLAY_CURRENT_FIELD_EDGE_PATH_NUMBER'); - if vehicle.cp.fieldEdge.customField.fieldNum > 0 then - vehicle.cp.hud.content.pages[page][line][2].text = tostring(vehicle.cp.fieldEdge.customField.fieldNum); - else - vehicle.cp.hud.content.pages[page][line][2].text = '---'; - end; - else - self:disableButtonWithFunction(vehicle,page, 'setCustomFieldEdgePathNumber') - vehicle.cp.hud.clearCustomFieldEdgeButton:setShow(false) - vehicle.cp.hud.toggleCustomFieldEdgePathShowButton:setShow(false) - end - - - elseif entry.functionToCall == 'addCustomSingleFieldEdgeToList' then - if vehicle.cp.fieldEdge.customField.isCreated and vehicle.cp.fieldEdge.customField.fieldNum > 0 and not vehicle.cp.canDrive then - self:enableButtonWithFunction(vehicle,page, 'addCustomSingleFieldEdgeToList') - if vehicle.cp.fieldEdge.customField.selectedFieldNumExists then - vehicle.cp.hud.content.pages[page][line][1].text = string.format(courseplay:loc('COURSEPLAY_OVERWRITE_CUSTOM_FIELD_EDGE_PATH_IN_LIST'), vehicle.cp.fieldEdge.customField.fieldNum); - else - vehicle.cp.hud.content.pages[page][line][1].text = string.format(courseplay:loc('COURSEPLAY_ADD_CUSTOM_FIELD_EDGE_PATH_TO_LIST'), vehicle.cp.fieldEdge.customField.fieldNum); - end; - else - self:disableButtonWithFunction(vehicle,page, 'addCustomSingleFieldEdgeToList') - end + elseif entry.functionToCall == 'fieldEdgePath:changeByX' then + --- Updates the custom field edge buttons and the texts. + self:updateFieldEdgePathTextsAndButtons(vehicle,page,line) elseif entry.functionToCall == 'symmetricLaneChange:toggle' then --SymmetricLaneChangeSetting vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.symmetricLaneChange:getLabel() - if vehicle.cp.laneOffset ~= 0 then - vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.symmetricLaneChange:getText() - else - vehicle.cp.hud.content.pages[page][line][2].text = '---'; - end; + vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.symmetricLaneChange:getText() + elseif entry.functionToCall == 'changeToolOffsetX' then --Tool horizontal offset vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.toolOffsetX:getLabel() @@ -1141,9 +1076,6 @@ function courseplay.hud:updatePageContent(vehicle, page) --ShovelModeAIDriverTriggerHandlerIsActiveSetting vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.shovelModeAIDriverTriggerHandlerIsActive:getLabel() vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.shovelModeAIDriverTriggerHandlerIsActive:getText() - elseif entry.functionToCall == 'changeBladeWorkWidth' then - vehicle.cp.hud.content.pages[page][line][1].text = courseplay:loc('COURSEPLAY_MODE10_BLADE_WIDTH'); - vehicle.cp.hud.content.pages[page][line][2].text = ('%.1f%s'):format(vehicle.cp.workWidth, courseplay:loc('COURSEPLAY_UNIT_METER')); elseif entry.functionToCall == 'bunkerSpeed:changeByX' then --BunkerSpeedSetting vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.bunkerSpeed:getLabel() @@ -1251,6 +1183,68 @@ function courseplay.hud:updateWorkingToolPositionTexts(vehicle,page,line,setting end -------------------------------------- +--- Updates the custom field edge buttons and the texts. +---@param vehicle table +---@param page number +---@param line number +function courseplay.hud:updateFieldEdgePathTextsAndButtons(vehicle,page,line) + + local function disableBtn(btn) + btn:setDisabled(true) + btn:setShow(false) + + end + + local function enableBtn(btn) + btn:setDisabled(false) + btn:setShow(true) + end + + local setting = vehicle.cp.courseGeneratorSettings.fieldEdgePath + local isDisabled = setting:isDisabled() + --- Is a field edge path generated, but not saved yet. + local hasCustomFieldPath = setting:hasUnsavedCustomFieldPath() + local buttons = vehicle.cp.hud.fieldEdgePathButtons + if not isDisabled then + if hasCustomFieldPath then + disableBtn(buttons.create) + enableBtn(buttons.clear) + --- Only allow saving when a valid field number is displayed + --- and change the tooltip. + if setting:isValidNumber() then + enableBtn(buttons.save) + buttons.save:setToolTip(setting:getSaveLabel()) + else + disableBtn(buttons.save) + end + enableBtn(buttons.toggleVisibility) + for _,btn in pairs(buttons.selectNumber) do + enableBtn(btn) + end + vehicle.cp.hud.content.pages[page][line][1].text = setting:getCurrentNrLabel() + vehicle.cp.hud.content.pages[page][line][2].text = setting:getText() + else + enableBtn(buttons.create) + disableBtn(buttons.clear) + disableBtn(buttons.save) + disableBtn(buttons.toggleVisibility) + for _,btn in pairs(buttons.selectNumber) do + disableBtn(btn) + end + vehicle.cp.hud.content.pages[page][line][1].text = setting:getCreateLabel() + end + else + for name,btn in pairs(buttons) do + if name == "selectNumber" then + for _,b in pairs(btn) do + disableBtn(b) + end + else + disableBtn(btn) + end + end + end +end function courseplay.hud:setReloadPageOrder(vehicle, page, bool) -- self = courseplay.hud @@ -1522,9 +1516,27 @@ function courseplay.hud:setupCopyCourseButton(vehicle,page,line) vehicle.cp.hud.copyCourseButton = courseplay.button:new(vehicle, page, { 'iconSprite.png', 'copy' }, 'copyCourse', nil, self.buttonPosX[3], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h); end +--- Setups the custom field edge buttons. +---@param vehicle table +---@param page number +---@param line number function courseplay.hud:setupCustomFieldEdgeButtons(vehicle,page,line) - vehicle.cp.hud.clearCustomFieldEdgeButton = courseplay.button:new(vehicle, page, { 'iconSprite.png', 'cancel' }, 'clearCustomFieldEdge', nil, self.buttonPosX[4], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, nil, false); - vehicle.cp.hud.toggleCustomFieldEdgePathShowButton = courseplay.button:new(vehicle, page, { 'iconSprite.png', 'eye' }, 'toggleCustomFieldEdgePathShow', nil, self.buttonPosX[3], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, nil, false); + local setting = vehicle.cp.courseGeneratorSettings.fieldEdgePath + vehicle.cp.hud.fieldEdgePathButtons = { + clear = courseplay.button:new(vehicle, page, { 'iconSprite.png', 'cancel' }, 'clearPath', + nil, self.buttonPosX[4], self.linesButtonPosY[line], self.buttonSize.small.w, + self.buttonSize.small.h, line, nil, false, + nil,nil,setting:getClearLabel()):setSetting(setting), + toggleVisibility = courseplay.button:new(vehicle, page, { 'iconSprite.png', 'eye' }, 'toggleVisibility', + nil, self.buttonPosX[3], self.linesButtonPosY[line], self.buttonSize.small.w, + self.buttonSize.small.h, line, nil, false, + nil,nil,setting:getVisibilityLabel()):setSetting(setting), + save = courseplay.button:new(vehicle, page, { 'iconSprite.png', 'save' }, 'savePath', + nil, self.buttonPosX[5], self.linesButtonPosY[line], self.buttonSize.small.w, + self.buttonSize.small.h, line, nil, false):setSetting(setting), + create = self:addRowButton(vehicle,setting,'createPath', page, line, 1 ), + selectNumber = self:addSettingsRow(vehicle,setting,'changeByX', page, line, 2 ) + } end function courseplay.hud:setupCombinesListPageButtons(vehicle,page,assignedCombinesSetting) @@ -1618,7 +1630,7 @@ function courseplay.hud:setupCourseGeneratorButton(vehicle) end function courseplay.hud:setupCalculateWorkWidthButton(vehicle,page,line) - courseplay.button:new(vehicle, page, { 'iconSprite.png', 'calculator' }, 'calculateWorkWidth', nil, self.buttonPosX[3], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, nil, false); + courseplay.button:new(vehicle, page, { 'iconSprite.png', 'calculator' }, 'setToDefault', nil, self.buttonPosX[3], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, nil, false):setSetting(vehicle.cp.courseGeneratorSettings.workWidth); end function courseplay.hud:setupSetAutoToolOffsetXButton(vehicle,page,line) @@ -2153,10 +2165,9 @@ function courseplay.hud:setFieldWorkAIDriverContent(vehicle) --self:setupCourseGeneratorButton(vehicle) self:addRowButton(vehicle,vehicle.cp.settings.autoDriveMode,'changeByX', 1, 3, 1 ) self:addRowButton(vehicle,nil,'openAdvancedCourseGeneratorSettings', 1, 4, 1 ):setOnlyCallLocal() - self:addRowButton(vehicle,nil,'setCustomSingleFieldEdge', 1, 5, 1 ) - self:addSettingsRow(vehicle,nil,'setCustomFieldEdgePathNumber', 1, 5, 2 ) - self:setupCustomFieldEdgeButtons(vehicle,1,5) - self:addRowButton(vehicle,nil,'addCustomSingleFieldEdgeToList', 1, 6, 1 ) + + self:setupCustomFieldEdgeButtons(vehicle,1,6) + -- shown in place of the custom field row when a course is loaded self:addRowButton(vehicle,vehicle.cp.settings.returnToFirstPoint,'changeByX', 1, 5, 1 ) self:addRowButton(vehicle,vehicle.cp.settings.foldImplementAtEnd,'toggle', 6, 6, 1 ) @@ -2164,7 +2175,7 @@ function courseplay.hud:setFieldWorkAIDriverContent(vehicle) --page 3 settings self:enablePageButton(vehicle, 3) self:addSettingsRow(vehicle,vehicle.cp.settings.turnDiameter,'changeByX', 3, 1, 1 ) - self:addSettingsRow(vehicle,nil,'changeWorkWidth', 3, 2, 1, 0.1 ) + self:addSettingsRow(vehicle,vehicle.cp.courseGeneratorSettings.workWidth,'changeByX', 3, 2, 1) self:setupCalculateWorkWidthButton(vehicle,3, 2) self:addRowButton(vehicle,vehicle.cp.settings.convoyActive,'toggle', 3, 3, 1 ) self:addSettingsRow(vehicle,vehicle.cp.settings.convoyMinDistance,'changeByX', 3, 4, 1 ) @@ -2173,8 +2184,7 @@ function courseplay.hud:setFieldWorkAIDriverContent(vehicle) --page 8 fieldwork settings self:enablePageButton(vehicle, 8) - self:addSettingsRowWithArrows(vehicle,nil,'changeLaneNumber', 8, 1, 1 ) - self:addSettingsRowWithArrows(vehicle,nil,'changeLaneOffset', 8, 1, 2 ) + self:addSettingsRowWithArrows(vehicle,vehicle.cp.courseGeneratorSettings.multiTools,'changeLaneNumber', 8, 1, 1 ) self:addRowButton(vehicle,vehicle.cp.settings.symmetricLaneChange,'toggle', 8, 2, 1 ) self:addRowButton(vehicle,vehicle.cp.settings.turnOnField,'toggle', 8, 3, 1 ) self:addRowButton(vehicle,vehicle.cp.settings.useRealisticDriving,'toggle', 8, 4, 1 ) @@ -2285,7 +2295,7 @@ function courseplay.hud:setShovelModeAIDriverContent(vehicle) self:setupToolPositionButtons(vehicle,vehicle.cp.settings.frontloaderToolPositions,9,1) self:addRowButton(vehicle,vehicle.cp.settings.alwaysWaitForShovelPositions,'toggle', 9, 5, 1 ) self:addRowButton(vehicle,vehicle.cp.settings.shovelModeAIDriverTriggerHandlerIsActive,'toggle', 9, 8, 1 ) - self:addSettingsRow(vehicle,nil,'changeWorkWidth',9,6,1, 0.1) + self:addSettingsRow(vehicle,vehicle.cp.courseGeneratorSettings.workWidth,'changeByX', 9, 6, 1) self:setupCalculateWorkWidthButton(vehicle,9, 6) self:addSettingsRow(vehicle,vehicle.cp.settings.bunkerSpeed,'changeByX', 9, 7, 1 ) end @@ -2307,7 +2317,7 @@ function courseplay.hud:setLevelCompactAIDriverContent(vehicle) self:addRowButton(vehicle,vehicle.cp.settings.levelCompactSearchOnlyAutomatedDriver,'changeByX', 10, 1, 1) self:addRowButton(vehicle,vehicle.cp.settings.levelCompactSiloTyp,'changeByX', 10, 2, 1) self:addSettingsRow(vehicle,vehicle.cp.settings.levelCompactSearchRadius,'changeByX', 10, 3, 1 ) - self:addSettingsRow(vehicle,nil,'changeBladeWorkWidth', 10, 4, 1) + self:addSettingsRow(vehicle,vehicle.cp.courseGeneratorSettings.workWidth,'changeByX', 10, 4, 1) self:setupCalculateWorkWidthButton(vehicle,10, 4) self:addSettingsRow(vehicle,vehicle.cp.settings.bunkerSpeed,'changeByX', 10, 5, 1 ) -- self:addSettingsRow(vehicle,vehicle.cp.settings.levelCompactShieldHeight,'changeByX', 10, 6, 1 ) @@ -2318,10 +2328,9 @@ function courseplay.hud:setBaleCollectorAIDriverContent(vehicle) self:debug(vehicle,"setBaleCollectorAIDriverContent") self:addRowButton(vehicle,vehicle.cp.settings.autoDriveMode,'changeByX', 1, 3, 1 ) self:addSettingsRow(vehicle, vehicle.cp.settings.baleCollectionField,'changeByX', 1, 4, 1 ) - self:addRowButton(vehicle,nil,'setCustomSingleFieldEdge', 1, 5, 1 ) - self:addSettingsRow(vehicle,nil,'setCustomFieldEdgePathNumber', 1, 5, 2 ) - self:setupCustomFieldEdgeButtons(vehicle,1,5) - self:addRowButton(vehicle,nil,'addCustomSingleFieldEdgeToList', 1, 6, 1 ) + + self:setupCustomFieldEdgeButtons(vehicle,1,6) + -- shown in place of the custom field row when a course is loaded --page 3 settings @@ -2352,7 +2361,7 @@ function courseplay.hud:setMixerWagonAIDriverContent(vehicle) --page 9 self:enablePageButton(vehicle, 9) self:setupToolPositionButtons(vehicle,vehicle.cp.settings.mixerWagonToolPositions,9,1) - self:addSettingsRow(vehicle,nil,'changeWorkWidth',9,7,1, 0.1) + self:addSettingsRow(vehicle,vehicle.cp.courseGeneratorSettings.workWidth,'changeByX', 9, 7, 1) self:setupCalculateWorkWidthButton(vehicle,9, 7) self:addSettingsRow(vehicle,vehicle.cp.settings.bunkerSpeed,'changeByX', 9, 8, 1 ) end @@ -2360,7 +2369,7 @@ end function courseplay.hud:setBunkerSiloLoaderAIDriverContent(vehicle) --page 9 self:enablePageButton(vehicle, 9) - self:addSettingsRow(vehicle,nil,'changeWorkWidth',9,1,1, 0.1) + self:addSettingsRow(vehicle,vehicle.cp.courseGeneratorSettings.workWidth,'changeByX', 9, 1, 1) self:setupCalculateWorkWidthButton(vehicle,9, 1) end @@ -2387,14 +2396,15 @@ end function courseplay.hud:addSettingsRow(vehicle,setting,funct, hudPage, line, column,parameter ) self:debug(vehicle," addSettingsRow: "..tostring(funct)) local parameter = parameter or 1 - courseplay.button:new(vehicle, hudPage, { 'iconSprite.png', 'navMinus' }, funct, -parameter, self.buttonPosX[2], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, -5, false):setSetting(setting); - courseplay.button:new(vehicle, hudPage, { 'iconSprite.png', 'navPlus' }, funct, parameter, self.buttonPosX[1], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, 5, false):setSetting(setting); - courseplay.button:new(vehicle, hudPage, nil, funct, parameter, self.contentMinX, self.linesButtonPosY[line], self.contentMaxWidth, self.lineHeight, line, 5, true, true):setSetting(setting); + local b1 = courseplay.button:new(vehicle, hudPage, { 'iconSprite.png', 'navMinus' }, funct, -parameter, self.buttonPosX[2], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, -5, false):setSetting(setting); + local b2 = courseplay.button:new(vehicle, hudPage, { 'iconSprite.png', 'navPlus' }, funct, parameter, self.buttonPosX[1], self.linesButtonPosY[line], self.buttonSize.small.w, self.buttonSize.small.h, line, 5, false):setSetting(setting); + local b3 = courseplay.button:new(vehicle, hudPage, nil, funct, parameter, self.contentMinX, self.linesButtonPosY[line], self.contentMaxWidth, self.lineHeight, line, 5, true, true):setSetting(setting); if setting then vehicle.cp.hud.content.pages[hudPage][line][column].functionToCall = setting:getName()..":"..funct else vehicle.cp.hud.content.pages[hudPage][line][column].functionToCall = funct end + return {b1,b2,b3} end function courseplay.hud:addSettingsRowWithArrows(vehicle,setting,funct, hudPage, line, column ) diff --git a/settings.lua b/settings.lua index 805db7813..ccc3bae47 100644 --- a/settings.lua +++ b/settings.lua @@ -97,48 +97,6 @@ function courseplay:setHudPage(vehicle, pageNum) courseplay.hud:setReloadPageOrder(vehicle, vehicle.cp.hud.currentPage, true); end; -function courseplay:changeLaneOffset(vehicle, changeBy, force) - vehicle.cp.laneOffset = force or (courseplay:round(vehicle.cp.laneOffset, 1) + changeBy*0.1); - if abs(vehicle.cp.laneOffset) < 0.1 then - vehicle.cp.laneOffset = 0; - end; -end; - -function courseplay:changeLaneNumber(vehicle, changeBy, reset) - -- This function takes input from the hud. And calculates laneOffset by dividing tool workwidth and multiplying that - -- by the lane number counting outwards. - local toolsIsEven = vehicle.cp.courseGeneratorSettings.multiTools:get() % 2 == 0 - - if reset then - vehicle.cp.laneNumber = 0; - vehicle.cp.laneOffset = 0 - else - -- skip zero if multiTools is even - if toolsIsEven then - if vehicle.cp.laneNumber == -1 and changeBy > 0 then - changeBy = 2 - elseif vehicle.cp.laneNumber == 1 and changeBy < 0 then - changeBy = -2 - end - end - vehicle.cp.laneNumber = MathUtil.clamp(vehicle.cp.laneNumber + changeBy, - math.floor(vehicle.cp.courseGeneratorSettings.multiTools:get() / 2) * -1, - math.floor(vehicle.cp.courseGeneratorSettings.multiTools:get() / 2)); - local newOffset = 0 - if toolsIsEven then - if vehicle.cp.laneNumber > 0 then - newOffset = vehicle.cp.workWidth/2 + (vehicle.cp.workWidth*(vehicle.cp.laneNumber-1)) - else - newOffset = -vehicle.cp.workWidth/2 + (vehicle.cp.workWidth*(vehicle.cp.laneNumber+1)) - end - else - newOffset = vehicle.cp.workWidth * vehicle.cp.laneNumber - end - courseplay:changeLaneOffset(vehicle, nil , newOffset) - end; - -end; - --- These three tool offset function should be handled by the setting. function courseplay:changeToolOffsetX(vehicle, changeBy) vehicle.cp.settings.toolOffsetX:changeBy(changeBy * 0.1,true) @@ -157,70 +115,6 @@ function courseplay:changeToolOffsetZ(vehicle, changeBy, force, noDraw) courseplay:setCustomTimer(vehicle, 'showWorkWidth', 2); end; -function courseplay:calculateWorkWidth(vehicle, noDraw) - - if vehicle.cp.manualWorkWidth and noDraw ~= nil then - --courseplay:changeWorkWidth(vehicle, nil, vehicle.cp.manualWorkWidth, noDraw); - return - end - - courseplay:changeWorkWidth(vehicle, nil,vehicle.cp.courseGeneratorSettings.workWidth:getAutoWorkWidth(), noDraw); - -end; - -function courseplay:changeBladeWorkWidth(vehicle, changeBy, force, noDraw) - courseplay:changeWorkWidth(vehicle, changeBy/10, force, noDraw) -end - -function courseplay:changeWorkWidth(vehicle, changeBy, force, noDraw) - local isSetManually = false - if force == nil and noDraw == nil then - --print("is set manually") - isSetManually = true - elseif force ~= nil and noDraw ~= nil then - --print("is set by script") - if not vehicle.cp.isDriving and vehicle.cp.manualWorkWidth then - return - end - elseif force ~= nil and noDraw == nil then - vehicle.cp.manualWorkWidth = nil - courseplay:changeLaneNumber(vehicle, 0, true) - vehicle.cp.courseGeneratorSettings.multiTools:set(1) - --print("is set by calculate button") - end - if force then - if force == 0 then - return - end - local newWidth = max(courseplay:round(abs(force), 1), 0.1) - --vehicle.cp.workWidth = min(vehicle.cp.workWidth,newWidth); --TODO: check what is better:the smallest or the widest work width to consider - vehicle.cp.workWidth = newWidth - else - if vehicle.cp.workWidth + changeBy > 10 then - if abs(changeBy) == 0.1 and not (Input.keyPressedState[Input.KEY_lalt]) then -- pressing left Alt key enables to have small 0.1 steps even over 10.0 - changeBy = 0.5 * MathUtil.sign(changeBy); - elseif abs(changeBy) == 0.5 then - changeBy = 2 * MathUtil.sign(changeBy); - end; - end; - - if (vehicle.cp.workWidth < 10 and vehicle.cp.workWidth + changeBy > 10) or (vehicle.cp.workWidth > 10 and vehicle.cp.workWidth + changeBy < 10) then - vehicle.cp.workWidth = 10; - else - vehicle.cp.workWidth = max(vehicle.cp.workWidth + changeBy, 0.1); - end; - end; - if isSetManually then - vehicle.cp.manualWorkWidth = vehicle.cp.workWidth - end - if not noDraw then - courseplay:setCustomTimer(vehicle, 'showWorkWidth', 2); - end; - - courseplay.hud:setReloadPageOrder(vehicle, vehicle.cp.hud.currentPage, true); - -end; - --legancy Code in toolManager still using it! function courseplay:changeReverseSpeed(vehicle, changeBy, force, forceReloadPage) local speed = force or (vehicle.cp.speeds.reverse + changeBy); @@ -602,10 +496,10 @@ end function courseplay:validateCanSwitchMode(vehicle) vehicle:setCpVar('canSwitchMode', not vehicle:getIsCourseplayDriving() and not vehicle.cp.isRecording and - not vehicle.cp.recordingIsPaused and not vehicle.cp.fieldEdge.customField.isCreated,courseplay.isClient); - courseplay:debug(('%s: validateCanSwitchMode(): isDriving=%s, isRecording=%s, recordingIsPaused=%s, customField.isCreated=%s ==> canSwitchMode=%s'):format( + not vehicle.cp.recordingIsPaused ,courseplay.isClient); + courseplay:debug(('%s: validateCanSwitchMode(): isDriving=%s, isRecording=%s, recordingIsPaused=%s ==> canSwitchMode=%s'):format( nameNum(vehicle), tostring(vehicle:getIsCourseplayDriving()), tostring(vehicle.cp.isRecording), - tostring(vehicle.cp.recordingIsPaused), tostring(vehicle.cp.fieldEdge.customField.isCreated), tostring(vehicle.cp.canSwitchMode)), courseplay.DBG_UNCATEGORIZED); + tostring(vehicle.cp.recordingIsPaused), tostring(vehicle.cp.canSwitchMode)), courseplay.DBG_UNCATEGORIZED); end; function courseplay:reloadCoursesFromXML(vehicle) @@ -678,135 +572,6 @@ function courseplay:changeDebugChannelSection(vehicle, changeBy) end end - ---FIELD EDGE PATHS -function courseplay:createFieldEdgeButtons(vehicle) - if not vehicle.cp.fieldEdge.selectedField.buttonsCreated and courseplay.fields.numAvailableFields > 0 then - local w, h = courseplay.hud.buttonSize.small.w, courseplay.hud.buttonSize.small.h; - local mouseWheelArea = { - x = courseplay.hud.contentMinX, - w = courseplay.hud.contentMaxWidth, - h = courseplay.hud.lineHeight - }; - vehicle.cp.hud.showSelectedFieldEdgePathButton = courseplay.button:new(vehicle, 8, { 'iconSprite.png', 'eye' }, 'toggleSelectedFieldEdgePathShow', nil, courseplay.hud.buttonPosX[3], courseplay.hud.linesButtonPosY[1], w, h, 1, nil, false); - courseplay.button:new(vehicle, 8, { 'iconSprite.png', 'navUp' }, 'setFieldEdgePath', 1, courseplay.hud.buttonPosX[1], courseplay.hud.linesButtonPosY[1], w, h, 1, 5, false); - courseplay.button:new(vehicle, 8, { 'iconSprite.png', 'navDown' }, 'setFieldEdgePath', -1, courseplay.hud.buttonPosX[2], courseplay.hud.linesButtonPosY[1], w, h, 1, -5, false); - courseplay.button:new(vehicle, 8, nil, 'setFieldEdgePath', 1, mouseWheelArea.x, courseplay.hud.linesButtonPosY[1], mouseWheelArea.w, mouseWheelArea.h, 1, 5, true, true); - vehicle.cp.fieldEdge.selectedField.buttonsCreated = true; - end; -end; - -function courseplay:setFieldEdgePath(vehicle, changeDir, force) - vehicle.cp.courseGeneratorSettings.selectedField:changeByX(changeDir) - - --courseplay:toggleSelectedFieldEdgePathShow(vehicle, false); - if vehicle.cp.fieldEdge.customField.show then - courseplay:toggleCustomFieldEdgePathShow(vehicle, false); - end; -end; - -function courseplay:toggleSelectedFieldEdgePathShow(vehicle, force) - vehicle.cp.fieldEdge.selectedField.show = Utils.getNoNil(force, not vehicle.cp.fieldEdge.selectedField.show); - --print(string.format("%s: selectedField.show=%s", nameNum(vehicle), tostring(vehicle.cp.fieldEdge.selectedField.show))); - --courseplay.buttons:setActiveEnabled(vehicle, "selectedFieldShow"); -end; - ---CUSTOM SINGLE FIELD EDGE PATH -function courseplay:setCustomSingleFieldEdge(vehicle) - --print(string.format("%s: call setCustomSingleFieldEdge()", nameNum(vehicle))); - - local x,y,z = getWorldTranslation(vehicle.rootNode); - local isField = x and z and courseplay:isField(x, z, 0, 0); --TODO: use width/height of 0.1 ? - courseplay.fields:dbg(string.format("Custom field scan: x,z=%.1f,%.1f, isField=%s", x, z, tostring(isField)), 'customLoad'); - vehicle.cp.fieldEdge.customField.points = nil; - if isField then - local edgePoints = courseplay.fields:setSingleFieldEdgePath(vehicle.rootNode, x, z, courseplay.fields.scanStep, 2000, 10, nil, true, 'customLoad'); - vehicle.cp.fieldEdge.customField.points = edgePoints; - vehicle.cp.fieldEdge.customField.numPoints = edgePoints ~= nil and #edgePoints or 0; - end; - - --print(tableShow(vehicle.cp.fieldEdge.customField.points, nameNum(vehicle) .. " fieldEdge.customField.points")); - vehicle.cp.fieldEdge.customField.isCreated = vehicle.cp.fieldEdge.customField.points ~= nil; - courseplay:toggleCustomFieldEdgePathShow(vehicle, vehicle.cp.fieldEdge.customField.isCreated); - courseplay:validateCanSwitchMode(vehicle); -end; - -function courseplay:clearCustomFieldEdge(vehicle) - vehicle.cp.fieldEdge.customField.points = nil; - vehicle.cp.fieldEdge.customField.numPoints = 0; - vehicle.cp.fieldEdge.customField.isCreated = false; - courseplay:setCustomFieldEdgePathNumber(vehicle, nil, 0); - courseplay:toggleCustomFieldEdgePathShow(vehicle, false); - courseplay:validateCanSwitchMode(vehicle); -end; - -function courseplay:toggleCustomFieldEdgePathShow(vehicle, force) - vehicle.cp.fieldEdge.customField.show = Utils.getNoNil(force, not vehicle.cp.fieldEdge.customField.show); - --print(string.format("%s: customField.show=%s", nameNum(vehicle), tostring(vehicle.cp.fieldEdge.customField.show))); - --courseplay.buttons:setActiveEnabled(vehicle, "customFieldShow"); -end; - -function courseplay:setCustomFieldEdgePathNumber(vehicle, changeBy, force) - vehicle.cp.fieldEdge.customField.fieldNum = force or MathUtil.clamp(vehicle.cp.fieldEdge.customField.fieldNum + changeBy, 0, courseplay.fields.customFieldMaxNum); - vehicle.cp.fieldEdge.customField.selectedFieldNumExists = courseplay.fields.fieldData[vehicle.cp.fieldEdge.customField.fieldNum] ~= nil; - --print(string.format("%s: customField.fieldNum=%d, selectedFieldNumExists=%s", nameNum(vehicle), vehicle.cp.fieldEdge.customField.fieldNum, tostring(vehicle.cp.fieldEdge.customField.selectedFieldNumExists))); -end; - -function courseplay:addCustomSingleFieldEdgeToList(vehicle) - --print(string.format("%s: call addCustomSingleFieldEdgeToList()", nameNum(vehicle))); - local data = { - fieldNum = vehicle.cp.fieldEdge.customField.fieldNum; - points = vehicle.cp.fieldEdge.customField.points; - numPoints = vehicle.cp.fieldEdge.customField.numPoints; - name = string.format("%s %d (%s)", courseplay:loc('COURSEPLAY_FIELD'), vehicle.cp.fieldEdge.customField.fieldNum, courseplay:loc('COURSEPLAY_USER')); - isCustom = true; - }; - local area, _, dimensions = courseplay.fields:getPolygonData(data.points, nil, nil, true); - data.areaSqm = area; - data.areaHa = area / 10000; - data.dimensions = dimensions; - - courseplay.fields.fieldData[vehicle.cp.fieldEdge.customField.fieldNum] = data; - courseplay.fields.numAvailableFields = table.maxn(courseplay.fields.fieldData); - - --print(string.format("\tfieldNum=%d, name=%s, #points=%d", courseplay.fields.fieldData[vehicle.cp.fieldEdge.customField.fieldNum].fieldNum, courseplay.fields.fieldData[vehicle.cp.fieldEdge.customField.fieldNum].name, #courseplay.fields.fieldData[vehicle.cp.fieldEdge.customField.fieldNum].points)); - - --RESET - courseplay:setCustomFieldEdgePathNumber(vehicle, nil, 0); - courseplay:clearCustomFieldEdge(vehicle); - courseplay:toggleSelectedFieldEdgePathShow(vehicle, false); - --print(string.format("\t[AFTER RESET] fieldNum=%d, points=%s, fieldEdge.customField.isCreated=%s", vehicle.cp.fieldEdge.customField.fieldNum, tostring(vehicle.cp.fieldEdge.customField.points), tostring(vehicle.cp.fieldEdge.customField.isCreated))); -end; - -function courseplay:showFieldEdgePath(vehicle, pathType) - local points, numPoints = nil, 0; - if pathType == "customField" then - points = vehicle.cp.fieldEdge.customField.points; - numPoints = vehicle.cp.fieldEdge.customField.numPoints; - elseif pathType == "selectedField" then - points = courseplay.fields.fieldData[vehicle.cp.courseGeneratorSettings.selectedField:get()].points; - numPoints = courseplay.fields.fieldData[vehicle.cp.courseGeneratorSettings.selectedField:get()].numPoints; - end; - - if numPoints > 0 then - local pointHeight = 3; - for i,point in pairs(points) do - if i < numPoints then - local nextPoint = points[i + 1]; - cpDebug:drawLine(point.cx,point.cy+pointHeight,point.cz, 0,0,1, nextPoint.cx,nextPoint.cy+pointHeight,nextPoint.cz); - - if i == 1 then - cpDebug:drawPoint(point.cx, point.cy + pointHeight, point.cz, 0,1,0); - else - cpDebug:drawPoint(point.cx, point.cy + pointHeight, point.cz, 1,1,0); - end; - else - cpDebug:drawPoint(point.cx, point.cy + pointHeight, point.cz, 1,0,0); - end; - end; - end; -end; - function courseplay:setEngineState(vehicle, on) if vehicle == nil or on == nil or vehicle.spec_motorized.isMotorStarted == on then return; @@ -1060,7 +825,11 @@ end function Setting:raiseEvent(eventIx,value) local event = self:getEvent(eventIx) value = event.getValueFunc and event.getValueFunc(self) or value - SettingEvent.sendEvent(self.vehicle,self,event,value) + if self.vehicle ~= nil then + VehicleSettingEvent.sendEvent(self.vehicle,self,event,value) + else + GlobalSettingEvent.sendEvent(self,event,value) + end end --- Setting debug. @@ -1095,6 +864,11 @@ function Setting:debugReadStream(value,valueName) end end +--- Is synchronizing of this setting allowed. +function Setting:isSyncAllowed() + return self.syncValue +end + ---@class FloatSetting :Setting FloatSetting = CpObject(Setting) --- @param name string name of this settings, will be used as an identifier in containers and XML @@ -1139,7 +913,7 @@ end function FloatSetting:set(value,noEventSend) self.value = value if noEventSend == nil or noEventSend == false then - if self.syncValue then + if self:isSyncAllowed() then self:sendEvent(value) end end @@ -1201,7 +975,7 @@ function IntSetting:set(value,noEventSend) if minOk and maxOk and value then self.value = value if noEventSend == nil or noEventSend == false then - if self.syncValue then + if self:isSyncAllowed() then self:sendEvent(value) end end @@ -1299,7 +1073,7 @@ function SettingList:setToIx(ix, noEventSend) self:onChange() self.lastChangeTimeMilliseconds = g_time if noEventSend == nil or noEventSend == false then - if self.syncValue then + if self:isSyncAllowed() then self:sendEvent() end end @@ -2262,7 +2036,7 @@ function FieldNumberSetting:loadFields() table.sort( values, function( a, b ) return a < b end ) -- then convert to text for _, fieldNumber in ipairs(values) do - table.insert(texts, tostring(fieldNumber)) + table.insert(texts, CpFieldUtil.getFieldName(fieldNumber)) end return values, texts end @@ -3494,24 +3268,30 @@ function ShowVisualWaypointsSetting:init(vehicle) } ) self:set(1) - self.syncValue = false end function ShowVisualWaypointsSetting:onChange() courseplay.signs:setSignsVisibility(self.vehicle) end +function ShowVisualWaypointsSetting:isSyncAllowed() + return false +end + ---@class ShowVisualWaypointsCrossPointSetting : BooleanSetting ShowVisualWaypointsCrossPointSetting = CpObject(BooleanSetting) function ShowVisualWaypointsCrossPointSetting:init(vehicle) BooleanSetting.init(self, 'showVisualWaypointsCrossPoint','-', '-', vehicle) self:set(false) - self.syncValue = false end function ShowVisualWaypointsCrossPointSetting:onChange() courseplay.signs:setSignsVisibility(self.vehicle) end +function ShowVisualWaypointsCrossPointSetting:isSyncAllowed() + return false +end + ---@class ConvoyActiveSetting : BooleanSetting ConvoyActiveSetting = CpObject(BooleanSetting) function ConvoyActiveSetting:init(vehicle) @@ -4236,7 +4016,10 @@ function CourseDrawModeSetting:init(vehicle) } SettingList.init(self,"courseDrawMode","","",vehicle,values,texts) self:set(self.COURSE_2D_DISPLAY_OFF) - self.syncValue = false +end + +function CourseDrawModeSetting:isSyncAllowed() + return false end function CourseDrawModeSetting:isDeactivated() @@ -4312,7 +4095,7 @@ end function SettingsContainer:onReadStream(stream) for k, setting in pairs(self) do - if self.validateSetting(setting) and setting.syncValue then + if self.validateSetting(setting) and setting:isSyncAllowed() then setting:onReadStream(stream) end end @@ -4320,7 +4103,7 @@ end function SettingsContainer:onWriteStream(stream) for k, setting in pairs(self) do - if self.validateSetting(setting) and setting.syncValue then + if self.validateSetting(setting) and setting:isSyncAllowed() then setting:onWriteStream(stream) end end diff --git a/start_stop.lua b/start_stop.lua index b7156b853..2966a84c5 100644 --- a/start_stop.lua +++ b/start_stop.lua @@ -135,12 +135,6 @@ function courseplay:stop(self) courseplay:resetCustomTimer(self, 'slippingStage2'); - if self.cp.manualWorkWidth ~= nil then - courseplay:changeWorkWidth(self, nil, self.cp.manualWorkWidth, true) - if self.cp.hud.currentPage == courseplay.hud.PAGE_COURSE_GENERATION then - courseplay.hud:setReloadPageOrder(self, self.cp.hud.currentPage, true); - end - end self.cp.totalLength, self.cp.totalLengthOffset = 0, 0; self.cp.numWorkTools = 0; diff --git a/toolManager.lua b/toolManager.lua index 54e660dea..ff51dc9b7 100644 --- a/toolManager.lua +++ b/toolManager.lua @@ -64,7 +64,6 @@ function courseplay:resetTools(vehicle) courseplay.hud:setReloadPageOrder(vehicle, -1, true); - courseplay:calculateWorkWidth(vehicle, true); end; function courseplay:isAttachedMixer(workTool) @@ -399,74 +398,94 @@ function courseplay:resetTipTrigger(vehicle, changeToForward) end end; +WorkWidthUtil = {} + --- Iterator for all work areas of an object -function courseplay:workAreaIterator(object) +---@param object table +function WorkWidthUtil.workAreaIterator(object) local i = 0 return function() i = i + 1 - local wa = object and object.getWorkAreaByIndex and object:getWorkAreaByIndex(i) + local wa = WorkWidthUtil.hasValidWorkArea(object) and object:getWorkAreaByIndex(i) if wa then return i, wa end end end -function courseplay:hasWorkAreas(object) - return object and object.getWorkAreaByIndex and object:getWorkAreaByIndex(1) +--- Gets work areas if possible. +---@param object table +function WorkWidthUtil.hasWorkAreas(object) + return WorkWidthUtil.hasValidWorkArea(object) and object:getWorkAreaByIndex(1) +end + +function WorkWidthUtil.hasValidWorkArea(object) + return object and object.getWorkAreaByIndex and object.spec_workArea.workAreas end ---- Get the working width of thing. Will return the maximum of the working width of thing and --- all of its implements --- TODO: consolidate this with the logic in FieldworkAIDriver:setMarkers() -function courseplay:getWorkWidth(thing, logPrefix) +--- Gets an automatic calculated work width or a pre configured in vehicle configurations. +---@param object table +---@param logPrefix string +function WorkWidthUtil.getAutomaticWorkWidth(object,logPrefix) logPrefix = logPrefix and logPrefix .. ' ' or '' - courseplay.debugFormat(courseplay.DBG_IMPLEMENTS,'%s%s: getting working width...', logPrefix, nameNum(thing)) + WorkWidthUtil.debug(object,logPrefix,'getting working width...') -- check if we have a manually configured working width - local width = g_vehicleConfigurations:get(thing, 'workingWidth') + local width = g_vehicleConfigurations:get(object, 'workingWidth') if not width then --- Gets the work width if the object is a shield. - width = AIDriverUtil.getShieldWorkWidth(thing,logPrefix) + width = WorkWidthUtil.getShieldWorkWidth(object,logPrefix) + end + + if not width then + --- Gets the work width if the object is a shovel. + width = WorkWidthUtil.getShovelWorkWidth(object,logPrefix) end if not width then -- no manual config, check AI markers - width = courseplay:getAIMarkerWidth(thing, logPrefix) + width = WorkWidthUtil.getAIMarkerWidth(object, logPrefix) end if not width then -- no AI markers, check work areas - width = courseplay:getWorkAreaWidth(thing, logPrefix) + width = WorkWidthUtil.getWorkAreaWidth(object, logPrefix) end - local implements = thing:getAttachedImplements() + local implements = object:getAttachedImplements() if implements then -- get width of all implements for _, implement in ipairs(implements) do - width = math.max( width, courseplay:getWorkWidth(implement.object, logPrefix)) + width = math.max( width, WorkWidthUtil.getAutomaticWorkWidth(implement.object, logPrefix)) end end - courseplay.debugFormat(courseplay.DBG_IMPLEMENTS, '%s%s: working width is %.1f', logPrefix, nameNum(thing), width) + WorkWidthUtil.debug(object,logPrefix,'working width is %.1f.',width) return width end -function courseplay:getWorkAreaWidth(object, logPrefix) + +---@param object table +---@param logPrefix string +function WorkWidthUtil.getWorkAreaWidth(object, logPrefix) logPrefix = logPrefix or '' -- TODO: check if there's a better way to find out if the implement has a work area local width = 0 - for i, wa in courseplay:workAreaIterator(object) do + for i, wa in WorkWidthUtil.workAreaIterator(object) do -- work areas are defined by three nodes: start, width and height. These nodes -- define a rectangular work area which you can make visible with the -- gsVehicleDebugAttributes console command and then pressing F5 local x, _, _ = localToLocal(wa.width, wa.start, 0, 0, 0) width = math.max(width, math.abs(x)) local _, _, z = localToLocal(wa.height, wa.start, 0, 0, 0) - courseplay.debugFormat(courseplay.DBG_IMPLEMENTS, '%s%s: work area %d is %s, %.1f by %.1f m', - logPrefix, nameNum(object), i, g_workAreaTypeManager.workAreaTypes[wa.type].name, math.abs(x), math.abs(z)) + WorkWidthUtil.debug(object,logPrefix,'work area %d is %s, %.1f by %.1f m', + i, g_workAreaTypeManager.workAreaTypes[wa.type].name, math.abs(x), math.abs(z) + ) end if width == 0 then - courseplay.debugFormat(courseplay.DBG_IMPLEMENTS, '%s%s: has NO work area', logPrefix, nameNum(object)) + WorkWidthUtil.debug(object,logPrefix,'has NO work area.') end return width end -function courseplay:getAIMarkerWidth(object, logPrefix) +---@param object table +---@param logPrefix string +function WorkWidthUtil.getAIMarkerWidth(object, logPrefix) logPrefix = logPrefix or '' if object.getAIMarkers then local aiLeftMarker, aiRightMarker = object:getAIMarkers() @@ -474,17 +493,137 @@ function courseplay:getAIMarkerWidth(object, logPrefix) local left, _, _ = localToLocal(aiLeftMarker, object.cp.directionNode or object.rootNode, 0, 0, 0); local right, _, _ = localToLocal(aiRightMarker, object.cp.directionNode or object.rootNode, 0, 0, 0); local width, _, _ = localToLocal(aiLeftMarker, aiRightMarker, 0, 0, 0) - courseplay.debugFormat( courseplay.DBG_IMPLEMENTS, '%s%s aiMarkers: left=%.2f, right=%.2f (width %.2f)', logPrefix, nameNum(object), left, right, width) - + WorkWidthUtil.debug(object,logPrefix,'aiMarkers: left=%.2f, right=%.2f (width %.2f)',left, right,width) if left < right then left, right = right, left -- yes, lua can do this! - courseplay.debugFormat(courseplay.DBG_IMPLEMENTS, '%s%s left < right -> switch -> left=%.2f, right=%.2f', logPrefix, nameNum(object), left, right) + WorkWidthUtil.debug(object,logPrefix,'left < right -> switch -> left=%.2f, right=%.2f',left, right) end return left - right; end end end +--- Gets ai markers for an object. +---@param object table +---@param logPrefix string +function WorkWidthUtil.getAIMarkers(object,logPrefix,suppressLog) + local aiLeftMarker, aiRightMarker, aiBackMarker + if object.getAIMarkers then + aiLeftMarker, aiRightMarker, aiBackMarker = object:getAIMarkers() + end + if not aiLeftMarker or not aiRightMarker or not aiBackMarker then + -- use the root node if there are no AI markers + if not suppressLog then + WorkWidthUtil.debug(object,logPrefix,'has no AI markers, try work areas') + end + aiLeftMarker, aiRightMarker, aiBackMarker = WorkWidthUtil.getAIMarkersFromWorkAreas(object) + if not aiLeftMarker or not aiRightMarker or not aiLeftMarker then + if not suppressLog then + WorkWidthUtil.debug(object,logPrefix,'has no work areas, giving up') + end + return nil, nil, nil + else + return aiLeftMarker, aiRightMarker, aiBackMarker + end + else + return aiLeftMarker, aiRightMarker, aiBackMarker + end +end + +--- Calculate the front and back marker nodes of a work area +---@param object table +function WorkWidthUtil.getAIMarkersFromWorkAreas(object) + -- work areas are defined by three nodes: start, width and height. These nodes + -- define a rectangular work area which you can make visible with the + -- gsVehicleDebugAttributes console command and then pressing F5 + for _, area in WorkWidthUtil.workAreaIterator(object) do + if WorkWidthUtil.isValidWorkArea(area) then + -- for now, just use the first valid work area we find + WorkWidthUtil.debug(object,nil,'%s: Using %s work area markers as AIMarkers', g_workAreaTypeManager.workAreaTypes[area.type].name) + return area.start, area.width, area.height + end + end +end + +---@param area table +function WorkWidthUtil.isValidWorkArea(area) + return area.start and area.height and area.width and + area.type ~= WorkAreaType.RIDGEMARKER and + area.type ~= WorkAreaType.COMBINESWATH and + area.type ~= WorkAreaType.COMBINECHOPPER +end + +---@param object table +---@param logPrefix string +function WorkWidthUtil.getShieldWorkWidth(object,logPrefix) + if object.spec_leveler then + local width = object.spec_leveler.nodes[1].maxDropWidth * 2 + WorkWidthUtil.debug(object,logPrefix,'is a shield with work width: %.1f',width) + return width + end +end + +---@param object table +---@param logPrefix string +function WorkWidthUtil.getShovelWorkWidth(object,logPrefix) + if object.spec_shovel then + local width = object.spec_shovel.shovelNodes[1].width + WorkWidthUtil.debug(object,logPrefix,'is a shovel with work width: %.1f',width) + return width + end +end + +--- Shows the current work width selected with the tool offsets applied. +---@param vehicle table +---@param workWidth number +---@param offsX number +---@param offsZ number +function WorkWidthUtil.showWorkWidth(vehicle,workWidth,offsX,offsZ) + local firstObject = AIDriverUtil.getFirstAttachedImplement(vehicle) + local lastObject = AIDriverUtil.getLastAttachedImplement(vehicle) + + local function show(object,workWidth,offsX,offsZ) + if object == nil then + return + end + local f, b = 0,0 + local aiLeftMarker, _, aiBackMarker = WorkWidthUtil.getAIMarkers(object,nil,true) + if aiLeftMarker and aiBackMarker then + _,_,b = localToLocal(aiBackMarker, object.rootNode, 0, 0, 0) + _,_,f = localToLocal(aiLeftMarker, object.rootNode, 0, 0, 0) + end + + local left = (workWidth * 0.5) + offsX + local right = (workWidth * -0.5) + offsX + + local p1x, p1y, p1z = localToWorld(object.rootNode, left, 1.6, b - offsZ) + local p2x, p2y, p2z = localToWorld(object.rootNode, right, 1.6, b - offsZ) + local p3x, p3y, p3z = localToWorld(object.rootNode, right, 1.6, f - offsZ) + local p4x, p4y, p4z = localToWorld(object.rootNode, left, 1.6, f - offsZ) + + cpDebug:drawPoint(p1x, p1y, p1z, 1, 1, 0) + cpDebug:drawPoint(p2x, p2y, p2z, 1, 1, 0) + cpDebug:drawPoint(p3x, p3y, p3z, 1, 1, 0) + cpDebug:drawPoint(p4x, p4y, p4z, 1, 1, 0) + + cpDebug:drawLine(p1x, p1y, p1z, 1, 0, 0, p2x, p2y, p2z) + cpDebug:drawLine(p2x, p2y, p2z, 1, 0, 0, p3x, p3y, p3z) + cpDebug:drawLine(p3x, p3y, p3z, 1, 0, 0, p4x, p4y, p4z) + cpDebug:drawLine(p4x, p4y, p4z, 1, 0, 0, p1x, p1y, p1z) + end + show(firstObject,workWidth,offsX,offsZ) + if firstObject ~= lastObject then + show(lastObject,workWidth,offsX,offsZ) + end +end + + +---@param object table +---@param logPrefix string +function WorkWidthUtil.debug(object,logPrefix,str,...) + courseplay.debugFormat(courseplay.DBG_IMPLEMENTS,'%s%s: '..str, logPrefix or "", nameNum(object), ...) +end + --this one enable the buttons and allows the user to change the mode function courseplay:getIsToolCombiValidForCpMode(vehicle,cpModeToCheck) --5 is always valid diff --git a/translations/translation_br.xml b/translations/translation_br.xml index 31050b1f3..c4fd7ae58 100644 --- a/translations/translation_br.xml +++ b/translations/translation_br.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml index 367ebfbe2..e4e8f3ba5 100644 --- a/translations/translation_cs.xml +++ b/translations/translation_cs.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml index 826cbf19a..8953ca462 100644 --- a/translations/translation_cz.xml +++ b/translations/translation_cz.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_de.xml b/translations/translation_de.xml index 8264a02eb..ffe7dbfbe 100644 --- a/translations/translation_de.xml +++ b/translations/translation_de.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_en.xml b/translations/translation_en.xml index b210812a7..ca07d16b3 100644 --- a/translations/translation_en.xml +++ b/translations/translation_en.xml @@ -297,6 +297,8 @@ + + diff --git a/translations/translation_es.xml b/translations/translation_es.xml index f7ce5d4f8..90a5123d8 100644 --- a/translations/translation_es.xml +++ b/translations/translation_es.xml @@ -297,6 +297,8 @@ + + diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml index 8b64dceb5..2ca0f8ac0 100644 --- a/translations/translation_fr.xml +++ b/translations/translation_fr.xml @@ -297,6 +297,8 @@ + + diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml index 11c03cb5d..f21f9f434 100644 --- a/translations/translation_hu.xml +++ b/translations/translation_hu.xml @@ -292,6 +292,8 @@ + + diff --git a/translations/translation_it.xml b/translations/translation_it.xml index 19dad63ab..4093bd3e9 100644 --- a/translations/translation_it.xml +++ b/translations/translation_it.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml index 6807c63c7..38bc72d29 100644 --- a/translations/translation_jp.xml +++ b/translations/translation_jp.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml index 10a02d016..3ea2446c2 100644 --- a/translations/translation_nl.xml +++ b/translations/translation_nl.xml @@ -292,6 +292,8 @@ + + diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml index f0374f862..39bcfe13a 100644 --- a/translations/translation_pl.xml +++ b/translations/translation_pl.xml @@ -296,6 +296,8 @@ + + diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml index 5607fc26b..224684192 100644 --- a/translations/translation_pt.xml +++ b/translations/translation_pt.xml @@ -292,6 +292,8 @@ + + diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml index 52fe8cbfe..8efde873b 100644 --- a/translations/translation_ru.xml +++ b/translations/translation_ru.xml @@ -297,6 +297,8 @@ + + diff --git a/translations/translation_sl.xml b/translations/translation_sl.xml index 886ade0ff..67ade7397 100644 --- a/translations/translation_sl.xml +++ b/translations/translation_sl.xml @@ -296,6 +296,8 @@ + + diff --git a/turn.lua b/turn.lua index 04c463664..8e211023b 100644 --- a/turn.lua +++ b/turn.lua @@ -27,8 +27,7 @@ function courseplay:turn(vehicle, dt, turnContext) --- This is in case we use manually recorded fieldswork course and not generated. if not vehicle.cp.courseWorkWidth then - courseplay:calculateWorkWidth(vehicle, true); - vehicle.cp.courseWorkWidth = vehicle.cp.workWidth; + vehicle.cp.courseWorkWidth = vehicle.cp.courseGeneratorSettings.workWidth:getAutoWorkWidth(); end; --- Get front and back markers @@ -1217,7 +1216,8 @@ function courseplay.generateTurnTypeHeadlandCornerReverseStraightTractor(vehicle -- drive forward until our implement reaches the headland after the turn fromPoint.x, _, fromPoint.z = localToWorld( helperNode, 0, 0, 0 ) -- drive forward only until our implement reaches the headland area after the turn so we leave an unworked area here at the corner - toPoint = vehicle.cp.turnCorner:getPointAtDistanceFromCornerStart((vehicle.cp.workWidth / 2) + turnInfo.frontMarker - turnInfo.wpChangeDistance) + local workWidth = vehicle.cp.courseGeneratorSettings.workWidth:get() + toPoint = vehicle.cp.turnCorner:getPointAtDistanceFromCornerStart((workWidth / 2) + turnInfo.frontMarker - turnInfo.wpChangeDistance) -- is this now in front of us? We may not need to drive forward local dx, dy, dz = worldToLocal( helperNode, toPoint.x, toPoint.y, toPoint.z ) -- at which waypoint we have to raise the implement @@ -1238,7 +1238,7 @@ function courseplay.generateTurnTypeHeadlandCornerReverseStraightTractor(vehicle _, _, dz = worldToLocal( helperNode, toPoint.x, toPoint.y, toPoint.z ) courseplay.destroyNode(helperNode) courseplay:debug(("%s:(Turn) courseplay:generateTurnTypeHeadlandCornerReverseStraightTractor(), from ( %.2f %.2f ), to ( %.2f %.2f) workWidth: %.1f, dz = %.1f"):format( - nameNum(vehicle), fromPoint.x, fromPoint.z, toPoint.x, toPoint.z, vehicle.cp.workWidth, dz ), 14) + nameNum(vehicle), fromPoint.x, fromPoint.z, toPoint.x, toPoint.z, workWidth, dz ), 14) courseplay:generateTurnStraightPoints(vehicle, fromPoint, toPoint, dz < 0); -- Generate turn circle (Forward) @@ -1259,9 +1259,9 @@ function courseplay.generateTurnTypeHeadlandCornerReverseStraightTractor(vehicle if turnInfo.reversingWorkTool and turnInfo.reversingWorkTool.cp.realTurningNode then -- with towed reversing tools the reference point is the tool, not the tractor so don't care about frontMarker and such - toPoint = vehicle.cp.turnCorner:getPointAtDistanceFromCornerEnd(-(vehicle.cp.workWidth / 2) - turnInfo.reverseWPChangeDistance - 10) + toPoint = vehicle.cp.turnCorner:getPointAtDistanceFromCornerEnd(-(workWidth / 2) - turnInfo.reverseWPChangeDistance - 10) else - toPoint = vehicle.cp.turnCorner:getPointAtDistanceFromCornerEnd(-(vehicle.cp.workWidth / 2) - turnInfo.frontMarker - turnInfo.reverseWPChangeDistance - 10) + toPoint = vehicle.cp.turnCorner:getPointAtDistanceFromCornerEnd(-(workWidth / 2) - turnInfo.frontMarker - turnInfo.reverseWPChangeDistance - 10) end courseplay:generateTurnStraightPoints(vehicle, fromPoint, toPoint, true, true, turnInfo.reverseWPChangeDistance);