LUADOC - Farming Simulator 22

Script v1_7_1_0

Engine v1_7_1_0

Foundation Reference

Cutter

Description
Specialization for cutters providing cutter effects; requires WorkArea specialization
Functions

doCheckSpeedLimit

Description
Definition
doCheckSpeedLimit()
Code
1255function Cutter:doCheckSpeedLimit(superFunc)
1256 return superFunc(self) or (self:getIsTurnedOn() and (self.getIsLowered == nil or self:getIsLowered()))
1257end

getAllowCutterAIFruitRequirements

Description
Definition
getAllowCutterAIFruitRequirements()
Code
744function Cutter:getAllowCutterAIFruitRequirements()
745 return true
746end

getCombine

Description
Definition
getCombine()
Code
723function Cutter:getCombine()
724 local spec = self.spec_cutter
725
726 if self.verifyCombine ~= nil then
727 return self:verifyCombine(spec.currentInputFruitType, spec.currentOutputFillType)
728 else
729 if self.getAttacherVehicle ~= nil then
730 local attacherVehicle = self:getAttacherVehicle()
731 if attacherVehicle ~= nil then
732 if attacherVehicle.verifyCombine ~= nil then
733 return attacherVehicle:verifyCombine(spec.currentInputFruitType, spec.currentOutputFillType)
734 end
735 end
736 end
737 end
738
739 return nil
740end

getConsumingLoad

Description
Definition
getConsumingLoad()
Code
1318function Cutter:getConsumingLoad(superFunc)
1319 local value, count = superFunc(self)
1320
1321 local loadPercentage = self:getCutterLoad()
1322
1323 return value+loadPercentage, count+1
1324end

getCutterLoad

Description
Definition
getCutterLoad()
Code
1063function Cutter:getCutterLoad()
1064 local speedLimitFactor = MathUtil.clamp(self:getLastSpeed() / self.speedLimit, 0, 1) * 0.75 + 0.25
1065 return self.spec_cutter.cutterLoad * speedLimitFactor
1066end

getCutterStoneMultiplier

Description
Definition
getCutterStoneMultiplier()
Code
1070function Cutter:getCutterStoneMultiplier()
1071 local spec = self.spec_cutter
1072
1073 if spec.stoneLastState ~= 0 and spec.stoneWearMultiplierData ~= nil then
1074 return spec.stoneWearMultiplierData[spec.stoneLastState] or 1
1075 end
1076
1077 return 1
1078end

getCutterTiltDelta

Description
Returns current cutter tilt delta and active state
Definition
getCutterTiltDelta()
Return Values
floatdeltatilt delta in deg
booleanisActivecutter tilt is active
booleandoResetreset header tilt to initial position
Code
1145function Cutter:getCutterTiltDelta()
1146 local spec = self.spec_cutter
1147 local isActive = self:getCutterTiltIsActive(spec.automaticTilt)
1148 return isActive and spec.automaticTilt.currentDelta or 0, isActive
1149end

getCutterTiltIsActive

Description
Returns if cutter tilt is active
Definition
getCutterTiltIsActive()
Return Values
booleanisActivecutter tilt is active
booleandoResetreset header tilt to initial position
Code
1128function Cutter:getCutterTiltIsActive(automaticTilt)
1129 if not automaticTilt.isAvailable or not self.isActive then
1130 return false, false
1131 end
1132
1133 if not self:getIsLowered(true) or not (self.getAttacherVehicle == nil or self:getAttacherVehicle() ~= nil) then
1134 return false, true
1135 end
1136
1137 return true, false
1138end

getCutterTiltIsAvailable

Description
Returns if cutter tilt is available
Definition
getCutterTiltIsAvailable()
Return Values
booleanisAvailablecutter tilt is available
Code
1120function Cutter:getCutterTiltIsAvailable()
1121 return self.spec_cutter.automaticTilt.isAvailable
1122end

getDefaultSpeedLimit

Description
Definition
getDefaultSpeedLimit()
Code
1442function Cutter.getDefaultSpeedLimit()
1443 return 10
1444end

getDirtMultiplier

Description
Definition
getDirtMultiplier()
Code
1271function Cutter:getDirtMultiplier(superFunc)
1272 local spec = self.spec_cutter
1273
1274 if spec.isWorking then
1275 return superFunc(self) + self:getWorkDirtMultiplier() * self:getLastSpeed() / self.speedLimit
1276 end
1277
1278 return superFunc(self)
1279end

getIsGroundReferenceNodeThreshold

Description
Definition
getIsGroundReferenceNodeThreshold()
Code
1328function Cutter:getIsGroundReferenceNodeThreshold(superFunc, groundReferenceNode)
1329 local threshold = superFunc(self, groundReferenceNode)
1330
1331 threshold = threshold + self.spec_cutter.currentCutHeight
1332
1333 return threshold
1334end

getIsRandomlyMovingPartActive

Description
Definition
getIsRandomlyMovingPartActive()
Code
1230function Cutter:getIsRandomlyMovingPartActive(superFunc, part)
1231 local retValue = superFunc(self, part)
1232
1233 if part.moveOnlyIfCut then
1234 retValue = retValue and (self.spec_cutter.lastAreaBiggerZeroTime >= (g_currentMission.time - 150))
1235 end
1236
1237 return retValue
1238end

getIsSpeedRotatingPartActive

Description
Definition
getIsSpeedRotatingPartActive()
Code
1210function Cutter:getIsSpeedRotatingPartActive(superFunc, speedRotatingPart)
1211 if speedRotatingPart.rotateIfTurnedOn and not self:getIsTurnedOn() then
1212 return false
1213 end
1214
1215 return superFunc(self, speedRotatingPart)
1216end

getIsWorkAreaActive

Description
Definition
getIsWorkAreaActive()
Code
1242function Cutter:getIsWorkAreaActive(superFunc, workArea)
1243 local spec = self.spec_cutter
1244 if self.getAllowsLowering == nil or self:getAllowsLowering() then
1245 if not spec.allowCuttingWhileRaised and not self:getIsLowered(true) then
1246 return false
1247 end
1248 end
1249
1250 return superFunc(self, workArea)
1251end

getWearMultiplier

Description
Definition
getWearMultiplier()
Code
1283function Cutter:getWearMultiplier(superFunc)
1284 local spec = self.spec_cutter
1285
1286 if spec.isWorking then
1287 local stoneMultiplier = 1
1288 if spec.stoneLastState ~= 0 and spec.stoneWearMultiplierData ~= nil then
1289 stoneMultiplier = spec.stoneWearMultiplierData[spec.stoneLastState] or 1
1290 end
1291
1292 return superFunc(self) + self:getWorkWearMultiplier() * self:getLastSpeed() / self.speedLimit * stoneMultiplier
1293 end
1294
1295 return superFunc(self)
1296end

initSpecialization

