-- -- AILexion600Combine -- Specialization for ai combines -- -- @author Stefan Geiger -- @date 10/01/09 -- -- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved. AILexion600Combine = {}; function AILexion600Combine.prerequisitesPresent(specializations) return SpecializationUtil.hasSpecialization(Hirable, specializations) and SpecializationUtil.hasSpecialization(Lexion600Combine2, specializations); end; function AILexion600Combine:load(xmlFile) self.startAIThreshing = SpecializationUtil.callSpecializationsFunction("startAIThreshing"); self.stopAIThreshing = SpecializationUtil.callSpecializationsFunction("stopAIThreshing"); self.onTrafficCollisionTrigger = AILexion600Combine.onTrafficCollisionTrigger; self.onCutterTrafficCollisionTrigger = AILexion600Combine.onCutterTrafficCollisionTrigger; self.onTrailerTrigger = AILexion600Combine.onTrailerTrigger; self.isAIThreshing = false; self.aiTreshingDirectionNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTreshingDirectionNode#index")); if self.aiTreshingDirectionNode == nil then self.aiTreshingDirectionNode = self.components[1].node; end; self.lookAheadDistance = 10; self.turnTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnTimeout"), 200); self.turnTimeoutLong = self.turnTimeout*10; self.turnTimer = self.turnTimeout; self.turnEndDistance = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnEndDistance"), 4); self.waitForTurnTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waitForTurnTime"), 1500); self.waitForTurnTime = 0; self.sideWatchDirOffset = -8; self.sideWatchDirSize = 8; self.frontAreaSize = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.frontAreaSize#value"), 1.5); self.waitingForDischarge = false; self.waitForDischargeTime = 0; self.waitForDischargeTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.waitForDischargeTime"), 8000); self.turnStage = 0; self.aiTrafficCollisionTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTrafficCollisionTrigger#index")); self.aiTrailerTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTrailerTrigger#index")); self.aiTurnThreshWidthScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTurnThreshWidthScale#value"), 0.9); self.isTrailerInRange = false; self.trafficCollisionIgnoreList = {}; for k,v in pairs(self.components) do self.trafficCollisionIgnoreList[v.node] = true; end; self.numCollidingVehicles = 0; self.numCutterCollidingVehicles = {}; self.driveBackTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.driveBackTimeout"), 800); self.driveBackTime = 0; self.driveBackAfterDischarge = false; self.aiLastGrainTankFruitType = self.currentGrainTankFruitType; self.dtSum = 0; local aiMotorSound = getXMLString(xmlFile, "vehicle.aiMotorSound#file"); if aiMotorSound ~= nil and aiMotorSound ~= "" then aiMotorSound = Utils.getFilename(aiMotorSound, self.baseDirectory); self.aiMotorSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#pitchOffset"), 0); self.aiMotorSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#radius"), 50); self.aiMotorSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#innerRadius"), 10); self.aiMotorSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiMotorSound#volume"), 1); self.aiMotorSound = createAudioSource("aiMotorSound", aiMotorSound, self.aiMotorSoundRadius, self.aiMotorSoundInnerRadius, self.aiMotorSoundVolume, 0); link(self.components[1].node, self.aiMotorSound); setVisibility(self.aiMotorSound, false); end; local aiThreshingSound = getXMLString(xmlFile, "vehicle.aiTreshingSound#file"); if aiThreshingSound ~= nil and aiThreshingSound ~= "" then aiThreshingSound = Utils.getFilename(aiThreshingSound, self.baseDirectory); self.aiThreshingSoundPitchOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#pitchOffset"), 0); self.aiThreshingSoundRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#radius"), 50); self.aiThreshingSoundInnerRadius = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#innerRadius"), 10); self.aiThreshingSoundVolume = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiTreshingSound#volume"), 1); self.aiThreshingSound = createAudioSource("aiThreshingSound", aiThreshingSound, self.aiThreshingSoundRadius, self.aiThreshingSoundInnerRadius, self.aiThreshingSoundVolume, 0); link(self.components[1].node, self.aiThreshingSound); setVisibility(self.aiThreshingSound, false); end; self.turnStage1Timeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnForwardTimeout"), 20000); self.turnStage2Timeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.turnBackwardTimeout"), 20000); self.turnStage4Timeout = 3000; self.waitingForWeather = false; self.aiRescueTimeout = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#timeout"), 1500); self.aiRescueTimer = self.aiRescueTimeout; self.aiRescueForce = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#force"), 60); self.aiRescueSpeedThreshold = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiRescue#speedThreshold"), 0.0001); self.aiRescueNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiRescue#index")); if self.aiRescueNode == nil then self.aiRescueNode = self.components[1].node; end; --self.debugDirection = loadI3DFile("data/debugDirection.i3d"); --link(self.aiTreshingDirectionNode, self.debugDirection); --self.debugPosition = loadI3DFile("data/debugPosition.i3d"); --link(self.aiTreshingDirectionNode, self.debugPosition); end; function AILexion600Combine:delete() --self:stopAIThreshing(); if self.aiTrailerTrigger ~= nil then removeTrigger(self.aiTrailerTrigger); end; if self.aiTrafficCollisionTrigger ~= nil then removeTrigger(self.aiTrafficCollisionTrigger); end; for cutter,implement in pairs(self.attachedCutters) do AILexion600Combine.removeCutterTrigger(self, cutter); end; end; function AILexion600Combine:mouseEvent(posX, posY, isDown, isUp, button) end; function AILexion600Combine:keyEvent(unicode, sym, modifier, isDown) end; function AILexion600Combine:update(dt) if self:getIsActiveForInput() then if not g_currentMission.disableCombineAI and g_currentMission.allowSteerableMoving then if self.treshingMaschineActive or self.waitingForWeather then if InputBinding.hasEvent(InputBinding.TOGGLE_AI) then if self.isAIThreshing then self:stopAIThreshing(); else self:startAIThreshing(); end; end; end; end; end; if self.isAIThreshing then if self.isBroken then self:stopAIThreshing(); end; --if not self.treshingMaschineActive then -- self:stopAIThreshing(); --end; if self.transportModeActive then self:stopAIThreshing(); end; self.dtSum = self.dtSum + dt; if self.dtSum > 20 then AILexion600Combine.updateAIMovement(self, self.dtSum); self.dtSum = 0; end; if self.grainTankFillLevel > 0 and (self.grainTankFillLevel >= self.grainTankCapacity*0.8 or self.isTrailerInRange) then self:openPipe(); if self.isTrailerInRange then self.waitForDischargeTime = self.time + self.waitForDischargeTimeout; end; if self.grainTankFillLevel >= self.grainTankCapacity then self.driveBackAfterDischarge = true; self.waitingForDischarge = true; self.waitForDischargeTime = self.time + self.waitForDischargeTimeout; end; else -- no trailer in range and not full if (self.waitingForDischarge and self.grainTankFillLevel <= 0) or self.waitForDischargeTime <= self.time then self.waitingForDischarge = false; if self.driveBackAfterDischarge then self.driveBackTime = self.time + self.driveBackTimeout; self.driveBackAfterDischarge = false; end; self:closePipe(); if Cutter.allowThreshing(true) then self.cutterActive = true; end; end; end; self.isTrailerInRange = false; else self.dtSum = 0; end; end; function AILexion600Combine:draw() if not g_currentMission.disableCombineAI then if self.numAttachedCutters > 0 then if self.treshingMaschineActive then if self.isAIThreshing then g_currentMission:addHelpButtonText(g_i18n:getText("DismissEmployee"), InputBinding.TOGGLE_AI); else g_currentMission:addHelpButtonText(g_i18n:getText("HireEmployee"), InputBinding.TOGGLE_AI); end; elseif self.waitingForWeather then if self.isAIThreshing then g_currentMission:addHelpButtonText(g_i18n:getText("DismissEmployee"), InputBinding.TOGGLE_AI); end; end; end; end; end; function AILexion600Combine:startAIThreshing() self:hire(); if not self.isAIThreshing then if self.transportModeActive then self.transportModeActive = false; end; self.isAIThreshing = true; self.turnTimer = self.turnTimeoutLong; self.turnStage = 0; local x,y,z = localDirectionToWorld(self.aiTreshingDirectionNode, 0, 0, 1); local length = Utils.vector2Length(x,z); self.aiThreshingDirectionX = x/length; self.aiThreshingDirectionZ = z/length; local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); self.aiThreshingTargetX = x; self.aiThreshingTargetZ = z; self.speedDisplayScale = 0.9; self.gasMode = 3; self.waitingForDischarge = false; if not self.isThreshing then self:startThreshing(); self.cutterActive = true; else self.cutterActive = true; end; if self.aiTrailerTrigger ~= nil then addTrigger(self.aiTrailerTrigger, "onTrailerTrigger", self); end; self.numCollidingVehicles = 0; if self.aiTrafficCollisionTrigger ~= nil then addTrigger(self.aiTrafficCollisionTrigger, "onTrafficCollisionTrigger", self); end; for cutter,implement in pairs(self.attachedCutters) do AILexion600Combine.addCutterTrigger(self, cutter); end; self.isTrailerInRange = false; self.checkSpeedLimit = false; setVisibility(self.aiMotorSound, true); setVisibility(self.aiThreshingSound, true); self.waitingForWeather = false; end; end; function AILexion600Combine:stopAIThreshing() self:dismiss(); if self.isAIThreshing then --restore allowsThreshing flag self.allowsThreshing = true; self.speedDisplayScale = 0.5; if self.isThreshing then self.cutterActive = false; end; self.motor:setSpeedLevel(0, false); self.motor.maxRpmOverride = nil; self.gasMode = 1; WheelsUtil.updateWheelsPhysics(self, 0, self.lastSpeed, 0, false, self.requiredDriveMode); if self.aiTrailerTrigger ~= nil then removeTrigger(self.aiTrailerTrigger); end; if self.aiTrafficCollisionTrigger ~= nil then removeTrigger(self.aiTrafficCollisionTrigger); end; for cutter,implement in pairs(self.attachedCutters) do AILexion600Combine.removeCutterTrigger(self, cutter); end; self.isAIThreshing = false; if not self:getIsActive() then Lexion600Combine.onDeactivate(self); end; self.checkSpeedLimit = true; setVisibility(self.aiMotorSound, false); setVisibility(self.aiThreshingSound, false); self.waitingForWeather = false; end; end; function AILexion600Combine.updateAIMovement(self, dt) local allowedToDrive = true; if self.grainTankFillLevel >= self.grainTankCapacity or self.waitingForDischarge or self.numCollidingVehicles > 0 then allowedToDrive = false; end; for k,v in pairs(self.numCutterCollidingVehicles) do if v > 0 then allowedToDrive = false; break; end; end; if self.turnStage > 0 then if self.waitForTurnTime > self.time or self.lastUnloadingTrailer ~= nil then allowedToDrive = false; end; end; if not Cutter.allowThreshing(true) then allowedToDrive = false; self.cutterActive = false; self.gasMode = 1; if not self.cutterActive then self:stopThreshing(); end; self.waitingForWeather = true; else if self.waitingForWeather then if self.turnStage == 0 then self.driveBackTime = self.time + self.driveBackTimeout; end; self:startThreshing(); self.cutterActive = true; self.waitingForWeather = false; self.gasMode = 3; end; end; if not allowedToDrive then --local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); local lx, lz = 0, 1; --AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, self.aiThreshingTargetX, y, self.aiThreshingTargetZ); AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, false, moveForwards, lx, lz) return; end; local speedLevel = 1; local leftMarker = nil; local rightMarker = nil; local fruitType = self.aiLastGrainTankFruitType; for cutter,implement in pairs(self.attachedCutters) do if cutter.aiLeftMarker ~= nil then if leftMarker == nil then leftMarker = cutter.aiLeftMarker; end; end; if cutter.aiRightMarker ~= nil then if rightMarker == nil then rightMarker = cutter.aiRightMarker; end; end; if Cutter.getUseLowSpeedLimit(cutter) then speedLevel = 1; end; end; if leftMarker == nil or rightMarker == nil then self:stopAIThreshing(); return; end; if self.driveBackTime >= self.time then local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); local lx, lz = AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, self.aiThreshingTargetX, y, self.aiThreshingTargetZ); AIVehicleUtil.driveInDirection(self, dt, 30, 0, 0, 28, true, false, lx, lz, speedLevel, 1) return; end; local hasArea = true; if self.lastArea < 1 then local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; local lInX, lInY, lInZ = getWorldTranslation(leftMarker); local rInX, rInY, rInZ = getWorldTranslation(rightMarker); local heightX = lInX + dirX * self.frontAreaSize; local heightZ = lInZ + dirZ * self.frontAreaSize; local area = Utils.getFruitArea(fruitType, lInX, lInZ, rInX, rInZ, heightX, heightZ); if area < 1 then hasArea = false; end; end; if hasArea then self.turnTimer = self.turnTimeout; else self.turnTimer = self.turnTimer - dt; end; local newTargetX, newTargetY, newTargetZ; local moveForwards = true; local updateWheels = true; if self.turnTimer < 0 or self.turnStage > 0 then if self.turnStage > 0 then local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; local myDirX, myDirY, myDirZ = localDirectionToWorld(self.aiTreshingDirectionNode, 0, 0, 1); newTargetX = self.aiThreshingTargetX; newTargetY = y; newTargetZ = self.aiThreshingTargetZ; if self.turnStage == 1 then self.turnStageTimer = self.turnStageTimer - dt; if self.lastSpeed < self.aiRescueSpeedThreshold then self.aiRescueTimer = self.aiRescueTimer - dt; else self.aiRescueTimer = self.aiRescueTimeout; end; if myDirX*dirX + myDirZ*dirZ > 0.25 or self.turnStageTimer < 0 or self.aiRescueTimer < 0 then self.turnStage = 2; moveForwards = false; if self.turnStageTimer < 0 or self.aiRescueTimer < 0 then self.aiThreshingTargetBeforeSaveX = self.aiThreshingTargetX; self.aiThreshingTargetBeforeSaveZ = self.aiThreshingTargetZ; newTargetX = self.aiThreshingTargetBeforeTurnX; newTargetZ = self.aiThreshingTargetBeforeTurnZ; moveForwards = false; self.turnStage = 4; self.turnStageTimer = self.turnStage4Timeout; else self.turnStageTimer = self.turnStage2Timeout; end; self.aiRescueTimer = self.aiRescueTimeout; end; elseif self.turnStage == 2 then self.turnStageTimer = self.turnStageTimer - dt; if self.lastSpeed < self.aiRescueSpeedThreshold then self.aiRescueTimer = self.aiRescueTimer - dt; else self.aiRescueTimer = self.aiRescueTimeout; end; if myDirX*dirX + myDirZ*dirZ > 0.85 or self.turnStageTimer < 0 or self.aiRescueTimer < 0 then AILexion600Combine.switchToTurnStage3(self); else moveForwards = false; end; elseif self.turnStage == 3 then --[[if Utils.vector2Length(x-newTargetX, z-newTargetZ) < self.turnEndDistance then self.turnTimer = self.turnTimeoutLong; self.turnStage = 0; --print("turning done"); end;]] if self.lastSpeed < self.aiRescueSpeedThreshold then self.aiRescueTimer = self.aiRescueTimer - dt; else self.aiRescueTimer = self.aiRescueTimeout; end; local dx, dz = x-newTargetX, z-newTargetZ; local dot = dx*dirX + dz*dirZ; if -dot < self.turnEndDistance then self.turnTimer = self.turnTimeoutLong; self.turnStage = 0; elseif self.aiRescueTimer < 0 then self.aiThreshingTargetBeforeSaveX = self.aiThreshingTargetX; self.aiThreshingTargetBeforeSaveZ = self.aiThreshingTargetZ; newTargetX = self.aiThreshingTargetBeforeTurnX; newTargetZ = self.aiThreshingTargetBeforeTurnZ; moveForwards = false; self.turnStage = 4; self.turnStageTimer = self.turnStage4Timeout; end; elseif self.turnStage == 4 then self.turnStageTimer = self.turnStageTimer - dt; if self.lastSpeed < self.aiRescueSpeedThreshold then self.aiRescueTimer = self.aiRescueTimer - dt; else self.aiRescueTimer = self.aiRescueTimeout; end; if self.aiRescueTimer < 0 then self.aiRescueTimer = self.aiRescueTimeout; local x,y,z = localDirectionToWorld(self.aiRescueNode, 0, 0, -1); local scale = self.aiRescueForce/Utils.vector2Length(x,z); addForce(self.aiRescueNode, x*scale, 0, z*scale, 0, 0, 0, true); end; if self.turnStageTimer < 0 then self.aiRescueTimer = self.aiRescueTimeout; self.turnStageTimer = self.turnStage1Timeout; self.turnStage = 1; newTargetX = self.aiThreshingTargetBeforeSaveX; newTargetZ = self.aiThreshingTargetBeforeSaveZ; else local dirX, dirZ = -dirX, -dirZ; -- just drive along direction local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ; local dx, dz = x-targetX, z-targetZ; local dot = dx*dirX + dz*dirZ; local projTargetX = targetX +dirX*dot; local projTargetZ = targetZ +dirZ*dot; newTargetX = projTargetX-dirX*self.lookAheadDistance; newTargetZ = projTargetZ-dirZ*self.lookAheadDistance; moveForwards = false; end; end; elseif fruitType == FruitUtil.FRUITTYPE_UNKNOWN then self:stopAIThreshing(); return; else -- turn local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; local sideX, sideZ = -dirZ, dirX; local lInX, lInY, lInZ = getWorldTranslation(leftMarker); local rInX, rInY, rInZ = getWorldTranslation(rightMarker); local threshWidth = Utils.vector2Length(lInX-rInX, lInZ-rInZ); local turnLeft = true; local lWidthX = x - sideX*0.5*threshWidth + dirX * self.sideWatchDirOffset; local lWidthZ = z - sideZ*0.5*threshWidth + dirZ * self.sideWatchDirOffset; local lStartX = lWidthX - sideX*0.7*threshWidth; local lStartZ = lWidthZ - sideZ*0.7*threshWidth; local lHeightX = lStartX + dirX*self.sideWatchDirSize; local lHeightZ = lStartZ + dirZ*self.sideWatchDirSize; local rWidthX = x + sideX*0.5*threshWidth + dirX * self.sideWatchDirOffset; local rWidthZ = z + sideZ*0.5*threshWidth + dirZ * self.sideWatchDirOffset; local rStartX = rWidthX + sideX*0.7*threshWidth; local rStartZ = rWidthZ + sideZ*0.7*threshWidth; local rHeightX = rStartX + dirX*self.sideWatchDirSize; local rHeightZ = rStartZ + dirZ*self.sideWatchDirSize; local leftFruit = Utils.getFruitArea(fruitType, lStartX, lStartZ, lWidthX, lWidthZ, lHeightX, lHeightZ); local rightFruit = Utils.getFruitArea(fruitType, rStartX, rStartZ, rWidthX, rWidthZ, rHeightX, rHeightZ); -- turn to where more fruit is to cut if leftFruit > 0 or rightFruit > 0 then if leftFruit > rightFruit then turnLeft = true; else turnLeft = false; end else self:stopAIThreshing(); return; end; local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ; --local dx, dz = x-targetX, z-targetZ; --local dot = dx*dirX + dz*dirZ; --local x, z = targetX + dirX*dot, targetZ + dirZ*dot; threshWidth = threshWidth*self.aiTurnThreshWidthScale; local x,z = Utils.projectOnLine(x, z, targetX, targetZ, dirX, dirZ) if turnLeft then newTargetX = x-sideX*threshWidth; newTargetY = y; newTargetZ = z-sideZ*threshWidth; else newTargetX = x+sideX*threshWidth; newTargetY = y; newTargetZ = z+sideZ*threshWidth; end; self.aiThreshingDirectionX = -dirX; self.aiThreshingDirectionZ = -dirZ; self.turnStage = 1; self.aiRescueTimer = self.aiRescueTimeout; self.turnStageTimer = self.turnStage1Timeout; self.aiThreshingTargetBeforeTurnX = self.aiThreshingTargetX; self.aiThreshingTargetBeforeTurnZ = self.aiThreshingTargetZ; self.waitForTurnTime = self.time + self.waitForTurnTimeout; for cutter,implement in pairs(self.attachedCutters) do local jointDesc = self.attacherJoints[implement.jointDescIndex]; jointDesc.moveDown = false; end; -- do not thresh while turning self.allowsThreshing = false; updateWheels = false; if turnLeft then --print("turning left ", threshWidth); else --print("turning right ", threshWidth); end; end; else local x,y,z = getWorldTranslation(self.aiTreshingDirectionNode); local dirX, dirZ = self.aiThreshingDirectionX, self.aiThreshingDirectionZ; -- just drive along direction local targetX, targetZ = self.aiThreshingTargetX, self.aiThreshingTargetZ; local dx, dz = x-targetX, z-targetZ; local dot = dx*dirX + dz*dirZ; local projTargetX = targetX +dirX*dot; local projTargetZ = targetZ +dirZ*dot; --print("old target: "..targetX.." ".. targetZ .. " distOnDir " .. dot.." proj: "..projTargetX.." "..projTargetZ); newTargetX = projTargetX+self.aiThreshingDirectionX*self.lookAheadDistance; newTargetY = y; newTargetZ = projTargetZ+self.aiThreshingDirectionZ*self.lookAheadDistance; --print(distOnDir.." target: "..newTargetX.." ".. newTargetZ); end; if updateWheels then local lx, lz = AIVehicleUtil.getDriveDirection(self.aiTreshingDirectionNode, newTargetX, newTargetY, newTargetZ); if self.turnStage == 2 and math.abs(lx) < 0.1 then AILexion600Combine.switchToTurnStage3(self); moveForwards = true; end; AIVehicleUtil.driveInDirection(self, dt, 25, 0.5, 0.5, 20, true, moveForwards, lx, lz, speedLevel, 0.9); --local maxAngle = 0.785398163; --45°; local maxlx = 0.7071067; --math.sin(maxAngle); local colDirX = lx; local colDirZ = lz; if colDirX > maxlx then colDirX = maxlx; colDirZ = 0.7071067; --math.cos(maxAngle); elseif colDirX < -maxlx then colDirX = -maxlx; colDirZ = 0.7071067; --math.cos(maxAngle); end; if self.aiTrafficCollisionTrigger ~= nil then AIVehicleUtil.setCollisionDirection(self.aiTreshingDirectionNode, self.aiTrafficCollisionTrigger, colDirX, colDirZ); end; for k,v in pairs(self.numCutterCollidingVehicles) do AIVehicleUtil.setCollisionDirection(self.aiTreshingDirectionNode, k, colDirX, colDirZ); end; end; self.aiThreshingTargetX = newTargetX; self.aiThreshingTargetZ = newTargetZ; if self.grainTankFillLevel > 0 then self.aiLastGrainTankFruitType = self.currentGrainTankFruitType; end; end; function AILexion600Combine.switchToDirection(self, myDirX, myDirZ) self.aiThreshingDirectionX = myDirX; self.aiThreshingDirectionZ = myDirZ; --print("switch to direction"); end; function AILexion600Combine.addCutterTrigger(self, cutter) if cutter.aiTrafficCollisionTrigger ~= nil then addTrigger(cutter.aiTrafficCollisionTrigger, "onCutterTrafficCollisionTrigger", self); self.numCutterCollidingVehicles[cutter.aiTrafficCollisionTrigger] = 0; end; for k,v in pairs(cutter.components) do self.trafficCollisionIgnoreList[v.node] = true; end; end; function AILexion600Combine:removeCutterTrigger(cutter) if cutter.aiTrafficCollisionTrigger ~= nil then removeTrigger(cutter.aiTrafficCollisionTrigger); self.numCutterCollidingVehicles[cutter.aiTrafficCollisionTrigger] = nil; end; for k,v in pairs(cutter.components) do self.trafficCollisionIgnoreList[v.node] = nil; end; end; function AILexion600Combine:attachImplement(implement) local object = implement.object; if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER then if self.isAIThreshing then AILexion600Combine.addCutterTrigger(self, object); end; end; end; function AILexion600Combine:detachImplement(implementIndex) local object = self.attachedImplements[implementIndex].object; if object.attacherJoint.jointType == Vehicle.JOINTTYPE_CUTTER then if self.isAIThreshing then AILexion600Combine.removeCutterTrigger(self, object); end; end; end; function AILexion600Combine:onTrafficCollisionTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId) if onEnter or onLeave then if otherId == Player.rootNode then if onEnter then self.numCollidingVehicles = self.numCollidingVehicles+1; elseif onLeave then self.numCollidingVehicles = math.max(self.numCollidingVehicles-1, 0); end; else local vehicle = g_currentMission.nodeToVehicle[otherId]; if vehicle ~= nil and self.trafficCollisionIgnoreList[otherId] == nil then if onEnter then self.numCollidingVehicles = self.numCollidingVehicles+1; elseif onLeave then self.numCollidingVehicles = math.max(self.numCollidingVehicles-1, 0); end; end; end; end; end; function AILexion600Combine:onCutterTrafficCollisionTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId) if onEnter or onLeave then if otherId == Player.rootNode then if onEnter then self.numCutterCollidingVehicles[triggerId] = self.numCutterCollidingVehicles[triggerId]+1; elseif onLeave then self.numCutterCollidingVehicles[triggerId] = math.max(self.numCutterCollidingVehicles[triggerId]-1, 0); end; else local vehicle = g_currentMission.nodeToVehicle[otherId]; if vehicle ~= nil and self.trafficCollisionIgnoreList[otherId] == nil then if onEnter then self.numCutterCollidingVehicles[triggerId] = self.numCutterCollidingVehicles[triggerId]+1; elseif onLeave then self.numCutterCollidingVehicles[triggerId] = math.max(self.numCutterCollidingVehicles[triggerId]-1, 0); end; end; end; end; end; function AILexion600Combine:onTrailerTrigger(triggerId, otherId, onEnter, onLeave, onStay, otherShapeId) if onEnter or onStay then self.isTrailerInRange = true; end; end; function AILexion600Combine.switchToTurnStage3(self) self.turnStage = 3; for cutter,implement in pairs(self.attachedCutters) do local jointDesc = self.attacherJoints[implement.jointDescIndex]; jointDesc.moveDown = true; end; self.allowsThreshing = true; self.aiRescueTimer = self.aiRescueTimeout; end; function AILexion600Combine:getSaveAttributesAndNodes(nodeIdent) local attributes if self.aiThreshingDirectionX and self.aiThreshingDirectionZ then attributes = 'lastDirection="'..self.aiThreshingDirectionX..' 0 '..self.aiThreshingDirectionZ..'" ' end return attributes end; function AILexion600Combine:loadFromAttributesAndNodes(xmlFile, key, resetVehicles) local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#lastDirection")); if x and z then self.aiThreshingDirectionX = x self.aiThreshingDirectionZ = z end end