41 | function Mower:load(savegame) |
42 | |
43 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerTurnOnAnimation#name", "vehicle.turnOnVehicle.turnedAnimation#name") |
44 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerTurnOnAnimation#turnOnSpeed", "vehicle.turnOnVehicle.turnedAnimation#turnOnSpeedScale") |
45 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerTurnOnAnimation#turnOffSpeed", "vehicle.turnOnVehicle.turnedAnimation#turnOnSpeedScale") |
46 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerCutAreas#pickupFillScale", "vehicle.mower#pickupFillScale") |
47 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerToggleWindrowDrop#startEnabled", "vehicle.mower.toggleWindrowDrop#startEnabled") |
48 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerToggleWindrowDrop#disableText", "vehicle.mower.toggleWindrowDrop#disableText") |
49 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerToggleWindrowDrop#animationName", "vehicle.mower.toggleWindrowDrop#animationName") |
50 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerToggleWindrowDrop#animationEnableSpeed", "vehicle.mower.toggleWindrowDrop#animationEnableSpeed") |
51 | Utils.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.mowerToggleWindrowDrop#animationDisableSpeed", "vehicle.mower.toggleWindrowDrop#animationDisableSpeed") |
52 | |
53 | self.doCheckSpeedLimit = Utils.overwrittenFunction(self.doCheckSpeedLimit, Mower.doCheckSpeedLimit) |
54 | self.setUseMowerWindrowDropAreas = Mower.setUseMowerWindrowDropAreas |
55 | self.setEffectEnabled = Mower.setEffectEnabled |
56 | self.processMowerAreas = Mower.processMowerAreas |
57 | |
58 | self.mower = {} |
59 | |
60 | if self.isClient then |
61 | self.mower.sampleMowerStart = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.mowerStartSound", nil, self.baseDirectory) |
62 | self.mower.sampleMowerStop = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.mowerStopSound", nil, self.baseDirectory) |
63 | self.mower.sampleMower = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.mowerSound", nil, self.baseDirectory, self.components[1].node) |
64 | self.mower.sampleMowerCuttingPitchOffset = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.mowerSound#cuttingPitchOffset"), self.mower.sampleMower.pitchOffset) |
65 | self.mower.sampleMowerTargetPitchOffset = self.mower.sampleMower.pitchOffset |
66 | |
67 | self.mower.effects = {} |
68 | local i = 0 |
69 | while true do |
70 | local key = string.format("vehicle.mowerEffects.mowerEffect(%d)", i) |
71 | if not hasXMLProperty(self.xmlFile, key) then |
72 | break |
73 | end |
74 | local effects = EffectManager:loadEffect(self.xmlFile, key, self.components, self) |
75 | if effects ~= nil then |
76 | local mowerEffect = {} |
77 | mowerEffect.effects = effects |
78 | mowerEffect.mowerCutArea = Utils.getNoNil(getXMLInt(self.xmlFile, key .. "#mowerCutArea"), 0) + 1 |
79 | mowerEffect.activeTime = -1 |
80 | mowerEffect.isActive = false |
81 | mowerEffect.isActiveSent = false |
82 | table.insert(self.mower.effects, mowerEffect) |
83 | end |
84 | i = i + 1 |
85 | end |
86 | end |
87 | |
88 | self.mower.turnedOnRotationNodes = Utils.loadRotationNodes(self.xmlFile, {}, "vehicle.turnedOnRotationNodes.turnedOnRotationNode", "mower", self.components) |
89 | self.mower.useWindrowDropAreas = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.mower.toggleWindrowDrop#startEnabled"), table.getn(self:getTypedWorkAreas(WorkArea.AREATYPE_MOWERDROP)) > 0) |
90 | |
91 | local i18n = g_i18n |
92 | if self.customEnvironment ~= nil then |
93 | i18n = _G[self.customEnvironment].g_i18n |
94 | end |
95 | |
96 | self.mower.toggleWindrowDropEnableText = getXMLString(self.xmlFile, "vehicle.mower.toggleWindrowDrop#enableText") |
97 | self.mower.toggleWindrowDropDisableText = getXMLString(self.xmlFile, "vehicle.mower.toggleWindrowDrop#disableText") |
98 | if self.mower.toggleWindrowDropEnableText ~= nil then |
99 | self.mower.toggleWindrowDropEnableText = i18n:getText(self.mower.toggleWindrowDropEnableText) |
100 | end |
101 | if self.mower.toggleWindrowDropDisableText ~= nil then |
102 | self.mower.toggleWindrowDropDisableText = i18n:getText(self.mower.toggleWindrowDropDisableText) |
103 | end |
104 | |
105 | self.mower.toggleWindrowDropAnimation = getXMLString(self.xmlFile, "vehicle.mower.toggleWindrowDrop#animationName") |
106 | self.mower.enableWindrowDropAnimationSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.mower.toggleWindrowDrop#animationEnableSpeed"), 1) |
107 | self.mower.disableWindrowDropAnimationSpeed = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.mower.toggleWindrowDrop#animationDisableSpeed"), -self.mower.enableWindrowDropAnimationSpeed) |
108 | |
109 | self.mower.pickupFillScale = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.mower#pickupFillScale"), 1) |
110 | |
111 | local cutAreas = self:getTypedWorkAreas(WorkArea.AREATYPE_MOWER) |
112 | local dropAreas = self:getTypedWorkAreas(WorkArea.AREATYPE_MOWERDROP) |
113 | for _, workArea in pairs(cutAreas) do |
114 | if workArea.dropAreaIndex ~= nil and dropAreas[workArea.dropAreaIndex] == nil then |
115 | workArea.dropAreaIndex = nil |
116 | else |
117 | dropAreas[workArea.dropAreaIndex].cutArea = workArea |
118 | end |
119 | end |
120 | |
121 | self.mower.isSpeedLimitActive = false |
122 | self.mower.remainingWindrowToDrop = 0 |
123 | self.mower.dirtyFlag = self:getNextDirtyFlag() |
124 | |
125 | self.showFieldNotOwnedWarning = false |
126 | |
127 | local desc = FruitUtil.fruitTypes["grass"] |
128 | self.aiRequiredFruitType = desc.index |
129 | self.aiRequiredMinGrowthState = desc.minHarvestingGrowthState |
130 | self.aiRequiredMaxGrowthState = desc.maxHarvestingGrowthState |
131 | |
132 | table.insert(self.terrainDetailRequiredValueRanges, {g_currentMission.grassValue, g_currentMission.grassValue, g_currentMission.terrainDetailTypeFirstChannel, g_currentMission.terrainDetailTypeNumChannels}) |
133 | end |
232 | function Mower:updateTick(dt) |
233 | self.mower.isSpeedLimitActive = false |
234 | local showFieldNotOwnedWarning = false |
235 | |
236 | if self:getIsActive() then |
237 | if self:getIsTurnedOn() then |
238 | local hasGroundContact, typedWorkAreas = self:getIsTypedWorkAreaActive(WorkArea.AREATYPE_MOWER) |
239 | if hasGroundContact then |
240 | self.mower.isSpeedLimitActive = true |
241 | if self.isServer then |
242 | local numWorkAreas = 0 |
243 | local numDropAreas = 0 |
244 | local workAreas = self.mowerCutAreas |
245 | local dropAreas = self:getTypedWorkAreas(WorkArea.AREATYPE_MOWERDROP) |
246 | |
247 | for _,workArea in pairs(typedWorkAreas) do |
248 | if self:getIsWorkAreaActive(workArea) then |
249 | local x,_,z = getWorldTranslation(workArea.start); |
250 | if not g_currentMission:getIsFieldOwnedAtWorldPos(x,z) then |
251 | showFieldNotOwnedWarning = true; |
252 | break; |
253 | else |
254 | numWorkAreas = numWorkAreas + 1 |
255 | workAreas[numWorkAreas].x, _, workAreas[numWorkAreas].z = getWorldTranslation(workArea.start) |
256 | workAreas[numWorkAreas].x1, _, workAreas[numWorkAreas].z1 = getWorldTranslation(workArea.width) |
257 | workAreas[numWorkAreas].x2, _, workAreas[numWorkAreas].z2 = getWorldTranslation(workArea.height) |
258 | workAreas[numWorkAreas].dropWindrow = workArea.dropWindrow |
259 | workAreas[numWorkAreas].dropArea = workArea.dropAreaIndex |
260 | if self.mower.useWindrowDropAreas then |
261 | workAreas[numWorkAreas].dropWindrow = true |
262 | else |
263 | workAreas[numWorkAreas].dropArea = nil |
264 | end |
265 | end |
266 | end |
267 | end |
268 | |
269 | for k, dropArea in pairs(dropAreas) do |
270 | numDropAreas = numDropAreas + 1 |
271 | dropArea.x, _, dropArea.z = getWorldTranslation(dropArea.start) |
272 | dropArea.x1,_, dropArea.z1 = getWorldTranslation(dropArea.width) |
273 | dropArea.x2,_, dropArea.z2 = getWorldTranslation(dropArea.height) |
274 | dropArea.dropAreaId = k |
275 | end |
276 | |
277 | if numWorkAreas > 0 then |
278 | local numDropAreasUsed, pickedUpWindrowLiters, remainingWindrowToDrop = self:processMowerAreas(workAreas, numWorkAreas, dropAreas, numDropAreas, self.mower.remainingWindrowToDrop) |
279 | self.mower.remainingWindrowToDrop = remainingWindrowToDrop |
280 | |
281 | for _, mowerEffect in pairs(self.mower.effects) do |
282 | local hasContact = false |
283 | if dropAreas[mowerEffect.mowerCutArea] ~= nil and dropAreas[mowerEffect.mowerCutArea].cutArea ~= nil then |
284 | local dropArea = dropAreas[mowerEffect.mowerCutArea] |
285 | hasContact = self:getIsWorkAreaActive(dropArea.cutArea) |
286 | local active = dropArea.value > 0 and hasContact and self:getLastSpeed() > 1 and numDropAreasUsed > 0 |
287 | if active then |
288 | mowerEffect.activeTime = 250 |
289 | else |
290 | mowerEffect.activeTime = mowerEffect.activeTime - dt |
291 | if mowerEffect.activeTime > 0 then |
292 | active = true |
293 | end |
294 | end |
295 | if active then |
296 | self:setEffectEnabled(mowerEffect, true) |
297 | else |
298 | self:setEffectEnabled(mowerEffect, false) |
299 | end |
300 | end |
301 | end |
302 | |
303 | if pickedUpWindrowLiters > 0 and self.allowFillType ~= nil and self:allowFillType(FillUtil.FILLTYPE_GRASS_WINDROW) then |
304 | --local fillDelta = pickedUpWindrow * FruitUtil.getFillTypeLiterPerSqm(FillUtil.FILLTYPE_GRASS_WINDROW, 1) * self.mower.pickupFillScale |
305 | local fillDelta = pickedUpWindrowLiters * self.mower.pickupFillScale |
306 | self:setFillLevel(self:getFillLevel(FillUtil.FILLTYPE_GRASS_WINDROW)+fillDelta, FillUtil.FILLTYPE_GRASS_WINDROW) |
307 | end |
308 | end |
309 | |
310 | local isDirty = false |
311 | for _, mowerEffect in pairs(self.mower.effects) do |
312 | if mowerEffect.isActive ~= mowerEffect.isActiveSent then |
313 | isDirty = true |
314 | end |
315 | mowerEffect.isActiveSent = mowerEffect.isActive |
316 | end |
317 | if isDirty then |
318 | self:raiseDirtyFlags(self.mower.dirtyFlag) |
319 | end |
320 | end |
321 | else |
322 | for _, mowerEffect in pairs(self.mower.effects) do |
323 | self:setEffectEnabled(mowerEffect, false) |
324 | end |
325 | end |
326 | |
327 | if self.isClient then |
328 | |
329 | if self.mower.sampleMowerTargetPitchOffset ~= nil then |
330 | local percent = 0 |
331 | for _,mowerEffect in pairs(self.mower.effects) do |
332 | if mowerEffect.isActive then |
333 | percent = percent + 1 |
334 | end |
335 | end |
336 | percent = percent / table.getn(self.mower.effects) |
337 | local pitchDelta = self.mower.sampleMowerCuttingPitchOffset - self.mower.sampleMower.pitchOffset |
338 | local targetPitch = self.mower.sampleMower.pitchOffset + (percent * pitchDelta) |
339 | |
340 | if targetPitch > self.mower.sampleMowerTargetPitchOffset then |
341 | self.mower.sampleMowerTargetPitchOffset = math.min(targetPitch, self.mower.sampleMowerTargetPitchOffset + (math.abs(pitchDelta) * dt/1500)) |
342 | elseif targetPitch < self.mower.sampleMowerTargetPitchOffset then |
343 | self.mower.sampleMowerTargetPitchOffset = math.max(targetPitch, self.mower.sampleMowerTargetPitchOffset - (math.abs(pitchDelta) * dt/1500)) |
344 | end |
345 | |
346 | SoundUtil.setSamplePitch(self.mower.sampleMower, self.mower.sampleMowerTargetPitchOffset) |
347 | end |
348 | |
349 | if self:getIsActiveForSound() then |
350 | if not SoundUtil.isSamplePlaying(self.mower.sampleMowerStart, 1.8*dt) then |
351 | SoundUtil.playSample(self.mower.sampleMower, 0, 0, nil) |
352 | end |
353 | SoundUtil.stop3DSample(self.mower.sampleMower) |
354 | else |
355 | SoundUtil.play3DSample(self.mower.sampleMower) |
356 | end |
357 | end |
358 | end |
359 | end |
360 | |
361 | if self.isServer then |
362 | if showFieldNotOwnedWarning ~= self.showFieldNotOwnedWarning then |
363 | self.showFieldNotOwnedWarning = showFieldNotOwnedWarning |
364 | self:raiseDirtyFlags(self.mower.dirtyFlag) |
365 | end |
366 | end |
367 | end |
524 | function Mower:processMowerAreas(workAreas, numWorkAreas, dropAreas, numDropAreas, remainingWindrowToDrop) |
525 | |
526 | local pickedUpWindrow = 0 |
527 | local numDropAreasUsed = 0 |
528 | local fillType = FruitUtil.fruitTypeToWindrowFillType[FruitUtil.FRUITTYPE_GRASS] |
529 | |
530 | for i=1, numWorkAreas do |
531 | local area = workAreas[i] |
532 | |
533 | local x0 = area.x |
534 | local z0 = area.z |
535 | local x1 = area.x1 |
536 | local z1 = area.z1 |
537 | local x2 = area.x2 |
538 | local z2 = area.z2 |
539 | |
540 | local areaPixelsSum, _, sprayFactor, _ = Utils.cutFruitArea(FruitUtil.FRUITTYPE_GRASS, x0,z0, x1,z1, x2,z2, true, true) |
541 | if areaPixelsSum > 0 then |
542 | local multi = g_currentMission:getHarvestScaleMultiplier(sprayFactor, 1) |
543 | areaPixelsSum = areaPixelsSum * multi |
544 | end |
545 | |
546 | local pixelToSqm = g_currentMission:getFruitPixelsToSqm() |
547 | local sqm = areaPixelsSum * pixelToSqm |
548 | local litersChanged = sqm * FruitUtil.getFillTypeLiterPerSqm(FruitUtil.fruitTypeToWindrowFillType[FruitUtil.FRUITTYPE_GRASS], 1) |
549 | |
550 | if area.dropArea ~= nil then |
551 | dropAreas[area.dropArea].valueAccum = dropAreas[area.dropArea].valueAccum + litersChanged |
552 | else |
553 | if not area.dropWindrow then |
554 | pickedUpWindrow = pickedUpWindrow + litersChanged |
555 | else |
556 | |
557 | local lx_2 = 0.5*(area.x2 - area.x) |
558 | local lz_2 = 0.5*(area.z2 - area.z) |
559 | local sx = area.x + lx_2 |
560 | local sz = area.z + lz_2 |
561 | local ex = area.x1 + lx_2 |
562 | local ez = area.z1 + lz_2 |
563 | local sy = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, sx,0,sz) |
564 | local ey = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ex,0,ez) |
565 | |
566 | local toDrop = remainingWindrowToDrop + litersChanged |
567 | local dropped, lineOffset = TipUtil.tipToGroundAroundLine(self, toDrop, fillType, sx,sy,sz, ex,ey,ez, 0, nil, self.mowerLineOffset, false, nil, false) |
568 | self.mowerLineOffset = lineOffset |
569 | remainingWindrowToDrop = remainingWindrowToDrop + (toDrop - dropped) |
570 | end |
571 | end |
572 | |
573 | end |
574 | |
575 | local unitLength = TipUtil.densityToWorldMap |
576 | |
577 | for i=1, numDropAreas do |
578 | local area = dropAreas[i] |
579 | local widthX = area.x1 - area.x |
580 | local widthZ = area.z1 - area.z |
581 | local widthLength = Utils.vector2Length(widthX, widthZ) |
582 | local widthLength_2 = 0.5 * widthLength |
583 | |
584 | local widthX_norm = widthX / widthLength |
585 | local widthZ_norm = widthZ / widthLength |
586 | |
587 | local middleX = 0.5 * (area.x1 + area.x) |
588 | local middleZ = 0.5 * (area.z1 + area.z) |
589 | |
590 | local sx = middleX + ( widthX_norm * math.max(0, widthLength_2 - unitLength) ) |
591 | local sz = middleZ + ( widthZ_norm * math.max(0, widthLength_2 - unitLength) ) |
592 | |
593 | local ex = middleX - ( widthX_norm * math.max(0, widthLength_2 - unitLength) ) |
594 | local ez = middleZ - ( widthZ_norm * math.max(0, widthLength_2 - unitLength) ) |
595 | |
596 | local sy = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, sx,0,sz) |
597 | local ey = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ex,0,ez) |
598 | |
599 | --drawDebugLine(sx,sy,sz, 1,0,0, sx,sy+2,sz, 1,0,0) |
600 | --drawDebugLine(ex,ey,ez, 0,1,0, ex,ey+2,ez, 0,1,0) |
601 | |
602 | local toDrop = area.valueAccum |
603 | local dropped, lineOffset = TipUtil.tipToGroundAroundLine(self, toDrop, fillType, sx,sy,sz, ex,ey,ez, 0, nil, area.mowerLineOffset, false, nil, false) |
604 | area.mowerLineOffset = lineOffset |
605 | |
606 | area.valueAccum = area.valueAccum - dropped |
607 | area.value = dropped |
608 | |
609 | if dropped > 0 then |
610 | numDropAreasUsed = numDropAreasUsed + 1 |
611 | end |
612 | |
613 | end |
614 | |
615 | return numDropAreasUsed, pickedUpWindrow, remainingWindrowToDrop |
616 | end |