28 | function TreePlanter:load(savegame) |
29 | |
30 | self.setPlantLimitToField = TreePlanter.setPlantLimitToField; |
31 | self.getDoGroundManipulation = Utils.overwrittenFunction(self.getDoGroundManipulation, TreePlanter.getDoGroundManipulation); |
32 | self.getDirtMultiplier = Utils.overwrittenFunction(self.getDirtMultiplier, TreePlanter.getDirtMultiplier); |
33 | self.getIsSpeedRotatingPartActive = Utils.overwrittenFunction(self.getIsSpeedRotatingPartActive, TreePlanter.getIsSpeedRotatingPartActive); |
34 | self.doCheckSpeedLimit = Utils.overwrittenFunction(self.doCheckSpeedLimit, TreePlanter.doCheckSpeedLimit); |
35 | self.setUnitFillLevel = Utils.overwrittenFunction(self.setUnitFillLevel, TreePlanter.setUnitFillLevel); |
36 | self.createTree = TreePlanter.createTree; |
37 | self.loadPallet = TreePlanter.loadPallet; |
38 | self.removeMountedObject = Utils.appendedFunction(self.removeMountedObject, TreePlanter.removeMountedObject); |
39 | |
40 | if self.isClient then |
41 | self.sampleTreePlanter = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.treePlanterSound", nil, self.baseDirectory); |
42 | self.treePlanterTurnedOnRotationNodes = Utils.loadRotationNodes(self.xmlFile, {}, "vehicle.turnedOnRotationNodes.turnedOnRotationNode", "treePlanter", self.components); |
43 | end |
44 | |
45 | self.treePlanterNode = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.treePlanter#node")); |
46 | self.treePlanterMinDistance = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.treePlanter#minDistance"), 20); -- distance to next tree |
47 | local refNodeIndex = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.treePlanter#refNodeIndex"), 0) + 1; |
48 | self.treePlanterRefNode = self.groundReferenceNodes[refNodeIndex]; |
49 | if table.getn(self.groundReferenceNodes) == 0 or self.treePlanterRefNode == nil then |
50 | print("Warning: No groundReferenceNode specified or invalid groundReferenceNode index in '".. self.configFileName .. "'"); |
51 | end |
52 | |
53 | self.treePlanterActivatable = TreePlanterActivatable:new(self); |
54 | |
55 | self.saplingPalletGrabNode = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.treePlanter#saplingPalletGrabNode")), self.rootNode); |
56 | self.saplingPalletMountNode = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.treePlanter#saplingPalletMountNode")), self.rootNode); |
57 | self.mountedSaplingPallet = nil; |
58 | |
59 | self.treePlanter = {}; |
60 | self.treePlanter.fillUnitIndex = 1; |
61 | |
62 | self.currentTree = 1; |
63 | self.lastTreePos = nil; |
64 | self.synchronizeFullFillLevel = true; |
65 | |
66 | self.nearestPalletDistance = 6.0; |
67 | self.treePlantingRefNode = 1; |
68 | self.showFieldNotOwnedWarning = false; |
69 | self.showTooManyTreesWarning = false; |
70 | self.treePlanterHasGroundContact = false |
71 | |
72 | self.plantLimitToField = true; |
73 | self.forcePlantLimitToField = false; |
74 | |
75 | self.isTreePlanterSpeedLimitActive = false; |
76 | |
77 | -- attributes for AI |
78 | table.insert(self.terrainDetailRequiredValueRanges, {g_currentMission.cultivatorValue, g_currentMission.cultivatorValue, g_currentMission.terrainDetailTypeFirstChannel, g_currentMission.terrainDetailTypeNumChannels}); |
79 | table.insert(self.terrainDetailRequiredValueRanges, {g_currentMission.ploughValue, g_currentMission.ploughValue, g_currentMission.terrainDetailTypeFirstChannel, g_currentMission.terrainDetailTypeNumChannels}); |
80 | table.insert(self.terrainDetailRequiredValueRanges, {g_currentMission.sowingValue, g_currentMission.sowingValue, g_currentMission.terrainDetailTypeFirstChannel, g_currentMission.terrainDetailTypeNumChannels}); |
81 | |
82 | self.aiProhibitedFruitType = FruitUtil.FRUITTYPE_POPLAR; |
83 | |
84 | self.treePlanterDirtyFlag = self:getNextDirtyFlag(); |
85 | |
86 | if savegame ~= nil and not savegame.resetVehicles then |
87 | local lastTreePos = getXMLString(savegame.xmlFile, savegame.key.."#lastTreePos"); |
88 | if lastTreePos ~= nil then |
89 | self.lastTreePos = Utils.getVectorNFromString(lastTreePos, 3); |
90 | end |
91 | |
92 | local palletClassName = getXMLString(savegame.xmlFile, savegame.key..".pallet#className"); |
93 | if palletClassName ~= nil then |
94 | local modName = getXMLString(savegame.xmlFile, savegame.key..".pallet#modName"); |
95 | if modName == nil or g_modIsLoaded[modName] then |
96 | local itemClass = getClassObject(palletClassName); |
97 | if itemClass ~= nil and itemClass.new ~= nil then |
98 | local item = itemClass:new(self.isServer, self.isClient); |
99 | if item ~= nil and item.loadFromAttributesAndNodes ~= nil and item:loadFromAttributesAndNodes(savegame.xmlFile, savegame.key..".pallet") then |
100 | item:register(); |
101 | self.palletIdToMount = networkGetObjectId(item); |
102 | end |
103 | end |
104 | end |
105 | end |
106 | end |
107 | end |
190 | function TreePlanter:update(dt) |
191 | if self.firstTimeRun then |
192 | if self.palletIdToMount ~= nil then |
193 | local pallet = networkGetObject(self.palletIdToMount); |
194 | if pallet ~= nil then |
195 | pallet:mount(self, self.saplingPalletMountNode, 0,0,0, 0,0,0); |
196 | |
197 | self:setUnitCapacity(self.treePlanter.fillUnitIndex, pallet.fillLevel); |
198 | self:setUnitFillLevel(self.treePlanter.fillUnitIndex, pallet.fillLevel, pallet.fillType, false, nil); |
199 | |
200 | self.mountedSaplingPallet = pallet; |
201 | g_currentMission:removeActivatableObject(self.treePlanterActivatable) |
202 | self.palletIdToMount = nil; |
203 | end |
204 | end |
205 | end |
206 | |
207 | if self:getIsActive() then |
208 | if self.mountedSaplingPallet == nil then |
209 | self.nearestSaplingPallet = TreePlanter.getSaplingPalletInRange(self, self.saplingPalletGrabNode); |
210 | if self.nearestSaplingPallet ~= nil then |
211 | g_currentMission:addActivatableObject(self.treePlanterActivatable); |
212 | else |
213 | g_currentMission:removeActivatableObject(self.treePlanterActivatable); |
214 | end |
215 | end |
216 | |
217 | if self:getIsActiveForInput() then |
218 | if not self.forcePlantLimitToField then |
219 | if InputBinding.hasEvent(InputBinding.IMPLEMENT_EXTRA3) then |
220 | self:setPlantLimitToField(not self.plantLimitToField); |
221 | end |
222 | end |
223 | end |
224 | end |
225 | |
226 | if self.isClient then |
227 | Utils.updateRotationNodes(self, self.treePlanterTurnedOnRotationNodes, dt, self:getIsActive() and self:getIsTurnedOn()); |
228 | end |
229 | |
230 | end |
235 | function TreePlanter:updateTick(dt) |
236 | self.isTreePlanterSpeedLimitActive = false; |
237 | self.showTooManyTreesWarning = false; |
238 | if self:getIsActive() then |
239 | local showFieldNotOwnedWarning = false; |
240 | |
241 | if self.isServer then |
242 | local hasGroundContact = false; |
243 | if self.treePlanterRefNode ~= nil then |
244 | hasGroundContact = self.treePlanterRefNode.isActive; |
245 | end |
246 | if self.treePlanterHasGroundContact ~= hasGroundContact then |
247 | self:raiseDirtyFlags(self.treePlanterDirtyFlag); |
248 | end |
249 | self.treePlanterHasGroundContact = hasGroundContact; |
250 | end |
251 | local hasGroundContact = self.treePlanterHasGroundContact; |
252 | |
253 | if self:getIsHired() then |
254 | if self.mountedSaplingPallet == nil or (self:getUnitFillType(self.treePlanter.fillUnitIndex) == 0 and not g_currentMission.missionInfo.helperBuySeeds) then |
255 | local rootVehicle = self:getRootAttacherVehicle() |
256 | rootVehicle:stopAIVehicle(AIVehicle.STOP_REASON_OUT_OF_FILL); |
257 | end |
258 | end |
259 | |
260 | if hasGroundContact and self.mountedSaplingPallet ~= nil then |
261 | if self:getIsTurnedOn() then |
262 | self.isTreePlanterSpeedLimitActive = true; |
263 | |
264 | if self.isServer then |
265 | local fillLevel = self:getUnitFillLevel(self.treePlanter.fillUnitIndex); |
266 | local fillType = self:getUnitFillType(self.treePlanter.fillUnitIndex); |
267 | |
268 | if fillLevel > 0 or g_currentMission.missionInfo.helperBuySeeds then |
269 | |
270 | if fillType == FillUtil.FILLTYPE_TREESAPLINGS then |
271 | if self:getLastSpeed() > 1 then |
272 | local x,y,z = getWorldTranslation(self.treePlanterNode); |
273 | if g_currentMission:getIsFieldOwnedAtWorldPos(x,z) then |
274 | showFieldNotOwnedWarning = false; |
275 | if self.lastTreePos ~= nil then |
276 | local distance = Utils.vector3Length(x-self.lastTreePos[1], y-self.lastTreePos[2], z-self.lastTreePos[3]); |
277 | if distance > self.treePlanterMinDistance then |
278 | self:createTree(); |
279 | end |
280 | else |
281 | self:createTree(); |
282 | end |
283 | else |
284 | showFieldNotOwnedWarning = true; |
285 | end |
286 | end |
287 | else |
288 | local x,_,z = getWorldTranslation(self.treePlanterNode); |
289 | if g_currentMission:getIsFieldOwnedAtWorldPos(x,z) then |
290 | local width = math.sqrt( g_currentMission:getFruitPixelsToSqm() ) * 0.5; |
291 | |
292 | local sx,_,sz = localToWorld(self.treePlanterNode, -width,0,width); |
293 | local wx,_,wz = localToWorld(self.treePlanterNode, width,0,width); |
294 | local hx,_,hz = localToWorld(self.treePlanterNode, -width,0,3*width); |
295 | |
296 | local fruitType = FruitUtil.fillTypeToFruitType[fillType]; |
297 | local fruitDesc = FruitUtil.fruitIndexToDesc[fruitType]; |
298 | |
299 | local dx,_,dz = localDirectionToWorld(self.treePlanterNode, 0, 0, 1); |
300 | local angleRad = Utils.getYRotationFromDirection(dx, dz) |
301 | local desc = FruitUtil.fruitIndexToDesc[fruitType]; |
302 | if desc ~= nil and desc.directionSnapAngle ~= 0 then |
303 | angleRad = math.floor(angleRad / desc.directionSnapAngle + 0.5) * desc.directionSnapAngle; |
304 | end |
305 | local angle = Utils.convertToDensityMapAngle(angleRad, g_currentMission.terrainDetailAngleMaxValue); |
306 | |
307 | -- cultivate |
308 | local limitToField = self.plantLimitToField or self.forcePlantLimitToField; |
309 | local limitGrassDestructionToField = self.plantLimitToField or self.forcePlantLimitToField; |
310 | Utils.updateCultivatorArea(sx,sz, wx,wz, hx,hz, not limitToField, not limitGrassDestructionToField, angle); |
311 | Utils.eraseTireTrack(sx,sz, wx,wz, hx,hz) |
312 | |
313 | -- plant, shift area |
314 | local sx,_,sz = localToWorld(self.treePlanterNode, -width,0,-3*width); |
315 | local wx,_,wz = localToWorld(self.treePlanterNode, width,0,-3*width); |
316 | local hx,_,hz = localToWorld(self.treePlanterNode, -width,0,-width); |
317 | local area, _ = Utils.updateSowingArea(fruitType, sx,sz, wx,wz, hx,hz, angle, true, 2); |
318 | |
319 | local usage = fruitDesc.seedUsagePerSqm * area; |
320 | |
321 | if self:getIsHired() and g_currentMission.missionInfo.helperBuySeeds then |
322 | local price = usage * g_currentMission.economyManager:getCostPerLiter(FillUtil.FILLTYPE_SEEDS) * 1.5 -- increase price if AI is active to reward the player's manual work |
323 | g_currentMission.missionStats:updateStats("expenses", price); |
324 | g_currentMission:addSharedMoney(-price, "purchaseSeeds"); |
325 | else |
326 | self:setUnitFillLevel(self.treePlanter.fillUnitIndex, fillLevel-usage, fillType, false, nil); |
327 | end |
328 | |
329 | self.lastSowingArea = Utils.areaToHa(area, g_currentMission:getFruitPixelsToSqm()); |
330 | g_currentMission.missionStats:updateStats("seedUsage", usage); |
331 | g_currentMission.missionStats:updateStats("sownHectares", self.lastSowingArea); |
332 | g_currentMission.missionStats:updateStats("sownTime", dt/(1000*60)); |
333 | g_currentMission.missionStats:updateStats("workedHectares", self.lastSowingArea); |
334 | g_currentMission.missionStats:updateStats("workedTime", dt/(1000*60)); |
335 | else |
336 | showFieldNotOwnedWarning = true; |
337 | end |
338 | end |
339 | |
340 | end |
341 | end |
342 | end |
343 | end |
344 | |
345 | if self.isServer then |
346 | if showFieldNotOwnedWarning ~= self.showFieldNotOwnedWarning then |
347 | self.showFieldNotOwnedWarning = showFieldNotOwnedWarning; |
348 | self:raiseDirtyFlags(self.treePlanterDirtyFlag); |
349 | end |
350 | end |
351 | |
352 | if self.isClient then |
353 | if self:getIsTurnedOn() and self.treePlanterHasGroundContact and self:getIsActiveForSound() and self:getLastSpeed() > 1 then |
354 | SoundUtil.playSample(self.sampleTreePlanter, 0, 0, nil); |
355 | else |
356 | SoundUtil.stopSample(self.sampleTreePlanter, true); |
357 | end |
358 | end |
359 | end |
360 | end |
430 | function TreePlanter:createTree() |
431 | if not TreePlantUtil.canPlantTree() then |
432 | self.showTooManyTreesWarning = true; |
433 | return; |
434 | end |
435 | if self.isServer and self.mountedSaplingPallet ~= nil then |
436 | local x,y,z = getWorldTranslation(self.treePlanterNode); |
437 | local yRot = math.random() * 2*math.pi; |
438 | TreePlantUtil.plantTree(g_currentMission.plantedTrees, self.mountedSaplingPallet.treeType, x,y,z, 0,yRot,0, 0); |
439 | self.lastTreePos = {x,y,z}; |
440 | |
441 | if g_currentMission.missionInfo.helperBuySeeds and self:getIsHired() then |
442 | |
443 | local filename = self.mountedSaplingPallet.xmlFilename; |
444 | local storeItem = StoreItemsUtil.storeItemsByXMLFilename[filename:lower()]; |
445 | local pricePerSapling = 1.5 * (storeItem.price / self.mountedSaplingPallet.capacity); |
446 | |
447 | g_currentMission.missionStats:updateStats("expenses", pricePerSapling); |
448 | g_currentMission:addSharedMoney(-pricePerSapling, "purchaseSeeds"); |
449 | |
450 | else |
451 | |
452 | local fillLevel = self:getUnitFillLevel(self.treePlanter.fillUnitIndex); |
453 | local fillType = self:getUnitFillType(self.treePlanter.fillUnitIndex); |
454 | self:setUnitFillLevel(self.treePlanter.fillUnitIndex, fillLevel - 1, fillType, false, nil); |
455 | |
456 | end |
457 | |
458 | -- increase tree plant counter for achievements |
459 | g_currentMission.missionStats:updateStats("plantedTreeCount", 1); |
460 | end |
461 | end |
553 | function TreePlanter:getSaveAttributesAndNodes(nodeIdent, usedModNames) |
554 | local attributes = ''; |
555 | local nodes = ''; |
556 | if self.lastTreePos ~= nil then |
557 | attributes = 'lastTreePos="'..self.lastTreePos[1]..' ' .. self.lastTreePos[2] .. ' ' .. self.lastTreePos[3] ..'"'; |
558 | end |
559 | if self.mountedSaplingPallet ~= nil then |
560 | local modExtra = ""; |
561 | local modName = self.mountedSaplingPallet.customEnvironment; |
562 | local classModName = getClassModName(self.mountedSaplingPallet.className); |
563 | if modName == nil then |
564 | modName = classModName; |
565 | end |
566 | if modName ~= nil then |
567 | if usedModNames ~= nil then |
568 | usedModNames[modName] = modName; |
569 | end |
570 | modExtra = ' modName="'..modName..'"'; |
571 | end |
572 | if classModName ~= nil then |
573 | if usedModNames ~= nil then |
574 | usedModNames[classModName] = classModName; |
575 | end |
576 | end |
577 | nodes = nodeIdent..'<pallet className="'..self.mountedSaplingPallet.className..'"'..modExtra; |
578 | local palletAttributes, palletNodes = self.mountedSaplingPallet:getSaveAttributesAndNodes(nodeIdent.." ", usedModNames); |
579 | if palletAttributes ~= nil and palletAttributes ~= "" then |
580 | nodes = nodes.." "..palletAttributes; |
581 | end |
582 | if palletNodes ~= nil and palletNodes ~= "" then |
583 | nodes = nodes..">\n"..palletNodes.."\n"..nodeIdent.."</pallet>\n"; |
584 | else |
585 | nodes = nodes..'/>'; |
586 | end |
587 | end |
588 | return attributes, nodes; |
589 | end |