50 | function Drivable:load(savegame) |
51 | Drivable.loadSettingsFromXML(self, self.xmlFile); |
52 | |
53 | self.isDrivable = true; |
54 | self.steeringEnabled = true; |
55 | self.nonTabbable = false |
56 | |
57 | self.axisSmoothTime = 500; |
58 | self.axisForward = 0; |
59 | self.lastDigitalForward = 0; |
60 | self.axisForwardIsAnalog = false; |
61 | self.axisSide = 0; |
62 | self.axisSideIsAnalog = false; |
63 | self.doHandbrake = false; |
64 | self.reverserDirection = 1; |
65 | |
66 | self.tickDt = 30; |
67 | |
68 | -- mirrors |
69 | self.mirrors = {}; |
70 | local useMirrors = g_gameSettings:getValue("maxNumMirrors") > 0; |
71 | local i = 0; |
72 | while true do |
73 | local key = string.format("vehicle.mirrors.mirror(%d)", i); |
74 | if not hasXMLProperty(self.xmlFile, key) then |
75 | break; |
76 | end |
77 | local node = Utils.indexToObject(self.components, getXMLString(self.xmlFile, key.."#index")); |
78 | if node ~= nil then |
79 | local prio = Utils.getNoNil(getXMLInt(self.xmlFile, key.."#prio"), 2) |
80 | setReflectionMapObjectMasks(node, 2147483776, 2155872256, true); --0x80000080, 0x80800000 |
81 | if getObjectMask(node) == 0 then |
82 | -- Mirrors should not be visible in other mirrors |
83 | setObjectMask(node, 16711807); -- 0x00FF007F |
84 | end |
85 | |
86 | if useMirrors then |
87 | table.insert(self.mirrors, {node=node, prio=prio, cosAngle=1}); |
88 | else |
89 | setVisibility(node, false) |
90 | end |
91 | end |
92 | i = i + 1; |
93 | end |
94 | |
95 | self:setMirrorVisible(self.cameras[self.camIndex].useMirror); |
96 | |
97 | self.cruiseControl = {}; |
98 | self.cruiseControl.maxSpeed = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.cruiseControl#maxSpeed"), math.ceil(self.motor:getMaximumForwardSpeed()*3.6)); |
99 | self.cruiseControl.minSpeed = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.cruiseControl#minSpeed"), math.min(1, self.cruiseControl.maxSpeed)); |
100 | self.cruiseControl.speed = self.cruiseControl.maxSpeed; |
101 | self.cruiseControl.isActive = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.cruiseControl#enabled"), true); |
102 | self.cruiseControl.state = Drivable.CRUISECONTROL_STATE_OFF; |
103 | self.cruiseControl.topSpeedTime = 2000; |
104 | self.cruiseControl.changeDelay = 250; |
105 | self.cruiseControl.changeCurrentDelay = 0; |
106 | self.cruiseControl.changeMultiplier = 1; |
107 | self.cruiseControl.speedSent = self.cruiseControl.speed; |
108 | |
109 | self.lastOperatingTime = 0; |
110 | self.maxOperatingTime = 99999.9 * 1000 * 60 * 60; |
111 | |
112 | self.operatingTimeHud = VehicleHudUtils.loadHud(self, self.xmlFile, "operatingTime"); |
113 | self.cruiseControlHud = VehicleHudUtils.loadHud(self, self.xmlFile, "cruiseControl"); |
114 | |
115 | if self.cruiseControlHud ~= nil then |
116 | VehicleHudUtils.setHudValue(self, self.cruiseControlHud, self.cruiseControl.speed, 9999); |
117 | end |
118 | |
119 | if self.isClient then |
120 | self.turnLightRepetitionCount = 0; |
121 | |
122 | local useDefaultSample = true; |
123 | if hasXMLProperty(self.xmlFile, "vehicle.turnLightSound") then |
124 | useDefaultSample = false; |
125 | local customFile = getXMLString(self.xmlFile, "vehicle.turnLightSound#file"); |
126 | local customFilename = Utils.getFilename(customFile, self.baseDirectory); |
127 | useDefaultSample = g_currentMission.defaultVehicleSounds[customFilename] ~= nil; |
128 | end |
129 | if not useDefaultSample then |
130 | self.sampleTurnLight = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.turnLightSound", defaultSample, self.baseDirectory); |
131 | end |
132 | |
133 | local useDefaultSample = true; |
134 | if hasXMLProperty(self.xmlFile, "vehicle.waterSplashSound") then |
135 | useDefaultSample = false; |
136 | local customFile = getXMLString(self.xmlFile, "vehicle.waterSplashSound#file"); |
137 | local customFilename = Utils.getFilename(customFile, self.baseDirectory); |
138 | useDefaultSample = g_currentMission.defaultVehicleSounds[customFilename] ~= nil; |
139 | end |
140 | if not useDefaultSample then |
141 | self.sampleWaterSplash = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.waterSplashSound", defaultSample, self.baseDirectory); |
142 | end |
143 | |
144 | end |
145 | |
146 | self.drivableGroundFlag = self:getNextDirtyFlag(); |
147 | |
148 | if savegame ~= nil then |
149 | local maxSpeed = getXMLInt(savegame.xmlFile, savegame.key.."#cruiseControl"); |
150 | self:setCruiseControlMaxSpeed(maxSpeed); |
151 | |
152 | local dir = getXMLInt(savegame.xmlFile, savegame.key.."#attacherJointComboDir"); |
153 | if dir ~= nil then |
154 | self.attacherJointCombos.direction = dir; |
155 | if dir == 1 then |
156 | self.attacherJointCombos.currentTime = self.attacherJointCombos.duration; |
157 | end |
158 | end |
159 | end |
160 | |
161 | self.showChangeToolSelectionHelp = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.showChangeToolSelectionHelp"), true) |
162 | self.showToolSelectionHud = true; |
163 | end |
292 | function Drivable:update(dt) |
293 | if self.isEntered and self.isClient then |
294 | |
295 | local turnLightRepetitionCount = math.floor((getShaderTimeSec()*7 + math.acos(-0.2)) / (math.pi*2)); |
296 | if self.turnLightRepetitionCount ~= nil and turnLightRepetitionCount ~= self.turnLightRepetitionCount then |
297 | if self.turnLightState > Lights.TURNLIGHT_OFF then |
298 | if self.sampleTurnLight then |
299 | SoundUtil.playSample(self.sampleTurnLight, 1, 0, nil); |
300 | else |
301 | SoundUtil.playSample(g_currentMission.sampleTurnLight, 1, 0, nil); |
302 | end |
303 | end |
304 | end |
305 | self.turnLightRepetitionCount = turnLightRepetitionCount; |
306 | |
307 | -- |
308 | if self.cruiseControl.isActive then |
309 | -- Cruise Control |
310 | if self:getIsActiveForInput(false, false) then |
311 | if InputBinding.hasEvent(InputBinding.TOGGLE_CRUISE_CONTROL) then |
312 | if self.cruiseControl.state == Drivable.CRUISECONTROL_STATE_OFF then |
313 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_ACTIVE); |
314 | else |
315 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF); |
316 | end |
317 | self.cruiseControl.topSpeedTime = 500; |
318 | elseif InputBinding.isPressed(InputBinding.TOGGLE_CRUISE_CONTROL) then |
319 | self.cruiseControl.topSpeedTime = self.cruiseControl.topSpeedTime - dt; |
320 | if self.cruiseControl.topSpeedTime < 0 then |
321 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_FULL); |
322 | end |
323 | else |
324 | self.cruiseControl.topSpeedTime = 500; |
325 | end |
326 | end |
327 | |
328 | if not (g_gui:getIsGuiVisible() or g_currentMission.isPlayerFrozen or g_currentMission.inGameMessage:getIsVisible()) then --and self.selectedImplement == nil then |
329 | -- Cruise Control speed |
330 | local inputW = InputBinding.getDigitalInputAxis(InputBinding.AXIS_CRUISE_CONTROL); |
331 | if InputBinding.isAxisZero(inputW) then |
332 | inputW = InputBinding.getAnalogInputAxis(InputBinding.AXIS_CRUISE_CONTROL) |
333 | end |
334 | |
335 | if inputW ~= 0 then |
336 | self.cruiseControl.changeCurrentDelay = self.cruiseControl.changeCurrentDelay - dt*self.cruiseControl.changeMultiplier; |
337 | self.cruiseControl.changeMultiplier = math.min(self.cruiseControl.changeMultiplier + dt*0.003, 10); |
338 | if self.cruiseControl.changeCurrentDelay < 0 then |
339 | local dir = 0; |
340 | if inputW > g_analogStickVTolerance then |
341 | dir = 1; |
342 | elseif inputW < -g_analogStickVTolerance then |
343 | dir = -1; |
344 | end |
345 | |
346 | if dir ~= 0 then |
347 | self.cruiseControl.wasSpeedChanged = true; |
348 | self:setCruiseControlMaxSpeed(self.cruiseControl.speed + dir); |
349 | self.cruiseControl.changeCurrentDelay = self.cruiseControl.changeDelay; |
350 | end |
351 | end |
352 | else |
353 | self.cruiseControl.wasSpeedChanged = false; |
354 | self.cruiseControl.changeMultiplier = 1; |
355 | self.cruiseControl.changeCurrentDelay = 0; |
356 | end |
357 | |
358 | if not self.cruiseControl.wasSpeedChanged and self.cruiseControl.speed ~= self.cruiseControl.speedSent then |
359 | if g_server ~= nil then |
360 | g_server:broadcastEvent(SetCruiseControlSpeedEvent:new(self, self.cruiseControl.speed), nil, nil, self); |
361 | else |
362 | g_client:getServerConnection():sendEvent(SetCruiseControlSpeedEvent:new(self, self.cruiseControl.speed)); |
363 | end |
364 | self.cruiseControl.speedSent = self.cruiseControl.speed; |
365 | end |
366 | |
367 | end |
368 | end |
369 | |
370 | if not self.isHired then |
371 | -- do all the input handling |
372 | if self:getIsActiveForInput(false, false) then |
373 | |
374 | if InputBinding.hasEvent(InputBinding.TOGGLE_TIPSTATE) then |
375 | self:handleToggleTipStateEvent(); |
376 | end |
377 | |
378 | if InputBinding.hasEvent(InputBinding.ATTACH) then |
379 | self:handleAttachEvent(); |
380 | end |
381 | |
382 | if InputBinding.hasEvent(InputBinding.LOWER_IMPLEMENT) then |
383 | self:handleLowerImplementEvent(); |
384 | end |
385 | |
386 | if InputBinding.hasEvent(InputBinding.LOWER_ALL_IMPLEMENTS) then |
387 | self:toggleLowerAllImplements(); |
388 | end |
389 | |
390 | if InputBinding.hasEvent(InputBinding.SWITCH_IMPLEMENT) then |
391 | self:selectNextSelectableImplement(); |
392 | end |
393 | |
394 | if self.hasTurnLights then |
395 | if InputBinding.hasEvent(InputBinding.TOGGLE_TURNLIGHT_HAZARD) then |
396 | local state = Lights.TURNLIGHT_OFF; |
397 | if self.turnLightState ~= Lights.TURNLIGHT_HAZARD then |
398 | state = Lights.TURNLIGHT_HAZARD; |
399 | end |
400 | self:setTurnLightState(state); |
401 | end |
402 | if InputBinding.hasEvent(InputBinding.TOGGLE_TURNLIGHT_LEFT) then |
403 | local state = Lights.TURNLIGHT_OFF; |
404 | if self.turnLightState ~= Lights.TURNLIGHT_LEFT then |
405 | state = Lights.TURNLIGHT_LEFT; |
406 | end |
407 | self:setTurnLightState(state); |
408 | end |
409 | if InputBinding.hasEvent(InputBinding.TOGGLE_TURNLIGHT_RIGHT) then |
410 | local state = Lights.TURNLIGHT_OFF; |
411 | if self.turnLightState ~= Lights.TURNLIGHT_RIGHT then |
412 | state = Lights.TURNLIGHT_RIGHT; |
413 | end |
414 | self:setTurnLightState(state); |
415 | end |
416 | end |
417 | |
418 | if InputBinding.hasEvent(InputBinding.TOGGLE_BEACON_LIGHTS) then |
419 | self:setBeaconLightsVisibility(not self.beaconLightsActive); |
420 | end |
421 | |
422 | end |
423 | |
424 | if self:getIsActiveForInput(false, false) then |
425 | |
426 | self.doHandbrake = false; |
427 | local axisAccelerate = InputBinding.getDigitalInputAxis(InputBinding.AXIS_ACCELERATE_VEHICLE); |
428 | local axisBrake = InputBinding.getDigitalInputAxis(InputBinding.AXIS_BRAKE_VEHICLE) |
429 | local axisForward = Utils.clamp((axisAccelerate-axisBrake)*0.5, -1, 1); |
430 | |
431 | if InputBinding.isAxisZero(axisForward) then |
432 | axisAccelerate = InputBinding.getAnalogInputAxis(InputBinding.AXIS_ACCELERATE_VEHICLE); |
433 | axisBrake = InputBinding.getAnalogInputAxis(InputBinding.AXIS_BRAKE_VEHICLE) |
434 | self.axisForward = Utils.clamp((axisAccelerate-axisBrake)*0.5, -1, 1); |
435 | if not InputBinding.isAxisZero(self.axisForward) then |
436 | self.axisForwardIsAnalog = true; |
437 | end |
438 | self.lastDigitalForward = 0; |
439 | if math.abs(self.axisForward) > 0.1 and g_gui.currentGuiName ~= "ChatDialog" then |
440 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF); |
441 | end |
442 | else |
443 | self.axisForward = axisForward; |
444 | self.axisForwardIsAnalog = false; |
445 | self.lastDigitalForward = self.axisForward; |
446 | if g_gui.currentGuiName ~= "ChatDialog" then |
447 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF); |
448 | end |
449 | end |
450 | else |
451 | self.doHandbrake = true; |
452 | end |
453 | else |
454 | self.axisForward = 0; |
455 | self.axisForwardIsAnalog = false; |
456 | end |
457 | |
458 | -- do mirror checks |
459 | if self.activeCamera ~= nil and self.activeCamera.useMirror then |
460 | self:setMirrorVisible(true) |
461 | end |
462 | |
463 | if not self.isHired then |
464 | self.axisSide = InputBinding.getDigitalInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE); |
465 | if InputBinding.isAxisZero(self.axisSide) then |
466 | self.axisSide = InputBinding.getAnalogInputAxis(InputBinding.AXIS_MOVE_SIDE_VEHICLE) |
467 | if not InputBinding.isAxisZero(self.axisSide) then |
468 | self.axisSideIsAnalog = true; |
469 | end |
470 | else |
471 | self.axisSideIsAnalog = false; |
472 | end |
473 | |
474 | if not self:getIsActiveForInput(false, true) then |
475 | if not self.axisSideIsAnalog or g_gui.currentGuiName == "PlacementScreen" then |
476 | self.axisSide = 0; |
477 | end |
478 | if not self.axisForwardIsAnalog or g_gui.currentGuiName == "PlacementScreen" then |
479 | self.axisForward = 0; |
480 | end |
481 | |
482 | if self.steeringEnabled then |
483 | if g_gui:getIsGuiVisible() and g_gui.currentGuiName ~= "ChatDialog" then |
484 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF); |
485 | end |
486 | end |
487 | end |
488 | |
489 | if g_isServerStreamingVersion then self.axisSide = self.axisSide * 0.5; end -- This is the factor to slow down the steering |
490 | end |
491 | |
492 | if self.isServer then |
493 | if self.steeringEnabled then |
494 | Drivable.updateVehiclePhysics(self, self.axisForward, self.axisForwardIsAnalog, self.axisSide, self.axisSideIsAnalog, self.doHandbrake, dt); |
495 | end |
496 | else |
497 | self:raiseDirtyFlags(self.drivableGroundFlag); |
498 | end |
499 | end |
500 | |
501 | if self:getIsActive() then |
502 | -- lock max speed to working tool |
503 | local speed,_ = self:getSpeedLimit(true); |
504 | if self.cruiseControl.state == Drivable.CRUISECONTROL_STATE_ACTIVE then |
505 | speed = math.min(speed, self.cruiseControl.speed); |
506 | end |
507 | self.motor:setSpeedLimit(speed); |
508 | end |
509 | end |
514 | function Drivable:updateTick(dt) |
515 | self.tickDt = dt; |
516 | if self:getIsActive() then |
517 | local xt,yt,zt = getTranslation(self.components[1].node); |
518 | local deltaWater = yt-g_currentMission.waterY+2.5; |
519 | if deltaWater < 0 then |
520 | if self.aiIsStarted then |
521 | self:stopAIVehicle(AIVehicle.STOP_REASON_UNKOWN); |
522 | end |
523 | self.isBroken = true; |
524 | g_currentMission:onSunkVehicle(self); |
525 | |
526 | if self.isEntered then |
527 | if self:getIsActiveForSound() then |
528 | local volume = math.min(1, self:getLastSpeed()/30); |
529 | if self.sampleWaterSplash ~= nil then |
530 | SoundUtil.playSample(self.sampleWaterSplash, 1, 0, volume); |
531 | else |
532 | SoundUtil.playSample(g_currentMission.sampleWaterSplash, 1, 0, volume); |
533 | end |
534 | end |
535 | g_currentMission:onLeaveVehicle(); |
536 | end |
537 | end |
538 | self.showWaterWarning = deltaWater < 2; |
539 | |
540 | if self.attacherJointCombos ~= nil and self.attacherJointCombos.isRunning then |
541 | for k, joint in pairs(self.attacherJointCombos.joints) do |
542 | if self.attacherJointCombos.direction == 1 and self.attacherJointCombos.currentTime >= joint.time then |
543 | self:lowerImplementByJointIndex(joint.jointIndex, true); |
544 | elseif self.attacherJointCombos.direction == -1 and self.attacherJointCombos.currentTime <= self.attacherJointCombos.duration-joint.time then |
545 | self:lowerImplementByJointIndex(joint.jointIndex, false); |
546 | end |
547 | end |
548 | |
549 | if (self.attacherJointCombos.direction == -1 and self.attacherJointCombos.currentTime == 0) or |
550 | (self.attacherJointCombos.direction == 1 and self.attacherJointCombos.currentTime == self.attacherJointCombos.duration) then |
551 | self.attacherJointCombos.isRunning = false; |
552 | end |
553 | |
554 | self.attacherJointCombos.currentTime = Utils.clamp(self.attacherJointCombos.currentTime + dt*self.attacherJointCombos.direction, 0, self.attacherJointCombos.duration); |
555 | end |
556 | end |
557 | end |
561 | function Drivable:draw() |
562 | |
563 | if not self.isEntered then |
564 | return; |
565 | end |
566 | |
567 | -- mirror debug rendering |
568 | --[[for k, mirror in pairs(self.mirrors) do |
569 | setTextAlignment(RenderText.ALIGN_LEFT); |
570 | renderText(0.5, 0.9-(k-1)*0.015, 0.012, string.format("%s\t: %s %.4f", getName(mirror.node), tostring(getVisibility(mirror.node)), math.deg(math.acos(mirror.cosAngle)))) |
571 | |
572 | local x,y,z = getWorldTranslation(mirror.node) |
573 | Utils.renderTextAtWorldPosition(x,y,z, string.format("%s: %.3f", getName(mirror.node), math.deg(math.acos(mirror.cosAngle))), 0.015, 0) |
574 | end]] |
575 | |
576 | g_currentMission:drawVehicleHud(self); |
577 | |
578 | if self.showWaterWarning then |
579 | g_currentMission:showBlinkingWarning(g_i18n:getText("warning_dontDriveIntoWater"), 2000); |
580 | end |
581 | |
582 | if not self.isHired then |
583 | if self == g_currentMission.controlledVehicle then |
584 | if g_currentMission.trailerInTipRange ~= nil and (g_currentMission.trailerInTipRange.getFillLevel ~= nil and g_currentMission.trailerInTipRange:getFillLevel() > 0) then |
585 | g_currentMission:enableHudIcon("tip", 4); |
586 | g_currentMission:addHelpButtonText(g_currentMission.trailerInTipRange.tipText, InputBinding.TOGGLE_TIPSTATE, nil, GS_PRIO_VERY_HIGH); |
587 | end |
588 | |
589 | -- enable attachment icon if needed |
590 | if (g_currentMission.attachableInMountRange ~= nil and g_currentMission.attachableInMountRangeVehicle.attacherJoints[g_currentMission.attachableInMountRangeIndex].jointIndex == 0) then |
591 | g_currentMission:enableHudIcon("attach", 3); |
592 | g_currentMission:addHelpButtonText(g_i18n:getText("action_attach"), InputBinding.ATTACH, nil, GS_PRIO_VERY_HIGH); |
593 | else |
594 | -- if something is detachable is attached, show button help |
595 | if self:detachingIsPossible() then |
596 | g_currentMission:addHelpButtonText(g_i18n:getText("action_detach"), InputBinding.ATTACH, nil, GS_PRIO_VERY_HIGH); |
597 | end |
598 | end |
599 | |
600 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil and self.selectedImplement.object.attacherVehicle ~= nil and not self.isHired then |
601 | local object = self.selectedImplement.object; |
602 | local jointDesc = object.attacherVehicle.attacherJoints[self.selectedImplement.jointDescIndex]; |
603 | |
604 | if object.attacherJoint.allowsLowering and jointDesc.allowsLowering and object.foldMiddleAnimTime == nil then |
605 | if jointDesc.moveDown then |
606 | g_currentMission:addHelpButtonText(string.format(g_i18n:getText("action_liftOBJECT"), object.typeDesc), InputBinding.LOWER_IMPLEMENT, nil, GS_PRIO_HIGH); |
607 | else |
608 | g_currentMission:addHelpButtonText(string.format(g_i18n:getText("action_lowerOBJECT"), object.typeDesc), InputBinding.LOWER_IMPLEMENT, nil, GS_PRIO_HIGH); |
609 | end |
610 | end |
611 | end |
612 | end |
613 | |
614 | if #self.beaconLights > 0 then |
615 | g_currentMission:addHelpButtonText(g_i18n:getText("input_TOGGLE_BEACON_LIGHTS"), InputBinding.TOGGLE_BEACON_LIGHTS, nil, GS_PRIO_VERY_LOW); |
616 | end |
617 | |
618 | if self.cruiseControl.state == Drivable.CRUISECONTROL_STATE_OFF then |
619 | g_currentMission:addHelpButtonText(g_i18n:getText("action_activateCruiseControl"), InputBinding.TOGGLE_CRUISE_CONTROL, nil, GS_PRIO_LOW); |
620 | else |
621 | g_currentMission:addHelpButtonText(g_i18n:getText("action_deactivateCruiseControl"), InputBinding.TOGGLE_CRUISE_CONTROL, nil, GS_PRIO_LOW); |
622 | end |
623 | |
624 | g_currentMission:addHelpButtonText(g_i18n:getText("action_changeCruiseControlLevel"), InputBinding.AXIS_CRUISE_CONTROL, nil, GS_PRIO_LOW); |
625 | |
626 | local numDirectImplements = table.getn(self.attachedImplements); |
627 | local hasMoreThan1Selectable = (numDirectImplements > 1) or (self:getIsSelectable() and numDirectImplements > 0); |
628 | if not hasMoreThan1Selectable then |
629 | for _, implement in pairs(self.attachedImplements) do |
630 | if implement.object ~= nil and table.getn(implement.object.attachedImplements) > 0 then |
631 | hasMoreThan1Selectable = true; |
632 | break; |
633 | end |
634 | end |
635 | end |
636 | |
637 | if hasMoreThan1Selectable and self.showChangeToolSelectionHelp then |
638 | g_currentMission:addHelpButtonText(g_i18n:getText("action_changeToolSelection"), InputBinding.SWITCH_IMPLEMENT, nil, GS_PRIO_NORMAL); |
639 | end |
640 | end |
641 | |
642 | if GS_IS_CONSOLE_VERSION then |
643 | g_currentMission:addHelpButtonText(g_i18n:getText("input_RADIO_TOGGLE"), InputBinding.RADIO_TOGGLE, nil, GS_PRIO_VERY_LOW); |
644 | if g_gameSettings:getValue("radioIsActive") then |
645 | g_currentMission:addHelpButtonText(g_i18n:getText("input_RADIO_NEXT_CHANNEL"), InputBinding.RADIO_NEXT_CHANNEL, nil, GS_PRIO_VERY_LOW); |
646 | g_currentMission:addHelpButtonText(g_i18n:getText("input_RADIO_PREVIOUS_CHANNEL"), InputBinding.RADIO_PREVIOUS_CHANNEL, nil, GS_PRIO_VERY_LOW); |
647 | end |
648 | end |
649 | end |
742 | function Drivable:setMirrorVisible(visible) |
743 | if next(self.mirrors) == nil then |
744 | return |
745 | end |
746 | if visible then |
747 | local numVisibleMirrors = 0 |
748 | for _, mirror in pairs(self.mirrors) do |
749 | -- only use mirrors which are visible on screen |
750 | if getIsInCameraFrustum(mirror.node, self.activeCamera.cameraNode, g_presentedScreenAspectRatio) then |
751 | -- calculate angle between mirror and camera |
752 | local dirX, dirY, dirZ = worldToLocal(self.activeCamera.cameraNode, getWorldTranslation(mirror.node)) |
753 | |
754 | dirY = dirY * g_screenAspectRatio |
755 | local length = Utils.vector3Length(dirX, dirY, dirZ) |
756 | mirror.cosAngle = -dirZ / length |
757 | else |
758 | mirror.cosAngle = math.huge |
759 | end |
760 | end |
761 | |
762 | -- sort mirrors based on prio and angle |
763 | table.sort(self.mirrors, |
764 | function(mirror1, mirror2) |
765 | if mirror1.prio == mirror2.prio then |
766 | -- the bigger cosAngle, the smaller the angle to the z-axis |
767 | return mirror1.cosAngle > mirror2.cosAngle |
768 | else |
769 | return mirror1.prio < mirror2.prio |
770 | end |
771 | end) |
772 | |
773 | local maxNumMirrors = g_gameSettings:getValue("maxNumMirrors") |
774 | -- show first mirrors within the limit |
775 | for _, mirror in ipairs(self.mirrors) do |
776 | if mirror.cosAngle ~= math.huge and numVisibleMirrors < maxNumMirrors then |
777 | setVisibility(mirror.node, true) |
778 | numVisibleMirrors = numVisibleMirrors + 1 |
779 | else |
780 | setVisibility(mirror.node, false) |
781 | end |
782 | end |
783 | else |
784 | for _, mirror in pairs(self.mirrors) do |
785 | setVisibility(mirror.node, false); |
786 | end |
787 | end |
788 | end |
830 | function Drivable.updateVehiclePhysics(self, axisForward, axisForwardIsAnalog, axisSide, axisSideIsAnalog, doHandbrake, dt) |
831 | |
832 | axisForward = axisForward; |
833 | axisSide = self.reverserDirection * axisSide; |
834 | |
835 | local acceleration = 0; |
836 | if self.isMotorStarted and self.motorStartTime <= g_currentMission.time then |
837 | acceleration = -axisForward; |
838 | if math.abs(acceleration) > 0.8 then |
839 | self:setCruiseControlState(Drivable.CRUISECONTROL_STATE_OFF, true); |
840 | end |
841 | if self.cruiseControl.state ~= Drivable.CRUISECONTROL_STATE_OFF then |
842 | acceleration = 1.0; |
843 | end |
844 | end |
845 | if self.fuelFillLevel == 0 then |
846 | acceleration = 0; |
847 | end |
848 | |
849 | -- only update steering if a player is in the vehicle |
850 | if self.isControlled then |
851 | local inputAxisX = axisSide; |
852 | if axisSideIsAnalog then |
853 | local targetRotatedTime = 0; |
854 | if inputAxisX < 0 then |
855 | -- 0 to maxRotTime |
856 | targetRotatedTime = math.min(-self.maxRotTime*inputAxisX, self.maxRotTime); |
857 | else |
858 | -- 0 to minRotTime |
859 | targetRotatedTime = math.max(self.minRotTime*inputAxisX, self.minRotTime); |
860 | end |
861 | local maxTime = self.maxRotatedTimeSpeed*dt; |
862 | if math.abs(targetRotatedTime-self.rotatedTime) > maxTime then |
863 | if targetRotatedTime > self.rotatedTime then |
864 | self.rotatedTime = self.rotatedTime + maxTime; |
865 | else |
866 | self.rotatedTime = self.rotatedTime - maxTime; |
867 | end |
868 | else |
869 | self.rotatedTime = targetRotatedTime; |
870 | end |
871 | else |
872 | local rotScale = math.min(1.0/(self.lastSpeed*self.speedRotScale+self.speedRotScaleOffset), 1); |
873 | if inputAxisX < 0 then |
874 | self.rotatedTime = math.min(self.rotatedTime - dt*0.001*inputAxisX*rotScale, self.maxRotTime); |
875 | elseif inputAxisX > 0 then |
876 | self.rotatedTime = math.max(self.rotatedTime - dt*0.001*inputAxisX*rotScale, self.minRotTime); |
877 | else |
878 | if self.autoRotateBackSpeed ~= 0 then |
879 | if self.rotatedTime > 0 then |
880 | self.rotatedTime = math.max(self.rotatedTime - dt*0.001*self.autoRotateBackSpeed*rotScale, 0); |
881 | else |
882 | self.rotatedTime = math.min(self.rotatedTime + dt*0.001*self.autoRotateBackSpeed*rotScale, 0); |
883 | end |
884 | end |
885 | end |
886 | end |
887 | end |
888 | |
889 | if self.firstTimeRun then |
890 | if self.motorType == "locomotive" then |
891 | self.motor:updateInput(dt, acceleration); |
892 | else |
893 | self.motor:setRpmLimit( self:getRpmLimit() ); |
894 | WheelsUtil.updateWheelsPhysics(self, dt, self.lastSpeedReal, acceleration, doHandbrake, self.requiredDriveMode) |
895 | end |
896 | end |
897 | end |