Description
Definition
initSpecialization()
Code
17function Cutter.initSpecialization()
18 g_workAreaTypeManager:addWorkAreaType("cutter", false)
19
20 local schema = Vehicle.xmlSchema
21 schema:setXMLSpecializationType("Cutter")
22
23 schema:register(XMLValueType.STRING, "vehicle.cutter#fruitTypes", "List with supported fruit types")
24 schema:register(XMLValueType.STRING, "vehicle.cutter#fruitTypeCategories", "List with supported fruit types categories")
25 schema:register(XMLValueType.STRING, "vehicle.cutter#fruitTypeConverter", "Name of fruit type converter")
26 schema:register(XMLValueType.BOOL, "vehicle.cutter#useWindrowed", "Uses windrow types")
27
28 AnimationManager.registerAnimationNodesXMLPaths(schema, "vehicle.cutter.animationNodes")
29
30 schema:register(XMLValueType.NODE_INDEX, "vehicle.cutter.fruitExtraObjects.fruitExtraObject(?)#node", "Name of fruit type converter")
31 schema:register(XMLValueType.STRING, "vehicle.cutter.fruitExtraObjects.fruitExtraObject(?)#anim", "Change animation name")
32 schema:register(XMLValueType.BOOL, "vehicle.cutter.fruitExtraObjects.fruitExtraObject(?)#isDefault", "Is default active")
33 schema:register(XMLValueType.STRING, "vehicle.cutter.fruitExtraObjects.fruitExtraObject(?)#fruitType", "Name of fruit type")
34 schema:register(XMLValueType.BOOL, "vehicle.cutter.fruitExtraObjects#hideOnDetach", "Hide extra objects on detach", false)
35
36 EffectManager.registerEffectXMLPaths(schema, "vehicle.cutter.effect")
37 EffectManager.registerEffectXMLPaths(schema, "vehicle.cutter.fillEffect")
38
39 schema:register(XMLValueType.NODE_INDEX, Cutter.CUTTER_TILT_XML_KEY .. ".automaticTiltNode(?)#node", "Automatic tilt node")
40 schema:register(XMLValueType.ANGLE, Cutter.CUTTER_TILT_XML_KEY .. ".automaticTiltNode(?)#minAngle", "Min. angle", -5)
41 schema:register(XMLValueType.ANGLE, Cutter.CUTTER_TILT_XML_KEY .. ".automaticTiltNode(?)#maxAngle", "Max. angle", 5)
42 schema:register(XMLValueType.ANGLE, Cutter.CUTTER_TILT_XML_KEY .. ".automaticTiltNode(?)#maxSpeed", "Max. angle change per second", 1)
43 schema:register(XMLValueType.NODE_INDEX, Cutter.CUTTER_TILT_XML_KEY .. "#raycastNode1", "Raycast node 1")
44 schema:register(XMLValueType.NODE_INDEX, Cutter.CUTTER_TILT_XML_KEY .. "#raycastNode2", "Raycast node 2")
45
46 schema:register(XMLValueType.BOOL, "vehicle.cutter#allowsForageGrowthState", "Allows forage growth state", false)
47 schema:register(XMLValueType.BOOL, "vehicle.cutter#allowCuttingWhileRaised", "Allow cutting while raised", false)
48 schema:register(XMLValueType.INT, "vehicle.cutter#movingDirection", "Moving direction", 1)
49 schema:register(XMLValueType.FLOAT, "vehicle.cutter#strawRatio", "Straw ratio", 1)
50
51 schema:register(XMLValueType.NODE_INDEX, "vehicle.cutter.spikedDrums.spikedDrum(?)#node", "Spiked drum node (Needs to rotate on X axis)")
52 schema:register(XMLValueType.NODE_INDEX, "vehicle.cutter.spikedDrums.spikedDrum(?)#spline", "Reference spline")
53 schema:register(XMLValueType.NODE_INDEX, "vehicle.cutter.spikedDrums.spikedDrum(?).spike(?)#node", "Spike that is translated on Y axis depending on spline")
54
55 SoundManager.registerSampleXMLPaths(schema, "vehicle.cutter.sounds", "cut")
56
57 schema:register(XMLValueType.INT, WorkArea.WORK_AREA_XML_KEY .. ".chopperArea#index", "Chopper area index")
58 schema:register(XMLValueType.INT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. ".chopperArea#index", "Chopper area index")
59 schema:register(XMLValueType.BOOL, RandomlyMovingParts.RANDOMLY_MOVING_PART_XML_KEY .. "#moveOnlyIfCut", "Move only if cutters cuts something", false)
60 schema:register(XMLValueType.BOOL, SpeedRotatingParts.SPEED_ROTATING_PART_XML_KEY .. "#rotateIfTurnedOn", "Rotate only if turned on", false)
61
62 schema:setXMLSpecializationType()
63end

isAttachAllowed

Description
Returns true if attaching the vehicle is allowed
Definition
isAttachAllowed(integer farmId, table attacherVehicle)
Arguments
integerfarmIdfarmId of attacher vehicle
tableattacherVehicleattacher vehicle
Return Values
booleandetachAlloweddetach is allowed
stringwarning[optional] warning text to display
Code
1304function Cutter:isAttachAllowed(superFunc, farmId, attacherVehicle)
1305 local spec = self.spec_cutter
1306
1307 if attacherVehicle.spec_combine ~= nil then
1308 if not attacherVehicle:getIsCutterCompatible(spec.fillTypes) then
1309 return false, g_i18n:getText("info_attach_not_allowed")
1310 end
1311 end
1312
1313 return superFunc(self, farmId, attacherVehicle)
1314end

loadCutterTiltFromXML

Description
Loads header tilt from xml file
Definition
loadCutterTiltFromXML(table xmlFile, string key)
Arguments
tablexmlFilexml file object
stringkeykey to load from
Return Values
boolsuccesssuccessfully loaded
Code
1085function Cutter:loadCutterTiltFromXML(xmlFile, key, target)
1086 target.nodes = {}
1087 xmlFile:iterate(key .. ".automaticTiltNode", function(index, nodeKey)
1088 local entry = {}
1089 entry.node = xmlFile:getValue(nodeKey .. "#node", nil, self.components, self.i3dMappings)
1090 if entry.node ~= nil then
1091 entry.minAngle = xmlFile:getValue(nodeKey .. "#minAngle", -5)
1092 entry.maxAngle = xmlFile:getValue(nodeKey .. "#maxAngle", 5)
1093
1094 entry.maxSpeed = xmlFile:getValue(nodeKey .. "#maxSpeed", 2) / 1000
1095
1096 table.insert(target.nodes, entry)
1097 end
1098 end)
1099
1100 target.raycastNode1 = xmlFile:getValue(key .. "#raycastNode1", nil, self.components, self.i3dMappings)
1101 target.raycastNode2 = xmlFile:getValue(key .. "#raycastNode2", nil, self.components, self.i3dMappings)
1102 if target.raycastNode1 ~= nil and target.raycastNode2 ~= nil then
1103 local x1, _, _ = localToLocal(target.raycastNode1, self.rootNode, 0, 0, 0)
1104 local x2, _, _ = localToLocal(target.raycastNode2, self.rootNode, 0, 0, 0)
1105 if x1 < x2 then
1106 local raycastNode1 = target.raycastNode1
1107 target.raycastNode1 = target.raycastNode2
1108 target.raycastNode2 = raycastNode1
1109 end
1110 else
1111 return false
1112 end
1113
1114 return true
1115end

loadRandomlyMovingPartFromXML

Description
Definition
loadRandomlyMovingPartFromXML()
Code
1220function Cutter:loadRandomlyMovingPartFromXML(superFunc, part, xmlFile, key)
1221 local retValue = superFunc(self, part, xmlFile, key)
1222
1223 part.moveOnlyIfCut = xmlFile:getValue(key .. "#moveOnlyIfCut", false)
1224
1225 return retValue
1226end

loadSpeedRotatingPartFromXML

Description
Definition
loadSpeedRotatingPartFromXML()
Code
1198function Cutter:loadSpeedRotatingPartFromXML(superFunc, speedRotatingPart, xmlFile, key)
1199 if not superFunc(self, speedRotatingPart, xmlFile, key) then
1200 return false
1201 end
1202
1203 speedRotatingPart.rotateIfTurnedOn = xmlFile:getValue(key .. "#rotateIfTurnedOn", false)
1204
1205 return true
1206end

loadWorkAreaFromXML

Description
Definition
loadWorkAreaFromXML()
Code
1261function Cutter:loadWorkAreaFromXML(superFunc, workArea, xmlFile, key)
1262 local retValue = superFunc(self, workArea, xmlFile, key)
1263
1264 workArea.chopperAreaIndex = xmlFile:getValue(key..".chopperArea#index")
1265
1266 return retValue
1267end

onAIImplementStart

Description
Definition
onAIImplementStart()
Code
1385function Cutter:onAIImplementStart()
1386 -- clear ai fruit requirements on start of ai vehicle to get the newest fruit type in front of the cutter
1387 if self:getAllowCutterAIFruitRequirements() then
1388 self:clearAIFruitRequirements()
1389
1390 -- default require all fruit types the cutter can handle so we don't get other fruit types
1391 local spec = self.spec_cutter
1392 for _, fruitTypeIndex in ipairs(spec.fruitTypes) do
1393 local fruitType = g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex)
1394 if fruitType ~= nil then
1395 local minState = spec.allowsForageGrowthState and fruitType.minForageGrowthState or fruitType.minHarvestingGrowthState
1396 self:addAIFruitRequirement(fruitType.index, minState, fruitType.maxHarvestingGrowthState)
1397 end
1398 end
1399 end
1400end

onDelete

Description
Definition
onDelete()
Code
384function Cutter:onDelete()
385 local spec = self.spec_cutter
386 g_effectManager:deleteEffects(spec.cutterEffects)
387 g_effectManager:deleteEffects(spec.fillEffects)
388 g_animationManager:deleteAnimations(spec.animationNodes)
389 g_soundManager:deleteSamples(spec.samples)
390end

onEndWorkAreaProcessing

