168 | function AIJobDeliver:applyCurrentState(vehicle, mission, farmId, isDirectStart) |
169 | AIJobDeliver:superClass().applyCurrentState(self, vehicle, mission, farmId, isDirectStart) |
170 | |
171 | self.vehicleParameter:setVehicle(vehicle) |
172 | self.loopingParameter:setIsLooping(true) |
173 | |
174 | local x, z, angle, _ |
175 | if vehicle.getLastJob ~= nil then |
176 | local lastJob = vehicle:getLastJob() |
177 | if lastJob ~= nil and lastJob:isa(AIJobDeliver) then |
178 | self.unloadingStationParameter:setUnloadingStation(lastJob.unloadingStationParameter:getUnloadingStation()) |
179 | self.loopingParameter:setIsLooping(lastJob.loopingParameter:getIsLooping()) |
180 | x, z = lastJob.positionAngleParameter:getPosition() |
181 | angle = lastJob.positionAngleParameter:getAngle() |
182 | end |
183 | end |
184 | |
185 | if x == nil or z == nil then |
186 | x, _, z = getWorldTranslation(vehicle.rootNode) |
187 | end |
188 | if angle == nil then |
189 | local dirX, _, dirZ = localDirectionToWorld(vehicle.rootNode, 0, 0, 1) |
190 | angle = MathUtil.getYRotationFromDirection(dirX, dirZ) |
191 | end |
192 | |
193 | self.positionAngleParameter:setPosition(x, z) |
194 | self.positionAngleParameter:setAngle(angle) |
195 | |
196 | local unloadingStations = {} |
197 | for _, unloadingStation in pairs(g_currentMission.storageSystem:getUnloadingStations()) do |
198 | if g_currentMission.accessHandler:canPlayerAccess(unloadingStation) and unloadingStation:isa(UnloadingStation) then |
199 | local fillTypes = unloadingStation:getAISupportedFillTypes() |
200 | if next(fillTypes) ~= nil then |
201 | table.insert(unloadingStations, unloadingStation) |
202 | end |
203 | end |
204 | end |
205 | |
206 | self.unloadingStationParameter:setValidUnloadingStations(unloadingStations) |
207 | end |
344 | function AIJobDeliver:canContinueWork() |
345 | local vehicle = self.vehicleParameter:getVehicle() |
346 | if vehicle == nil then |
347 | return false, AIMessageErrorVehicleDeleted.new() |
348 | end |
349 | |
350 | local unloadingStation = self.unloadingStationParameter:getUnloadingStation() |
351 | if unloadingStation == nil then |
352 | return false, AIMessageErrorUnloadingStationDeleted.new() |
353 | end |
354 | |
355 | -- check if unloading station is full |
356 | if self.currentTaskIndex == self.waitForFillingTask.taskIndex then |
357 | local hasSpace = false |
358 | |
359 | for _, dischargeNodeInfo in ipairs(self.dischargeNodeInfos) do |
360 | local dischargeVehicle = dischargeNodeInfo.vehicle |
361 | local fillUnitIndex = dischargeNodeInfo.dischargeNode.fillUnitIndex |
362 | if dischargeVehicle:getFillUnitFillLevel(fillUnitIndex) > 1 then |
363 | local fillTypeIndex = dischargeVehicle:getFillUnitFillType(fillUnitIndex) |
364 | if unloadingStation:getFreeCapacity(fillTypeIndex, self.startedFarmId) > 0 then |
365 | hasSpace = true |
366 | break |
367 | end |
368 | end |
369 | end |
370 | |
371 | if not hasSpace then |
372 | return false, AIMessageErrorUnloadingStationFull.new() |
373 | end |
374 | end |
375 | |
376 | return true, nil |
377 | end |
485 | function AIJobDeliver:getDescription() |
486 | local desc = AIJobLoadAndDeliver:superClass().getDescription(self) |
487 | |
488 | local nextTask = self:getTaskByIndex(self.currentTaskIndex) |
489 | if nextTask == self.driveToLoadingTask then |
490 | desc = desc .. " - " .. g_i18n:getText("ai_taskDescriptionDriveToLoadingStation") |
491 | elseif nextTask == self.waitForFillingTask then |
492 | desc = desc .. " - " .. g_i18n:getText("ai_taskDescriptionWaitForFilling") |
493 | elseif nextTask == self.driveToUnloadingTask then |
494 | desc = desc .. " - " .. g_i18n:getText("ai_taskDescriptionDriveToUnloadingStation") |
495 | elseif nextTask == self.dischargeTask then |
496 | desc = desc .. " - " .. g_i18n:getText("ai_taskDescriptionUnloading") |
497 | end |
498 | |
499 | return desc |
500 | end |
420 | function AIJobDeliver:getIsAvailableForVehicle(vehicle) |
421 | if vehicle.createAgent == nil or vehicle.setAITarget == nil or not vehicle:getCanStartAIVehicle() then |
422 | return false |
423 | end |
424 | |
425 | if vehicle.getAIDischargeNodes ~= nil then |
426 | local nodes = vehicle:getAIDischargeNodes() |
427 | if next(nodes) ~= nil then |
428 | return true |
429 | end |
430 | end |
431 | |
432 | local vehicles = vehicle:getChildVehicles() |
433 | for _, childVehicle in ipairs(vehicles) do |
434 | if childVehicle.getAIDischargeNodes ~= nil then |
435 | local nodes = childVehicle:getAIDischargeNodes() |
436 | if next(nodes) ~= nil then |
437 | return true |
438 | end |
439 | end |
440 | end |
441 | end |
462 | function AIJobDeliver:getIsStartable(connection) |
463 | if g_currentMission.aiSystem:getAILimitedReached() then |
464 | return false, AIJobDeliver.START_ERROR_LIMIT_REACHED |
465 | end |
466 | |
467 | local vehicle = self.vehicleParameter:getVehicle() |
468 | if vehicle == nil then |
469 | return false, AIJobDeliver.START_ERROR_VEHICLE_DELETED |
470 | end |
471 | |
472 | if not g_currentMission:getHasPlayerPermission("hireAssistant", connection, vehicle:getOwnerFarmId()) then |
473 | return false, AIJobDeliver.START_ERROR_NO_PERMISSION |
474 | end |
475 | |
476 | if vehicle:getIsInUse(connection) then |
477 | return false, AIJobDeliver.START_ERROR_VEHICLE_IN_USE |
478 | end |
479 | |
480 | return true, AIJob.START_SUCCESS |
481 | end |
504 | function AIJobDeliver.getIsStartErrorText(state) |
505 | if state == AIJobDeliver.START_ERROR_LIMIT_REACHED then |
506 | return g_i18n:getText("ai_startStateLimitReached") |
507 | elseif state == AIJobDeliver.START_ERROR_VEHICLE_DELETED then |
508 | return g_i18n:getText("ai_startStateVehicleDeleted") |
509 | elseif state == AIJobDeliver.START_ERROR_NO_PERMISSION then |
510 | return g_i18n:getText("ai_startStateNoPermission") |
511 | elseif state == AIJobDeliver.START_ERROR_VEHICLE_IN_USE then |
512 | return g_i18n:getText("ai_startStateVehicleInUse") |
513 | end |
514 | |
515 | return g_i18n:getText("ai_startStateSuccess") |
516 | end |
287 | function AIJobDeliver:getNextTaskIndex(isSkipTask) |
288 | if self.currentTaskIndex == self.waitForFillingTask.taskIndex or self.currentTaskIndex == self.dischargeTask.taskIndex then |
289 | local lastUnloadTrigger |
290 | if self.currentTaskIndex == self.dischargeTask.taskIndex then |
291 | lastUnloadTrigger = self.dischargeTask.unloadTrigger |
292 | end |
293 | |
294 | -- check if there are more dischargeNodes that need discharge to the last unloadtrigger |
295 | local nextFillType = nil |
296 | local nextDischargeNodeInfo = nil |
297 | local unloadingStation = self.unloadingStationParameter:getUnloadingStation() |
298 | for _, dischargeNodeInfo in ipairs(self.dischargeNodeInfos) do |
299 | if dischargeNodeInfo.dirty then |
300 | local vehicle = dischargeNodeInfo.vehicle |
301 | local fillUnitIndex = dischargeNodeInfo.dischargeNode.fillUnitIndex |
302 | if vehicle:getFillUnitFillLevel(fillUnitIndex) > 1 then |
303 | local currentFillType = vehicle:getFillUnitFillType(fillUnitIndex) |
304 | if lastUnloadTrigger ~= nil and lastUnloadTrigger:getIsFillTypeSupported(currentFillType) then |
305 | self.dischargeTask:setDischargeNode(vehicle, dischargeNodeInfo.dischargeNode, dischargeNodeInfo.offsetZ) |
306 | dischargeNodeInfo.dirty = false |
307 | --#debug log("drive to discharge next trailer") |
308 | return self.currentTaskIndex |
309 | |
310 | elseif nextFillType == nil then |
311 | local _, _, _, _, trigger = unloadingStation:getAITargetPositionAndDirection(currentFillType) |
312 | if trigger ~= nil then |
313 | nextFillType = currentFillType |
314 | nextDischargeNodeInfo = dischargeNodeInfo |
315 | else |
316 | -- unloading station does not support fill type. mark node as handled |
317 | dischargeNodeInfo.dirty = false |
318 | end |
319 | end |
320 | end |
321 | end |
322 | end |
323 | |
324 | if nextFillType ~= nil then |
325 | --#debug log("Next index drive to uinloading", g_fillTypeManager:getFillTypeNameByIndex(nextFillType)) |
326 | local x, z, dirX, dirZ, trigger = unloadingStation:getAITargetPositionAndDirection(nextFillType) |
327 | self.driveToUnloadingTask:setTargetPosition(x, z) |
328 | self.driveToUnloadingTask:setTargetDirection(dirX, dirZ) |
329 | self.dischargeTask:setUnloadTrigger(trigger) |
330 | self.dischargeTask:setDischargeNode(nextDischargeNodeInfo.vehicle, nextDischargeNodeInfo.dischargeNode, nextDischargeNodeInfo.offsetZ) |
331 | nextDischargeNodeInfo.dirty = false |
332 | |
333 | return self.driveToUnloadingTask.taskIndex |
334 | end |
335 | end |
336 | |
337 | local nextTaskIndex = AIJobDeliver:superClass().getNextTaskIndex(self, isSkipTask) |
338 | --#debug log("Other: ", self.currentTaskIndex, "->", nextTaskIndex) |
339 | return nextTaskIndex |
340 | end |
252 | function AIJobDeliver:getStartTaskIndex() |
253 | local hasOneEmptyFillUnit = false |
254 | for _, dischargeNodeInfo in ipairs(self.dischargeNodeInfos) do |
255 | local vehicle = dischargeNodeInfo.vehicle |
256 | local fillUnitIndex = dischargeNodeInfo.dischargeNode.fillUnitIndex |
257 | if vehicle:getFillUnitFillLevel(fillUnitIndex) == 0 then |
258 | hasOneEmptyFillUnit = true |
259 | break |
260 | end |
261 | end |
262 | |
263 | |
264 | |
265 | -- check if already at target position |
266 | local vehicle = self.vehicleParameter:getVehicle() |
267 | local x, _, z = getWorldTranslation(vehicle.rootNode) |
268 | local tx, tz = self.positionAngleParameter:getPosition() |
269 | local targetReached = math.abs(x-tx) < 1 and math.abs(z-tz) < 1 |
270 | if targetReached then |
271 | if not hasOneEmptyFillUnit then |
272 | self.waitForFillingTask:skip() |
273 | end |
274 | return self.waitForFillingTask.taskIndex |
275 | end |
276 | |
277 | if not hasOneEmptyFillUnit then |
278 | self.driveToLoadingTask:skip() |
279 | self.waitForFillingTask:skip() |
280 | end |
281 | |
282 | return self.driveToLoadingTask.taskIndex |
283 | end |
22 | function AIJobDeliver.new(isServer, customMt) |
23 | local self = AIJob.new(isServer, customMt or AIJobDeliver_mt) |
24 | |
25 | self.dischargeNodeInfos = {} |
26 | |
27 | self.driveToLoadingTask = AITaskDriveTo.new(isServer, self) |
28 | self.waitForFillingTask = AITaskWaitForFilling.new(isServer, self) |
29 | self.driveToUnloadingTask = AITaskDriveTo.new(isServer, self) |
30 | self.dischargeTask = AITaskDischarge.new(isServer, self) |
31 | |
32 | self:addTask(self.driveToLoadingTask) |
33 | self:addTask(self.waitForFillingTask) |
34 | self:addTask(self.driveToUnloadingTask) |
35 | self:addTask(self.dischargeTask) |
36 | |
37 | self.vehicleParameter = AIParameterVehicle.new() |
38 | self.unloadingStationParameter = AIParameterUnloadingStation.new() |
39 | self.loopingParameter = AIParameterLooping.new() |
40 | self.positionAngleParameter = AIParameterPositionAngle.new(math.rad(5)) |
41 | |
42 | self:addNamedParameter("vehicle", self.vehicleParameter) |
43 | self:addNamedParameter("unloadingStation", self.unloadingStationParameter) |
44 | self:addNamedParameter("looping", self.loopingParameter) |
45 | self:addNamedParameter("positionAngle", self.positionAngleParameter) |
46 | |
47 | local vehicleGroup = AIParameterGroup.new(g_i18n:getText("ai_parameterGroupTitleVehicle")) |
48 | vehicleGroup:addParameter(self.vehicleParameter) |
49 | |
50 | local unloadTargetGroup = AIParameterGroup.new(g_i18n:getText("ai_parameterGroupTitleUnloadingStation")) |
51 | unloadTargetGroup:addParameter(self.unloadingStationParameter) |
52 | |
53 | local positionGroup = AIParameterGroup.new(g_i18n:getText("ai_parameterGroupTitleLoadingPosition")) |
54 | positionGroup:addParameter(self.positionAngleParameter) |
55 | |
56 | local loopingGroup = AIParameterGroup.new(g_i18n:getText("ai_parameterGroupTitleLooping")) |
57 | loopingGroup:addParameter(self.loopingParameter) |
58 | |
59 | table.insert(self.groupedParameters, vehicleGroup) |
60 | table.insert(self.groupedParameters, unloadTargetGroup) |
61 | table.insert(self.groupedParameters, positionGroup) |
62 | table.insert(self.groupedParameters, loopingGroup) |
63 | |
64 | return self |
65 | end |
69 | function AIJobDeliver:setValues() |
70 | self:resetTasks() |
71 | |
72 | local vehicle = self.vehicleParameter:getVehicle() |
73 | if vehicle == nil then |
74 | return |
75 | end |
76 | local unloadingStation = self.unloadingStationParameter:getUnloadingStation() |
77 | if unloadingStation == nil then |
78 | return |
79 | end |
80 | |
81 | self.driveToUnloadingTask:setVehicle(vehicle) |
82 | self.driveToLoadingTask:setVehicle(vehicle) |
83 | self.dischargeTask:setVehicle(vehicle) |
84 | self.waitForFillingTask:setVehicle(vehicle) |
85 | |
86 | self.dischargeNodeInfos = {} |
87 | |
88 | -- apply allowed fill types based on unloading trigger |
89 | for fillType, _ in pairs(unloadingStation:getAISupportedFillTypes()) do |
90 | self.waitForFillingTask:addAllowedFillType(fillType) |
91 | end |
92 | |
93 | if vehicle.getAIDischargeNodes ~= nil then |
94 | for _, dischargeNode in ipairs(vehicle:getAIDischargeNodes()) do |
95 | |
96 | local _, _, z = vehicle:getAIDischargeNodeZAlignedOffset(dischargeNode, vehicle) |
97 | table.insert(self.dischargeNodeInfos, {vehicle=vehicle, dischargeNode=dischargeNode, offsetZ=z, dirty=true}) |
98 | end |
99 | end |
100 | |
101 | local childVehicles = vehicle:getChildVehicles() |
102 | for _, childVehicle in ipairs(childVehicles) do |
103 | if childVehicle.getAIDischargeNodes ~= nil then |
104 | for _, dischargeNode in ipairs(childVehicle:getAIDischargeNodes()) do |
105 | local _, _, z = childVehicle:getAIDischargeNodeZAlignedOffset(dischargeNode, vehicle) |
106 | table.insert(self.dischargeNodeInfos, {vehicle=childVehicle, dischargeNode=dischargeNode, offsetZ=z, dirty=true}) |
107 | end |
108 | end |
109 | end |
110 | |
111 | table.sort(self.dischargeNodeInfos, function(a, b) |
112 | return a.offsetZ > b.offsetZ |
113 | end) |
114 | |
115 | for _, dischargeNodeInfo in ipairs(self.dischargeNodeInfos) do |
116 | self.waitForFillingTask:addFillUnits(dischargeNodeInfo.vehicle, dischargeNodeInfo.dischargeNode.fillUnitIndex) |
117 | end |
118 | |
119 | local maxOffset = self.dischargeNodeInfos[#self.dischargeNodeInfos].offsetZ |
120 | self.driveToLoadingTask:setTargetOffset(-maxOffset) |
121 | self.driveToUnloadingTask:setTargetOffset(-maxOffset) |
122 | |
123 | local x, z = self.positionAngleParameter:getPosition() |
124 | if x ~= nil then |
125 | self.driveToLoadingTask:setTargetPosition(x, z) |
126 | end |
127 | local xDir, zDir = self.positionAngleParameter:getDirection() |
128 | if xDir ~= nil then |
129 | self.driveToLoadingTask:setTargetDirection(xDir, zDir) |
130 | end |
131 | end |
135 | function AIJobDeliver:validate(farmId) |
136 | self:setParamterValid(true) |
137 | |
138 | local isVehicleValid, vehicleErrorMessage = self.vehicleParameter:validate() |
139 | if isVehicleValid then |
140 | if #self.dischargeNodeInfos == 0 then |
141 | isVehicleValid = false |
142 | vehicleErrorMessage = g_i18n:getText("ai_validationErrorNoAIDischargeNodesFound") |
143 | end |
144 | end |
145 | if not isVehicleValid then |
146 | self.vehicleParameter:setIsValid(false) |
147 | end |
148 | |
149 | local isUnloadingStationValid, unloadingStationErrorMessage = self.unloadingStationParameter:validate() |
150 | if not isUnloadingStationValid then |
151 | self.unloadingStationParameter:setIsValid(false) |
152 | end |
153 | |
154 | local isPositionValid, positionErrorMessage = self.positionAngleParameter:validate() |
155 | if not isPositionValid then |
156 | positionErrorMessage = g_i18n:getText("ai_validationErrorNoLoadingPoint") |
157 | self.positionAngleParameter:setIsValid(false) |
158 | end |
159 | |
160 | local isValid = isVehicleValid and isUnloadingStationValid and isPositionValid |
161 | local errorMessage = vehicleErrorMessage or unloadingStationErrorMessage or positionErrorMessage |
162 | |
163 | return isValid, errorMessage |
164 | end |