164 | function WorkArea:getIsAccessibleAtWorldPosition(farmId, x, z, workAreaType) |
165 | -- Disallow mission vehicles outside mission fields |
166 | if self.propertyState == Vehicle.PROPERTY_STATE_MISSION then |
167 | return g_missionManager:getIsMissionWorkAllowed(farmId, x, z, workAreaType), farmId, true |
168 | end |
169 | |
170 | local farmlandId = g_farmlandManager:getFarmlandIdAtWorldPosition(x, z) |
171 | if farmlandId == nil then -- no valid farmland, or not buyable |
172 | return false, nil, false |
173 | end |
174 | |
175 | if farmlandId == FarmlandManager.NOT_BUYABLE_FARM_ID then |
176 | return false, FarmlandManager.NO_OWNER_FARM_ID, false |
177 | end |
178 | |
179 | local landOwner = g_farmlandManager:getFarmlandOwner(farmlandId) |
180 | local accessible = (landOwner ~= 0 and g_currentMission.accessHandler:canFarmAccessOtherId(farmId, landOwner)) |
181 | or g_missionManager:getIsMissionWorkAllowed(farmId, x, z, workAreaType) |
182 | |
183 | return accessible, landOwner, true |
184 | end |
539 | function WorkArea:getTypedNetworkAreas(areaType, needsFieldProperty) |
540 | local workAreasSend = {} |
541 | local area = 0 |
542 | local typedWorkAreas = self:getTypedWorkAreas(areaType) |
543 | local showFarmlandNotOwnedWarning = false |
544 | |
545 | for _, workArea in pairs(typedWorkAreas) do |
546 | if self:getIsWorkAreaActive(workArea) then |
547 | local x,_,z = getWorldTranslation(workArea.start) |
548 | |
549 | local isAccessible = not needsFieldProperty |
550 | if needsFieldProperty then |
551 | local farmId = g_currentMission:getFarmId() |
552 | isAccessible = g_currentMission.accessHandler:canFarmAccessLand(farmId, x, z) or g_missionManager:getIsMissionWorkAllowed(farmId, x, z, areaType) |
553 | end |
554 | |
555 | if isAccessible then |
556 | local x1,_,z1 = getWorldTranslation(workArea.width) |
557 | local x2,_,z2 = getWorldTranslation(workArea.height) |
558 | area = area + math.abs((z1-z)*(x2-x) - (x1-x)*(z2-z)) |
559 | table.insert(workAreasSend, {x,z,x1,z1,x2,z2}) |
560 | else |
561 | showFarmlandNotOwnedWarning = true |
562 | end |
563 | end |
564 | end |
565 | |
566 | return workAreasSend, showFarmlandNotOwnedWarning, area |
567 | end |
363 | function WorkArea:loadWorkAreaFromXML(workArea, xmlFile, key) |
364 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key .. ".area#startIndex", key .. ".area#startNode") --FS17 to FS19 |
365 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key .. ".area#widthIndex", key .. ".area#widthNode") --FS17 to FS19 |
366 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key .. ".area#heightIndex", key .. ".area#heightNode") --FS17 to FS19 |
367 | |
368 | local start = xmlFile:getValue(key .. ".area#startNode", workArea.start, self.components, self.i3dMappings) |
369 | local width = xmlFile:getValue(key .. ".area#widthNode", workArea.width, self.components, self.i3dMappings) |
370 | local height = xmlFile:getValue(key .. ".area#heightNode", workArea.height, self.components, self.i3dMappings) |
371 | |
372 | if start ~= nil and width ~= nil and height ~= nil then |
373 | if calcDistanceFrom(start, width) < 0.001 then |
374 | Logging.xmlError(xmlFile, "'start' and 'width' have the same position for '%s'!", key) |
375 | return false |
376 | end |
377 | if calcDistanceFrom(width, height) < 0.001 then |
378 | Logging.xmlError(xmlFile, "'width' and 'height' have the same position for '%s'!", key) |
379 | return false |
380 | end |
381 | |
382 | local areaTypeStr = xmlFile:getValue(key .."#type") |
383 | workArea.type = g_workAreaTypeManager:getWorkAreaTypeIndexByName(areaTypeStr) or WorkAreaType.DEFAULT |
384 | |
385 | if workArea.type == nil then |
386 | Logging.xmlWarning(xmlFile, "Invalid workArea type '%s' for workArea '%s'!", areaTypeStr, key) |
387 | return false |
388 | end |
389 | |
390 | workArea.requiresGroundContact = xmlFile:getValue(key .. "#requiresGroundContact", true) |
391 | |
392 | if workArea.type ~= WorkAreaType.AUXILIARY then |
393 | |
394 | if workArea.requiresGroundContact then |
395 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key .. "#refNodeIndex", key .. ".groundReferenceNode#index") --FS17 to FS19 |
396 | local groundReferenceNodeIndex = xmlFile:getValue(key ..".groundReferenceNode#index") |
397 | if groundReferenceNodeIndex == nil then |
398 | Logging.xmlWarning(xmlFile, "Missing groundReference 'groundReferenceNode#index' for workArea '%s'. Add requiresGroundContact=\"false\" if groundContact is not required!", key) |
399 | return false |
400 | end |
401 | local groundReferenceNode = self:getGroundReferenceNodeFromIndex(groundReferenceNodeIndex) |
402 | if groundReferenceNode ~= nil then |
403 | workArea.groundReferenceNode = groundReferenceNode |
404 | else |
405 | Logging.xmlWarning(xmlFile, "Invalid groundReferenceNode-index for workArea '%s'!", key) |
406 | return false |
407 | end |
408 | end |
409 | |
410 | workArea.disableBackwards = xmlFile:getValue(key .. "#disableBackwards", true) |
411 | workArea.onlyActiveWhenLowered = xmlFile:getValue(key .. ".onlyActiveWhenLowered#value", false) |
412 | |
413 | workArea.functionName = xmlFile:getValue(key .. "#functionName") |
414 | if workArea.functionName == nil then |
415 | Logging.xmlWarning(xmlFile, "Missing 'functionName' for workArea '%s'!", key) |
416 | return false |
417 | else |
418 | if self[workArea.functionName] == nil then |
419 | Logging.xmlWarning(xmlFile, "Given functionName '%s' not defined. Please add missing function or specialization!", tostring(workArea.functionName)) |
420 | return false |
421 | end |
422 | workArea.processingFunction = self[workArea.functionName] |
423 | end |
424 | |
425 | if g_isDevelopmentVersion then |
426 | if not SpecializationUtil.hasSpecialization(Cutter, self.specializations) |
427 | and not SpecializationUtil.hasSpecialization(Pickup, self.specializations) |
428 | and not SpecializationUtil.hasSpecialization(Drivable, self.specializations) |
429 | and xmlFile:getString(key .. ".onlyActiveWhenLowered#value") == nil then |
430 | Logging.xmlDevWarning(xmlFile, "Work area has no 'onlyActiveWhenLowered' attribute set! '%s'", key) |
431 | end |
432 | end |
433 | |
434 | workArea.preprocessFunctionName = xmlFile:getValue(key .. "#preprocessFunctionName") |
435 | if workArea.preprocessFunctionName ~= nil then |
436 | if self[workArea.preprocessFunctionName] == nil then |
437 | Logging.xmlWarning(xmlFile, "Given preprocessFunctionName '%s' not defined. Please add missing function or specialization!", tostring(workArea.preprocessFunctionName)) |
438 | return false |
439 | end |
440 | workArea.preprocessingFunction = self[workArea.preprocessFunctionName] |
441 | end |
442 | |
443 | workArea.postprocessFunctionName = xmlFile:getValue(key .. "#postprocessFunctionName") |
444 | if workArea.postprocessFunctionName ~= nil then |
445 | if self[workArea.postprocessFunctionName] == nil then |
446 | Logging.xmlWarning(xmlFile, "Given postprocessFunctionName '%s' not defined. Please add missing function or specialization!", tostring(workArea.postprocessFunctionName)) |
447 | return false |
448 | end |
449 | workArea.postprocessingFunction = self[workArea.postprocessFunctionName] |
450 | end |
451 | |
452 | workArea.requiresOwnedFarmland = xmlFile:getValue(key .. "#requiresOwnedFarmland", true) |
453 | end |
454 | |
455 | workArea.lastProcessingTime = 0 |
456 | workArea.start = start |
457 | workArea.width = width |
458 | workArea.height = height |
459 | |
460 | workArea.workWidth = -1 |
461 | |
462 | return true |
463 | end |
464 | |
465 | return false |
466 | end |
107 | function WorkArea:onLoad(savegame) |
108 | local spec = self.spec_workArea |
109 | |
110 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.workAreas.workArea(0)#startIndex", "vehicle.workAreas.workArea(0).area#startIndex") --FS17 to FS19 |
111 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.workAreas.workArea(0)#widthIndex", "vehicle.workAreas.workArea(0).area#widthIndex") --FS17 to FS19 |
112 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.workAreas.workArea(0)#heightIndex", "vehicle.workAreas.workArea(0).area#heightIndex") --FS17 to FS19 |
113 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.workAreas.workArea(0)#foldMinLimit", "vehicle.workAreas.workArea(0).folding#minLimit") --FS17 to FS19 |
114 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.workAreas.workArea(0)#foldMaxLimit", "vehicle.workAreas.workArea(0).folding#maxLimit") --FS17 to FS19 |
115 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.workAreas.workArea(0)#refNodeIndex", "vehicle.workAreas.workArea(0).groundReferenceNode#index") --FS17 to FS19 |
116 | |
117 | local configurationId = Utils.getNoNil(self.configurations["workArea"], 1) |
118 | local configKey = string.format("vehicle.workAreas.workAreaConfigurations.workAreaConfiguration(%d)", configurationId - 1) |
119 | ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.workAreas.workAreaConfigurations.workAreaConfiguration", configurationId , self.components, self) |
120 | |
121 | if not self.xmlFile:hasProperty(configKey) then |
122 | configKey = "vehicle.workAreas" |
123 | end |
124 | |
125 | spec.workAreas = {} |
126 | local i = 0 |
127 | while true do |
128 | local key = string.format("%s.workArea(%d)", configKey, i) |
129 | if not self.xmlFile:hasProperty(key) then |
130 | break |
131 | end |
132 | local workArea = {} |
133 | if self:loadWorkAreaFromXML(workArea, self.xmlFile, key) then |
134 | table.insert(spec.workAreas, workArea) |
135 | workArea.index = #spec.workAreas |
136 | |
137 | self:updateWorkAreaWidth(workArea.index) |
138 | end |
139 | |
140 | i = i + 1 |
141 | end |
142 | |
143 | spec.workAreaByType = {} |
144 | for _, area in pairs(spec.workAreas) do |
145 | if spec.workAreaByType[area.type] == nil then |
146 | spec.workAreaByType[area.type] = {} |
147 | end |
148 | table.insert(spec.workAreaByType[area.type], area) |
149 | end |
150 | |
151 | spec.lastAccessedFarmlandOwner = 0 |
152 | spec.lastActiveMissionWork = false |
153 | spec.lastWorkedArea = -1 |
154 | spec.showFarmlandNotOwnedWarning = false |
155 | spec.warningCantUseMissionVehiclesOnOtherLand = g_i18n:getText("warning_cantUseMissionVehiclesOnOtherLand") |
156 | spec.warningYouDontHaveAccessToThisLand = g_i18n:getText("warning_youDontHaveAccessToThisLand") |
157 | end |
216 | function WorkArea:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
217 | local spec = self.spec_workArea |
218 | SpecializationUtil.raiseEvent(self, "onStartWorkAreaProcessing", dt, spec.workAreas) |
219 | |
220 | spec.showFarmlandNotOwnedWarning = false |
221 | -- do not reset last accessed farmland owner |
222 | -- spec.lastAccessedFarmlandOwner = 0 |
223 | local hasProcessed = false |
224 | |
225 | local farmId = self:getActiveFarm() |
226 | if farmId == nil then -- Shop |
227 | farmId = AccessHandler.EVERYONE |
228 | end |
229 | |
230 | local isOwned = false |
231 | local isBuyable = false |
232 | local allowWarning = false |
233 | for i=1, #spec.workAreas do |
234 | local workArea = spec.workAreas[i] |
235 | if workArea.type ~= WorkAreaType.AUXILIARY then |
236 | workArea.lastWorkedHectares = 0 |
237 | |
238 | local isAreaActive = self:getIsWorkAreaActive(workArea) |
239 | if isAreaActive and workArea.requiresOwnedFarmland then |
240 | local xs,_,zs = getWorldTranslation(workArea.start) |
241 | local isAccessible, farmlandOwner, buyable = self:getIsAccessibleAtWorldPosition(farmId, xs, zs, workArea.type) |
242 | isBuyable = isBuyable or buyable |
243 | if isAccessible then |
244 | if farmlandOwner ~= nil then |
245 | spec.lastAccessedFarmlandOwner = farmlandOwner |
246 | spec.lastActiveMissionWork = g_missionManager:getIsMissionWorkAllowed(farmId, xs, zs, workArea.type) |
247 | end |
248 | isOwned = true |
249 | else |
250 | local xw,_,zw = getWorldTranslation(workArea.width) |
251 | isAccessible, _, buyable = self:getIsAccessibleAtWorldPosition(farmId, xw, zw, workArea.type) |
252 | isBuyable = isBuyable or buyable |
253 | if isAccessible then |
254 | isOwned = true |
255 | else |
256 | local xh,_,zh = getWorldTranslation(workArea.height) |
257 | isAccessible, _, buyable = self:getIsAccessibleAtWorldPosition(farmId, xh, zh, workArea.type) |
258 | isBuyable = isBuyable or buyable |
259 | if isAccessible then |
260 | isOwned = true |
261 | else |
262 | local x = xw + (xh - xs) |
263 | local z = zw + (zh - zs) |
264 | isAccessible, _, buyable = self:getIsAccessibleAtWorldPosition(farmId, x, z, workArea.type) |
265 | isBuyable = isBuyable or buyable |
266 | if isAccessible then |
267 | isOwned = true |
268 | end |
269 | end |
270 | end |
271 | end |
272 | |
273 | if not isOwned then |
274 | isAreaActive = false |
275 | end |
276 | |
277 | allowWarning = isBuyable |
278 | end |
279 | |
280 | if isAreaActive then |
281 | if workArea.preprocessingFunction ~= nil then |
282 | workArea.preprocessingFunction(self, workArea, dt) |
283 | end |
284 | |
285 | if workArea.processingFunction ~= nil then |
286 | local realArea, _ = workArea.processingFunction(self, workArea, dt) |
287 | |
288 | if realArea > 0 then |
289 | workArea.lastWorkedHectares = MathUtil.areaToHa(realArea, g_currentMission:getFruitPixelsToSqm()) -- 4096px are mapped to 2048m |
290 | |
291 | workArea.lastProcessingTime = g_currentMission.time |
292 | |
293 | -- Adding an area of interest for the wildlife spawners / keep (xw,zw) and (xh,zh) |
294 | if g_currentMission.wildlifeSpawner ~= nil then |
295 | local workAreaType = g_workAreaTypeManager:getWorkAreaTypeByIndex(workArea.type) |
296 | if workAreaType.attractWildlife then |
297 | local xw,_,zw = getWorldTranslation(workArea.width) |
298 | local xh,_,zh = getWorldTranslation(workArea.height) |
299 | |
300 | local radius = 3.0 |
301 | local posX = 0.5 * xw + 0.5 * xh |
302 | local posZ = 0.5 * zw + 0.5 * zh |
303 | local lifeTime = 0 |
304 | g_currentMission.wildlifeSpawner:addAreaOfInterest(lifeTime, posX, posZ, radius) |
305 | end |
306 | end |
307 | else |
308 | workArea.lastWorkedHectares = 0 |
309 | end |
310 | end |
311 | |
312 | if workArea.postprocessingFunction ~= nil then |
313 | workArea.postprocessingFunction(self, workArea, dt) |
314 | end |
315 | |
316 | hasProcessed = true |
317 | end |
318 | end |
319 | end |
320 | |
321 | -- display warning if none of the work areas got valid owned ground |
322 | if allowWarning and not isOwned then |
323 | spec.showFarmlandNotOwnedWarning = true |
324 | end |
325 | |
326 | SpecializationUtil.raiseEvent(self, "onEndWorkAreaProcessing", dt, hasProcessed) |
327 | |
328 | if spec.lastWorkedArea >= 0 then |
329 | local stats = g_currentMission:farmStats(self:getLastTouchedFarmlandFarmId()) |
330 | local ha = MathUtil.areaToHa(spec.lastWorkedArea, g_currentMission:getFruitPixelsToSqm()) -- 4096px are mapped to 2048m |
331 | stats:updateStats("workedHectares", ha) |
332 | stats:updateStats("workedTime", dt/(1000*60)) |
333 | |
334 | spec.lastWorkedArea = -1 |
335 | end |
336 | end |
71 | function WorkArea.registerFunctions(vehicleType) |
72 | SpecializationUtil.registerFunction(vehicleType, "loadWorkAreaFromXML", WorkArea.loadWorkAreaFromXML) |
73 | SpecializationUtil.registerFunction(vehicleType, "getWorkAreaByIndex", WorkArea.getWorkAreaByIndex) |
74 | SpecializationUtil.registerFunction(vehicleType, "getIsWorkAreaActive", WorkArea.getIsWorkAreaActive) |
75 | SpecializationUtil.registerFunction(vehicleType, "updateWorkAreaWidth", WorkArea.updateWorkAreaWidth) |
76 | SpecializationUtil.registerFunction(vehicleType, "getWorkAreaWidth", WorkArea.getWorkAreaWidth) |
77 | SpecializationUtil.registerFunction(vehicleType, "getIsWorkAreaProcessing", WorkArea.getIsWorkAreaProcessing) |
78 | SpecializationUtil.registerFunction(vehicleType, "getTypedNetworkAreas", WorkArea.getTypedNetworkAreas) |
79 | SpecializationUtil.registerFunction(vehicleType, "getTypedWorkAreas", WorkArea.getTypedWorkAreas) |
80 | SpecializationUtil.registerFunction(vehicleType, "getIsTypedWorkAreaActive", WorkArea.getIsTypedWorkAreaActive) |
81 | SpecializationUtil.registerFunction(vehicleType, "getIsFarmlandNotOwnedWarningShown", WorkArea.getIsFarmlandNotOwnedWarningShown) |
82 | SpecializationUtil.registerFunction(vehicleType, "getLastTouchedFarmlandFarmId", WorkArea.getLastTouchedFarmlandFarmId) |
83 | SpecializationUtil.registerFunction(vehicleType, "getLastActiveMissionWork", WorkArea.getLastActiveMissionWork) |
84 | SpecializationUtil.registerFunction(vehicleType, "getIsAccessibleAtWorldPosition", WorkArea.getIsAccessibleAtWorldPosition) |
85 | SpecializationUtil.registerFunction(vehicleType, "updateLastWorkedArea", WorkArea.updateLastWorkedArea) |
86 | end |
39 | function WorkArea.registerWorkAreaXMLPaths(schema, basePath) |
40 | schema:register(XMLValueType.STRING, basePath .. "#type", "Work area type", "DEFAULT") |
41 | schema:register(XMLValueType.BOOL, basePath .. "#requiresGroundContact", "Requires ground contact to work", true) |
42 | schema:register(XMLValueType.BOOL, basePath .. "#disableBackwards", "Area is disabled while driving backwards", true) |
43 | schema:register(XMLValueType.BOOL, basePath .. "#requiresOwnedFarmland", "Requires owned farmland", true) |
44 | schema:register(XMLValueType.STRING, basePath .. "#functionName", "Work area script function") |
45 | schema:register(XMLValueType.STRING, basePath .. "#preprocessFunctionName", "Pre process work area script function") |
46 | schema:register(XMLValueType.STRING, basePath .. "#postprocessFunctionName", "Post process work area script function") |
47 | |
48 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".area#startNode", "Start node") |
49 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".area#widthNode", "Width node") |
50 | schema:register(XMLValueType.NODE_INDEX, basePath .. ".area#heightNode", "Height node") |
51 | |
52 | schema:register(XMLValueType.INT, basePath .. ".groundReferenceNode#index", "Ground reference node index") |
53 | schema:register(XMLValueType.BOOL, basePath .. ".onlyActiveWhenLowered#value", "Work area is only active when lowered", false) |
54 | end |
506 | function WorkArea:updateWorkAreaWidth(workAreaIndex) |
507 | local spec = self.spec_workArea |
508 | local workArea = spec.workAreas[workAreaIndex] |
509 | |
510 | local x1, _, _ = localToLocal(self.components[1].node, workArea.start, 0, 0, 0) |
511 | local x2, _, _ = localToLocal(self.components[1].node, workArea.width, 0, 0, 0) |
512 | local x3, _, _ = localToLocal(self.components[1].node, workArea.height, 0, 0, 0) |
513 | |
514 | workArea.workWidth = math.max(x1, x2, x3) - math.min(x1, x2, x3) |
515 | end |