Description
Definition
onEndWorkAreaProcessing()
Code
954function Cutter:onEndWorkAreaProcessing(dt, hasProcessed)
955 if self.isServer then
956 local spec = self.spec_cutter
957
958 local lastRealArea = spec.workAreaParameters.lastRealArea
959 local lastThreshedArea = spec.workAreaParameters.lastThreshedArea
960 local lastStatsArea = spec.workAreaParameters.lastStatsArea
961 local lastArea = spec.workAreaParameters.lastArea
962
963 if lastRealArea > 0 then
964 if spec.workAreaParameters.combineVehicle ~= nil then
965 local inputFruitType = spec.workAreaParameters.lastFruitType
966
967 -- always use the same input fruit type while the ai is active to prevent situations where the combine is unable to unload a different fruit type
968 if self:getIsAIActive() then
969 local requirements = self:getAIFruitRequirements()
970 -- Assume there is only 1 requirement, because we can't quickly test if all requirements have the same fruit type
971 -- and having more than 1 fruit type makes it uncertain which fruit we should be using here.
972 local requirement = requirements[1]
973 if #requirements == 1 and requirement ~= nil and requirement.fruitType ~= FruitType.UNKNOWN then
974 inputFruitType = requirement.fruitType
975 end
976 end
977
978 -- take fill type conversion into consideration
979 local conversionFactor = spec.currentConversionFactor or 1
980 local outputFillType = spec.currentOutputFillType
981
982 local targetOutputFillType = outputFillType
983
984 if spec.lastOutputFillTypes[outputFillType] == nil then
985 spec.lastOutputFillTypes[outputFillType] = lastRealArea
986 else
987 spec.lastOutputFillTypes[outputFillType] = spec.lastOutputFillTypes[outputFillType] + lastRealArea
988 end
989
990 if spec.lastPrioritizedOutputType ~= FillType.UNKNOWN then
991 outputFillType = spec.lastPrioritizedOutputType
992 end
993
994 lastRealArea = lastRealArea * conversionFactor
995 local farmId = self:getLastTouchedFarmlandFarmId()
996 local strawGroundType = spec.workAreaParameters.lastChopperValue
997 local appliedDelta = spec.workAreaParameters.combineVehicle:addCutterArea(lastArea, lastRealArea, inputFruitType, outputFillType, spec.strawRatio, strawGroundType, farmId, self:getCutterLoad())
998 if appliedDelta > 0 and outputFillType == targetOutputFillType then
999 spec.lastValidInputFruitType = inputFruitType
1000 end
1001 end
1002
1003 local ha = MathUtil.areaToHa(lastStatsArea, g_currentMission:getFruitPixelsToSqm()) -- 4096px are mapped to 2048m
1004 local stats = g_currentMission:farmStats(self:getLastTouchedFarmlandFarmId())
1005 stats:updateStats("threshedHectares", ha)
1006 self:updateLastWorkedArea(lastStatsArea)
1007
1008 spec.lastAreaBiggerZero = lastArea > 0
1009 if spec.lastAreaBiggerZero then
1010 spec.lastAreaBiggerZeroTime = g_currentMission.time
1011 end
1012 if spec.lastAreaBiggerZero ~= spec.lastAreaBiggerZeroSent then
1013 self:raiseDirtyFlags(spec.dirtyFlag)
1014 spec.lastAreaBiggerZeroSent = spec.lastAreaBiggerZero
1015 end
1016
1017 if spec.currentInputFruitType ~= spec.currentInputFruitTypeSent then
1018 self:raiseDirtyFlags(spec.effectDirtyFlag)
1019 spec.currentInputFruitTypeSent = spec.currentInputFruitType
1020 end
1021
1022 if self:getAllowCutterAIFruitRequirements() then
1023 if self.setAIFruitRequirements ~= nil then
1024 -- we do not allow changes of the required type while working, just on ai start (if the cutter has more than one requirement or no requirement -> only one requirement allowed at the time)
1025 -- prevents fruit type changes on field borders
1026 local requirements = self:getAIFruitRequirements()
1027 local requirement = requirements[1]
1028 if #requirements > 1 or requirement == nil or requirement.fruitType == FruitType.UNKNOWN then
1029 local fruitType = g_fruitTypeManager:getFruitTypeByIndex(spec.currentInputFruitTypeAI)
1030 if fruitType ~= nil then
1031 local minState = spec.allowsForageGrowthState and fruitType.minForageGrowthState or fruitType.minHarvestingGrowthState
1032 self:setAIFruitRequirements(spec.currentInputFruitTypeAI, minState, fruitType.maxHarvestingGrowthState)
1033 end
1034 end
1035 end
1036
1037 spec.aiNoValidGroundTimer = 0
1038 end
1039 else
1040 if self:getAllowCutterAIFruitRequirements() then
1041 if hasProcessed then
1042 local rootVehicle = self.rootVehicle
1043 if rootVehicle.getAIFieldWorkerIsTurning ~= nil then
1044 if rootVehicle:getIsAIActive() and rootVehicle:getLastSpeed() > 5 and not rootVehicle:getAIFieldWorkerIsTurning() then
1045 spec.aiNoValidGroundTimer = spec.aiNoValidGroundTimer + dt
1046 if spec.aiNoValidGroundTimer > 5000 then
1047 if rootVehicle.stopCurrentAIJob ~= nil then
1048 rootVehicle:stopCurrentAIJob(AIMessageErrorUnknown.new())
1049 end
1050 end
1051 else
1052 spec.aiNoValidGroundTimer = 0
1053 end
1054 end
1055 end
1056 end
1057 end
1058 end
1059end

onLoad

