469 | function Sprayer:getExternalFill(fillType, dt) |
470 | local found = false |
471 | local isUnknownFillType = fillType == FillType.UNKNOWN |
472 | local allowLiquidManure = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.LIQUIDMANURE) |
473 | local allowDigestate = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.DIGESTATE) |
474 | local allowManure = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.MANURE) |
475 | local allowLiquidFertilizer = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.LIQUIDFERTILIZER) |
476 | local allowFertilizer = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.FERTILIZER) |
477 | local allowsLiquidManureDigistate = allowLiquidManure or allowDigestate |
478 | local usage = 0 |
479 | |
480 | local farmId = self:getActiveFarm() |
481 | local stats = g_currentMission:farmStats(self:getLastTouchedFarmlandFarmId()) |
482 | |
483 | if fillType == FillType.LIQUIDMANURE or fillType == FillType.DIGESTATE or (isUnknownFillType and allowsLiquidManureDigistate) then |
484 | if g_currentMission.missionInfo.helperSlurrySource == 2 then -- buy manure |
485 | found = true |
486 | if g_currentMission.economyManager:getCostPerLiter(FillType.LIQUIDMANURE, false) then |
487 | fillType = FillType.LIQUIDMANURE |
488 | else |
489 | fillType = FillType.DIGESTATE |
490 | end |
491 | |
492 | usage = self:getSprayerUsage(fillType, dt) |
493 | if self.isServer then |
494 | local price = usage * g_currentMission.economyManager:getCostPerLiter(fillType, false) * 1.5 -- increase price if AI is active to reward the player's manual work |
495 | stats:updateStats("expenses", price) |
496 | g_currentMission:addMoney(-price, farmId, MoneyType.PURCHASE_FERTILIZER) |
497 | end |
498 | elseif g_currentMission.missionInfo.helperSlurrySource > 2 then |
499 | local loadingStation = g_currentMission.liquidManureLoadingStations[g_currentMission.missionInfo.helperSlurrySource-2] |
500 | if self.isServer and loadingStation ~= nil then -- Can be nil if pen was removed |
501 | usage = self:getSprayerUsage(FillType.LIQUIDMANURE, dt) |
502 | local remainingDelta = loadingStation:removeFillLevel(FillType.LIQUIDMANURE, usage, farmId or self:getOwnerFarmId()) |
503 | if usage - remainingDelta > 0.000001 then |
504 | found = true |
505 | fillType = FillType.LIQUIDMANURE |
506 | else |
507 | remainingDelta = loadingStation:removeFillLevel(FillType.DIGESTATE, usage, farmId or self:getOwnerFarmId()) |
508 | if usage - remainingDelta > 0.000001 then |
509 | found = true |
510 | fillType = FillType.DIGESTATE |
511 | end |
512 | end |
513 | end |
514 | end |
515 | elseif fillType == FillType.MANURE or (fillType == FillType.UNKNOWN and allowManure) then |
516 | if g_currentMission.missionInfo.helperManureSource == 2 then -- buy manure |
517 | found = true |
518 | fillType = FillType.MANURE |
519 | |
520 | usage = self:getSprayerUsage(fillType, dt) |
521 | if self.isServer then |
522 | local price = usage * g_currentMission.economyManager:getCostPerLiter(fillType, false) * 1.5 -- increase price if AI is active to reward the player's manual work |
523 | stats:updateStats("expenses", price) |
524 | g_currentMission:addMoney(-price, farmId, MoneyType.PURCHASE_FERTILIZER) |
525 | end |
526 | elseif g_currentMission.missionInfo.helperManureSource > 2 then |
527 | local loadingStation = g_currentMission.manureLoadingStations[g_currentMission.missionInfo.helperManureSource-2] |
528 | if self.isServer and loadingStation ~= nil then -- Can be nil if pen was removed |
529 | usage = self:getSprayerUsage(FillType.MANURE, dt) |
530 | local remainingDelta = loadingStation:removeFillLevel(FillType.MANURE, usage, farmId or self:getOwnerFarmId()) |
531 | if usage - remainingDelta > 0.000001 then |
532 | found = true |
533 | fillType = FillType.MANURE |
534 | end |
535 | end |
536 | end |
537 | elseif fillType == FillType.FERTILIZER or |
538 | fillType == FillType.LIQUIDFERTILIZER or |
539 | fillType == FillType.HERBICIDE or |
540 | fillType == FillType.LIME or |
541 | (fillType == FillType.UNKNOWN and (allowLiquidFertilizer or allowFertilizer)) then |
542 | if g_currentMission.missionInfo.helperBuyFertilizer then |
543 | found = true |
544 | if fillType == FillType.UNKNOWN then |
545 | if self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.LIQUIDFERTILIZER) then |
546 | fillType = FillType.LIQUIDFERTILIZER |
547 | else |
548 | fillType = FillType.FERTILIZER |
549 | end |
550 | end |
551 | |
552 | usage = self:getSprayerUsage(fillType, dt) |
553 | if self.isServer then |
554 | local price = usage * g_currentMission.economyManager:getCostPerLiter(fillType, false) * 1.5 -- increase price if AI is active to reward the player's manual work |
555 | stats:updateStats("expenses", price) |
556 | g_currentMission:addMoney(-price, farmId, MoneyType.PURCHASE_FERTILIZER) |
557 | end |
558 | end |
559 | end |
560 | |
561 | if found then |
562 | return fillType, usage |
563 | end |
564 | |
565 | return FillType.UNKNOWN, 0 |
566 | end |
34 | function Sprayer.initSpecialization() |
35 | g_workAreaTypeManager:addWorkAreaType("sprayer", false) |
36 | |
37 | local schema = Vehicle.xmlSchema |
38 | schema:setXMLSpecializationType("Sprayer") |
39 | |
40 | schema:register(XMLValueType.BOOL, "vehicle.sprayer#allowsSpraying", "Allows spraying", true) |
41 | schema:register(XMLValueType.BOOL, "vehicle.sprayer#activateTankOnLowering", "Activate tank on lowering", false) |
42 | schema:register(XMLValueType.BOOL, "vehicle.sprayer#activateOnLowering", "Activate on lowering", false) |
43 | |
44 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.usageScales#scale", "Usage scale", 1) |
45 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.usageScales#workingWidth", "Working width", 12) |
46 | schema:register(XMLValueType.INT, "vehicle.sprayer.usageScales#workAreaIndex", "Work area that is used for working width reference instead of #workingWidth") |
47 | |
48 | schema:register(XMLValueType.STRING, "vehicle.sprayer.usageScales.sprayUsageScale(?)#fillType", "Fill type name") |
49 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.usageScales.sprayUsageScale(?)#scale", "Scale") |
50 | |
51 | schema:register(XMLValueType.INT, Sprayer.SPRAY_TYPE_XML_KEY .. "#fillUnitIndex", "Fill unit index") |
52 | schema:register(XMLValueType.INT, Sprayer.SPRAY_TYPE_XML_KEY .. "#unloadInfoIndex", "Unload info index") |
53 | schema:register(XMLValueType.INT, Sprayer.SPRAY_TYPE_XML_KEY .. "#fillVolumeIndex", "Fill volume index") |
54 | |
55 | SoundManager.registerSampleXMLPaths(schema, Sprayer.SPRAY_TYPE_XML_KEY .. ".sounds", "work") |
56 | SoundManager.registerSampleXMLPaths(schema, Sprayer.SPRAY_TYPE_XML_KEY .. ".sounds", "spray") |
57 | AnimationManager.registerAnimationNodesXMLPaths(schema, Sprayer.SPRAY_TYPE_XML_KEY .. ".animationNodes") |
58 | EffectManager.registerEffectXMLPaths(schema, Sprayer.SPRAY_TYPE_XML_KEY .. ".effects") |
59 | |
60 | schema:register(XMLValueType.STRING, Sprayer.SPRAY_TYPE_XML_KEY .. ".turnedAnimation#name", "Turned animation name") |
61 | schema:register(XMLValueType.FLOAT, Sprayer.SPRAY_TYPE_XML_KEY .. ".turnedAnimation#turnOnSpeedScale", "Speed Scale while turned on", 1) |
62 | schema:register(XMLValueType.FLOAT, Sprayer.SPRAY_TYPE_XML_KEY .. ".turnedAnimation#turnOffSpeedScale", "Speed Scale while turned off", "Inversed #turnOnSpeedScale") |
63 | schema:register(XMLValueType.BOOL, Sprayer.SPRAY_TYPE_XML_KEY .. ".turnedAnimation#externalFill", "Animation is played while sprayer is externally filled", true) |
64 | |
65 | schema:register(XMLValueType.NODE_INDEX, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai.areaMarkers#leftNode", "AI marker left node") |
66 | schema:register(XMLValueType.NODE_INDEX, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai.areaMarkers#rightNode", "AI marker right node") |
67 | schema:register(XMLValueType.NODE_INDEX, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai.areaMarkers#backNode", "AI marker back node") |
68 | schema:register(XMLValueType.NODE_INDEX, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai.sizeMarkers#leftNode", "AI size marker left node") |
69 | schema:register(XMLValueType.NODE_INDEX, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai.sizeMarkers#rightNode", "AI size marker right node") |
70 | schema:register(XMLValueType.NODE_INDEX, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai.sizeMarkers#backNode", "AI size marker back node") |
71 | |
72 | AIImplement.registerAICollisionTriggerXMLPaths(schema, Sprayer.SPRAY_TYPE_XML_KEY .. ".ai") |
73 | |
74 | schema:register(XMLValueType.STRING, Sprayer.SPRAY_TYPE_XML_KEY .. "#fillTypes", "Fill types") |
75 | schema:register(XMLValueType.FLOAT, Sprayer.SPRAY_TYPE_XML_KEY .. ".usageScales#workingWidth", "Work width", 12) |
76 | schema:register(XMLValueType.INT, Sprayer.SPRAY_TYPE_XML_KEY .. ".usageScales#workAreaIndex", "Work area that is used for working width reference instead of #workingWidth") |
77 | |
78 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, Sprayer.SPRAY_TYPE_XML_KEY) |
79 | |
80 | EffectManager.registerEffectXMLPaths(schema, "vehicle.sprayer.effects") |
81 | SoundManager.registerSampleXMLPaths(schema, "vehicle.sprayer.sounds", "work") |
82 | SoundManager.registerSampleXMLPaths(schema, "vehicle.sprayer.sounds", "spray") |
83 | AnimationManager.registerAnimationNodesXMLPaths(schema, "vehicle.sprayer.animationNodes") |
84 | |
85 | schema:register(XMLValueType.STRING, "vehicle.sprayer.animation#name", "Spray animation name") |
86 | schema:register(XMLValueType.INT, "vehicle.sprayer#fillUnitIndex", "Fill unit index", 1) |
87 | schema:register(XMLValueType.INT, "vehicle.sprayer#unloadInfoIndex", "Unload info index", 1) |
88 | schema:register(XMLValueType.INT, "vehicle.sprayer#fillVolumeIndex", "Fill volume index") |
89 | schema:register(XMLValueType.VECTOR_3, "vehicle.sprayer#fillVolumeDischargeScrollSpeed", "Fill volume discharge scroll speed", "0 0 0") |
90 | |
91 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.doubledAmount#decreasedSpeed", "Speed while doubled amount is sprayed", "automatically calculated") |
92 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.doubledAmount#decreaseFactor", "Decrease factor that is applied on speedLimit while doubled amount is sprayed", 0.5) |
93 | schema:register(XMLValueType.STRING, "vehicle.sprayer.doubledAmount#toggleButton", "Name of input action to toggle doubled amount", "IMPLEMENT_EXTRA4") |
94 | |
95 | schema:register(XMLValueType.L10N_STRING, "vehicle.sprayer.doubledAmount#deactivateText", "Deactivated text", "action_deactivateDoubledSprayAmount") |
96 | schema:register(XMLValueType.L10N_STRING, "vehicle.sprayer.doubledAmount#activateText", "Activate text", "action_activateDoubledSprayAmount") |
97 | |
98 | schema:register(XMLValueType.STRING, "vehicle.sprayer.turnedAnimation#name", "Turned animation name") |
99 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.turnedAnimation#turnOnSpeedScale", "Speed Scale while turned on", 1) |
100 | schema:register(XMLValueType.FLOAT, "vehicle.sprayer.turnedAnimation#turnOffSpeedScale", "Speed Scale while turned off", "Inversed #turnOnSpeedScale") |
101 | schema:register(XMLValueType.BOOL, "vehicle.sprayer.turnedAnimation#externalFill", "Animation is played while sprayer is externally filled", true) |
102 | |
103 | schema:register(XMLValueType.INT, WorkArea.WORK_AREA_XML_KEY .. "#sprayType", "Spray type index") |
104 | schema:register(XMLValueType.INT, WorkArea.WORK_AREA_XML_CONFIG_KEY .. "#sprayType", "Spray type index") |
105 | |
106 | schema:setXMLSpecializationType() |
107 | end |
738 | function Sprayer:loadSprayTypeFromXML(xmlFile, key, sprayType) |
739 | sprayType.fillUnitIndex = xmlFile:getValue(key.. "#fillUnitIndex", 1) |
740 | sprayType.unloadInfoIndex = xmlFile:getValue(key.. "#unloadInfoIndex", 1) |
741 | sprayType.fillVolumeIndex = xmlFile:getValue(key.. "#fillVolumeIndex") |
742 | |
743 | sprayType.samples = {} |
744 | sprayType.samples.work = g_soundManager:loadSampleFromXML(xmlFile, key.. ".sounds", "work", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
745 | sprayType.samples.spray = g_soundManager:loadSampleFromXML(xmlFile, key.. ".sounds", "spray", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
746 | |
747 | sprayType.effects = g_effectManager:loadEffect(xmlFile, key.. ".effects", self.components, self, self.i3dMappings) |
748 | |
749 | sprayType.animationNodes = g_animationManager:loadAnimations(xmlFile, key.. ".animationNodes", self.components, self, self.i3dMappings) |
750 | |
751 | sprayType.turnedAnimation = self.xmlFile:getValue(key .. ".turnedAnimation#name", "") |
752 | sprayType.turnedAnimationTurnOnSpeedScale = self.xmlFile:getValue(key .. ".turnedAnimation#turnOnSpeedScale", 1) |
753 | sprayType.turnedAnimationTurnOffSpeedScale = self.xmlFile:getValue(key .. ".turnedAnimation#turnOffSpeedScale", -sprayType.turnedAnimationTurnOnSpeedScale) |
754 | sprayType.turnedAnimationExternalFill = self.xmlFile:getValue(key .. ".turnedAnimation#externalFill", true) |
755 | |
756 | sprayType.ai = {} |
757 | sprayType.ai.leftMarker = self.xmlFile:getValue(key .. ".ai.areaMarkers#leftNode", nil, self.components, self.i3dMappings) |
758 | sprayType.ai.rightMarker = self.xmlFile:getValue(key .. ".ai.areaMarkers#rightNode", nil, self.components, self.i3dMappings) |
759 | sprayType.ai.backMarker = self.xmlFile:getValue(key .. ".ai.areaMarkers#backNode", nil, self.components, self.i3dMappings) |
760 | |
761 | sprayType.ai.sizeLeftMarker = self.xmlFile:getValue(key .. ".ai.sizeMarkers#leftNode", nil, self.components, self.i3dMappings) |
762 | sprayType.ai.sizeRightMarker = self.xmlFile:getValue(key .. ".ai.sizeMarkers#rightNode", nil, self.components, self.i3dMappings) |
763 | sprayType.ai.sizeBackMarker = self.xmlFile:getValue(key .. ".ai.sizeMarkers#backNode", nil, self.components, self.i3dMappings) |
764 | |
765 | if self.loadAICollisionTriggerFromXML ~= nil then |
766 | sprayType.ai.collisionTrigger = self:loadAICollisionTriggerFromXML(self.xmlFile, key .. ".ai") |
767 | end |
768 | |
769 | local fillTypesStr = xmlFile:getValue(key.. "#fillTypes") |
770 | if fillTypesStr ~= nil then |
771 | sprayType.fillTypes = fillTypesStr:split(" ") |
772 | end |
773 | |
774 | sprayType.objectChanges = {} |
775 | ObjectChangeUtil.loadObjectChangeFromXML(self.xmlFile, key, sprayType.objectChanges, self.components, self) |
776 | ObjectChangeUtil.setObjectChanges(sprayType.objectChanges, false) |
777 | |
778 | sprayType.usageScale = {} |
779 | sprayType.usageScale.workingWidth = self.xmlFile:getValue(key..".usageScales#workingWidth", 12) |
780 | sprayType.usageScale.workAreaIndex = self.xmlFile:getValue(key..".usageScales#workAreaIndex") |
781 | |
782 | return true |
783 | end |
1288 | function Sprayer:onEndWorkAreaProcessing(dt, hasProcessed) |
1289 | local spec = self.spec_sprayer |
1290 | |
1291 | if self.isServer then |
1292 | if spec.workAreaParameters.isActive then |
1293 | local sprayVehicle = spec.workAreaParameters.sprayVehicle |
1294 | local usage = spec.workAreaParameters.usage |
1295 | |
1296 | if sprayVehicle ~= nil then |
1297 | local sprayVehicleFillUnitIndex = spec.workAreaParameters.sprayVehicleFillUnitIndex |
1298 | local sprayFillType = spec.workAreaParameters.sprayFillType |
1299 | |
1300 | local unloadInfoIndex = spec.unloadInfoIndex |
1301 | local sprayType = self:getActiveSprayType() |
1302 | if sprayType ~= nil then |
1303 | unloadInfoIndex = sprayType.unloadInfoIndex |
1304 | end |
1305 | |
1306 | local unloadInfo = self:getFillVolumeUnloadInfo(unloadInfoIndex) |
1307 | sprayVehicle:addFillUnitFillLevel(self:getOwnerFarmId(), sprayVehicleFillUnitIndex, -usage, sprayFillType, ToolType.UNDEFINED, unloadInfo) |
1308 | end |
1309 | |
1310 | local ha = MathUtil.areaToHa(spec.workAreaParameters.lastStatsArea, g_currentMission:getFruitPixelsToSqm()) |
1311 | local stats = g_currentMission:farmStats(self:getLastTouchedFarmlandFarmId()) |
1312 | stats:updateStats("sprayedHectares", ha) |
1313 | stats:updateStats("sprayedTime", dt/(1000*60)) |
1314 | stats:updateStats("sprayUsage", usage) |
1315 | self:updateLastWorkedArea(spec.workAreaParameters.lastStatsArea) |
1316 | end |
1317 | end |
1318 | |
1319 | self:updateSprayerEffects() |
1320 | end |
186 | function Sprayer:onLoad(savegame) |
187 | local spec = self.spec_sprayer |
188 | |
189 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.sprayParticles.emitterShape", "vehicle.sprayer.effects.effectNode#effectClass='ParticleEffect'") --FS17 to FS19 |
190 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.sprayer#needsTankActivation") --FS19 to FS22 |
191 | |
192 | spec.allowsSpraying = self.xmlFile:getValue("vehicle.sprayer#allowsSpraying", true) |
193 | spec.activateTankOnLowering = self.xmlFile:getValue("vehicle.sprayer#activateTankOnLowering", false) |
194 | spec.activateOnLowering = self.xmlFile:getValue("vehicle.sprayer#activateOnLowering", false) |
195 | |
196 | spec.usageScale = {} |
197 | spec.usageScale.default = self.xmlFile:getValue("vehicle.sprayer.usageScales#scale", 1) |
198 | spec.usageScale.workingWidth = self.xmlFile:getValue("vehicle.sprayer.usageScales#workingWidth", 12) |
199 | spec.usageScale.workAreaIndex = self.xmlFile:getValue("vehicle.sprayer.usageScales#workAreaIndex") |
200 | spec.usageScale.fillTypeScales = {} |
201 | local i = 0 |
202 | while true do |
203 | local key = string.format("vehicle.sprayer.usageScales.sprayUsageScale(%d)", i) |
204 | if not self.xmlFile:hasProperty(key) then |
205 | break |
206 | end |
207 | local fillTypeStr = self.xmlFile:getValue(key.. "#fillType") |
208 | local scale = self.xmlFile:getValue(key.. "#scale") |
209 | if fillTypeStr ~= nil and scale ~= nil then |
210 | local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeStr) |
211 | if fillTypeIndex ~= nil then |
212 | spec.usageScale.fillTypeScales[fillTypeIndex] = scale |
213 | else |
214 | print("Warning: Invalid spray usage scale fill type '"..fillTypeStr.."' in '" .. self.configFileName.. "'") |
215 | end |
216 | end |
217 | i = i + 1 |
218 | end |
219 | |
220 | spec.sprayTypes = {} |
221 | i = 0 |
222 | while true do |
223 | local key = string.format("vehicle.sprayer.sprayTypes.sprayType(%d)", i) |
224 | if not self.xmlFile:hasProperty(key) then |
225 | break |
226 | end |
227 | |
228 | local sprayType = {} |
229 | if self:loadSprayTypeFromXML(self.xmlFile, key, sprayType) then |
230 | table.insert(spec.sprayTypes, sprayType) |
231 | sprayType.index = #spec.sprayTypes |
232 | end |
233 | |
234 | i = i + 1 |
235 | end |
236 | |
237 | spec.lastActiveSprayType = nil |
238 | |
239 | if self.isClient then |
240 | spec.effects = g_effectManager:loadEffect(self.xmlFile, "vehicle.sprayer.effects", self.components, self, self.i3dMappings) |
241 | |
242 | spec.animationName = self.xmlFile:getValue("vehicle.sprayer.animation#name", "") |
243 | |
244 | spec.samples = {} |
245 | spec.samples.work = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.sprayer.sounds", "work", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
246 | spec.samples.spray = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.sprayer.sounds", "spray", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
247 | |
248 | spec.sampleFillEnabled = false |
249 | spec.sampleFillStopTime = -1 |
250 | spec.lastFillLevel = -1 |
251 | |
252 | spec.animationNodes = g_animationManager:loadAnimations(self.xmlFile, "vehicle.sprayer.animationNodes", self.components, self, self.i3dMappings) |
253 | end |
254 | |
255 | if self.addAIGroundTypeRequirements ~= nil then |
256 | self:addAIGroundTypeRequirements(Sprayer.AI_REQUIRED_GROUND_TYPES) |
257 | end |
258 | |
259 | spec.supportedSprayTypes = {} |
260 | |
261 | spec.fillUnitIndex = self.xmlFile:getValue("vehicle.sprayer#fillUnitIndex", 1) |
262 | spec.unloadInfoIndex = self.xmlFile:getValue("vehicle.sprayer#unloadInfoIndex", 1) |
263 | spec.fillVolumeIndex = self.xmlFile:getValue("vehicle.sprayer#fillVolumeIndex") |
264 | spec.dischargeUVScrollSpeed = self.xmlFile:getValue("vehicle.sprayer#fillVolumeDischargeScrollSpeed", "0 0 0", true) |
265 | |
266 | if self:getFillUnitByIndex(spec.fillUnitIndex) == nil then |
267 | Logging.xmlError(self.xmlFile, "FillUnit '%d' not defined!", spec.fillUnitIndex) |
268 | self:setLoadingState(VehicleLoadingUtil.VEHICLE_LOAD_ERROR) |
269 | return |
270 | end |
271 | |
272 | local decreasedSpeedLimit = self.xmlFile:getValue("vehicle.sprayer.doubledAmount#decreasedSpeed") |
273 | if decreasedSpeedLimit == nil then |
274 | local decreaseFactor = self.xmlFile:getValue("vehicle.sprayer.doubledAmount#decreaseFactor", 0.5) |
275 | decreasedSpeedLimit = self:getSpeedLimit() * decreaseFactor |
276 | end |
277 | spec.doubledAmountSpeed = decreasedSpeedLimit |
278 | spec.doubledAmountIsActive = false |
279 | |
280 | local toggleButtonStr = self.xmlFile:getValue("vehicle.sprayer.doubledAmount#toggleButton") |
281 | if toggleButtonStr ~= nil then |
282 | spec.toggleDoubledAmountInputBinding = InputAction[toggleButtonStr] |
283 | end |
284 | spec.toggleDoubledAmountInputBinding = spec.toggleDoubledAmountInputBinding or InputAction.DOUBLED_SPRAY_AMOUNT |
285 | spec.doubledAmountDeactivateText = self.xmlFile:getValue("vehicle.sprayer.doubledAmount#deactivateText", "action_deactivateDoubledSprayAmount", self.customEnvironment) |
286 | spec.doubledAmountActivateText = self.xmlFile:getValue("vehicle.sprayer.doubledAmount#activateText", "action_activateDoubledSprayAmount", self.customEnvironment) |
287 | |
288 | spec.turnedAnimation = self.xmlFile:getValue("vehicle.sprayer.turnedAnimation#name", "") |
289 | spec.turnedAnimationTurnOnSpeedScale = self.xmlFile:getValue("vehicle.sprayer.turnedAnimation#turnOnSpeedScale", 1) |
290 | spec.turnedAnimationTurnOffSpeedScale = self.xmlFile:getValue("vehicle.sprayer.turnedAnimation#turnOffSpeedScale", -spec.turnedAnimationTurnOnSpeedScale) |
291 | spec.turnedAnimationExternalFill = self.xmlFile:getValue("vehicle.sprayer.turnedAnimation#externalFill", true) |
292 | |
293 | spec.needsToBeFilledToTurnOn = true |
294 | spec.useSpeedLimit = true |
295 | spec.isWorking = false |
296 | spec.lastEffectsState = false |
297 | |
298 | spec.isSlurryTanker = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.LIQUIDMANURE) or self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.DIGESTATE) |
299 | spec.isManureSpreader = self:getFillUnitAllowsFillType(self:getSprayerFillUnitIndex(), FillType.MANURE) |
300 | spec.isFertilizerSprayer = not spec.isSlurryTanker and not spec.isManureSpreader |
301 | |
302 | spec.workAreaParameters = {} |
303 | spec.workAreaParameters.sprayVehicle = nil |
304 | spec.workAreaParameters.sprayVehicleFillUnitIndex = nil |
305 | spec.workAreaParameters.lastChangedArea = 0 |
306 | spec.workAreaParameters.lastTotalArea = 0 |
307 | spec.workAreaParameters.lastIsExternallyFilled = false |
308 | spec.workAreaParameters.lastSprayTime = -math.huge |
309 | spec.workAreaParameters.usage = 0 |
310 | spec.workAreaParameters.usagePerMin = 0 |
311 | end |
1184 | function Sprayer:onStartWorkAreaProcessing(dt) |
1185 | local spec = self.spec_sprayer |
1186 | |
1187 | local sprayVehicle = nil |
1188 | local sprayVehicleFillUnitIndex = nil |
1189 | local fillType = self:getFillUnitFillType(self:getSprayerFillUnitIndex()) |
1190 | local usage = self:getSprayerUsage(fillType, dt) |
1191 | local sprayFillLevel = self:getFillUnitFillLevel(self:getSprayerFillUnitIndex()) |
1192 | |
1193 | if sprayFillLevel > 0 then |
1194 | sprayVehicle = self |
1195 | sprayVehicleFillUnitIndex = self:getSprayerFillUnitIndex() |
1196 | else |
1197 | for _, supportedSprayType in ipairs(spec.supportedSprayTypes) do |
1198 | for _, src in ipairs(spec.fillTypeSources[supportedSprayType]) do |
1199 | local vehicle = src.vehicle |
1200 | if vehicle:getIsFillUnitActive(src.fillUnitIndex) then |
1201 | local vehicleFillType = vehicle:getFillUnitFillType(src.fillUnitIndex) |
1202 | local vehicleFillLevel = vehicle:getFillUnitFillLevel(src.fillUnitIndex) |
1203 | if vehicleFillLevel > 0 and vehicleFillType == supportedSprayType then |
1204 | sprayVehicle = vehicle |
1205 | sprayVehicleFillUnitIndex = src.fillUnitIndex |
1206 | fillType = sprayVehicle:getFillUnitFillType(sprayVehicleFillUnitIndex) |
1207 | usage = self:getSprayerUsage(fillType, dt) |
1208 | sprayFillLevel = vehicleFillLevel |
1209 | break |
1210 | end |
1211 | else |
1212 | -- if the ai is active we try to activate the source to activate the fill unit |
1213 | if self:getIsAIActive() then |
1214 | if vehicle.setIsTurnedOn ~= nil then |
1215 | if not vehicle:getIsTurnedOn() then |
1216 | vehicle:setIsTurnedOn(true) |
1217 | end |
1218 | end |
1219 | end |
1220 | end |
1221 | end |
1222 | end |
1223 | end |
1224 | |
1225 | local isExternallyFilled = self:getIsSprayerExternallyFilled() |
1226 | if isExternallyFilled then |
1227 | -- only consume while ai is on field, not while turning, so we check for the tools turned on state |
1228 | if self:getIsTurnedOn() then |
1229 | fillType, usage = self:getExternalFill(fillType, dt) |
1230 | sprayFillLevel = usage |
1231 | |
1232 | -- do not consume the fill unit fill level if we buy fertilizer |
1233 | sprayVehicle = nil |
1234 | sprayVehicleFillUnitIndex = nil |
1235 | end |
1236 | end |
1237 | |
1238 | if isExternallyFilled ~= spec.workAreaParameters.lastIsExternallyFilled then |
1239 | local sprayType = self:getActiveSprayType() |
1240 | if sprayType ~= nil then |
1241 | if isExternallyFilled then |
1242 | if not sprayType.turnedAnimationExternalFill and self:getIsAnimationPlaying(sprayType.turnedAnimation) then |
1243 | self:stopAnimation(sprayType.turnedAnimation) |
1244 | end |
1245 | else |
1246 | if not self:getIsAnimationPlaying(sprayType.turnedAnimation) then |
1247 | self:playAnimation(sprayType.turnedAnimation, sprayType.turnedAnimationTurnOnSpeedScale, self:getAnimationTime(sprayType.turnedAnimation), true) |
1248 | end |
1249 | end |
1250 | end |
1251 | |
1252 | if isExternallyFilled then |
1253 | if not spec.turnedAnimationExternalFill and self:getIsAnimationPlaying(spec.turnedAnimation) then |
1254 | self:stopAnimation(spec.turnedAnimation) |
1255 | end |
1256 | else |
1257 | if not self:getIsAnimationPlaying(spec.turnedAnimation) then |
1258 | self:playAnimation(spec.turnedAnimation, spec.turnedAnimationTurnOnSpeedScale, self:getAnimationTime(spec.turnedAnimation), true) |
1259 | end |
1260 | end |
1261 | |
1262 | spec.workAreaParameters.lastIsExternallyFilled = isExternallyFilled |
1263 | end |
1264 | |
1265 | if self.isServer then |
1266 | if fillType ~= FillType.UNKNOWN and fillType ~= spec.workAreaParameters.sprayFillType then |
1267 | self:setSprayerAITerrainDetailProhibitedRange(fillType) |
1268 | end |
1269 | end |
1270 | |
1271 | spec.workAreaParameters.sprayType = g_sprayTypeManager:getSprayTypeIndexByFillTypeIndex(fillType) |
1272 | spec.workAreaParameters.sprayFillType = fillType |
1273 | spec.workAreaParameters.sprayFillLevel = sprayFillLevel |
1274 | spec.workAreaParameters.usage = usage |
1275 | spec.workAreaParameters.usagePerMin = usage / dt * 1000 * 60 |
1276 | spec.workAreaParameters.sprayVehicle = sprayVehicle |
1277 | spec.workAreaParameters.sprayVehicleFillUnitIndex = sprayVehicleFillUnitIndex |
1278 | spec.workAreaParameters.lastChangedArea = 0 |
1279 | spec.workAreaParameters.lastTotalArea = 0 |
1280 | spec.workAreaParameters.lastStatsArea = 0 |
1281 | spec.workAreaParameters.isActive = false |
1282 | |
1283 | spec.isWorking = false |
1284 | end |
333 | function Sprayer:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
334 | local activeSprayType = self:getActiveSprayType() |
335 | if activeSprayType ~= nil then |
336 | local spec = self.spec_sprayer |
337 | if activeSprayType ~= spec.lastActiveSprayType then |
338 | for _, sprayType in ipairs(spec.sprayTypes) do |
339 | if sprayType == spec.lastActiveSprayType then |
340 | g_effectManager:stopEffects(sprayType.effects) |
341 | g_animationManager:stopAnimations(sprayType.animationNodes) |
342 | end |
343 | end |
344 | |
345 | SpecializationUtil.raiseEvent(self, "onSprayTypeChange", activeSprayType) |
346 | spec.lastActiveSprayType = activeSprayType |
347 | |
348 | self:updateSprayerEffects(true) |
349 | end |
350 | end |
351 | |
352 | if self.isClient then |
353 | local spec = self.spec_sprayer |
354 | local actionEvent = spec.actionEvents[spec.toggleDoubledAmountInputBinding] |
355 | if actionEvent ~= nil then |
356 | local text |
357 | if spec.doubledAmountIsActive then |
358 | text = spec.doubledAmountDeactivateText |
359 | else |
360 | text = spec.doubledAmountActivateText |
361 | end |
362 | g_inputBinding:setActionEventText(actionEvent.actionEventId, text) |
363 | |
364 | local _, isAllowed = self:getSprayerDoubledAmountActive(spec.workAreaParameters.sprayType) |
365 | g_inputBinding:setActionEventActive(actionEvent.actionEventId, isAllowed) |
366 | end |
367 | end |
368 | |
369 | if self.isServer then |
370 | local spec = self.spec_sprayer |
371 | if spec.pendingActivationAfterLowering then |
372 | if self:getCanBeTurnedOn() then |
373 | self:setIsTurnedOn(true) |
374 | spec.pendingActivationAfterLowering = false |
375 | end |
376 | end |
377 | end |
378 | end |
401 | function Sprayer:processSprayerArea(workArea, dt) |
402 | local spec = self.spec_sprayer |
403 | |
404 | if self:getIsAIActive() and self.isServer then |
405 | if spec.workAreaParameters.sprayFillType == nil or spec.workAreaParameters.sprayFillType == FillType.UNKNOWN then |
406 | local rootVehicle = self.rootVehicle |
407 | rootVehicle:stopCurrentAIJob(AIMessageErrorOutOfFill.new()) |
408 | |
409 | return 0, 0 |
410 | end |
411 | end |
412 | |
413 | if spec.workAreaParameters.sprayFillLevel <= 0 then |
414 | return 0, 0 |
415 | end |
416 | |
417 | local sx,_,sz = getWorldTranslation(workArea.start) |
418 | local wx,_,wz = getWorldTranslation(workArea.width) |
419 | local hx,_,hz = getWorldTranslation(workArea.height) |
420 | |
421 | local sprayAmount = self:getSprayerDoubledAmountActive(spec.workAreaParameters.sprayType) and 2 or 1 |
422 | local changedArea, totalArea = FSDensityMapUtil.updateSprayArea(sx,sz, wx,wz, hx,hz, spec.workAreaParameters.sprayType, sprayAmount) |
423 | |
424 | spec.workAreaParameters.isActive = true |
425 | spec.workAreaParameters.lastChangedArea = spec.workAreaParameters.lastChangedArea + changedArea |
426 | spec.workAreaParameters.lastStatsArea = spec.workAreaParameters.lastStatsArea + changedArea |
427 | spec.workAreaParameters.lastTotalArea = spec.workAreaParameters.lastTotalArea + totalArea |
428 | spec.workAreaParameters.lastSprayTime = g_time |
429 | |
430 | if self:getLastSpeed() > 1 then |
431 | spec.isWorking = true |
432 | end |
433 | |
434 | return changedArea, totalArea |
435 | end |
125 | function Sprayer.registerFunctions(vehicleType) |
126 | SpecializationUtil.registerFunction(vehicleType, "processSprayerArea", Sprayer.processSprayerArea) |
127 | SpecializationUtil.registerFunction(vehicleType, "getIsSprayerExternallyFilled", Sprayer.getIsSprayerExternallyFilled) |
128 | SpecializationUtil.registerFunction(vehicleType, "getExternalFill", Sprayer.getExternalFill) |
129 | SpecializationUtil.registerFunction(vehicleType, "getAreEffectsVisible", Sprayer.getAreEffectsVisible) |
130 | SpecializationUtil.registerFunction(vehicleType, "updateSprayerEffects", Sprayer.updateSprayerEffects) |
131 | SpecializationUtil.registerFunction(vehicleType, "getSprayerUsage", Sprayer.getSprayerUsage) |
132 | SpecializationUtil.registerFunction(vehicleType, "getUseSprayerAIRequirements", Sprayer.getUseSprayerAIRequirements) |
133 | SpecializationUtil.registerFunction(vehicleType, "setSprayerAITerrainDetailProhibitedRange", Sprayer.setSprayerAITerrainDetailProhibitedRange) |
134 | SpecializationUtil.registerFunction(vehicleType, "getSprayerFillUnitIndex", Sprayer.getSprayerFillUnitIndex) |
135 | SpecializationUtil.registerFunction(vehicleType, "loadSprayTypeFromXML", Sprayer.loadSprayTypeFromXML) |
136 | SpecializationUtil.registerFunction(vehicleType, "getActiveSprayType", Sprayer.getActiveSprayType) |
137 | SpecializationUtil.registerFunction(vehicleType, "getIsSprayTypeActive", Sprayer.getIsSprayTypeActive) |
138 | SpecializationUtil.registerFunction(vehicleType, "setSprayerDoubledAmountActive", Sprayer.setSprayerDoubledAmountActive) |
139 | SpecializationUtil.registerFunction(vehicleType, "getSprayerDoubledAmountActive", Sprayer.getSprayerDoubledAmountActive) |
140 | end |
144 | function Sprayer.registerOverwrittenFunctions(vehicleType) |
145 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAIMarkers", Sprayer.getAIMarkers) |
146 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAISizeMarkers", Sprayer.getAISizeMarkers) |
147 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAIImplementCollisionTrigger", Sprayer.getAIImplementCollisionTrigger) |
148 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getDrawFirstFillText", Sprayer.getDrawFirstFillText) |
149 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAreControlledActionsAllowed", Sprayer.getAreControlledActionsAllowed) |
150 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getCanToggleTurnedOn", Sprayer.getCanToggleTurnedOn) |
151 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getCanBeTurnedOn", Sprayer.getCanBeTurnedOn) |
152 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "loadWorkAreaFromXML", Sprayer.loadWorkAreaFromXML) |
153 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsWorkAreaActive", Sprayer.getIsWorkAreaActive) |
154 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "doCheckSpeedLimit", Sprayer.doCheckSpeedLimit) |
155 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getRawSpeedLimit", Sprayer.getRawSpeedLimit) |
156 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getFillVolumeUVScrollSpeed", Sprayer.getFillVolumeUVScrollSpeed) |
157 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAIRequiresTurnOffOnHeadland", Sprayer.getAIRequiresTurnOffOnHeadland) |
158 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getDirtMultiplier", Sprayer.getDirtMultiplier) |
159 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getWearMultiplier", Sprayer.getWearMultiplier) |
160 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getEffectByNode", Sprayer.getEffectByNode) |
161 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getVariableWorkWidthUsage", Sprayer.getVariableWorkWidthUsage) |
162 | end |
663 | function Sprayer:setSprayerAITerrainDetailProhibitedRange(fillType) |
664 | if self:getUseSprayerAIRequirements() then |
665 | if self.addAITerrainDetailProhibitedRange ~= nil then |
666 | self:clearAITerrainDetailProhibitedRange() |
667 | self:clearAIFruitRequirements() |
668 | self:clearAIFruitProhibitions() |
669 | |
670 | self:addAIGroundTypeRequirements(Sprayer.AI_REQUIRED_GROUND_TYPES) |
671 | |
672 | local sprayTypeDesc = g_sprayTypeManager:getSprayTypeByFillTypeIndex(fillType) |
673 | if sprayTypeDesc ~= nil then |
674 | if sprayTypeDesc.isHerbicide then |
675 | local weedSystem = g_currentMission.weedSystem |
676 | if weedSystem ~= nil then |
677 | local weedMapId, weedFirstChannel, weedNumChannels = weedSystem:getDensityMapData() |
678 | local replacementData = weedSystem:getHerbicideReplacements() |
679 | if replacementData.weed ~= nil then |
680 | local startState, lastState = -1, -1 |
681 | for sourceState, targetState in pairs(replacementData.weed.replacements) do |
682 | if startState == -1 then |
683 | startState = sourceState |
684 | else |
685 | if sourceState ~= lastState + 1 then |
686 | self:addAIFruitRequirement(nil, startState, lastState, weedMapId, weedFirstChannel, weedNumChannels) |
687 | startState = sourceState |
688 | end |
689 | end |
690 | |
691 | lastState = sourceState |
692 | end |
693 | |
694 | if startState ~= -1 then |
695 | self:addAIFruitRequirement(nil, startState, lastState, weedMapId, weedFirstChannel, weedNumChannels) |
696 | end |
697 | end |
698 | end |
699 | else |
700 | local mission = g_currentMission |
701 | local sprayTypeMapId, sprayTypeFirstChannel, sprayTypeNumChannels = mission.fieldGroundSystem:getDensityMapData(FieldDensityMap.SPRAY_TYPE) |
702 | local sprayLevelMapId, sprayLevelFirstChannel, sprayLevelNumChannels = mission.fieldGroundSystem:getDensityMapData(FieldDensityMap.SPRAY_LEVEL) |
703 | local sprayLevelMaxValue = mission.fieldGroundSystem:getMaxValue(FieldDensityMap.SPRAY_LEVEL) |
704 | self:addAITerrainDetailProhibitedRange(sprayTypeDesc.sprayGroundType, sprayTypeDesc.sprayGroundType, sprayTypeFirstChannel, sprayTypeNumChannels) |
705 | -- TODO enable after field crops query support bitVectorMaps |
706 | --self:addAIFruitProhibitions(0, sprayTypeDesc.sprayGroundType, sprayTypeDesc.sprayGroundType, sprayTypeMapId, sprayTypeFirstChannel, sprayTypeNumChannels) |
707 | --self:addAIFruitProhibitions(0, sprayLevelMaxValue, sprayLevelMaxValue, sprayLevelMapId, sprayLevelFirstChannel, sprayLevelNumChannels) |
708 | end |
709 | |
710 | -- block ai from working on fully growth fields |
711 | if sprayTypeDesc.isHerbicide or sprayTypeDesc.isFertilizer then |
712 | for _, fruitType in pairs(g_fruitTypeManager:getFruitTypes()) do |
713 | if fruitType.terrainDataPlaneId ~= nil and fruitType.name:lower() ~= "grass" then |
714 | if fruitType.minHarvestingGrowthState ~= nil and fruitType.maxHarvestingGrowthState ~= nil then |
715 | self:addAIFruitProhibitions(fruitType.index, fruitType.minHarvestingGrowthState, fruitType.maxHarvestingGrowthState) |
716 | end |
717 | end |
718 | end |
719 | end |
720 | end |
721 | end |
722 | end |
723 | end |
576 | function Sprayer:updateSprayerEffects(force) |
577 | local spec = self.spec_sprayer |
578 | |
579 | local effectsState = self:getAreEffectsVisible() |
580 | if effectsState ~= spec.lastEffectsState or force then |
581 | if effectsState then |
582 | local fillType = self:getFillUnitLastValidFillType(self:getSprayerFillUnitIndex()) |
583 | if fillType == FillType.UNKNOWN then |
584 | fillType = self:getFillUnitFirstSupportedFillType(self:getSprayerFillUnitIndex()) |
585 | end |
586 | |
587 | g_effectManager:setFillType(spec.effects, fillType) |
588 | g_effectManager:startEffects(spec.effects) |
589 | |
590 | g_soundManager:playSample(spec.samples.spray) |
591 | |
592 | local sprayType = self:getActiveSprayType() |
593 | if sprayType ~= nil then |
594 | g_effectManager:setFillType(sprayType.effects, fillType) |
595 | g_effectManager:startEffects(sprayType.effects) |
596 | |
597 | g_animationManager:startAnimations(sprayType.animationNodes) |
598 | |
599 | g_soundManager:playSample(sprayType.samples.spray) |
600 | end |
601 | |
602 | g_animationManager:startAnimations(spec.animationNodes) |
603 | else |
604 | g_effectManager:stopEffects(spec.effects) |
605 | g_animationManager:stopAnimations(spec.animationNodes) |
606 | |
607 | g_soundManager:stopSample(spec.samples.spray) |
608 | |
609 | -- deactivate effects on all spray types (the spray type has may changed during activation) |
610 | for _, sprayType in ipairs(spec.sprayTypes) do |
611 | g_effectManager:stopEffects(sprayType.effects) |
612 | g_animationManager:stopAnimations(sprayType.animationNodes) |
613 | |
614 | g_soundManager:stopSample(sprayType.samples.spray) |
615 | end |
616 | end |
617 | |
618 | spec.lastEffectsState = effectsState |
619 | end |
620 | end |