Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
1 | -- |
2 | -- AICombine |
3 | -- Specialization for ai combines |
4 | -- |
5 | -- @author Stefan Geiger |
6 | -- @date 10/01/09 |
7 | -- |
8 | -- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved. |
9 | |
10 | AICombine = {}; |
11 | source("dataS/scripts/vehicles/specializations/AICombineSetStartedEvent.lua"); |
12 | source("dataS/scripts/vehicles/specializations/AISetImplementsMoveDownEvent.lua"); |
13 | |
14 | function AICombine.prerequisitesPresent(specializations) |
15 | return SpecializationUtil.hasSpecialization(Hirable, specializations) and SpecializationUtil.hasSpecialization(Combine, specializations); |
16 | end; |
17 | |
18 | function AICombine:load(xmlFile) |
19 | |
20 | self.startAIThreshing = SpecializationUtil.callSpecializationsFunction("startAIThreshing"); |
21 | self.stopAIThreshing = SpecializationUtil.callSpecializationsFunction("stopAIThreshing"); |
22 | self.setAIImplementsMoveDown = SpecializationUtil.callSpecializationsFunction("setAIImplementsMoveDown"); |
23 | |
24 | self.addCollisionTrigger = AICombine.addCollisionTrigger; |
25 | self.removeCollisionTrigger = AICombine.removeCollisionTrigger; |
26 | self.onTrafficCollisionTrigger = AICombine.onTrafficCollisionTrigger; |
27 | |
28 | self.canStartAIThreshing = AICombine.canStartAIThreshing; |
29 | self.getIsAIThreshingAllowed = AICombine.getIsAIThreshingAllowed; |
30 | |
31 | self.isAIThreshing = false; |
32 | self.aiTreshingDirectionNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTreshingDirectionNode#index")); |
33 | if self.aiTreshingDirectionNode == nil then |
34 | self.aiTreshingDirectionNode = self.components[1].node; |
35 | end; |
36 | |
37 | self.lookAheadDistance = 10; |
38 | self.turnTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnTimeout"), 200); |
39 | self.turnTimeoutLong = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnTimeoutLong"), 6000); |
40 | self.turnTimer = self.turnTimeout; |
41 | self.turnEndDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnEndDistance"), 4); |
42 | |
43 | self.waitForTurnTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waitForTurnTime"), 1500); |
44 | self.waitForTurnTime = 0; |
45 | |
46 | |
47 | self.sideWatchDirOffset = -8; |
48 | self.sideWatchDirSize = 8; |
49 | |
50 | self.frontAreaSize = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.frontAreaSize#value"), 2); |
51 | |
52 | self.waitingForTrailerToUnload = false; |
53 | |
54 | self.waitingForDischarge = false; |
55 | self.waitForDischargeTime = 0; |
56 | self.waitForDischargeTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waitForDischargeTime"), 10000); |
57 | |
58 | self.turnStage = 0; |
59 | |
60 | |
61 | self.aiLeftMarker = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiLeftMarker#index")); |
62 | self.aiRightMarker = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiRightMarker#index")); |
63 | self.aiTrafficCollisionTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTrafficCollisionTrigger#index")); |
64 | |
65 | self.aiTurnThreshWidthScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTurnThreshWidthScale#value"), 0.9); |
66 | self.aiTurnThreshWidthMaxDifference = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTurnThreshWidthMaxDifference#value"), 0.6); -- do at most a 0.6m overlap |
67 | |
68 | |
69 | self.trafficCollisionIgnoreList = {}; |
70 | for k,v in pairs(self.components) do |
71 | self.trafficCollisionIgnoreList[v.node] = true; |
72 | end; |
73 | self.numCollidingVehicles = {}; |
74 | |
75 | self.driveBackTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.driveBackTimeout"), 1000); |
76 | self.driveBackTime = 0; |
77 | self.driveBackAfterDischarge = false; |
78 | |
79 | self.dtSum = 0; |
80 | |
81 | self.turnStage1Timeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnForwardTimeout"), 20000); |
82 | self.turnStage1AngleCosThreshold = math.cos(math.rad(Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnForwardAngleThreshold"), 75))); |
83 | self.turnStage2Timeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnBackwardTimeout"), 20000); |
84 | self.turnStage2AngleCosThreshold = math.cos(math.rad(Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnBackwardAngleThreshold"), 15))); |
85 | self.turnStage4Timeout = 3000; |
86 | |
87 | self.waitingForWeather = false; |
88 | |
89 | self.aiRescueTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#timeout"), 10000); |
90 | self.aiRescueTimer = self.aiRescueTimeout; |
91 | self.aiRescueForce = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#force"), 60); |
92 | self.aiRescueSpeedThreshold = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#speedThreshold"), 0.0001); |
93 | self.aiRescueNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiRescue#index")); |
94 | if self.aiRescueNode == nil then |
95 | self.aiRescueNode = self.components[1].node; |
96 | end; |
97 | |
98 | self.numAttachedTrailers = 0; |
99 | |
100 | self.showFieldNotOwnedWarningTimer = 0; |
101 | |
102 | --self.debugDirection = loadI3DFile("data/debugDirection.i3d"); |
103 | --link(self.aiTreshingDirectionNode, self.debugDirection); |
104 | |
105 | --self.debugPosition = loadI3DFile("data/debugPosition.i3d"); |
106 | --link(self.aiTreshingDirectionNode, self.debugPosition); |
107 | |
108 | end; |
109 | |
110 | function AICombine:delete() |
111 | self:removeCollisionTrigger(self); |
112 | self:stopAIThreshing(true); |
113 | end; |
114 | |
115 | function AICombine:readStream(streamId, connection) |
116 | local isAIThreshing = streamReadBool(streamId); |
117 | if isAIThreshing then |
118 | self:startAIThreshing(true); |
119 | else |
120 | self:stopAIThreshing(true); |
121 | end; |
122 | end; |
123 | |
124 | function AICombine:writeStream(streamId, connection) |
125 | streamWriteBool(streamId, self.isAIThreshing); |
126 | end; |
127 | |
128 | function AICombine:mouseEvent(posX, posY, isDown, isUp, button) |
129 | end; |
130 | |
131 | function AICombine:keyEvent(unicode, sym, modifier, isDown) |
132 | end; |
133 | |
134 | function AICombine:update(dt) |
135 | --if self:getIsActiveForInput(false) then |
136 | local activeForInput = true; |
137 | if g_gui.currentGui ~= nil or g_currentMission.isPlayerFrozen then |
138 | activeForInput = false; |
139 | end; |
140 | if activeForInput and self.isEntered then |
141 | if InputBinding.hasEvent(InputBinding.TOGGLE_AI) and not g_currentMission.inGameMessage:getIsVisible() then |
142 | if g_currentMission:getHasPermission("hireAI") then |
143 | if self.isAIThreshing then |
144 | self:stopAIThreshing(); |
145 | else |
146 | if self:canStartAIThreshing() then |
147 | self:startAIThreshing(); |
148 | end; |
149 | end; |
150 | end |
151 | end; |
152 | end; |
153 | end; |
154 | |
155 | function AICombine:updateTick(dt) |
156 | if self.isServer then |
157 | if self.isAIThreshing then |
158 | if self.isBroken then |
159 | self:stopAIThreshing(); |
160 | end; |
161 | |
162 | self.dtSum = self.dtSum + dt; |
163 | if self.dtSum > 50 then |
164 | AICombine.updateAIMovement(self, self.dtSum); |
165 | self.dtSum = 0; |
166 | end; |
167 | |
168 | if self.isAIThreshing then |
169 | if (self.fillLevel > 0 or self:getCapacity() <= 0) and (self.fillLevel >= self:getCapacity()*0.8 or next(self.overloadingTrailersInRange) ~= nil) then |
170 | local pipeState = self:getOverloadingTrailerInRangePipeState(); |
171 | if pipeState > 0 then |
172 | self:setPipeState(pipeState); |
173 | else |
174 | self:setPipeState(2); |
175 | end; |
176 | if next(self.overloadingTrailersInRange) ~= nil then |
177 | self.waitForDischargeTime = g_currentMission.time + self.waitForDischargeTimeout; |
178 | end; |
179 | if self.fillLevel >= self:getCapacity() and self:getCapacity() > 0 then |
180 | self.driveBackAfterDischarge = true; |
181 | self.waitingForDischarge = true; |
182 | self.waitForDischargeTime = g_currentMission.time + self.waitForDischargeTimeout; |
183 | elseif next(self.overloadingTrailersInRange) == nil then |
184 | if self:getIsThreshingAllowed(true) then |
185 | self:setIsTurnedOn(true); |
186 | self.waitingForDischarge = false; |
187 | end; |
188 | end; |
189 | else |
190 | -- no trailer in range and not full |
191 | if (self.waitingForDischarge and self.fillLevel < self:getCapacity()) or self.waitForDischargeTime <= g_currentMission.time then |
192 | self.waitingForDischarge = false; |
193 | if next(self.overloadingTrailersInRange) == nil then |
194 | -- only close the pipe if no trailer is in range |
195 | self:setPipeState(1); |
196 | end; |
197 | if self:getIsThreshingAllowed(true) then |
198 | self:setIsTurnedOn(true); |
199 | end; |
200 | end; |
201 | end; |
202 | |
203 | if self:getCapacity() > 0 and #self.beaconLights > 0 then |
204 | if self.fillLevel >= 0.8 * self:getCapacity() and self.beaconLightsActive == false then |
205 | self:setBeaconLightsVisibility(true); |
206 | elseif self.fillLevel < 0.8 * self:getCapacity() and self.beaconLightsActive == true then |
207 | self:setBeaconLightsVisibility(false); |
208 | end; |
209 | end; |
210 | |
211 | if self.aiLeftMarker == nil or self.aiRightMarker == nil then |
212 | if not self:canStartAIThreshing() then |
213 | self:stopAIThreshing(); |
214 | end; |
215 | end; |
216 | |
217 | end |
218 | else |
219 | self.dtSum = 0; |
220 | end; |
221 | self.showFieldNotOwnedWarningTimer = self.showFieldNotOwnedWarningTimer - dt; |
222 | end; |
223 | end; |
224 | |
225 | function AICombine:draw() |
226 | if g_currentMission:getHasPermission("hireAI") then |
227 | if self.isAIThreshing then |
228 | g_currentMission:addHelpButtonText(g_i18n:getText("DismissEmployee"), InputBinding.TOGGLE_AI); |
229 | else |
230 | if self:canStartAIThreshing() then |
231 | g_currentMission:addHelpButtonText(g_i18n:getText("HireEmployee"), InputBinding.TOGGLE_AI); |
232 | end; |
233 | end; |
234 | end |
235 | if self.showFieldNotOwnedWarningTimer > 0 then |
236 | g_currentMission:showBlinkingWarning(g_i18n:getText("You_dont_own_this_field")); |
237 | end; |
238 | end; |
239 | |
240 | function AICombine:startAIThreshing(noEventSend) |
241 | if noEventSend == nil or noEventSend == false then |
242 | if g_server ~= nil then |
243 | g_server:broadcastEvent(AICombineSetStartedEvent:new(self, true), nil, nil, self); |
244 | else |
245 | g_client:getServerConnection():sendEvent(AICombineSetStartedEvent:new(self, true)); |
246 | end; |
247 | end; |
248 | |
249 | self:hire(); |
250 | if not self.isAIThreshing then |
251 | g_currentMission.missionStats:updateStats("workersHired", 1); |
252 | self.isAIThreshing = true; |
253 | |
254 | local hotspotX, _, hotspotZ = getWorldTranslation(self.rootNode); |
255 | self.mapAIHotspot = g_currentMission.ingameMap:createMapHotspot("mapAIHotspot", "dataS2/menu/hud/hud_pda_spot_helper.png", hotspotX, hotspotZ, nil, nil, false, false, false, self.components[1].node); |
256 | |
257 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_ACTIVE, noEventSend); |
258 | |
259 | if self.isServer then |
260 | self.turnTimer = self.turnTimeoutLong; |
261 | self.turnStage = 0; |
262 | |
263 | self.mapAIHotspot.enabled = false; |
264 | |
265 | local dx,_,dz = localDirectionToWorld(self.aiTreshingDirectionNode, 0, 0, 1); |
266 | if g_currentMission.snapAIDirection then |
267 | local snapAngle = self:getDirectionSnapAngle(); |
268 | snapAngle = math.max(snapAngle, math.pi/(g_currentMission.terrainDetailAngleMaxValue+1)); |
269 | |
270 | local angleRad = Utils.getYRotationFromDirection(dx, dz) |
271 | angleRad = math.floor(angleRad / snapAngle + 0.5) * snapAngle; |
272 | |
273 | self.aiThreshingDirectionX, self.aiThreshingDirectionZ = Utils.getDirectionFromYRotation(angleRad); |
274 | else |
275 | local length = Utils.vector2Length(dx,dz); |
276 | self.aiThreshingDirectionX = dx/length; |
277 | self.aiThreshingDirectionZ = dz/length; |
278 | end |
279 | |
280 | local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
281 | self.aiThreshingTargetX = x; |
282 | self.aiThreshingTargetZ = z; |
283 | |
284 | self:addCollisionTrigger(self); |
285 | AIVehicleUtil.registerCollisions(self, self); |
286 | end; |
287 | |
288 | for _,implement in pairs(self.attachedImplements) do |
289 | if implement.object ~= nil then |
290 | if implement.object.attacherJoint.needsLowering and implement.object.aiNeedsLowering then |
291 | self:setJointMoveDown(implement.jointDescIndex, true, true) |
292 | end; |
293 | implement.object:aiTurnOn(); |
294 | end |
295 | end; |
296 | if self.threshingStartAnimation ~= nil and self.playAnimation ~= nil then |
297 | self:playAnimation(self.threshingStartAnimation, self.threshingStartAnimationSpeedScale, nil, true); |
298 | end |
299 | |
300 | self.waitingForDischarge = false; |
301 | self:setIsTurnedOn(true, true); |
302 | |
303 | --self.checkSpeedLimit = true; |
304 | self.waitingForWeather = false; |
305 | end; |
306 | end; |
307 | |
308 | function AICombine:stopAIThreshing(noEventSend) |
309 | if noEventSend == nil or noEventSend == false then |
310 | if g_server ~= nil then |
311 | g_server:broadcastEvent(AICombineSetStartedEvent:new(self, false), nil, nil, self); |
312 | else |
313 | g_client:getServerConnection():sendEvent(AICombineSetStartedEvent:new(self, false)); |
314 | end; |
315 | end; |
316 | self:dismiss(); |
317 | if self.isAIThreshing then |
318 | g_currentMission.missionStats:updateStats("workersHired", -1); |
319 | self.isAIThreshing = false; |
320 | |
321 | self.allowsThreshing = true; |
322 | |
323 | --self.checkSpeedLimit = false; |
324 | self.waitingForWeather = false; |
325 | |
326 | if self.mapAIHotspot ~= nil then |
327 | g_currentMission.ingameMap:deleteMapHotspot(self.mapAIHotspot); |
328 | self.mapAIHotspot = nil; |
329 | end; |
330 | |
331 | self:setIsTurnedOn(false, true); |
332 | |
333 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF, noEventSend); |
334 | |
335 | if self.isServer then |
336 | WheelsUtil.updateWheelsPhysics(self, 0, self.lastSpeedReal, 0, false, self.requiredDriveMode); |
337 | |
338 | self:removeCollisionTrigger(self); |
339 | AIVehicleUtil.unregisterCollisions(self, self); |
340 | end; |
341 | for _,implement in pairs(self.attachedImplements) do |
342 | if implement.object ~= nil then |
343 | if implement.object.attacherJoint.needsLowering and implement.object.aiNeedsLowering then |
344 | self:setJointMoveDown(implement.jointDescIndex, false, true) |
345 | end; |
346 | implement.object:aiTurnOff(); |
347 | end |
348 | end; |
349 | |
350 | self.driveBackPosX = nil; |
351 | |
352 | if self.beaconLightsActive == true then |
353 | self:setBeaconLightsVisibility(false); |
354 | end |
355 | |
356 | if not self:getIsActive() then |
357 | self:onLeave(); |
358 | end; |
359 | end; |
360 | end; |
361 | |
362 | function AICombine:onEnter(isControlling) |
363 | if self.mapAIHotspot ~= nil then |
364 | self.mapAIHotspot.enabled = false; |
365 | end; |
366 | end; |
367 | |
368 | function AICombine:onLeave() |
369 | if self.mapAIHotspot ~= nil then |
370 | self.mapAIHotspot.enabled = true; |
371 | end; |
372 | end; |
373 | |
374 | function AICombine.updateAIMovement(self, dt) |
375 | |
376 | if not self:getIsAIThreshingAllowed() then |
377 | self:stopAIThreshing(); |
378 | return; |
379 | end; |
380 | |
381 | if not self.isControlled then |
382 | if g_currentMission.environment.needsLights then |
383 | self:setLightsVisibility(true); |
384 | else |
385 | self:setLightsVisibility(false); |
386 | end; |
387 | end; |
388 | |
389 | local allowedToDrive = true; |
390 | if self:getCapacity() == 0 then |
391 | if not self.pipeStateIsUnloading[self.currentPipeState] then |
392 | allowedToDrive = false; |
393 | end |
394 | if not self.isPipeUnloading and (self.lastArea > 0 or self.lastLostFillLevel > 0) then |
395 | -- there is some fruit to unload, but there is no trailer. Stop and wait for a trailer |
396 | self.waitingForTrailerToUnload = true; |
397 | end; |
398 | else |
399 | if self.fillLevel >= self:getCapacity() then |
400 | allowedToDrive = false; |
401 | end |
402 | end |
403 | |
404 | if self.waitingForTrailerToUnload then |
405 | if self.lastValidFillType ~= Fillable.FILLTYPE_UNKNOWN then |
406 | local trailer = self:findTrailerToUnload(self.lastValidFillType); |
407 | if trailer ~= nil then |
408 | -- there is a trailer to unload. Continue working |
409 | self.waitingForTrailerToUnload = false; |
410 | end; |
411 | else |
412 | -- we did not cut anything yet. We shouldn't have ended in this state. Just continue working |
413 | self.waitingForTrailerToUnload = false; |
414 | end; |
415 | end; |
416 | |
417 | if (self.fillLevel >= self:getCapacity() and self:getCapacity() > 0) or self.waitingForTrailerToUnload or self.waitingForDischarge then |
418 | allowedToDrive = false; |
419 | if self.driveBackPosX == nil then |
420 | self.driveBackPosX, self.driveBackPosY, self.driveBackPosZ = getWorldTranslation(self.aiTreshingDirectionNode); |
421 | end |
422 | end; |
423 | for _,v in pairs(self.numCollidingVehicles) do |
424 | if v > 0 then |
425 | allowedToDrive = false; |
426 | break; |
427 | end; |
428 | end; |
429 | if self.turnStage > 0 then |
430 | if self.waitForTurnTime > g_currentMission.time or (self.pipeIsUnloading and self.turnStage < 3) then |
431 | allowedToDrive = false; |
432 | end; |
433 | end; |
434 | if not self:getIsThreshingAllowed(true) then |
435 | if self.turnStage == 0 then |
436 | allowedToDrive = false; |
437 | self:setIsTurnedOn(false); |
438 | self.waitingForWeather = true; |
439 | if self.driveBackPosX == nil then |
440 | self.driveBackPosX, self.driveBackPosY, self.driveBackPosZ = getWorldTranslation(self.aiTreshingDirectionNode); |
441 | end |
442 | end; |
443 | else |
444 | if self.waitingForWeather then |
445 | self:startThreshing(); |
446 | self.waitingForWeather = false; |
447 | end; |
448 | end; |
449 | |
450 | -- check if cutter is lowered completly |
451 | if self.driveBackPosX == nil then |
452 | |
453 | if self.turnStage == 0 or self.turnStage > 2 then |
454 | for _,implement in pairs(self.attachedImplements) do |
455 | if implement.object ~= nil then |
456 | if implement.object.attacherJoint.needsLowering and implement.object.aiNeedsLowering then |
457 | local jointDesc = self.attacherJoints[implement.jointDescIndex]; |
458 | allowedToDrive = allowedToDrive and (jointDesc.moveAlpha == jointDesc.lowerAlpha); |
459 | end; |
460 | end; |
461 | end; |
462 | end; |
463 | |
464 | end |
465 | |
466 | |
467 | if not allowedToDrive then |
468 | self.isHirableBlocked = true; |
469 | --local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
470 | --local lx, lz = 0, 1; --AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, self.aiThreshingTargetX, y, self.aiThreshingTargetZ); |
471 | --AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, false, moveForwards, lx, lz) |
472 | AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, false, moveForwards, nil, nil) |
473 | return; |
474 | elseif self:getIsTurnedOn() == false then |
475 | self:setIsTurnedOn(true); |
476 | end; |
477 | self.isHirableBlocked = false; |
478 | |
479 | local maxSpeed,_ = self:getSpeedLimit(); |
480 | maxSpeed = math.min(maxSpeed, self.cruiseControl.speed); |
481 | if self.turnStage > 0 then |
482 | --maxSpeed = math.max(8, maxSpeed / 2); |
483 | end; |
484 | |
485 | local leftMarker = self.aiLeftMarker; |
486 | local rightMarker = self.aiRightMarker; |
487 | local hasFruitPreparer = false; |
488 | local fruitType = self.lastValidInputFruitType; |
489 | if self.fruitPreparerFruitType ~= nil and self.fruitPreparerFruitType == fruitType then |
490 | hasFruitPreparer = true; |
491 | end |
492 | for cutter,implement in pairs(self.attachedCutters) do |
493 | if cutter.aiLeftMarker ~= nil and leftMarker == nil then |
494 | leftMarker = cutter.aiLeftMarker; |
495 | end; |
496 | if cutter.aiRightMarker ~= nil and rightMarker == nil then |
497 | rightMarker = cutter.aiRightMarker; |
498 | end; |
499 | if Cutter.getUseLowSpeedLimit(cutter) then |
500 | maxSpeed = maxSpeed*0.5; |
501 | end; |
502 | end; |
503 | |
504 | if leftMarker == nil or rightMarker == nil then |
505 | self:stopAIThreshing(); |
506 | return; |
507 | end; |
508 | |
509 | if self.driveBackPosX ~= nil then |
510 | local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
511 | local dx, dy, dz = worldToLocal(self.aiTreshingDirectionNode, self.driveBackPosX, self.driveBackPosY, self.driveBackPosZ); |
512 | local lx, lz = AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, self.aiThreshingTargetX, y, self.aiThreshingTargetZ); |
513 | AIVehicleUtil.driveInDirection(self, dt, 30, 1, 0.5, 28, true, false, lx, lz, maxSpeed, 1); -- dz > 0, lx, lz, maxSpeed, 1) |
514 | if dz >= 0 then |
515 | self.driveBackPosX = nil; |
516 | else |
517 | return; |
518 | end |
519 | end; |
520 | |
521 | local hasArea = true; |
522 | if self.lastArea < 1 then |
523 | local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
524 | local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; |
525 | local lInX, lInY, lInZ = getWorldTranslation(leftMarker); |
526 | local rInX, rInY, rInZ = getWorldTranslation(rightMarker); |
527 | |
528 | local heightX = lInX + dirX * self.frontAreaSize; |
529 | local heightZ = lInZ + dirZ * self.frontAreaSize; |
530 | |
531 | local area = Utils.getFruitArea(fruitType, lInX, lInZ, rInX, rInZ, heightX, heightZ, hasFruitPreparer); |
532 | if area < 1 then |
533 | hasArea = false; |
534 | end; |
535 | end; |
536 | if hasArea then |
537 | self.turnTimer = self.turnTimeout; |
538 | else |
539 | self.turnTimer = self.turnTimer - dt; |
540 | end; |
541 | |
542 | local newTargetX, newTargetY, newTargetZ; |
543 | |
544 | local moveForwards = true; |
545 | local updateWheels = true; |
546 | |
547 | if self.turnTimer < 0 or self.turnStage > 0 then |
548 | if self.turnStage > 0 then |
549 | local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
550 | local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; |
551 | local myDirX, myDirY, myDirZ = localDirectionToWorld(self.aiTreshingDirectionNode, 0, 0, 1); |
552 | |
553 | newTargetX = self.aiThreshingTargetX; |
554 | newTargetY = y; |
555 | newTargetZ = self.aiThreshingTargetZ; |
556 | if self.turnStage == 1 then |
557 | self.turnStageTimer = self.turnStageTimer - dt; |
558 | if self.lastSpeed < self.aiRescueSpeedThreshold then |
559 | self.aiRescueTimer = self.aiRescueTimer - dt; |
560 | else |
561 | self.aiRescueTimer = self.aiRescueTimeout; |
562 | end; |
563 | if myDirX*dirX + myDirZ*dirZ > self.turnStage1AngleCosThreshold or self.turnStageTimer < 0 or self.aiRescueTimer < 0 then |
564 | self.turnStage = 2; |
565 | moveForwards = false; |
566 | if self.turnStageTimer < 0 or self.aiRescueTimer < 0 then |
567 | |
568 | self.aiThreshingTargetBeforeSaveX = self.aiThreshingTargetX; |
569 | self.aiThreshingTargetBeforeSaveZ = self.aiThreshingTargetZ; |
570 | |
571 | newTargetX = self.aiThreshingTargetBeforeTurnX; |
572 | newTargetZ = self.aiThreshingTargetBeforeTurnZ; |
573 | |
574 | moveForwards = false; |
575 | self.turnStage = 4; |
576 | self.turnStageTimer = self.turnStage4Timeout; |
577 | else |
578 | self.turnStageTimer = self.turnStage2Timeout; |
579 | end; |
580 | self.aiRescueTimer = self.aiRescueTimeout; |
581 | end; |
582 | elseif self.turnStage == 2 then |
583 | self.turnStageTimer = self.turnStageTimer - dt; |
584 | if self.lastSpeed < self.aiRescueSpeedThreshold then |
585 | self.aiRescueTimer = self.aiRescueTimer - dt; |
586 | else |
587 | self.aiRescueTimer = self.aiRescueTimeout; |
588 | end; |
589 | if myDirX*dirX + myDirZ*dirZ > self.turnStage2AngleCosThreshold or self.turnStageTimer < 0 or self.aiRescueTimer < 0 then |
590 | AICombine.switchToTurnStage3(self); |
591 | else |
592 | moveForwards = false; |
593 | end; |
594 | elseif self.turnStage == 3 then |
595 | --[[if Utils.vector2Length(x-newTargetX, z-newTargetZ) < self.turnEndDistance then |
596 | self.turnTimer = self.turnTimeoutLong; |
597 | self.turnStage = 0; |
598 | --print("turning done"); |
599 | end;]] |
600 | if self.lastSpeed < self.aiRescueSpeedThreshold then |
601 | self.aiRescueTimer = self.aiRescueTimer - dt; |
602 | else |
603 | self.aiRescueTimer = self.aiRescueTimeout; |
604 | end; |
605 | local dx, dz = x-newTargetX, z-newTargetZ; |
606 | local dot = dx*dirX + dz*dirZ; |
607 | if -dot < self.turnEndDistance then |
608 | self.turnTimer = self.turnTimeoutLong; |
609 | self.turnStage = 0; |
610 | elseif self.aiRescueTimer < 0 then |
611 | self.aiThreshingTargetBeforeSaveX = self.aiThreshingTargetX; |
612 | self.aiThreshingTargetBeforeSaveZ = self.aiThreshingTargetZ; |
613 | |
614 | newTargetX = self.aiThreshingTargetBeforeTurnX; |
615 | newTargetZ = self.aiThreshingTargetBeforeTurnZ; |
616 | |
617 | moveForwards = false; |
618 | self.turnStage = 4; |
619 | self.turnStageTimer = self.turnStage4Timeout; |
620 | end; |
621 | elseif self.turnStage == 4 then |
622 | self.turnStageTimer = self.turnStageTimer - dt; |
623 | if self.lastSpeed < self.aiRescueSpeedThreshold then |
624 | self.aiRescueTimer = self.aiRescueTimer - dt; |
625 | else |
626 | self.aiRescueTimer = self.aiRescueTimeout; |
627 | end; |
628 | if self.aiRescueTimer < 0 then |
629 | self.aiRescueTimer = self.aiRescueTimeout; |
630 | local x,y,z = localDirectionToWorld(self.aiRescueNode, 0, 0, -1); |
631 | local scale = self.aiRescueForce/Utils.vector2Length(x,z); |
632 | addForce(self.aiRescueNode, x*scale, 0, z*scale, 0, 0, 0, true); |
633 | end; |
634 | if self.turnStageTimer < 0 then |
635 | self.aiRescueTimer = self.aiRescueTimeout; |
636 | self.turnStageTimer = self.turnStage1Timeout; |
637 | self.turnStage = 1; |
638 | |
639 | newTargetX = self.aiThreshingTargetBeforeSaveX; |
640 | newTargetZ = self.aiThreshingTargetBeforeSaveZ; |
641 | else |
642 | local dirX, dirZ = -dirX, -dirZ; |
643 | -- just drive along direction |
644 | local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ; |
645 | local dx, dz = x-targetX, z-targetZ; |
646 | local dot = dx*dirX + dz*dirZ; |
647 | |
648 | local projTargetX = targetX +dirX*dot; |
649 | local projTargetZ = targetZ +dirZ*dot; |
650 | |
651 | newTargetX = projTargetX-dirX*self.lookAheadDistance; |
652 | newTargetZ = projTargetZ-dirZ*self.lookAheadDistance; |
653 | moveForwards = false; |
654 | end; |
655 | end; |
656 | elseif fruitType == FruitUtil.FRUITTYPE_UNKNOWN then |
657 | self:stopAIThreshing(); |
658 | return; |
659 | else |
660 | -- turn |
661 | |
662 | local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
663 | local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; |
664 | local sideX, sideZ = -dirZ, dirX; |
665 | local lInX, lInY, lInZ = getWorldTranslation(leftMarker); |
666 | local rInX, rInY, rInZ = getWorldTranslation(rightMarker); |
667 | |
668 | local threshWidth = Utils.vector2Length(lInX-rInX, lInZ-rInZ); |
669 | local turnLeft = true; |
670 | |
671 | local lWidthX = x - sideX*0.5*threshWidth + dirX * self.sideWatchDirOffset; |
672 | local lWidthZ = z - sideZ*0.5*threshWidth + dirZ * self.sideWatchDirOffset; |
673 | local lStartX = lWidthX - sideX*0.7*threshWidth; |
674 | local lStartZ = lWidthZ - sideZ*0.7*threshWidth; |
675 | local lHeightX = lStartX + dirX*self.sideWatchDirSize; |
676 | local lHeightZ = lStartZ + dirZ*self.sideWatchDirSize; |
677 | |
678 | local rWidthX = x + sideX*0.5*threshWidth + dirX * self.sideWatchDirOffset; |
679 | local rWidthZ = z + sideZ*0.5*threshWidth + dirZ * self.sideWatchDirOffset; |
680 | local rStartX = rWidthX + sideX*0.7*threshWidth; |
681 | local rStartZ = rWidthZ + sideZ*0.7*threshWidth; |
682 | local rHeightX = rStartX + dirX*self.sideWatchDirSize; |
683 | local rHeightZ = rStartZ + dirZ*self.sideWatchDirSize; |
684 | |
685 | local leftFruit = Utils.getFruitArea(fruitType, lStartX, lStartZ, lWidthX, lWidthZ, lHeightX, lHeightZ, hasFruitPreparer); |
686 | local rightFruit = Utils.getFruitArea(fruitType, rStartX, rStartZ, rWidthX, rWidthZ, rHeightX, rHeightZ, hasFruitPreparer); |
687 | -- turn to where more fruit is to cut |
688 | if leftFruit > 0 or rightFruit > 0 then |
689 | if leftFruit > rightFruit then |
690 | turnLeft = true; |
691 | else |
692 | turnLeft = false; |
693 | end |
694 | else |
695 | self:stopAIThreshing(); |
696 | return; |
697 | end; |
698 | local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ; |
699 | --local dx, dz = x-targetX, z-targetZ; |
700 | --local dot = dx*dirX + dz*dirZ; |
701 | --local x, z = targetX + dirX*dot, targetZ + dirZ*dot; |
702 | --threshWidth = threshWidth*self.aiTurnThreshWidthScale; |
703 | |
704 | |
705 | |
706 | local markerSideOffset; |
707 | if turnLeft then |
708 | markerSideOffset, _, _ = worldToLocal(self.aiTreshingDirectionNode, lInX, lInY, lInZ); |
709 | else |
710 | markerSideOffset, _, _ = worldToLocal(self.aiTreshingDirectionNode, rInX, rInY, rInZ); |
711 | end |
712 | markerSideOffset = 2*markerSideOffset; |
713 | |
714 | local areaOverlap = math.min(threshWidth*(1-self.aiTurnThreshWidthScale), self.aiTurnThreshWidthMaxDifference); |
715 | if markerSideOffset > 0 then |
716 | markerSideOffset = math.max(markerSideOffset - areaOverlap, 0.01); |
717 | else |
718 | markerSideOffset = math.min(markerSideOffset + areaOverlap, -0.01); |
719 | end |
720 | |
721 | local x,z = Utils.projectOnLine(x, z, targetX, targetZ, dirX, dirZ) |
722 | newTargetX = x-sideX*markerSideOffset; |
723 | newTargetY = y; |
724 | newTargetZ = z-sideZ*markerSideOffset; |
725 | |
726 | self.aiThreshingDirectionX = -dirX; |
727 | self.aiThreshingDirectionZ = -dirZ; |
728 | self.turnStage = 1; |
729 | self.aiRescueTimer = self.aiRescueTimeout; |
730 | self.turnStageTimer = self.turnStage1Timeout; |
731 | |
732 | self.aiThreshingTargetBeforeTurnX = self.aiThreshingTargetX; |
733 | self.aiThreshingTargetBeforeTurnZ = self.aiThreshingTargetZ; |
734 | |
735 | self.waitForTurnTime = g_currentMission.time + math.max(self.waitForTurnTimeout, self.strawToggleTime); |
736 | self:setAIImplementsMoveDown(false); |
737 | -- do not thresh while turning |
738 | self.allowsThreshing = false; |
739 | updateWheels = false; |
740 | if turnLeft then |
741 | --print("turning left ", threshWidth); |
742 | else |
743 | --print("turning right ", threshWidth); |
744 | end; |
745 | end; |
746 | else |
747 | local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); |
748 | local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; |
749 | -- just drive along direction |
750 | local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ; |
751 | local dx, dz = x-targetX, z-targetZ; |
752 | local dot = dx*dirX + dz*dirZ; |
753 | |
754 | local projTargetX = targetX +dirX*dot; |
755 | local projTargetZ = targetZ +dirZ*dot; |
756 | |
757 | --print("old target: "..targetX.." ".. targetZ .. " distOnDir " .. dot.." proj: "..projTargetX.." "..projTargetZ); |
758 | |
759 | newTargetX = projTargetX+self.aiThreshingDirectionX*self.lookAheadDistance; |
760 | newTargetY = y; |
761 | newTargetZ = projTargetZ+self.aiThreshingDirectionZ*self.lookAheadDistance; |
762 | --print(distOnDir.." target: "..newTargetX.." ".. newTargetZ); |
763 | |
764 | if not g_currentMission:getIsFieldOwnedAtWorldPos(x,z) then |
765 | self.showFieldNotOwnedWarningTimer = 1000; |
766 | --print("Stopping AICombine because field is not owned"); |
767 | self:stopAIThreshing(); |
768 | return; |
769 | end |
770 | end; |
771 | |
772 | if updateWheels then |
773 | local lx, lz = AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, newTargetX, newTargetY, newTargetZ); |
774 | |
775 | if self.turnStage == 2 and math.abs(lx) < 0.1 then |
776 | AICombine.switchToTurnStage3(self); |
777 | moveForwards = true; |
778 | end; |
779 | |
780 | AIVehicleUtil.driveInDirection(self, dt, 30, 0.5, 0.5, 20, true, moveForwards, lx, lz, maxSpeed, 0.9); |
781 | |
782 | --local maxAngle = 0.785398163; --45°; |
783 | local maxlx = 0.7071067; --math.sin(maxAngle); |
784 | local colDirX = lx; |
785 | local colDirZ = lz; |
786 | |
787 | if colDirX > maxlx then |
788 | colDirX = maxlx; |
789 | colDirZ = 0.7071067; --math.cos(maxAngle); |
790 | elseif colDirX < -maxlx then |
791 | colDirX = -maxlx; |
792 | colDirZ = 0.7071067; --math.cos(maxAngle); |
793 | end; |
794 | |
795 | for triggerId,_ in pairs(self.numCollidingVehicles) do |
796 | AIVehicleUtil.setCollisionDirection(self.aiTreshingDirectionNode, triggerId, colDirX, colDirZ); |
797 | end; |
798 | end; |
799 | |
800 | self.aiThreshingTargetX = newTargetX; |
801 | self.aiThreshingTargetZ = newTargetZ; |
802 | end; |
803 | |
804 | function AICombine.switchToDirection(self, myDirX, myDirZ) |
805 | self.aiThreshingDirectionX = myDirX; |
806 | self.aiThreshingDirectionZ = myDirZ; |
807 | --print("switch to direction"); |
808 | end; |
809 | |
810 | function AICombine:setAIImplementsMoveDown(moveDown) |
811 | if self.isServer then |
812 | g_server:broadcastEvent(AISetImplementsMoveDownEvent:new(self, moveDown), nil, nil, self); |
813 | end; |
814 | if moveDown then |
815 | -- same as Foldable.aiLower |
816 | if self.foldMiddleAnimTime ~= nil and self.foldMiddleAIRaiseDirection ~= 0 then |
817 | self:setFoldState(-self.foldMiddleAIRaiseDirection, false, true); |
818 | end |
819 | else |
820 | -- same as Foldable.aiRaise |
821 | if self.foldMiddleAnimTime ~= nil and self.foldMiddleAIRaiseDirection ~= 0 then |
822 | if self.foldMiddleAIRaiseDirection > 0 then |
823 | if self.foldAnimTime > self.foldMiddleAnimTime then |
824 | self:setFoldState(self.foldMiddleAIRaiseDirection, false, true); |
825 | else |
826 | self:setFoldState(self.foldMiddleAIRaiseDirection, true, true); |
827 | end; |
828 | else |
829 | if self.foldAnimTime < self.foldMiddleAnimTime then |
830 | self:setFoldState(self.foldMiddleAIRaiseDirection, false, true); |
831 | else |
832 | self:setFoldState(self.foldMiddleAIRaiseDirection, true, true); |
833 | end; |
834 | end; |
835 | end |
836 | end; |
837 | for _,implement in pairs(self.attachedImplements) do |
838 | if implement.object ~= nil then |
839 | if implement.object.attacherJoint.needsLowering and implement.object.aiNeedsLowering then |
840 | self:setJointMoveDown(implement.jointDescIndex, moveDown, true); |
841 | end; |
842 | if moveDown then |
843 | implement.object:aiLower(); |
844 | else |
845 | implement.object:aiRaise(); |
846 | end |
847 | end |
848 | end; |
849 | |
850 | if self.threshingStartAnimation ~= nil and self.playAnimation ~= nil then |
851 | if moveDown then |
852 | self:playAnimation(self.threshingStartAnimation, self.threshingStartAnimationSpeedScale, nil, true); |
853 | else |
854 | self:playAnimation(self.threshingStartAnimation, -self.threshingStartAnimationSpeedScale, nil, true); |
855 | end |
856 | end |
857 | end; |
858 | |
859 | function AICombine:addCollisionTrigger(object) |
860 | if self.isServer then |
861 | if object.aiTrafficCollisionTrigger ~= nil then |
862 | addTrigger(object.aiTrafficCollisionTrigger, "onTrafficCollisionTrigger", self); |
863 | self.numCollidingVehicles[object.aiTrafficCollisionTrigger] = 0; |
864 | end |
865 | if object ~= self then |
866 | for _,v in pairs(object.components) do |
867 | self.trafficCollisionIgnoreList[v.node] = true; |
868 | end |
869 | end |
870 | end |
871 | end |
872 | |
873 | function AICombine:removeCollisionTrigger(object) |
874 | if self.isServer then |
875 | if object.aiTrafficCollisionTrigger ~= nil then |
876 | removeTrigger(object.aiTrafficCollisionTrigger); |
877 | self.numCollidingVehicles[object.aiTrafficCollisionTrigger] = nil; |
878 | end |
879 | if object ~= self then |
880 | for _,v in pairs(object.components) do |
881 | self.trafficCollisionIgnoreList[v.node] = nil; |
882 | end |
883 | end |
884 | end |
885 | end |
886 | |
887 | |
888 | function AICombine:attachImplement(implement) |
889 | local object = implement.object; |
890 | if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER or object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTERHARVESTER then |
891 | -- |
892 | elseif object.attacherJoint.jointType == Vehicle.JOINTTYPE_TRAILER or object.attacherJoint.jointType == Vehicle.JOINTTYPE_TRAILERLOW then |
893 | self.numAttachedTrailers = self.numAttachedTrailers+1; |
894 | end; |
895 | if self.isServer then |
896 | self:removeCollisionTrigger(object); |
897 | AIVehicleUtil.unregisterCollisions(self, self); |
898 | self:addCollisionTrigger(self); |
899 | AIVehicleUtil.registerCollisions(self, self); |
900 | end; |
901 | end; |
902 | |
903 | function AICombine:detachImplement(implementIndex) |
904 | local object = self.attachedImplements[implementIndex].object; |
905 | if object ~= nil then |
906 | if self.aiLeftMarker == object.aiLeftMarker then |
907 | self.aiLeftMarker = nil; |
908 | self.aiRightMarker = nil; |
909 | end; |
910 | if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER or object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTERHARVESTER then |
911 | -- |
912 | elseif object.attacherJoint.jointType == Vehicle.JOINTTYPE_TRAILER or object.attacherJoint.jointType == Vehicle.JOINTTYPE_TRAILERLOW then |
913 | self.numAttachedTrailers = self.numAttachedTrailers-1; |
914 | end; |
915 | end |
916 | if self.isServer then |
917 | self:removeCollisionTrigger(object); |
918 | AIVehicleUtil.unregisterCollisions(self, object); |
919 | end; |
920 | end; |
921 | |
922 | function AICombine:onTrafficCollisionTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId) |
923 | if onEnter or onLeave then |
924 | if g_currentMission.players[otherId] ~= nil then |
925 | if onEnter then |
926 | self.numCollidingVehicles[triggerId] = self.numCollidingVehicles[triggerId]+1; |
927 | elseif onLeave then |
928 | self.numCollidingVehicles[triggerId] = math.max(self.numCollidingVehicles[triggerId]-1, 0); |
929 | end; |
930 | else |
931 | local vehicle = g_currentMission.nodeToVehicle[otherId]; |
932 | if vehicle ~= nil and self.trafficCollisionIgnoreList[otherId] == nil then |
933 | if onEnter then |
934 | self.numCollidingVehicles[triggerId] = self.numCollidingVehicles[triggerId]+1; |
935 | elseif onLeave then |
936 | self.numCollidingVehicles[triggerId] = math.max(self.numCollidingVehicles[triggerId]-1, 0); |
937 | end; |
938 | end; |
939 | end; |
940 | end; |
941 | end; |
942 | |
943 | function AICombine.switchToTurnStage3(self) |
944 | self.turnStage = 3; |
945 | self:setAIImplementsMoveDown(true); |
946 | self.allowsThreshing = true; |
947 | self.aiRescueTimer = self.aiRescueTimeout; |
948 | end; |
949 | |
950 | function AICombine:canStartAIThreshing() |
951 | if g_currentMission.disableCombineAI then |
952 | return false; |
953 | end; |
954 | if not self:getIsTurnedOnAllowed(true) then |
955 | return false; |
956 | end |
957 | if self.numAttachedTrailers > 0 then |
958 | return false; |
959 | end; |
960 | if Hirable.numHirablesHired >= g_currentMission.maxNumHirables then |
961 | return false; |
962 | end; |
963 | if self.aiLeftMarker == nil or self.aiRightMarker == nil then |
964 | for cutter,implement in pairs(self.attachedCutters) do |
965 | if cutter.aiLeftMarker ~= nil and self.aiLeftMarker == nil then |
966 | self.aiLeftMarker = cutter.aiLeftMarker; |
967 | end; |
968 | if cutter.aiRightMarker ~= nil and self.aiRightMarker == nil then |
969 | self.aiRightMarker = cutter.aiRightMarker; |
970 | end; |
971 | end; |
972 | if self.aiLeftMarker == nil or self.aiRightMarker == nil then |
973 | return false; |
974 | end; |
975 | end; |
976 | return true; |
977 | end; |
978 | |
979 | function AICombine:getIsAIThreshingAllowed() |
980 | if g_currentMission.disableCombineAI then |
981 | return false; |
982 | end; |
983 | if not self:getIsTurnedOnAllowed(true) then |
984 | return false; |
985 | end |
986 | if self.numAttachedTrailers > 0 then |
987 | return false; |
988 | end; |
989 | return true; |
990 | end;
|
Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de