Description
Definition
onLoad()
Code
130function Cutter:onLoad(savegame)
131 local spec = self.spec_cutter
132
133 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.turnedOnRotationNodes.turnedOnRotationNode#type", "vehicle.cutter.animationNodes.animationNode", "cutter") --FS17 to FS19
134 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.turnedOnScrollers", "vehicle.cutter.animationNodes.animationNode") --FS17 to FS19
135 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter.turnedOnScrollers", "vehicle.cutter.animationNodes.animationNode") --FS17 to FS19
136 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter.reelspikes", "vehicle.cutter.rotationNodes.rotationNode or vehicle.turnOnVehicle.turnedOnAnimation") --FS17 to FS19
137 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter.threshingParticleSystems.threshingParticleSystem", "vehicle.cutter.fillEffect.effectNode") --FS17 to FS19
138 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter.threshingParticleSystems.emitterShape", "vehicle.cutter.fillEffect.effectNode") --FS17 to FS19
139 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter#convertedFillTypeCategories", "vehicle.cutter#fruitTypeConverter") --FS17 to FS19
140 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter#startAnimationName", "vehicle.turnOnVehicle.turnOnAnimation#name") --FS17 to FS19
141 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.cutter.testAreas", "vehicle.workAreas.workArea.testAreas") --FS19 to FS22
142
143 -- load fruitTypes
144 local fruitTypes = nil
145 local fruitTypeNames = self.xmlFile:getValue("vehicle.cutter#fruitTypes")
146 local fruitTypeCategories = self.xmlFile:getValue("vehicle.cutter#fruitTypeCategories")
147 if fruitTypeCategories ~= nil and fruitTypeNames == nil then
148 fruitTypes = g_fruitTypeManager:getFruitTypesByCategoryNames(fruitTypeCategories, "Warning: Cutter has invalid fruitTypeCategory '%s' in '"..self.configFileName.."'")
149 elseif fruitTypeCategories == nil and fruitTypeNames ~= nil then
150 fruitTypes = g_fruitTypeManager:getFruitTypesByNames(fruitTypeNames, "Warning: Cutter has invalid fruitType '%s' in '"..self.configFileName.."'")
151 else
152 Logging.xmlWarning(self.xmlFile, "Cutter needs either the 'fruitTypeCategories' or 'fruitTypes' attribute!")
153 end
154
155 spec.currentCutHeight = 0
156
157 if fruitTypes ~= nil then
158 spec.fruitTypes = {}
159 for _,fruitType in pairs(fruitTypes) do
160 table.insert(spec.fruitTypes, fruitType)
161
162 if #spec.fruitTypes == 1 then
163 local cutHeight = g_fruitTypeManager:getCutHeightByFruitTypeIndex(fruitType, spec.allowsForageGrowthState)
164 self:setCutterCutHeight(cutHeight)
165 end
166 end
167 end
168
169 spec.fruitTypeConverters = {}
170 local category = self.xmlFile:getValue("vehicle.cutter#fruitTypeConverter")
171 if category ~= nil then
172 local data = g_fruitTypeManager:getConverterDataByName(category)
173 if data ~= nil then
174 for input, converter in pairs(data) do
175 spec.fruitTypeConverters[input] = converter
176 end
177 end
178 end
179
180 spec.fillTypes = {}
181 for _, fruitType in ipairs(spec.fruitTypes) do
182 if spec.fruitTypeConverters[fruitType] ~= nil then
183 table.insert(spec.fillTypes, spec.fruitTypeConverters[fruitType].fillTypeIndex)
184 else
185 local fillType = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(fruitType)
186 if fillType ~= nil then
187 table.insert(spec.fillTypes, fillType)
188 end
189 end
190 end
191
192 if self.isClient then
193 spec.animationNodes = g_animationManager:loadAnimations(self.xmlFile, "vehicle.cutter.animationNodes", self.components, self, self.i3dMappings)
194
195 spec.fruitExtraObjects = {}
196 local i = 0
197 while true do
198 local key = string.format("vehicle.cutter.fruitExtraObjects.fruitExtraObject(%d)", i)
199
200 XMLUtil.checkDeprecatedXMLElements(self.xmlFile, key.."#index", key.."#node")
201
202 local node = self.xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings)
203 local anim = self.xmlFile:getValue(key.."#anim")
204 local isDefault = self.xmlFile:getValue(key.."#isDefault", false)
205 local fruitType = g_fruitTypeManager:getFruitTypeByName(self.xmlFile:getValue(key.."#fruitType"))
206
207 if fruitType == nil or (node == nil and anim == nil) then
208 break
209 end
210
211 if node ~= nil then
212 setVisibility(node, false)
213 end
214
215 local extraObject = {node=node, anim=anim}
216 spec.fruitExtraObjects[fruitType.index] = extraObject
217 if isDefault then
218 spec.fruitExtraObjects[FruitType.UNKNOWN] = extraObject
219 end
220
221 i = i + 1
222 end
223 spec.hideExtraObjectsOnDetach = self.xmlFile:getValue("vehicle.cutter.fruitExtraObjects#hideOnDetach", false)
224
225 spec.spikedDrums = {}
226 self.xmlFile:iterate("vehicle.cutter.spikedDrums.spikedDrum", function(index, key)
227 local entry = {}
228 entry.node = self.xmlFile:getValue(key.."#node", nil, self.components, self.i3dMappings)
229 if entry.node ~= nil then
230 entry.spline = self.xmlFile:getValue(key.."#spline", nil, self.components, self.i3dMappings)
231 if entry.spline ~= nil then
232 setVisibility(entry.spline, false)
233
234 entry.spikes = {}
235 self.xmlFile:iterate(key .. ".spike", function(_, spikeKey)
236 local spike = {}
237 spike.node = self.xmlFile:getValue(spikeKey.."#node", nil, self.components, self.i3dMappings)
238 if spike.node ~= nil then
239 local parent = createTransformGroup(getName(spike.node).."Parent")
240 link(getParent(spike.node), parent, getChildIndex(spike.node))
241 setTranslation(parent, getTranslation(spike.node))
242 setRotation(parent, getRotation(spike.node))
243 link(parent, spike.node)
244 setTranslation(spike.node, 0, 0, 0)
245 setRotation(spike.node, 0, 0, 0)
246
247 local _, y, z = localToLocal(spike.node, entry.node, 0, 0, 0)
248 local angle = -MathUtil.getYRotationFromDirection(y, z)
249 local initalTime = angle / (2 * math.pi)
250 if initalTime < 0 then
251 initalTime = initalTime + 1
252 end
253
254 spike.initalTime = initalTime
255 table.insert(entry.spikes, spike)
256 end
257 end)
258
259 local splineTimes = {}
260 for t=0, 1, 0.01 do
261 local x, y, z = getSplinePosition(entry.spline, t)
262 local _
263 _, y, z = worldToLocal(entry.node, x, y, z)
264 local angle = -MathUtil.getYRotationFromDirection(y, z)
265
266 local alpha = angle / (2 * math.pi)
267 if alpha < 0 then
268 alpha = alpha + 1
269 end
270 table.insert(splineTimes, {alpha=alpha, time=t})
271 end
272
273 table.insert(splineTimes, {alpha=splineTimes[1].alpha - 0.000001, time=1})
274
275 table.sort(splineTimes, function(a, b)
276 return a.alpha < b.alpha
277 end)
278
279 entry.splineCurve = AnimCurve.new(linearInterpolator1)
280 for j=1, #splineTimes do
281 entry.splineCurve:addKeyframe({splineTimes[j].time, time=splineTimes[j].alpha})
282 end
283
284 for j=1, #spec.animationNodes do
285 local animationNode = spec.animationNodes[j]
286 if animationNode.node == entry.node then
287 entry.animationNode = animationNode
288 end
289 end
290
291 if entry.animationNode ~= nil then
292 table.insert(spec.spikedDrums, entry)
293 else
294 Logging.xmlWarning(self.xmlFile, "Could not find animation node for spikedDrum '%s'", getName(entry.node))
295 end
296 else
297 Logging.xmlWarning(self.xmlFile, "No spline defined for spiked drum '%s'", key)
298 end
299 else
300 Logging.xmlWarning(self.xmlFile, "No drum node defined for spiked drum '%s'", key)
301 end
302 end)
303
304 spec.cutterEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.cutter.effect", self.components, self, self.i3dMappings)
305 spec.fillEffects = g_effectManager:loadEffect(self.xmlFile, "vehicle.cutter.fillEffect", self.components, self, self.i3dMappings)
306
307 spec.samples = {}
308 spec.samples.cut = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.cutter.sounds", "cut", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self)
309 end
310
311 spec.lastAutomaticTiltRaycastPosition = {0, 0, 0}
312
313 spec.automaticTilt = {}
314 spec.automaticTilt.isAvailable = false
315 spec.automaticTilt.hasNodes = false
316 if self:loadCutterTiltFromXML(self.xmlFile, Cutter.CUTTER_TILT_XML_KEY, spec.automaticTilt) then
317 spec.automaticTilt.currentDelta = 0
318 spec.automaticTilt.lastHit = {0, 0, 0}
319 spec.automaticTilt.raycastHit = true
320 spec.automaticTilt.isAvailable = true
321 spec.automaticTilt.hasNodes = #spec.automaticTilt.nodes > 0
322 end
323
324 spec.allowsForageGrowthState = self.xmlFile:getValue("vehicle.cutter#allowsForageGrowthState", false)
325 spec.allowCuttingWhileRaised = self.xmlFile:getValue("vehicle.cutter#allowCuttingWhileRaised", false)
326 spec.movingDirection = MathUtil.sign(self.xmlFile:getValue("vehicle.cutter#movingDirection", 1))
327 spec.strawRatio = self.xmlFile:getValue("vehicle.cutter#strawRatio", 1)
328
329 spec.useWindrow = false
330 spec.currentInputFillType = FillType.UNKNOWN
331 spec.currentInputFruitType = FruitType.UNKNOWN
332 spec.currentInputFruitTypeAI = FruitType.UNKNOWN
333 spec.lastValidInputFruitType = FruitType.UNKNOWN
334 spec.currentInputFruitTypeSent = FruitType.UNKNOWN
335 spec.currentOutputFillType = FillType.UNKNOWN
336 spec.currentConversionFactor = 1
337 spec.currentGrowthStateTime = 0
338 spec.currentGrowthStateTimer = 0
339 spec.currentGrowthState = 0
340
341 spec.lastAreaBiggerZero = false
342 spec.lastAreaBiggerZeroSent = false
343 spec.lastAreaBiggerZeroTime = -1
344
345 spec.workAreaParameters = {}
346 spec.workAreaParameters.lastRealArea = 0
347 spec.workAreaParameters.lastArea = 0
348 spec.workAreaParameters.lastGrowthState = 0
349 spec.workAreaParameters.lastGrowthStateArea = 0
350 spec.workAreaParameters.fruitTypesToUse = {}
351 spec.workAreaParameters.lastFruitTypeToUse = {}
352
353 spec.lastOutputFillTypes = {}
354 spec.lastPrioritizedOutputType = FillType.UNKNOWN
355 spec.lastOutputTime = 0
356
357 spec.cutterLoad = 0
358 spec.isWorking = false
359
360 spec.stoneLastState = 0
361 spec.stoneWearMultiplierData = g_currentMission.stoneSystem:getWearMultiplierByType("CUTTER")
362
363 spec.workAreaParameters.countArea = true
364 spec.aiNoValidGroundTimer = 0
365
366 spec.dirtyFlag = self:getNextDirtyFlag()
367 spec.effectDirtyFlag = self:getNextDirtyFlag()
368end

onPostDetach

Description
Called if vehicle gets detached
Definition
onPostDetach(table attacherVehicle, table implement)
Arguments
tableattacherVehicleattacher vehicle
tableimplementimplement
Code
1352function Cutter:onPostDetach(attacherVehicle, implement)
1353 if self.isClient then
1354 Cutter.updateExtraObjects(self)
1355 end
1356end

onPostLoad

Description
Definition
onPostLoad()
Code
372function Cutter:onPostLoad(savegame)
373 if self.addCutterToCombine ~= nil then
374 self:addCutterToCombine(self)
375 end
376
377 if self.isClient then
378 Cutter.updateExtraObjects(self)
379 end
380end

onPreAttach

Description
Called if vehicle gets attached
Definition
onPreAttach(table attacherVehicle, integer inputJointDescIndex, integer jointDescIndex)
Arguments
tableattacherVehicleattacher vehicle
integerinputJointDescIndexindex of input attacher
integerjointDescIndexindex if attacher at the attacher vehicle
Code
1342function Cutter:onPreAttach(attacherVehicle, inputJointDescIndex, jointDescIndex)
1343 if self.isClient then
1344 Cutter.updateExtraObjects(self)
1345 end
1346end

onReadStream

Description
Definition
onReadStream()
Code
394function Cutter:onReadStream(streamId, connection)
395 self:readCutterFromStream(streamId, connection)
396
397 local spec = self.spec_cutter
398 spec.lastAreaBiggerZero = streamReadBool(streamId)
399 if spec.lastAreaBiggerZero then
400 spec.lastAreaBiggerZeroTime = g_currentMission.time
401 end
402
403 self:setTestAreaRequirements(spec.currentInputFruitType, nil, spec.allowsForageGrowthState)
404end

onReadUpdateStream

