175 | function AIVehicle:onLoad(savegame) |
176 | local spec = self.spec_aiVehicle |
177 | |
178 | spec.aiSteeringSpeed = Utils.getNoNil(getXMLFloat(spec.xmlFile, "vehicle.ai.steeringSpeed"), 1)*0.001 |
179 | |
180 | spec.isActive = false |
181 | |
182 | spec.aiImplementList = {} |
183 | spec.aiImplementDataDirtyFlag = true |
184 | |
185 | spec.aiDriveParams = { valid=false } |
186 | spec.aiUpdateLowFrequencyDt = 0 |
187 | spec.aiUpdateDt = 0 |
188 | |
189 | spec.taskList = {} |
190 | |
191 | spec.driveStrategies = {} |
192 | |
193 | spec.didNotMoveTimeout = Utils.getNoNil( getXMLFloat(spec.xmlFile, "vehicle.ai.didNotMoveTimeout#value"), 5000) |
194 | if getXMLBool(spec.xmlFile, "vehicle.ai.didNotMoveTimeout#deactivated") then |
195 | spec.didNotMoveTimeout = math.huge |
196 | end |
197 | |
198 | spec.didNotMoveTimer = spec.didNotMoveTimeout |
199 | |
200 | spec.debugTexts = {} |
201 | spec.debugLines = {} |
202 | |
203 | spec.aiTrafficCollision = nil |
204 | spec.aiTrafficCollisionTranslation = {0, 0, 20} |
205 | |
206 | spec.pricePerMS = Utils.getNoNil(getXMLFloat(spec.xmlFile, "vehicle.ai.pricePerHour"), 2000)/60/60/1000 |
207 | |
208 | spec.steeringNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.steeringNode#node"), self.i3dMappings) |
209 | spec.reverserNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.ai.reverserNode#node"), self.i3dMappings) |
210 | end |
250 | function AIVehicle:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
251 | local spec = self.spec_aiVehicle |
252 | |
253 | if VehicleDebug.state == VehicleDebug.DEBUG_AI and self:getIsActiveForInput(true, true) then |
254 | if #spec.debugTexts > 0 then |
255 | for i, text in pairs(spec.debugTexts) do |
256 | renderText(0.7, 0.92-(0.02*i), 0.02, text) |
257 | end |
258 | end |
259 | if #spec.debugLines > 0 then |
260 | for _, l in pairs(spec.debugLines) do |
261 | drawDebugLine(l.s[1],l.s[2],l.s[3], l.c[1],l.c[2],l.c[3], l.e[1],l.e[2],l.e[3], l.c[1],l.c[2],l.c[3]) |
262 | end |
263 | end |
264 | end |
265 | |
266 | if spec.aiImplementDataDirtyFlag then |
267 | spec.aiImplementDataDirtyFlag = false |
268 | self:updateAIImplementData() |
269 | end |
270 | |
271 | if self.isServer then |
272 | if self:getIsAIActive() then |
273 | if spec.driveStrategies ~= nil then |
274 | for i=1,#spec.driveStrategies do |
275 | local driveStrategy = spec.driveStrategies[i] |
276 | driveStrategy:update(dt) |
277 | end |
278 | end |
279 | |
280 | -- Find our index in the hiredHirables list (this is not deterministic, but stays constant as long as the array is not changed) |
281 | local hirableIndex = 0 |
282 | for hirable in pairs(AIVehicle.hiredHirables) do |
283 | if self == hirable then |
284 | break |
285 | end |
286 | hirableIndex = hirableIndex + 1 |
287 | end |
288 | spec.aiUpdateLowFrequencyDt = spec.aiUpdateLowFrequencyDt + dt |
289 | if (g_updateLoopIndex + hirableIndex) % AIVehicle.aiUpdateLowFrequencyDelay == 0 then |
290 | self:updateAILowFrequency(spec.aiUpdateLowFrequencyDt) |
291 | spec.aiUpdateLowFrequencyDt = 0 |
292 | end |
293 | spec.aiUpdateDt = spec.aiUpdateDt + dt |
294 | local aiUpdateDelay = dt > 25 and AIVehicle.aiUpdateDelayLowFps or AIVehicle.aiUpdateDelay |
295 | if (g_updateLoopIndex + hirableIndex) % aiUpdateDelay == 0 then |
296 | self:updateAI(spec.aiUpdateDt) |
297 | spec.aiUpdateDt = 0 |
298 | end |
299 | end |
300 | end |
301 | |
302 | if self:getIsAIActive() then |
303 | if spec.aiTrafficCollision ~= nil and not self:getAIIsTurning() then |
304 | local x, y, z = localToWorld(self.components[1].node, unpack(spec.aiTrafficCollisionTranslation)) |
305 | |
306 | setTranslation(spec.aiTrafficCollision, x, y, z) |
307 | setRotation(spec.aiTrafficCollision, localRotationToWorld(self.components[1].node, 0, 0, 0)) |
308 | end |
309 | |
310 | -- as long as the ai is turned on we raise active (e.g. if the vehicle stops due a collision the vehicle collision may sleep and stop raising active) |
311 | self:raiseActive() |
312 | end |
313 | end |
320 | function AIVehicle:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
321 | local spec = self.spec_aiVehicle |
322 | |
323 | if self.isClient then |
324 | local actionEvent = spec.actionEvents[InputAction.TOGGLE_AI] |
325 | if actionEvent ~= nil then |
326 | local showAction = false |
327 | |
328 | if self:getIsActiveForInput(true, true) then |
329 | -- if ai is active we always display the dismiss helper action |
330 | showAction = self:getCanStartAIVehicle() or self:getIsAIActive() |
331 | |
332 | if showAction then |
333 | if self:getIsAIActive() then |
334 | g_inputBinding:setActionEventText(actionEvent.actionEventId, g_i18n:getText("action_dismissEmployee")) |
335 | else |
336 | g_inputBinding:setActionEventText(actionEvent.actionEventId, g_i18n:getText("action_hireEmployee")) |
337 | end |
338 | end |
339 | end |
340 | |
341 | g_inputBinding:setActionEventActive(actionEvent.actionEventId, showAction) |
342 | end |
343 | end |
344 | end |
158 | function AIVehicle.registerEventListeners(vehicleType) |
159 | SpecializationUtil.registerEventListener(vehicleType, "onLoad", AIVehicle) |
160 | SpecializationUtil.registerEventListener(vehicleType, "onDelete", AIVehicle) |
161 | SpecializationUtil.registerEventListener(vehicleType, "onReadStream", AIVehicle) |
162 | SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", AIVehicle) |
163 | SpecializationUtil.registerEventListener(vehicleType, "onUpdate", AIVehicle) |
164 | SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", AIVehicle) |
165 | SpecializationUtil.registerEventListener(vehicleType, "onEnterVehicle", AIVehicle) |
166 | SpecializationUtil.registerEventListener(vehicleType, "onLeaveVehicle", AIVehicle) |
167 | SpecializationUtil.registerEventListener(vehicleType, "onStateChange", AIVehicle) |
168 | SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", AIVehicle) |
169 | SpecializationUtil.registerEventListener(vehicleType, "onSetBroken", AIVehicle) |
170 | end |
95 | function AIVehicle.registerFunctions(vehicleType) |
96 | SpecializationUtil.registerFunction(vehicleType, "getCanStartAIVehicle", AIVehicle.getCanStartAIVehicle) |
97 | SpecializationUtil.registerFunction(vehicleType, "getCanAIVehicleContinueWork", AIVehicle.getCanAIVehicleContinueWork) |
98 | SpecializationUtil.registerFunction(vehicleType, "registerAITask", AIVehicle.registerAITask) |
99 | SpecializationUtil.registerFunction(vehicleType, "registerSecureAITask", AIVehicle.registerSecureAITask) |
100 | SpecializationUtil.registerFunction(vehicleType, "startAIVehicle", AIVehicle.startAIVehicle) |
101 | SpecializationUtil.registerFunction(vehicleType, "stopAIVehicle", AIVehicle.stopAIVehicle) |
102 | SpecializationUtil.registerFunction(vehicleType, "getCurrentHelper", AIVehicle.getCurrentHelper) |
103 | |
104 | SpecializationUtil.registerFunction(vehicleType, "updateAI", AIVehicle.updateAI) |
105 | SpecializationUtil.registerFunction(vehicleType, "updateAILowFrequency", AIVehicle.updateAILowFrequency) |
106 | |
107 | SpecializationUtil.registerFunction(vehicleType, "getAICollisionTriggers", AIVehicle.getAICollisionTriggers) |
108 | SpecializationUtil.registerFunction(vehicleType, "getAIVehicleDirectionNode", AIVehicle.getAIVehicleDirectionNode) |
109 | SpecializationUtil.registerFunction(vehicleType, "getAIVehicleSteeringNode", AIVehicle.getAIVehicleSteeringNode) |
110 | SpecializationUtil.registerFunction(vehicleType, "getAIVehicleReverserNode", AIVehicle.getAIVehicleReverserNode) |
111 | SpecializationUtil.registerFunction(vehicleType, "getAISteeringSpeed", AIVehicle.getAISteeringSpeed) |
112 | SpecializationUtil.registerFunction(vehicleType, "getDirectionSnapAngle", AIVehicle.getDirectionSnapAngle) |
113 | SpecializationUtil.registerFunction(vehicleType, "getAINeedsTrafficCollisionBox", AIVehicle.getAINeedsTrafficCollisionBox) |
114 | |
115 | SpecializationUtil.registerFunction(vehicleType, "updateAIImplementData", AIVehicle.updateAIImplementData) |
116 | SpecializationUtil.registerFunction(vehicleType, "getAttachedAIImplements", AIVehicle.getAttachedAIImplements) |
117 | |
118 | SpecializationUtil.registerFunction(vehicleType, "getAIDidNotMoveTimeout", AIVehicle.getAIDidNotMoveTimeout) |
119 | |
120 | SpecializationUtil.registerFunction(vehicleType, "updateAIDriveStrategies", AIVehicle.updateAIDriveStrategies) |
121 | SpecializationUtil.registerFunction(vehicleType, "setAIMapHotspotVisibility", AIVehicle.setAIMapHotspotVisibility) |
122 | SpecializationUtil.registerFunction(vehicleType, "setAIMapHotspotBlinking", AIVehicle.setAIMapHotspotBlinking) |
123 | |
124 | SpecializationUtil.registerFunction(vehicleType, "getAIIsTurning", AIVehicle.getAIIsTurning) |
125 | SpecializationUtil.registerFunction(vehicleType, "getAILastAllowedToDrive", AIVehicle.getAILastAllowedToDrive) |
126 | |
127 | SpecializationUtil.registerFunction(vehicleType, "aiStartTurn", AIVehicle.aiStartTurn) |
128 | SpecializationUtil.registerFunction(vehicleType, "aiTurnProgress", AIVehicle.aiTurnProgress) |
129 | SpecializationUtil.registerFunction(vehicleType, "aiEndTurn", AIVehicle.aiEndTurn) |
130 | |
131 | SpecializationUtil.registerFunction(vehicleType, "aiBlock", AIVehicle.aiBlock) |
132 | SpecializationUtil.registerFunction(vehicleType, "aiContinue", AIVehicle.aiContinue) |
133 | |
134 | SpecializationUtil.registerFunction(vehicleType, "raiseAIEvent", AIVehicle.raiseAIEvent) |
135 | |
136 | SpecializationUtil.registerFunction(vehicleType, "clearAIDebugTexts", AIVehicle.clearAIDebugTexts) |
137 | SpecializationUtil.registerFunction(vehicleType, "addAIDebugText", AIVehicle.addAIDebugText) |
138 | SpecializationUtil.registerFunction(vehicleType, "clearAIDebugLines", AIVehicle.clearAIDebugLines) |
139 | SpecializationUtil.registerFunction(vehicleType, "addAIDebugLine", AIVehicle.addAIDebugLine) |
140 | end |
144 | function AIVehicle.registerOverwrittenFunctions(vehicleType) |
145 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsAIActive", AIVehicle.getIsAIActive) |
146 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getDeactivateOnLeave", AIVehicle.getDeactivateOnLeave) |
147 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsVehicleControlledByPlayer", AIVehicle.getIsVehicleControlledByPlayer) |
148 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getStopMotorOnLeave", AIVehicle.getStopMotorOnLeave) |
149 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getDisableVehicleCharacterOnLeave", AIVehicle.getDisableVehicleCharacterOnLeave) |
150 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getAllowTireTracks", AIVehicle.getAllowTireTracks) |
151 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getActiveFarm", AIVehicle.getActiveFarm) |
152 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsInUse", AIVehicle.getIsInUse) |
153 | SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsActive", AIVehicle.getIsActive) |
154 | end |
554 | function AIVehicle:startAIVehicle(helperIndex, noEventSend, startedFarmId) |
555 | local spec = self.spec_aiVehicle |
556 | |
557 | if not self:getIsAIActive() then |
558 | if helperIndex ~= nil then |
559 | spec.currentHelper = g_helperManager:getHelperByIndex(helperIndex) |
560 | else |
561 | spec.currentHelper = g_helperManager:getRandomHelper() |
562 | end |
563 | |
564 | g_helperManager:useHelper(spec.currentHelper) |
565 | |
566 | spec.startedFarmId = startedFarmId |
567 | |
568 | if self.isServer then |
569 | g_farmManager:updateFarmStats(startedFarmId, "workersHired", 1) |
570 | end |
571 | |
572 | if noEventSend == nil or noEventSend == false then |
573 | local event = AIVehicleSetStartedEvent:new(self, nil, true, spec.currentHelper, startedFarmId) |
574 | if g_server ~= nil then |
575 | g_server:broadcastEvent(event, nil, nil, self) |
576 | else |
577 | g_client:getServerConnection():sendEvent(event) |
578 | end |
579 | end |
580 | |
581 | AIVehicle.numHirablesHired = AIVehicle.numHirablesHired + 1 |
582 | AIVehicle.hiredHirables[self] = self |
583 | |
584 | if self.setRandomVehicleCharacter ~= nil then |
585 | self:setRandomVehicleCharacter() |
586 | end |
587 | |
588 | local hotspotX, _, hotspotZ = getWorldTranslation(spec.rootNode) |
589 | |
590 | local _, textSize = getNormalizedScreenValues(0, 9) |
591 | local _, textOffsetY = getNormalizedScreenValues(0, 18) |
592 | local width, height = getNormalizedScreenValues(24, 24) |
593 | spec.mapAIHotspot = MapHotspot:new("helper", MapHotspot.CATEGORY_AI) |
594 | spec.mapAIHotspot:setSize(width, height) |
595 | spec.mapAIHotspot:setLinkedNode(spec.components[1].node) |
596 | spec.mapAIHotspot:setText(spec.currentHelper.name) |
597 | spec.mapAIHotspot:setImage(nil, getNormalizedUVs(MapHotspot.UV.HELPER), {0.052, 0.1248, 0.672, 1}) |
598 | spec.mapAIHotspot:setBackgroundImage(nil, getNormalizedUVs(MapHotspot.UV.HELPER)) |
599 | spec.mapAIHotspot:setIconScale(0.7) |
600 | spec.mapAIHotspot:setTextOptions(textSize, nil, textOffsetY, {1, 1, 1, 1}, Overlay.ALIGN_VERTICAL_MIDDLE) |
601 | spec.mapAIHotspot:setHasDetails(false) |
602 | g_currentMission:addMapHotspot(spec.mapAIHotspot) |
603 | |
604 | spec.isActive = true |
605 | |
606 | if self.isServer then |
607 | self:updateAIImplementData() |
608 | self:updateAIDriveStrategies() |
609 | end |
610 | |
611 | self:raiseAIEvent("onAIStart", "onAIImplementStart") |
612 | self:requestActionEventUpdate() |
613 | |
614 | if self:getAINeedsTrafficCollisionBox() then |
615 | local collisionRoot = g_i3DManager:loadSharedI3DFile(AIVehicle.TRAFFIC_COLLISION_BOX_FILENAME, g_currentMission.baseDirectory, false, true, false) |
616 | if collisionRoot ~= nil and collisionRoot ~= 0 then |
617 | local collision = getChildAt(collisionRoot, 0) |
618 | |
619 | link(getRootNode(), collision) |
620 | spec.aiTrafficCollision = collision |
621 | |
622 | delete(collisionRoot) |
623 | end |
624 | end |
625 | end |
626 | end |
632 | function AIVehicle:stopAIVehicle(reason, noEventSend) |
633 | local spec = self.spec_aiVehicle |
634 | |
635 | if self:getIsAIActive() then |
636 | if noEventSend == nil or noEventSend == false then |
637 | local event = AIVehicleSetStartedEvent:new(self, reason, false, nil, spec.startedFarmId) |
638 | |
639 | if g_server ~= nil then |
640 | g_server:broadcastEvent(event, nil, nil, self) |
641 | else |
642 | g_client:getServerConnection():sendEvent(event) |
643 | end |
644 | end |
645 | spec.aiDriveParams.valid = false |
646 | |
647 | if self.isClient then |
648 | if g_currentMission.player ~= nil then |
649 | if g_currentMission.player.farmId == spec.startedFarmId then |
650 | if reason ~= nil and reason ~= AIVehicle.STOP_REASON_USER then |
651 | local notificationType = FSBaseMission.INGAME_NOTIFICATION_CRITICAL |
652 | if reason == AIVehicle.STOP_REASON_REGULAR then |
653 | notificationType = FSBaseMission.INGAME_NOTIFICATION_OK |
654 | end |
655 | |
656 | if g_currentMission.accessHandler:canPlayerAccess(self) then |
657 | g_currentMission:addIngameNotification(notificationType, string.format(g_i18n:getText(AIVehicle.REASON_TEXT_MAPPING[reason]), spec.currentHelper.name)) |
658 | end |
659 | end |
660 | end |
661 | end |
662 | end |
663 | |
664 | g_helperManager:releaseHelper(spec.currentHelper) |
665 | spec.currentHelper = nil |
666 | |
667 | if self.isServer then |
668 | g_farmManager:updateFarmStats(spec.startedFarmId, "workersHired", -1) |
669 | end |
670 | |
671 | AIVehicle.numHirablesHired = math.max(AIVehicle.numHirablesHired - 1, 0) |
672 | AIVehicle.hiredHirables[self] = nil |
673 | |
674 | if self.restoreVehicleCharacter ~= nil then |
675 | self:restoreVehicleCharacter() |
676 | end |
677 | |
678 | if spec.mapAIHotspot ~= nil then |
679 | g_currentMission:removeMapHotspot(spec.mapAIHotspot) |
680 | spec.mapAIHotspot:delete() |
681 | spec.mapAIHotspot = nil |
682 | end |
683 | |
684 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF, true) |
685 | if self.isServer then |
686 | WheelsUtil.updateWheelsPhysics(self, 0, spec.lastSpeedReal*spec.movingDirection, 0, true, true) |
687 | |
688 | if spec.driveStrategies ~= nil and #spec.driveStrategies > 0 then |
689 | for i=#spec.driveStrategies,1,-1 do |
690 | spec.driveStrategies[i]:delete() |
691 | table.remove(spec.driveStrategies, i) |
692 | end |
693 | spec.driveStrategies = {} |
694 | end |
695 | end |
696 | |
697 | spec.isActive = false |
698 | spec.isTurning = false |
699 | |
700 | -- move the collision far under the ground |
701 | if self:getAINeedsTrafficCollisionBox() then |
702 | setTranslation(spec.aiTrafficCollision, 0, -1000, 0) |
703 | end |
704 | |
705 | if self.brake ~= nil then |
706 | self:brake(1) |
707 | end |
708 | |
709 | self:raiseAIEvent("onAIEnd", "onAIImplementEnd") |
710 | self:requestActionEventUpdate() |
711 | end |
712 | end |
447 | function AIVehicle:updateAI(dt) |
448 | local spec = self.spec_aiVehicle |
449 | if spec.aiDriveParams.valid then |
450 | local moveForwards = spec.aiDriveParams.moveForwards |
451 | local tX = spec.aiDriveParams.tX |
452 | local tY = spec.aiDriveParams.tY |
453 | local tZ = spec.aiDriveParams.tZ |
454 | local maxSpeed = spec.aiDriveParams.maxSpeed |
455 | |
456 | local pX, _, pZ = worldToLocal(self:getAIVehicleSteeringNode(), tX,tY,tZ) |
457 | if not moveForwards and self.spec_articulatedAxis ~= nil then |
458 | if self.spec_articulatedAxis.aiRevereserNode ~= nil then |
459 | pX, _, pZ = worldToLocal(self.spec_articulatedAxis.aiRevereserNode, tX,tY,tZ) |
460 | end |
461 | end |
462 | |
463 | if not moveForwards and self:getAIVehicleReverserNode() ~= nil then |
464 | pX, _, pZ = worldToLocal(self:getAIVehicleReverserNode(), tX,tY,tZ) |
465 | end |
466 | |
467 | local acceleration = 1.0 |
468 | local isAllowedToDrive = maxSpeed ~= 0 |
469 | |
470 | AIVehicleUtil.driveToPoint(self, dt, acceleration, isAllowedToDrive, moveForwards, pX, pZ, maxSpeed) |
471 | end |
472 | end |
792 | function AIVehicle:updateAIDriveStrategies() |
793 | local spec = self.spec_aiVehicle |
794 | |
795 | if #spec.aiImplementList > 0 then |
796 | if spec.driveStrategies ~= nil and #spec.driveStrategies > 0 then |
797 | for i=#spec.driveStrategies,1,-1 do |
798 | spec.driveStrategies[i]:delete() |
799 | table.remove(spec.driveStrategies, i) |
800 | end |
801 | spec.driveStrategies = {} |
802 | end |
803 | |
804 | local foundCombine = false |
805 | local foundBaler = false |
806 | for _,implement in pairs(spec.aiImplementList) do |
807 | if SpecializationUtil.hasSpecialization(Combine, implement.object.specializations) then |
808 | foundCombine = true |
809 | end |
810 | if SpecializationUtil.hasSpecialization(Baler, implement.object.specializations) then |
811 | foundBaler = true |
812 | end |
813 | end |
814 | |
815 | foundCombine = foundCombine or SpecializationUtil.hasSpecialization(Combine, spec.specializations) |
816 | if foundCombine then |
817 | local driveStrategyCombine = AIDriveStrategyCombine:new() |
818 | driveStrategyCombine:setAIVehicle(self) |
819 | table.insert(spec.driveStrategies, driveStrategyCombine) |
820 | end |
821 | |
822 | foundBaler = foundBaler or SpecializationUtil.hasSpecialization(Baler, spec.specializations) |
823 | if foundBaler then |
824 | local driveStrategyCombine = AIDriveStrategyBaler:new() |
825 | driveStrategyCombine:setAIVehicle(self) |
826 | table.insert(spec.driveStrategies, driveStrategyCombine) |
827 | end |
828 | |
829 | local driveStrategyCollision = AIDriveStrategyCollision:new() |
830 | local driveStrategyStraight = AIDriveStrategyStraight:new() |
831 | |
832 | driveStrategyCollision:setAIVehicle(self) |
833 | driveStrategyStraight:setAIVehicle(self) |
834 | |
835 | table.insert(spec.driveStrategies, driveStrategyCollision) |
836 | table.insert(spec.driveStrategies, driveStrategyStraight) |
837 | end |
838 | end |
350 | function AIVehicle:updateAILowFrequency(dt) |
351 | local spec = self.spec_aiVehicle |
352 | |
353 | self:clearAIDebugTexts() |
354 | self:clearAIDebugLines() |
355 | |
356 | if self:getIsAIActive() then |
357 | local difficultyMultiplier = g_currentMission.missionInfo.buyPriceMultiplier; |
358 | local price = -dt * difficultyMultiplier * spec.pricePerMS |
359 | |
360 | -- If field was not owned, it is a mission. Increase the price for balancing. |
361 | if self.getLastTouchedFarmlandFarmId ~= nil and self:getLastTouchedFarmlandFarmId() == 0 then |
362 | price = price * MissionManager.AI_PRICE_MULTIPLIER |
363 | end |
364 | |
365 | g_currentMission:addMoney(price, spec.startedFarmId, MoneyType.AI, true) |
366 | |
367 | if spec.driveStrategies ~= nil and #spec.driveStrategies > 0 then |
368 | local vX,vY,vZ = getWorldTranslation(self:getAIVehicleSteeringNode()) |
369 | |
370 | local tX, tZ, moveForwards, maxSpeedStra, maxSpeed, distanceToStop |
371 | for i=1,#spec.driveStrategies do |
372 | local driveStrategy = spec.driveStrategies[i] |
373 | tX, tZ, moveForwards, maxSpeedStra, distanceToStop = driveStrategy:getDriveData(dt, vX,vY,vZ) |
374 | maxSpeed = math.min(maxSpeedStra or math.huge, maxSpeed or math.huge) |
375 | if tX ~= nil or not self:getIsAIActive() then |
376 | break |
377 | end |
378 | end |
379 | |
380 | if tX == nil then |
381 | if self:getIsAIActive() then -- check if AI is still active, because it might have been kicked by a strategy |
382 | self:stopAIVehicle(AIVehicle.STOP_REASON_REGULAR) |
383 | end |
384 | end |
385 | |
386 | if not self:getIsAIActive() then |
387 | return |
388 | end |
389 | |
390 | local minimumSpeed = 5 |
391 | local lookAheadDistance = 5 |
392 | |
393 | local distSpeed = math.max(minimumSpeed, maxSpeed * math.min(1, distanceToStop/lookAheadDistance)) |
394 | local speedLimit, _ = self:getSpeedLimit() |
395 | maxSpeed = math.min(maxSpeed, distSpeed, speedLimit) |
396 | maxSpeed = math.min(maxSpeed, self:getCruiseControlMaxSpeed()) |
397 | |
398 | local isAllowedToDrive = maxSpeed ~= 0 |
399 | |
400 | -- set drive values |
401 | spec.aiDriveParams.moveForwards = moveForwards |
402 | spec.aiDriveParams.tX = tX |
403 | spec.aiDriveParams.tY = vY |
404 | spec.aiDriveParams.tZ = tZ |
405 | spec.aiDriveParams.maxSpeed = maxSpeed |
406 | spec.aiDriveParams.valid = true |
407 | |
408 | spec.lastAllowedToDrive = isAllowedToDrive |
409 | |
410 | -- worst case check: did not move but should have moved |
411 | if isAllowedToDrive and self:getLastSpeed() < 0.5 then |
412 | spec.didNotMoveTimer = spec.didNotMoveTimer - dt |
413 | else |
414 | spec.didNotMoveTimer = spec.didNotMoveTimeout |
415 | end |
416 | |
417 | if spec.didNotMoveTimer < 0 then |
418 | self:stopAIVehicle(AIVehicle.STOP_REASON_BLOCKED_BY_OBJECT) |
419 | end |
420 | end |
421 | |
422 | if #spec.taskList > 0 then |
423 | for i, task in pairs(spec.taskList) do |
424 | if VehicleDebug.state == VehicleDebug.DEBUG_AI then |
425 | self:addAIDebugText(string.format("AI TASK: %d - %s", i, task.getFunc)) |
426 | end |
427 | if task.getObject ~= nil then |
428 | if task.getObject[task.getFunc](task.getObject, unpack(task.getParams)) then |
429 | task.setObject[task.setFunc](task.setObject, unpack(task.setParams)) |
430 | spec.taskList[i] = nil |
431 | end |
432 | else |
433 | task.setObject[task.setFunc](task.setObject, unpack(task.setParams)) |
434 | spec.taskList[i] = nil |
435 | end |
436 | end |
437 | end |
438 | |
439 | self:raiseAIEvent("onAIActive", "onAIImplementActive") |
440 | end |
441 | end |