41 | function Windrower:load(savegame) |
42 | self.updateWindrower = Windrower.updateWindrower; |
43 | self.doCheckSpeedLimit = Utils.overwrittenFunction(self.doCheckSpeedLimit, Windrower.doCheckSpeedLimit); |
44 | |
45 | self.processWindrowerAreas = Windrower.processWindrowerAreas; |
46 | |
47 | if self.isClient then |
48 | self.windrowerTurnedOnRotationNodes = Utils.loadRotationNodes(self.xmlFile, {}, "vehicle.turnedOnRotationNodes.turnedOnRotationNode", "windrower", self.components); |
49 | end |
50 | |
51 | if hasXMLProperty(self.xmlFile, "vehicle.animation") then |
52 | print("Warning: Windrower 'vehicle.animation' is not supported anymore in "..self.configFileName..". Use vehicle.windrowers.windrower instead."); |
53 | end |
54 | |
55 | self.windrowers = {}; |
56 | local i = 0; |
57 | while true do |
58 | local key = string.format("vehicle.windrowers.windrower(%d)", i); |
59 | if not hasXMLProperty(self.xmlFile, key) then |
60 | break; |
61 | end |
62 | |
63 | local windrower = {}; |
64 | if self:loadWindrowerFromXML(windrower, self.xmlFile, key, i) then |
65 | table.insert(self.windrowers, windrower); |
66 | self:updateWindrower(windrower); |
67 | end; |
68 | i = i + 1; |
69 | end |
70 | |
71 | local numWindrowerDropAreas = table.getn(self:getTypedWorkAreas(WorkArea.AREATYPE_WINDROWERDROP)); |
72 | if numWindrowerDropAreas == 0 then |
73 | print("Warning: No drop areas specified in '"..self.configFileName.."'"); |
74 | else |
75 | if numWindrowerDropAreas ~= 1 and numWindrowerDropAreas ~= table.getn(self:getTypedWorkAreas(WorkArea.AREATYPE_WINDROWER)) then |
76 | print("Warning: Number of cutting areas and drop areas should be equal in '"..self.configFileName.."'"); |
77 | end |
78 | end |
79 | |
80 | self.accumulatedFruitType = FruitUtil.FRUITTYPE_UNKNOWN; |
81 | |
82 | if hasXMLProperty(self.xmlFile, "vehicle.windrowerParticleSystems") then |
83 | local fillTypes = {}; |
84 | local fillTypeCategories = getXMLString(self.xmlFile, "vehicle.windrowerParticleSystems#fillTypeCategories"); |
85 | local fillTypeNames = getXMLString(self.xmlFile, "vehicle.windrowerParticleSystems#fillTypes"); |
86 | if fillTypeCategories ~= nil and fillTypeNames == nil then |
87 | fillTypes = FillUtil.getFillTypeByCategoryName(fillTypeCategories, "Warning: '"..self.configFileName.. "' has invalid fillTypeCategory '%s'.") |
88 | elseif fillTypeCategories == nil and fillTypeNames ~= nil then |
89 | fillTypes = FillUtil.getFillTypesByNames(fillTypeNames, "Warning: '"..self.configFileName.. "' has invalid fillType '%s'.") |
90 | else |
91 | print("Warning: '"..self.configFileName.. "' a windrower particleSystem needs either the 'fillTypeCategories' or 'fillTypes' attribute.") |
92 | end |
93 | |
94 | self.windrowerParticleSystems = {} |
95 | self.sortedWindrowerParticleSystems = {} |
96 | local i=0; |
97 | while true do |
98 | local key = string.format("vehicle.windrowerParticleSystems.emitterShape(%d)", i); |
99 | if not hasXMLProperty(self.xmlFile, key) then |
100 | break |
101 | end |
102 | local emitterShape = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key.."#node")); |
103 | local particleType = getXMLString(self.xmlFile, key.."#particleType") |
104 | if emitterShape ~= nil then |
105 | local workAreaIndex = Utils.getNoNil(getXMLInt(self.xmlFile, key.."#workAreaIndex"), 0) + 1; |
106 | if workAreaIndex ~= nil and self.workAreas[workAreaIndex] ~= nil then |
107 | if self.windrowerParticleSystems[workAreaIndex] == nil then |
108 | self.windrowerParticleSystems[workAreaIndex] = {} |
109 | end |
110 | |
111 | for _, fillType in pairs(fillTypes) do |
112 | local particleSystem = MaterialUtil.getParticleSystem(fillType, particleType) |
113 | if particleSystem ~= nil then |
114 | if self.windrowerParticleSystems[workAreaIndex][fillType] == nil then |
115 | self.windrowerParticleSystems[workAreaIndex][fillType] = {} |
116 | end |
117 | |
118 | local currentPS = ParticleUtil.copyParticleSystem(self.xmlFile, key, particleSystem, emitterShape) |
119 | currentPS.disableTime = 0; |
120 | currentPS.isEnabled = false; |
121 | table.insert(self.windrowerParticleSystems[workAreaIndex][fillType], currentPS) |
122 | table.insert(self.sortedWindrowerParticleSystems, currentPS) |
123 | end |
124 | end |
125 | end |
126 | end |
127 | |
128 | i=i+1; |
129 | end |
130 | end |
131 | |
132 | if self.isClient then |
133 | self.sampleWindrower = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.windrowerSound", nil, self.baseDirectory); |
134 | end |
135 | |
136 | self.isWindrowerSpeedLimitActive = false; |
137 | |
138 | self.showFieldNotOwnedWarning = false |
139 | |
140 | self.aiRequiredMinGrowthState = 0; |
141 | self.aiRequiredMaxGrowthState = g_currentMission.numWindrowChannels; |
142 | self.aiUseDensityHeightMap = true; |
143 | self.aiUseWindrowFruitType = true; |
144 | |
145 | table.insert(self.terrainDetailRequiredValueRanges, {g_currentMission.grassValue, g_currentMission.grassValue, g_currentMission.terrainDetailTypeFirstChannel, g_currentMission.terrainDetailTypeNumChannels}); |
146 | |
147 | self.windrowerGroundFlag = self:getNextDirtyFlag(); |
148 | end |
229 | function Windrower:updateTick(dt) |
230 | local showFieldNotOwnedWarning = false |
231 | self.isWindrowerSpeedLimitActive = false; |
232 | if self:getIsActive() then |
233 | if self:getIsTurnedOn() then |
234 | local hasGroundContact, typedWorkAreas = self:getIsTypedWorkAreaActive(WorkArea.AREATYPE_WINDROWER); |
235 | self.hasGroundContact = hasGroundContact; |
236 | |
237 | if hasGroundContact then |
238 | self.isWindrowerSpeedLimitActive = true; |
239 | if self.isServer then |
240 | |
241 | local workAreas = {}; |
242 | local typedWorkAreasDrop, showWarning = self:getTypedNetworkAreas(WorkArea.AREATYPE_WINDROWERDROP, true); |
243 | showFieldNotOwnedWarning = showWarning; |
244 | |
245 | if not showWarning then |
246 | |
247 | for k, workArea in pairs(typedWorkAreas) do |
248 | if self:getIsWorkAreaActive(workArea) then |
249 | local x,_,z = getWorldTranslation(workArea.start); |
250 | local x1,_,z1 = getWorldTranslation(workArea.width); |
251 | local x2,_,z2 = getWorldTranslation(workArea.height); |
252 | |
253 | local dropArea = typedWorkAreasDrop[workArea.dropAreaIndex]; |
254 | local dx, dz = dropArea[1], dropArea[2]; |
255 | local dx1,dz1 = dropArea[3], dropArea[4]; |
256 | local dx2,dz2 = dropArea[5], dropArea[6]; |
257 | |
258 | table.insert(workAreas, {x,z, x1,z1, x2,z2, dx,dz, dx1,dz1, dx2,dz2, 0, k}); |
259 | end |
260 | end |
261 | |
262 | if table.getn(typedWorkAreas) > 0 then |
263 | local workAreasRet, fruitType = self:processWindrowerAreas(workAreas, self.accumulatedWorkAreaValues, self.accumulatedFruitType); |
264 | |
265 | if table.getn(workAreasRet) > 0 then |
266 | self.accumulatedFruitType = fruitType; |
267 | |
268 | if self:getLastSpeed(true) > 0.5 then |
269 | for i=1, table.getn(workAreasRet) do |
270 | if workAreasRet[i][13] > 0 then |
271 | local workArea = workAreasRet[i][14]; |
272 | local psPerWorkArea = self.windrowerParticleSystems[workArea] |
273 | if psPerWorkArea ~= nil then |
274 | local fillType = FruitUtil.fruitTypeToWindrowFillType[fruitType]; |
275 | local psPerFillType = psPerWorkArea[fillType] |
276 | if psPerFillType ~= nil then |
277 | for _, ps in pairs(psPerFillType) do |
278 | ps.disableTime = g_currentMission.time + 300; |
279 | if not ps.isEnabled then |
280 | ps.isEnabled = true; |
281 | self:raiseDirtyFlags(self.windrowerGroundFlag); |
282 | if self.isClient then |
283 | ParticleUtil.setEmittingState(ps, true); |
284 | end |
285 | end |
286 | end |
287 | end |
288 | end |
289 | end |
290 | end |
291 | end |
292 | end |
293 | end |
294 | |
295 | end |
296 | end |
297 | end |
298 | |
299 | if self.isClient and self:getIsActiveForSound() then |
300 | SoundUtil.playSample(self.sampleWindrower, 0, 0, nil); |
301 | end |
302 | |
303 | if self.isServer then |
304 | for _, ps in ipairs(self.sortedWindrowerParticleSystems) do |
305 | if g_currentMission.time > ps.disableTime then |
306 | ps.isEnabled = false; |
307 | self:raiseDirtyFlags(self.windrowerGroundFlag); |
308 | if self.isClient then |
309 | ParticleUtil.setEmittingState(ps, false); |
310 | end |
311 | end |
312 | end |
313 | end |
314 | end |
315 | end |
316 | |
317 | if self.isServer then |
318 | if showFieldNotOwnedWarning ~= self.showFieldNotOwnedWarning then |
319 | self.showFieldNotOwnedWarning = showFieldNotOwnedWarning |
320 | self:raiseDirtyFlags(self.windrowerGroundFlag); |
321 | end |
322 | end |
323 | |
324 | if Utils.areRotationNodesRunning(self.windrowerTurnedOnRotationNodes) then |
325 | for _, windrower in pairs(self.windrowers) do |
326 | self:updateWindrower(windrower); |
327 | end |
328 | end |
329 | end |
442 | function Windrower:loadWindrowerFromXML(superFunc, windrower, xmlFile, key, index) |
443 | if superFunc ~= nil then |
444 | if not superFunc(self, windrower, xmlFile, key, index) then |
445 | return false; |
446 | end; |
447 | end; |
448 | |
449 | windrower.node = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key .. "#index")); |
450 | windrower.spikes = {}; |
451 | windrower.maxRotZ = math.rad(getXMLFloat(xmlFile, key.."#spikeMaxRotZ")); |
452 | windrower.dir = Utils.getNoNil(getXMLInt(xmlFile, key.."#dir"), 1); |
453 | |
454 | local moveUpRange = Utils.getRadiansFromString(getXMLString(xmlFile, key .. "#moveUpRange"), 2); |
455 | local moveDownRange = Utils.getRadiansFromString(getXMLString(xmlFile, key .. "#moveDownRange"), 2); |
456 | windrower.moveUpStart = moveUpRange[1]; |
457 | windrower.moveUpEnd = moveUpRange[2]; |
458 | windrower.moveDownStart = moveDownRange[1]; |
459 | windrower.moveDownEnd = moveDownRange[2]; |
460 | |
461 | local j = 0; |
462 | while true do |
463 | local spikeKey = string.format(key .. ".spike(%d)", j); |
464 | if not hasXMLProperty(xmlFile, spikeKey) then |
465 | break; |
466 | end |
467 | local spike = {} |
468 | spike.node = Utils.indexToObject(self.components, getXMLString(xmlFile, spikeKey .. "#index")) |
469 | spike.dir = Utils.getNoNil(getXMLInt(xmlFile, spikeKey.."#dir"), 1) |
470 | local _,y,_ = getRotation(getParent(spike.node)); |
471 | spike.yRotOffset = y; |
472 | table.insert(windrower.spikes, spike); |
473 | j = j + 1 |
474 | end |
475 | |
476 | windrower.yRotOffset = (2*math.pi) / #windrower.spikes; |
477 | |
478 | return true; |
479 | end; |
495 | function Windrower:processWindrowerAreas(workAreas, accumulatedWorkAreaValues, accumulatedFruitType) |
496 | |
497 | local numAreas = table.getn(workAreas); |
498 | local fruitType = FruitUtil.FRUITTYPE_GRASS; |
499 | local fruitTypeFix = false; |
500 | |
501 | local workAreasRet = {}; |
502 | for i=1, numAreas do |
503 | |
504 | local x0 = workAreas[i][1]; |
505 | local z0 = workAreas[i][2]; |
506 | local x1 = workAreas[i][3]; |
507 | local z1 = workAreas[i][4]; |
508 | local x2 = workAreas[i][5]; |
509 | local z2 = workAreas[i][6]; |
510 | local dx0 = workAreas[i][7]; |
511 | local dz0 = workAreas[i][8]; |
512 | local dx1 = workAreas[i][9]; |
513 | local dz1 = workAreas[i][10]; |
514 | local dx2 = workAreas[i][11]; |
515 | local dz2 = workAreas[i][12]; |
516 | |
517 | -- pick up |
518 | local hx = x2 - x0; |
519 | local hz = z2 - z0; |
520 | local hLength = Utils.vector2Length(hx, hz); |
521 | local hLength_2 = 0.5 * hLength; |
522 | |
523 | local wx = x1 - x0; |
524 | local wz = z1 - z0; |
525 | local wLength = Utils.vector2Length(wx, wz); |
526 | |
527 | local sx = x0 + (hx * 0.5) + ((wx/wLength)*hLength_2); |
528 | local sz = z0 + (hz * 0.5) + ((wz/wLength)*hLength_2); |
529 | |
530 | local ex = x1 + (hx * 0.5) - ((wx/wLength)*hLength_2); |
531 | local ez = z1 + (hz * 0.5) - ((wz/wLength)*hLength_2); |
532 | |
533 | local sy = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, sx,0,sz); |
534 | local ey = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ex,0,ez); |
535 | |
536 | local liters = 0; |
537 | if fruitTypeFix then |
538 | liters = -TipUtil.tipToGroundAroundLine(self, -math.huge, FruitUtil.fruitTypeToWindrowFillType[fruitType], sx,sy,sz, ex,ey,ez, hLength_2, nil, nil, false, nil); |
539 | else |
540 | -- first try dry grass to avoid wet grass destroying dry grass |
541 | fruitType = FruitUtil.FRUITTYPE_DRYGRASS; |
542 | liters = -TipUtil.tipToGroundAroundLine(self, -math.huge, FruitUtil.fruitTypeToWindrowFillType[FruitUtil.FRUITTYPE_DRYGRASS], sx,sy,sz, ex,ey,ez, hLength_2, nil, nil, false, nil); |
543 | |
544 | if liters == 0 then |
545 | for fruitId=1, FruitUtil.NUM_FRUITTYPES do |
546 | if fruitId ~= FruitUtil.FRUITTYPE_DRYGRASS then |
547 | local ids = g_currentMission.fruits[fruitId]; |
548 | if ids ~= nil and FruitUtil.fruitTypeToWindrowFillType[fruitId] ~= nil then |
549 | fruitType = fruitId; |
550 | liters = -TipUtil.tipToGroundAroundLine(self, -math.huge, FruitUtil.fruitTypeToWindrowFillType[fruitType], sx,sy,sz, ex,ey,ez, hLength_2, nil, nil, false, nil); |
551 | if liters > 0 then |
552 | break; |
553 | end |
554 | end |
555 | end |
556 | end |
557 | end |
558 | end |
559 | if liters > 0 then |
560 | fruitTypeFix = true; |
561 | |
562 | -- now that we removed the windrow, maybe there is some hidden drygrass to grow (set it to growth state 1 if there is some) |
563 | if fruitType == FruitUtil.FRUITTYPE_DRYGRASS then |
564 | Utils.switchFruitTypeArea(FruitUtil.FRUITTYPE_GRASS, FruitUtil.FRUITTYPE_DRYGRASS, x0, z0, x1, z1, x2, z2, 2); |
565 | end |
566 | |
567 | end |
568 | |
569 | -- handle grass as drygrass and vice versa |
570 | if fruitType == FruitUtil.FRUITTYPE_GRASS then |
571 | liters = liters - TipUtil.tipToGroundAroundLine(self, -math.huge, FruitUtil.fruitTypeToWindrowFillType[FruitUtil.FRUITTYPE_DRYGRASS], sx,sy,sz, ex,ey,ez, hLength_2, nil, nil, false, nil); |
572 | elseif fruitType == FruitUtil.FRUITTYPE_DRYGRASS then |
573 | liters = liters - TipUtil.tipToGroundAroundLine(self, -math.huge, FruitUtil.fruitTypeToWindrowFillType[FruitUtil.FRUITTYPE_GRASS], sx,sy,sz, ex,ey,ez, hLength_2, nil, nil, false, nil); |
574 | end |
575 | |
576 | -- add the accumulated value |
577 | if not fruitTypeFix or accumulatedFruitType == fruitType then |
578 | fruitType = accumulatedFruitType; |
579 | liters = liters + accumulatedWorkAreaValues[i]; |
580 | accumulatedWorkAreaValues[i] = 0; |
581 | end |
582 | |
583 | if liters > 0 then |
584 | |
585 | local hx = dx2 - dx0; |
586 | local hz = dz2 - dz0; |
587 | local hLength = Utils.vector2Length(hx, hz); |
588 | local hLength_2 = 0.5 * hLength; |
589 | |
590 | local wx = dx1 - dx0; |
591 | local wz = dz1 - dz0; |
592 | local wLength = Utils.vector2Length(wx, wz); |
593 | |
594 | local sx = dx0 + (hx * 0.5) + ((wx/wLength)*hLength_2); |
595 | local sz = dz0 + (hz * 0.5) + ((wz/wLength)*hLength_2); |
596 | |
597 | local ex = dx1 + (hx * 0.5) - ((wx/wLength)*hLength_2); |
598 | local ez = dz1 + (hz * 0.5) - ((wz/wLength)*hLength_2); |
599 | |
600 | local sy = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, sx,0,sz); |
601 | local ey = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, ex,0,ez); |
602 | |
603 | local dropped, lineOffset = TipUtil.tipToGroundAroundLine(self, liters, FruitUtil.fruitTypeToWindrowFillType[fruitType], sx,sy,sz, ex,ey,ez, hLength_2, nil, self.windrowerLineOffset, false, nil, false); |
604 | self.windrowerLineOffset = lineOffset; |
605 | |
606 | local remain = liters - dropped; |
607 | |
608 | if dropped > 0 then |
609 | table.insert(workAreasRet, {x0,z0, x1,z1, x2,z2, dx0,dz0, dx1,dz1, dx2,dz2, liters-remain, workAreas[i][14] }); |
610 | end |
611 | |
612 | accumulatedWorkAreaValues[i] = remain; |
613 | |
614 | end |
615 | end |
616 | |
617 | return workAreasRet, fruitType; |
618 | end |