Description
Definition
onReadUpdateStream()
Code
417function Cutter:onReadUpdateStream(streamId, timestamp, connection)
418 if connection:getIsServer() then
419 local spec = self.spec_cutter
420
421 if streamReadBool(streamId) then
422 self:readCutterFromStream(streamId, connection)
423 end
424
425 spec.lastAreaBiggerZero = streamReadBool(streamId)
426 if spec.lastAreaBiggerZero then
427 spec.lastAreaBiggerZeroTime = g_currentMission.time
428 end
429
430 self:setTestAreaRequirements(spec.currentInputFruitType, nil, spec.allowsForageGrowthState)
431 end
432end

onStartWorkAreaProcessing

Description
Definition
onStartWorkAreaProcessing()
Code
896function Cutter:onStartWorkAreaProcessing(dt)
897 local spec = self.spec_cutter
898
899 local combineVehicle, alternativeCombine, requiredFillType = self:getCombine()
900
901 if combineVehicle == nil and requiredFillType ~= nil then
902 combineVehicle = alternativeCombine
903 end
904
905 spec.workAreaParameters.combineVehicle = combineVehicle
906 spec.workAreaParameters.lastRealArea = 0
907 spec.workAreaParameters.lastThreshedArea = 0
908 spec.workAreaParameters.lastStatsArea = 0
909 spec.workAreaParameters.lastArea = 0
910 spec.workAreaParameters.lastGrowthState = 0
911 spec.workAreaParameters.lastGrowthStateArea = 0
912 spec.workAreaParameters.lastChopperValue = nil
913
914 if spec.workAreaParameters.lastFruitType == nil then
915 spec.workAreaParameters.fruitTypesToUse = spec.fruitTypes
916 else
917 for i=1, #spec.workAreaParameters.lastFruitTypeToUse do
918 spec.workAreaParameters.lastFruitTypeToUse[i] = nil
919 end
920
921 spec.workAreaParameters.lastFruitTypeToUse[1] = spec.workAreaParameters.lastFruitType
922 spec.workAreaParameters.fruitTypesToUse = spec.workAreaParameters.lastFruitTypeToUse
923 end
924
925 -- if the combine could not be found cause a different fruit type is loaded then the last picked up fruit type,
926 -- we use all fruit types that can result in the fill type the combine has loaded
927 if requiredFillType ~= nil then
928 for i=1, #spec.workAreaParameters.lastFruitTypeToUse do
929 spec.workAreaParameters.lastFruitTypeToUse[i] = nil
930 end
931
932 local fruitType = g_fruitTypeManager:getFruitTypeIndexByFillTypeIndex(requiredFillType)
933
934 for inputFruitType, fruitTypeConverter in pairs(spec.fruitTypeConverters) do
935 if fruitTypeConverter.fillTypeIndex == requiredFillType then
936 table.insert(spec.workAreaParameters.lastFruitTypeToUse, inputFruitType)
937 fruitType = nil
938 end
939 end
940
941 if fruitType ~= nil then
942 table.insert(spec.workAreaParameters.lastFruitTypeToUse, fruitType)
943 end
944
945 spec.workAreaParameters.fruitTypesToUse = spec.workAreaParameters.lastFruitTypeToUse
946 end
947
948 spec.workAreaParameters.lastFruitType = nil
949 spec.isWorking = false
950end

onTurnedOff

Description
Definition
onTurnedOff()
Code
1369function Cutter:onTurnedOff()
1370 local spec = self.spec_cutter
1371 if self.isClient then
1372 g_animationManager:stopAnimations(spec.animationNodes)
1373 g_effectManager:resetEffects(spec.currentCutterEffect)
1374 end
1375
1376 spec.currentInputFruitType = FruitType.UNKNOWN
1377 spec.currentInputFruitTypeSent = FruitType.UNKNOWN
1378 spec.currentInputFruitTypeAI = FruitType.UNKNOWN
1379 spec.currentInputFillType = FillType.UNKNOWN
1380 spec.currentOutputFillType = FillType.UNKNOWN
1381end

onTurnedOn

Description
Definition
onTurnedOn()
Code
1360function Cutter:onTurnedOn()
1361 if self.isClient then
1362 local spec = self.spec_cutter
1363 g_animationManager:startAnimations(spec.animationNodes)
1364 end
1365end

onUpdate

Description
Definition
onUpdate()
Code
488function Cutter:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
489 local spec = self.spec_cutter
490 if spec.automaticTilt.hasNodes then
491 local currentDelta, isActive, doReset = self:getCutterTiltDelta()
492 currentDelta = -currentDelta -- inverted on cutter side since we rotate the cutter itself
493
494 if self.isActive then
495 for i=1, #spec.automaticTilt.nodes do
496 local automaticTiltNode = spec.automaticTilt.nodes[i]
497
498 local _, _, curZ = getRotation(automaticTiltNode.node)
499 if not isActive and doReset then
500 currentDelta = -curZ * 0.1 -- return to idle is not active
501 end
502
503 if math.abs(currentDelta) > 0.00001 then
504 local speedScale = math.min(math.pow(math.abs(currentDelta) / 0.01745, 2), 1) * MathUtil.sign(currentDelta)
505 local rotSpeed = speedScale * automaticTiltNode.maxSpeed * dt
506
507 local newRotZ = MathUtil.clamp(curZ + rotSpeed, automaticTiltNode.minAngle, automaticTiltNode.maxAngle)
508 setRotation(automaticTiltNode.node, 0, 0, newRotZ)
509
510 if self.setMovingToolDirty ~= nil then
511 self:setMovingToolDirty(automaticTiltNode.node)
512 end
513 end
514 end
515 end
516 end
517
518 if self.isClient then
519 for i=1, #spec.spikedDrums do
520 local spikedDrum = spec.spikedDrums[i]
521 if spikedDrum.animationNode.state ~= RotationAnimation.STATE_OFF then
522 local rot, _, _ = getRotation(spikedDrum.node)
523 if rot < 0 then
524 rot = rot + 2 * math.pi
525 end
526 local alpha = rot / (2 * math.pi)
527
528 local numSpikes = #spikedDrum.spikes
529 for j=1, numSpikes do
530 local spike = spikedDrum.spikes[j]
531 local splineTime = spikedDrum.splineCurve:get((alpha + spike.initalTime) % 1)
532
533 local x, y, z = getSplinePosition(spikedDrum.spline, splineTime)
534 local _, spikeY, _ = worldToLocal(getParent(spike.node), x, y, z)
535 setTranslation(spike.node, 0, spikeY, 0)
536 end
537 end
538 end
539 end
540end

onUpdateTick

Description
Definition
onUpdateTick()
Code
544function Cutter:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
545 local spec = self.spec_cutter
546 local isTurnedOn = self:getIsTurnedOn()
547
548 local isEffectActive = isTurnedOn
549 and self.movingDirection == spec.movingDirection
550 and self:getLastSpeed() > 0.5
551 and (spec.allowCuttingWhileRaised or self:getIsLowered(true))
552 and spec.workAreaParameters.combineVehicle ~= nil
553
554 if isEffectActive then
555 local currentTestAreaMinX, currentTestAreaMaxX, testAreaMinX, testAreaMaxX = self:getTestAreaWidthByWorkAreaIndex(1)
556 local testAreaCharge = self:getTestAreaChargeByWorkAreaIndex(1)
557
558 if not spec.useWindrow then
559 spec.cutterLoad = spec.cutterLoad * 0.95 + testAreaCharge * 0.05
560 end
561
562 local reset = false
563 if currentTestAreaMinX == -math.huge and currentTestAreaMaxX == math.huge then
564 currentTestAreaMinX = 0
565 currentTestAreaMaxX = 0
566 reset = true
567 end
568
569 if spec.movingDirection > 0 then
570 currentTestAreaMinX = currentTestAreaMinX * -1
571 currentTestAreaMaxX = currentTestAreaMaxX * -1
572 if currentTestAreaMaxX < currentTestAreaMinX then
573 local t = currentTestAreaMinX
574 currentTestAreaMinX = currentTestAreaMaxX
575 currentTestAreaMaxX = t
576 end
577 end
578
579 local inputFruitType = spec.currentInputFruitType
580 if inputFruitType ~= spec.lastValidInputFruitType then
581 -- if we pickup a different fruit type than we are able to proceed to the combine we won't display the effect
582 inputFruitType = nil
583 end
584
585 if inputFruitType ~= nil then
586 Cutter.updateExtraObjects(self)
587 end
588
589 local isCollecting = spec.lastAreaBiggerZeroTime + 300 > g_currentMission.time
590 local fillType = spec.currentInputFillType
591
592 if spec.useWindrow then
593 if isCollecting then
594 spec.cutterLoad = spec.cutterLoad * 0.95 + 0.05
595 else
596 spec.cutterLoad = spec.cutterLoad * 0.9
597 end
598 end
599
600 if self.isClient then
601 local cutSoundActive = false
602 if fillType ~= FillType.UNKNOWN and isCollecting then
603 g_effectManager:setFillType(spec.fillEffects, fillType)
604 g_effectManager:setMinMaxWidth(spec.fillEffects, currentTestAreaMinX, currentTestAreaMaxX, currentTestAreaMinX / testAreaMinX, currentTestAreaMaxX / testAreaMaxX, reset)
605 g_effectManager:startEffects(spec.fillEffects)
606
607 cutSoundActive = true
608 else
609 g_effectManager:stopEffects(spec.fillEffects)
610 end
611
612 if inputFruitType ~= nil and not reset then
613 g_effectManager:setFruitType(spec.cutterEffects, inputFruitType, spec.currentGrowthState)
614 g_effectManager:setFillType(spec.cutterEffects, fillType)
615 g_effectManager:setMinMaxWidth(spec.cutterEffects, currentTestAreaMinX, currentTestAreaMaxX, currentTestAreaMinX / testAreaMinX, currentTestAreaMaxX / testAreaMaxX, reset)
616 g_effectManager:startEffects(spec.cutterEffects)
617
618 cutSoundActive = true
619 else
620 g_effectManager:stopEffects(spec.cutterEffects)
621 end
622
623 if cutSoundActive then
624 if not g_soundManager:getIsSamplePlaying(spec.samples.cut) then
625 g_soundManager:playSample(spec.samples.cut)
626 end
627 else
628 if g_soundManager:getIsSamplePlaying(spec.samples.cut) then
629 g_soundManager:stopSample(spec.samples.cut)
630 end
631 end
632 end
633 else
634 if self.isClient then
635 g_effectManager:stopEffects(spec.cutterEffects)
636 g_effectManager:stopEffects(spec.fillEffects)
637 g_soundManager:stopSample(spec.samples.cut)
638 end
639
640 spec.cutterLoad = spec.cutterLoad * 0.9
641 end
642
643 -- lastPrioritizedOutputType is always the fill type that was the most significant fill type of the last 500ms
644 -- this fill type is transfered to the combine to avoid quick changes of the fill type if we harvester 2 different fruit types at the same time
645 -- e.g. on field borders if can happen that a bit of grass if collected from the cutter
646 spec.lastOutputTime = spec.lastOutputTime + dt
647 if spec.lastOutputTime > 500 then
648 spec.lastPrioritizedOutputType = FillType.UNKNOWN
649
650 local max = 0
651 for i, _ in pairs(spec.lastOutputFillTypes) do
652 if spec.lastOutputFillTypes[i] > max then
653 spec.lastPrioritizedOutputType = i
654 max = spec.lastOutputFillTypes[i]
655 end
656
657 spec.lastOutputFillTypes[i] = 0
658 end
659
660 spec.lastOutputTime = 0
661 end
662
663 local automaticTilt = spec.automaticTilt
664 local isActive, _ = self:getCutterTiltIsActive(automaticTilt)
665 if isActive then
666 if automaticTilt ~= nil and automaticTilt.raycastNode1 ~= nil and automaticTilt.raycastNode2 ~= nil then
667 automaticTilt.currentDelta = 0
668
669 -- raycast 1
670 local rx, ry, rz = localToWorld(automaticTilt.raycastNode1, 0, 1, 0)
671 local rDirX, rDirY, rDirZ = localDirectionToWorld(automaticTilt.raycastNode1, 0, -1, 0)
672 automaticTilt.raycastHit = false
673 raycastAll(rx, ry, rz, rDirX, rDirY, rDirZ, "tiltRaycastDetectionCallback", 2, self, Cutter.AUTO_TILT_COLLISION_MASK)
674 local hit1X, hit1Y, hit1Z = automaticTilt.lastHit[1], automaticTilt.lastHit[2], automaticTilt.lastHit[3]
675 local node1X, node1Y, node1Z = getWorldTranslation(automaticTilt.raycastNode1)
676 if not automaticTilt.raycastHit then
677 hit1X, hit1Y, hit1Z = localToWorld(automaticTilt.raycastNode1, 0, -1, 0)
678 end
679
680 --raycast 2
681 rx, ry, rz = localToWorld(automaticTilt.raycastNode2, 0, 1, 0)
682 rDirX, rDirY, rDirZ = localDirectionToWorld(automaticTilt.raycastNode2, 0, -1, 0)
683 automaticTilt.raycastHit = false
684 raycastAll(rx, ry, rz, rDirX, rDirY, rDirZ, "tiltRaycastDetectionCallback", 2, self, Cutter.AUTO_TILT_COLLISION_MASK)
685 local hit2X, hit2Y, hit2Z = automaticTilt.lastHit[1], automaticTilt.lastHit[2], automaticTilt.lastHit[3]
686 local node2X, node2Y, node2Z = getWorldTranslation(automaticTilt.raycastNode2)
687 if not automaticTilt.raycastHit then
688 hit2X, hit2Y, hit2Z = localToWorld(automaticTilt.raycastNode2, 0, -1, 0)
689 end
690
691 -- calaculate ground angle
692 local gHeight = hit1Y - hit2Y
693 local gRefX, gRefY, gRefZ = hit2X + rDirX * gHeight, hit2Y + rDirY * gHeight, hit2Z + rDirZ * gHeight
694 local gDistance = MathUtil.vector3Length(hit1X-gRefX, hit1Y-gRefY, hit1Z-gRefZ)
695 local gDirection = (hit2Y > hit1Y and -1 or 1)
696 local gAngle = math.atan(math.abs(gHeight) / gDistance) * gDirection
697
698 -- calculate current cutter angle
699 local cHeight = node2Y - node1Y
700 --#debug local cRefX, cRefY, cRefZ = node2X + rDirX * cHeight, node2Y + rDirY * cHeight, node2Z + rDirZ * cHeight
701 local cDistance = MathUtil.vector3Length(node1X-node2X, node1Y-node2Y, node1Z-node2Z)
702 local cDirection = (node2Y > node1Y and -1 or 1)
703 local cAngle = math.atan(math.abs(cHeight) / cDistance) * cDirection
704
705 --#debug if VehicleDebug.state == VehicleDebug.DEBUG then
706 --#debug DebugUtil.drawDebugGizmoAtWorldPos(hit1X, hit1Y, hit1Z, 0, 0, 1, 0, 1, 0, "r1", false)
707 --#debug DebugUtil.drawDebugGizmoAtWorldPos(hit2X, hit2Y, hit2Z, 0, 0, 1, 0, 1, 0, "r2", false)
708 --#debug drawDebugLine(hit1X, hit1Y, hit1Z, 0, 1, 0, gRefX, gRefY, gRefZ, 0, 1, 0)
709 --#debug drawDebugLine(hit1X, hit1Y, hit1Z, 1, 1, 0, hit2X, hit2Y, hit2Z, 1, 1, 0)
710 --#debug drawDebugLine(node1X, node1Y, node1Z, 0, 1, 0, cRefX, cRefY, cRefZ, 0, 1, 0)
711 --#debug drawDebugLine(node1X, node1Y, node1Z, 1, 1, 0, node2X, node2Y, node2Z, 1, 1, 0)
712 --#debug end
713
714 if gAngle == gAngle and cAngle == cAngle then
715 automaticTilt.currentDelta = gAngle-cAngle
716 end
717 end
718 end
719end

onWriteStream

Description
Definition
onWriteStream()
Code
408function Cutter:onWriteStream(streamId, connection)
409 self:writeCutterToStream(streamId, connection)
410
411 local spec = self.spec_cutter
412 streamWriteBool(streamId, spec.lastAreaBiggerZeroSent)
413end

onWriteUpdateStream

Description
Definition
onWriteUpdateStream()
Code
436function Cutter:onWriteUpdateStream(streamId, connection, dirtyMask)
437 if not connection:getIsServer() then
438 local spec = self.spec_cutter
439
440 if streamWriteBool(streamId, bitAND(dirtyMask, spec.effectDirtyFlag) ~= 0) then
441 self:writeCutterToStream(streamId, connection)
442 end
443
444 streamWriteBool(streamId, spec.lastAreaBiggerZeroSent)
445 end
446end

prerequisitesPresent

Description
Definition
prerequisitesPresent()
Code
67function Cutter.prerequisitesPresent(specializations)
68 return SpecializationUtil.hasSpecialization(WorkArea, specializations) and SpecializationUtil.hasSpecialization(TestAreas, specializations)
69end

processCutterArea

Description
Definition
processCutterArea()
Code
750function Cutter:processCutterArea(workArea, dt)
751 local spec = self.spec_cutter
752
753 if spec.workAreaParameters.combineVehicle ~= nil then
754 local xs,_,zs = getWorldTranslation(workArea.start)
755 local xw,_,zw = getWorldTranslation(workArea.width)
756 local xh,_,zh = getWorldTranslation(workArea.height)
757
758 local lastRealArea = 0
759 local lastThreshedArea = 0
760 local lastArea = 0
761 local fieldGroundSystem = g_currentMission.fieldGroundSystem
762 for _, fruitTypeIndex in ipairs(spec.workAreaParameters.fruitTypesToUse) do
763 local fruitTypeDesc = g_fruitTypeManager:getFruitTypeByIndex(fruitTypeIndex)
764 local chopperValue = fieldGroundSystem:getChopperTypeValue(fruitTypeDesc.chopperTypeIndex)
765 local realArea, area, sprayFactor, plowFactor, limeFactor, weedFactor, stubbleFactor, rollerFactor, beeYieldBonusPerc, growthState, _, terrainDetailPixelsSum = FSDensityMapUtil.cutFruitArea(fruitTypeIndex, xs,zs, xw,zw, xh,zh, true, spec.allowsForageGrowthState, chopperValue)
766
767 if realArea > 0 then
768 if self.isServer then
769 if growthState ~= spec.currentGrowthState then
770 spec.currentGrowthStateTimer = spec.currentGrowthStateTimer + dt
771 if spec.currentGrowthStateTimer > 500 or spec.currentGrowthStateTime + 1000 < g_time then
772 spec.currentGrowthState = growthState
773 spec.currentGrowthStateTimer = 0
774 end
775 else
776 spec.currentGrowthStateTimer = 0
777 spec.currentGrowthStateTime = g_time
778 end
779
780 if fruitTypeIndex ~= spec.currentInputFruitType then
781 spec.currentInputFruitType = fruitTypeIndex
782
783 spec.currentOutputFillType = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(spec.currentInputFruitType)
784 if spec.fruitTypeConverters[spec.currentInputFruitType] ~= nil then
785 spec.currentOutputFillType = spec.fruitTypeConverters[spec.currentInputFruitType].fillTypeIndex
786 spec.currentConversionFactor = spec.fruitTypeConverters[spec.currentInputFruitType].conversionFactor
787 end
788
789 local cutHeight = g_fruitTypeManager:getCutHeightByFruitTypeIndex(fruitTypeIndex, spec.allowsForageGrowthState)
790 self:setCutterCutHeight(cutHeight)
791 end
792
793 self:setTestAreaRequirements(fruitTypeIndex, nil, spec.allowsForageGrowthState)
794
795 -- ai only works on terrain detail, so we do not allow the ai to require fruits that are out of a field
796 if terrainDetailPixelsSum > 0 then
797 spec.currentInputFruitTypeAI = fruitTypeIndex
798 end
799 spec.currentInputFillType = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(fruitTypeIndex)
800 spec.useWindrow = false
801 end
802
803 local multiplier = g_currentMission:getHarvestScaleMultiplier(fruitTypeIndex, sprayFactor, plowFactor, limeFactor, weedFactor, stubbleFactor, rollerFactor, beeYieldBonusPerc)
804 lastRealArea = realArea * multiplier
805 lastThreshedArea = realArea
806 lastArea = area
807
808 spec.workAreaParameters.lastFruitType = fruitTypeIndex
809 spec.workAreaParameters.lastChopperValue = chopperValue
810 break
811 end
812 end
813
814 if lastArea > 0 then
815 if workArea.chopperAreaIndex ~= nil and spec.workAreaParameters.lastChopperValue ~= nil then
816 local chopperWorkArea = self:getWorkAreaByIndex(workArea.chopperAreaIndex)
817 if chopperWorkArea ~= nil then
818 xs,_,zs = getWorldTranslation(chopperWorkArea.start)
819 xw,_,zw = getWorldTranslation(chopperWorkArea.width)
820 xh,_,zh = getWorldTranslation(chopperWorkArea.height)
821
822 FSDensityMapUtil.setGroundTypeLayerArea(xs, zs, xw, zw, xh, zh, spec.workAreaParameters.lastChopperValue)
823 else
824 workArea.chopperAreaIndex = nil
825 Logging.xmlWarning(self.xmlFile, "Invalid chopperAreaIndex '%d' for workArea '%d'!", workArea.chopperAreaIndex, workArea.index)
826 end
827 end
828
829 spec.stoneLastState = FSDensityMapUtil.getStoneArea(xs, zs, xw, zw, xh, zh)
830 spec.isWorking = true
831 end
832
833 spec.workAreaParameters.lastRealArea = spec.workAreaParameters.lastRealArea + lastRealArea
834 spec.workAreaParameters.lastThreshedArea = spec.workAreaParameters.lastThreshedArea + lastThreshedArea
835 spec.workAreaParameters.lastStatsArea = spec.workAreaParameters.lastStatsArea + lastThreshedArea
836 spec.workAreaParameters.lastArea = spec.workAreaParameters.lastArea + lastArea
837 end
838
839 return spec.workAreaParameters.lastRealArea, spec.workAreaParameters.lastArea
840end

processPickupCutterArea

Description
Definition
processPickupCutterArea()
Code
844function Cutter:processPickupCutterArea(workArea, dt)
845 local spec = self.spec_cutter
846
847 if spec.workAreaParameters.combineVehicle ~= nil then
848 local sx, sy, sz = getWorldTranslation(workArea.start)
849 local wx, wy, wz = getWorldTranslation(workArea.width)
850 local hx, hy, hz = getWorldTranslation(workArea.height)
851
852 local lsx, lsy, lsz, lex, ley, lez, lineRadius = DensityMapHeightUtil.getLineByAreaDimensions(sx, sy, sz, wx, wy, wz, hx, hy, hz)
853
854 for _, fruitType in ipairs(spec.workAreaParameters.fruitTypesToUse) do
855 local fillType = g_fruitTypeManager:getWindrowFillTypeIndexByFruitTypeIndex(fruitType)
856 if fillType ~= nil then
857 local pickedUpLiters = -DensityMapHeightUtil.tipToGroundAroundLine(self, -math.huge, fillType, lsx, lsy, lsz, lex, ley, lez, lineRadius, nil, nil, false, nil)
858
859 if self.isServer then
860 if pickedUpLiters > 0 then
861 local fruitDesc = g_fruitTypeManager:getFruitTypeByIndex(fruitType)
862 local literPerSqm = fruitDesc.literPerSqm
863 local lastCutterArea = pickedUpLiters / (g_currentMission:getFruitPixelsToSqm() * literPerSqm)
864
865 if fruitType ~= spec.currentInputFruitType then
866 spec.currentInputFruitType = fruitType
867
868 spec.currentOutputFillType = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(spec.currentInputFruitType)
869 if spec.fruitTypeConverters[spec.currentInputFruitType] ~= nil then
870 spec.currentOutputFillType = spec.fruitTypeConverters[spec.currentInputFruitType].fillTypeIndex
871 spec.currentConversionFactor = spec.fruitTypeConverters[spec.currentInputFruitType].conversionFactor
872 end
873 end
874
875 spec.useWindrow = true
876 spec.currentInputFillType = fillType
877 spec.workAreaParameters.lastFruitType = fruitType
878 spec.workAreaParameters.lastRealArea = spec.workAreaParameters.lastRealArea + lastCutterArea
879 spec.workAreaParameters.lastThreshedArea = spec.workAreaParameters.lastThreshedArea + lastCutterArea
880 spec.workAreaParameters.lastStatsArea = spec.workAreaParameters.lastStatsArea + lastCutterArea
881 spec.workAreaParameters.lastArea = spec.workAreaParameters.lastArea + lastCutterArea
882 spec.stoneLastState = FSDensityMapUtil.getStoneArea(sx, sz, wx, wz, hx, hz)
883 spec.isWorking = true
884 break
885 end
886 end
887 end
888 end
889 end
890
891 return spec.workAreaParameters.lastRealArea, spec.workAreaParameters.lastArea
892end

readCutterFromStream

Description
Definition
readCutterFromStream()
Code
450function Cutter:readCutterFromStream(streamId, connection)
451 local spec = self.spec_cutter
452
453 spec.currentGrowthState = streamReadUIntN(streamId, 4)
454
455 spec.currentInputFruitType = streamReadUIntN(streamId, FruitTypeManager.SEND_NUM_BITS)
456 if streamReadBool(streamId) then
457 spec.lastValidInputFruitType = spec.currentInputFruitType
458 else
459 spec.currentInputFruitType = FruitType.UNKNOWN
460 end
461
462 spec.currentOutputFillType = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(spec.currentInputFruitType)
463 if spec.fruitTypeConverters[spec.currentInputFruitType] ~= nil then
464 spec.currentOutputFillType = spec.fruitTypeConverters[spec.currentInputFruitType].fillTypeIndex
465 spec.currentConversionFactor = spec.fruitTypeConverters[spec.currentInputFruitType].conversionFactor
466 end
467
468 if streamReadBool(streamId) then
469 spec.currentInputFillType = g_fruitTypeManager:getWindrowFillTypeIndexByFruitTypeIndex(spec.currentInputFruitType)
470 else
471 spec.currentInputFillType = g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(spec.currentInputFruitType)
472 end
473end

registerEventListeners

Description
Definition
registerEventListeners()
Code
109function Cutter.registerEventListeners(vehicleType)
110 SpecializationUtil.registerEventListener(vehicleType, "onLoad", Cutter)
111 SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", Cutter)
112 SpecializationUtil.registerEventListener(vehicleType, "onDelete", Cutter)
113 SpecializationUtil.registerEventListener(vehicleType, "onReadStream", Cutter)
114 SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", Cutter)
115 SpecializationUtil.registerEventListener(vehicleType, "onReadUpdateStream", Cutter)
116 SpecializationUtil.registerEventListener(vehicleType, "onWriteUpdateStream", Cutter)
117 SpecializationUtil.registerEventListener(vehicleType, "onUpdate", Cutter)
118 SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", Cutter)
119 SpecializationUtil.registerEventListener(vehicleType, "onStartWorkAreaProcessing", Cutter)
120 SpecializationUtil.registerEventListener(vehicleType, "onEndWorkAreaProcessing", Cutter)
121 SpecializationUtil.registerEventListener(vehicleType, "onPreAttach", Cutter)
122 SpecializationUtil.registerEventListener(vehicleType, "onPostDetach", Cutter)
123 SpecializationUtil.registerEventListener(vehicleType, "onTurnedOn", Cutter)
124 SpecializationUtil.registerEventListener(vehicleType, "onTurnedOff", Cutter)
125 SpecializationUtil.registerEventListener(vehicleType, "onAIImplementStart", Cutter)
126end

registerFunctions

Description
Definition
registerFunctions()
Code
73function Cutter.registerFunctions(vehicleType)
74 SpecializationUtil.registerFunction(vehicleType, "readCutterFromStream", Cutter.readCutterFromStream)
75 SpecializationUtil.registerFunction(vehicleType, "writeCutterToStream", Cutter.writeCutterToStream)
76 SpecializationUtil.registerFunction(vehicleType, "getCombine", Cutter.getCombine)
77 SpecializationUtil.registerFunction(vehicleType, "getAllowCutterAIFruitRequirements", Cutter.getAllowCutterAIFruitRequirements)
78 SpecializationUtil.registerFunction(vehicleType, "processCutterArea", Cutter.processCutterArea)
79 SpecializationUtil.registerFunction(vehicleType, "processPickupCutterArea", Cutter.processPickupCutterArea)
80 SpecializationUtil.registerFunction(vehicleType, "getCutterLoad", Cutter.getCutterLoad)
81 SpecializationUtil.registerFunction(vehicleType, "getCutterStoneMultiplier", Cutter.getCutterStoneMultiplier)
82 SpecializationUtil.registerFunction(vehicleType, "loadCutterTiltFromXML", Cutter.loadCutterTiltFromXML)
83 SpecializationUtil.registerFunction(vehicleType, "getCutterTiltIsAvailable", Cutter.getCutterTiltIsAvailable)
84 SpecializationUtil.registerFunction(vehicleType, "getCutterTiltIsActive", Cutter.getCutterTiltIsActive)
85 SpecializationUtil.registerFunction(vehicleType, "getCutterTiltDelta", Cutter.getCutterTiltDelta)
86 SpecializationUtil.registerFunction(vehicleType, "tiltRaycastDetectionCallback", Cutter.tiltRaycastDetectionCallback)
87 SpecializationUtil.registerFunction(vehicleType, "setCutterCutHeight", Cutter.setCutterCutHeight)
88end

registerOverwrittenFunctions

Description
Definition
registerOverwrittenFunctions()
Code
92function Cutter.registerOverwrittenFunctions(vehicleType)
93 SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadSpeedRotatingPartFromXML", Cutter.loadSpeedRotatingPartFromXML)
94 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsSpeedRotatingPartActive", Cutter.getIsSpeedRotatingPartActive)
95 SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadRandomlyMovingPartFromXML", Cutter.loadRandomlyMovingPartFromXML)
96 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsRandomlyMovingPartActive", Cutter.getIsRandomlyMovingPartActive)
97 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsWorkAreaActive", Cutter.getIsWorkAreaActive)
98 SpecializationUtil.registerOverwrittenFunction(vehicleType, "doCheckSpeedLimit", Cutter.doCheckSpeedLimit)
99 SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadWorkAreaFromXML", Cutter.loadWorkAreaFromXML)
100 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getDirtMultiplier", Cutter.getDirtMultiplier)
101 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getWearMultiplier", Cutter.getWearMultiplier)
102 SpecializationUtil.registerOverwrittenFunction(vehicleType, "isAttachAllowed", Cutter.isAttachAllowed)
103 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getConsumingLoad", Cutter.getConsumingLoad)
104 SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsGroundReferenceNodeThreshold", Cutter.getIsGroundReferenceNodeThreshold)
105end

setCutterCutHeight

Description
Sets cutter cut height
Definition
setCutterCutHeight()
Return Values
floatheightcut height
Code
1171function Cutter:setCutterCutHeight(cutHeight)
1172 if cutHeight ~= nil then
1173 self.spec_cutter.currentCutHeight = cutHeight
1174
1175 if self.spec_attachable ~= nil then
1176 local inputAttacherJoint = self:getActiveInputAttacherJoint()
1177 if inputAttacherJoint ~= nil then
1178 if inputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_CUTTER
1179 or inputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_CUTTERHARVESTER then
1180 inputAttacherJoint.lowerDistanceToGround = cutHeight
1181 end
1182 else
1183 local inputAttacherJoints = self:getInputAttacherJoints()
1184 for i=1, #inputAttacherJoints do
1185 inputAttacherJoint = inputAttacherJoints[i]
1186 if inputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_CUTTER
1187 or inputAttacherJoint.jointType == AttacherJoints.JOINTTYPE_CUTTERHARVESTER then
1188 inputAttacherJoint.lowerDistanceToGround = cutHeight
1189 end
1190 end
1191 end
1192 end
1193 end
1194end

tiltRaycastDetectionCallback

Description
Definition
tiltRaycastDetectionCallback()
Code
1153function Cutter:tiltRaycastDetectionCallback(hitObjectId, x, y, z, distance)
1154 if getRigidBodyType(hitObjectId) ~= RigidBodyType.STATIC then
1155 return true
1156 end
1157
1158 local automaticTilt = self.spec_cutter.automaticTilt
1159 automaticTilt.lastHit[1] = x
1160 automaticTilt.lastHit[2] = y
1161 automaticTilt.lastHit[3] = z
1162
1163 automaticTilt.raycastHit = true
1164
1165 return false
1166end

updateDebugValues

Description
Definition
updateDebugValues()
Code
1448function Cutter:updateDebugValues(values)
1449 local spec = self.spec_cutter
1450 table.insert(values, {name="lastPrioritizedOutputType", value=string.format("%s", g_fillTypeManager:getFillTypeNameByIndex(spec.lastPrioritizedOutputType))})
1451
1452 local sum = 0
1453 for fillType, value in pairs(spec.lastOutputFillTypes) do
1454 sum = sum + value
1455 end
1456
1457 for fillType, value in pairs(spec.lastOutputFillTypes) do
1458 table.insert(values, {name=string.format("buffer (%s)", g_fillTypeManager:getFillTypeNameByIndex(fillType)), value=string.format("%.0f%%", value/sum*100)})
1459 end
1460end

updateExtraObjects

Description
Definition
updateExtraObjects()
Code
1404function Cutter.updateExtraObjects(self)
1405 local spec = self.spec_cutter
1406
1407 if spec.lastValidInputFruitType ~= nil then
1408 local extraObject = spec.fruitExtraObjects[spec.lastValidInputFruitType]
1409
1410 if spec.hideExtraObjectsOnDetach then
1411 if self.getAttacherVehicle == nil or self:getAttacherVehicle() == nil then
1412 extraObject = nil
1413 end
1414 end
1415
1416 if extraObject ~= spec.currentExtraObject then
1417 if spec.currentExtraObject ~= nil then
1418 if spec.currentExtraObject.node ~= nil then
1419 setVisibility(spec.currentExtraObject.node, false)
1420 end
1421 if spec.currentExtraObject.anim ~= nil and self.playAnimation ~= nil then
1422 self:playAnimation(spec.currentExtraObject.anim, -1, self:getAnimationTime(spec.currentExtraObject.anim), true)
1423 end
1424 spec.currentExtraObject = nil
1425 end
1426
1427 if extraObject ~= nil then
1428 if extraObject.node ~= nil then
1429 setVisibility(extraObject.node, true)
1430 end
1431 if extraObject.anim ~= nil and self.playAnimation ~= nil then
1432 self:playAnimation(extraObject.anim, 1, self:getAnimationTime(extraObject.anim), true)
1433 end
1434 spec.currentExtraObject = extraObject
1435 end
1436 end
1437 end
1438end

writeCutterToStream

Description
Definition
writeCutterToStream()
Code
477function Cutter:writeCutterToStream(streamId, connection)
478 local spec = self.spec_cutter
479
480 streamWriteUIntN(streamId, spec.currentGrowthState, 4)
481 streamWriteUIntN(streamId, spec.currentInputFruitType, FruitTypeManager.SEND_NUM_BITS)
482 streamWriteBool(streamId, spec.currentInputFruitType == spec.lastValidInputFruitType)
483 streamWriteBool(streamId, spec.useWindrow)
484end