24 | function Steerable:preLoad(savegame) |
25 | Vehicle.registerInteractionFlag("steerable") |
26 | |
27 | self.enterVehicle = Steerable.enterVehicle |
28 | self.leaveVehicle = Steerable.leaveVehicle |
29 | self.drawUIInfo = Utils.overwrittenFunction(self.drawUIInfo, Steerable.drawUIInfo); |
30 | self.getIsActiveForInput = Utils.overwrittenFunction(self.getIsActiveForInput, Steerable.getIsActiveForInput); |
31 | self.getDistanceToObject = Utils.overwrittenFunction(self.getDistanceToObject, Steerable.getDistanceToObject); |
32 | self.getInteractionHelp = Utils.overwrittenFunction(self.getInteractionHelp, Steerable.getInteractionHelp); |
33 | self.interact = Utils.overwrittenFunction(self.interact, Steerable.interact); |
34 | self.setActiveCameraIndex = Steerable.setActiveCameraIndex; |
35 | self.addToolCameras = Steerable.addToolCameras; |
36 | self.removeToolCameras = Steerable.removeToolCameras; |
37 | |
38 | self.onEnter = SpecializationUtil.callSpecializationsFunction("onEnter"); |
39 | self.onLeave = SpecializationUtil.callSpecializationsFunction("onLeave"); |
40 | self.onCameraChanged = SpecializationUtil.callSpecializationsFunction("onCameraChanged"); |
41 | end |
46 | function Steerable:load(savegame) |
47 | |
48 | self.isSteerable = true; |
49 | self.isAttachable = SpecializationUtil.hasSpecialization(Attachable, self.specializations); |
50 | self.isControlled = false; |
51 | |
52 | self.steering = Utils.indexToObject(self.components, getXMLString(self.xmlFile, "vehicle.steering#index")); |
53 | |
54 | Steerable.loadSettingsFromXML(self, self.xmlFile); |
55 | |
56 | self.numCameras = Utils.getNoNil(getXMLInt(self.xmlFile, "vehicle.cameras#count"), 0); |
57 | self.cameras = {}; |
58 | for i=1, self.numCameras do |
59 | local cameraKey = string.format("vehicle.cameras.camera%d", i); |
60 | local camera = VehicleCamera:new(self); |
61 | if camera:loadFromXML(self.xmlFile, cameraKey) then |
62 | table.insert(self.cameras, camera); |
63 | end |
64 | end |
65 | self.numCameras = table.getn(self.cameras); |
66 | if self.numCameras == 0 then |
67 | print("Error: No cameras in xml file: ".. self.configFileName); |
68 | end |
69 | |
70 | self.camIndex = 1; |
71 | |
72 | self.isEntered = false; |
73 | |
74 | self.controllerName = "Unknown"; |
75 | |
76 | self.disableCharacterOnLeave = true; |
77 | |
78 | self.deactivateLightsOnLeave = true; |
79 | |
80 | self.steerableNeedsAttacherVehicle = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.steerableNeedsAttacherVehicle"), true); |
81 | |
82 | self.forceSelectionOnEnter = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.forceSelectionOnEnter"), false); |
83 | |
84 | self.currentLightState = 0; |
85 | if self.lightStates == nil or #self.lightStates == 0 then |
86 | table.insert(self.lightStates, {0}); |
87 | table.insert(self.lightStates, {0, 1}); |
88 | table.insert(self.lightStates, {0, 1, 2}); |
89 | end |
90 | |
91 | local useDefaultSample = true; |
92 | if hasXMLProperty(self.xmlFile, "vehicle.toggleLightsSound") then |
93 | useDefaultSample = false; |
94 | local customFile = getXMLString(self.xmlFile, "vehicle.toggleLightsSound#file"); |
95 | local customFilename = Utils.getFilename(customFile, self.baseDirectory); |
96 | useDefaultSample = g_currentMission.defaultVehicleSounds[customFilename] ~= nil; |
97 | end |
98 | if not useDefaultSample then |
99 | self.sampleToggleLights = SoundUtil.loadSample(self.xmlFile, {}, "vehicle.toggleLightsSound", nil, self.baseDirectory); |
100 | end |
101 | |
102 | self.steerableGroundFlag = self:getNextDirtyFlag(); |
103 | |
104 | g_currentMission:addInteractiveVehicle(self); |
105 | end |
128 | function Steerable:loadSettingsFromXML(xmlFile) |
129 | self.enterReferenceNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.enterReferenceNode#index")); |
130 | self.exitPoint = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.exitPoint#index")); |
131 | |
132 | self.interactionRadius = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.enterReferenceNode#interactionRadius"), 6.0) |
133 | |
134 | self.vehicleCharacter = VehicleCharacter:new(self); |
135 | if not self.vehicleCharacter:load(xmlFile, "vehicle.characterNode") then |
136 | self.vehicleCharacter = nil |
137 | end |
138 | |
139 | self.nicknameRenderNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.nicknameRenderNode#index")); |
140 | self.nicknameRenderNodeOffset = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.nicknameRenderNode#offset"), 3); |
141 | if self.nicknameRenderNode == nil then |
142 | if self.vehicleCharacter ~= nil and self.vehicleCharacter.characterDistanceRefNode ~= nil then |
143 | self.nicknameRenderNode = self.vehicleCharacter.characterDistanceRefNode; |
144 | if self.nicknameRenderNodeOffset == nil then |
145 | self.nicknameRenderNodeOffset = {0,1.5,0}; |
146 | end |
147 | else |
148 | self.nicknameRenderNode = self.components[1].node; |
149 | end |
150 | end |
151 | if self.nicknameRenderNodeOffset == nil then |
152 | self.nicknameRenderNodeOffset = {0,4,0}; |
153 | end |
154 | |
155 | self.enterAnimation = getXMLString(xmlFile, "vehicle.enterAnimation#name"); |
156 | end |
234 | function Steerable:update(dt) |
235 | if self:getIsActive() then |
236 | |
237 | if InputBinding.hasEvent(InputBinding.ENTER) and self.isEntered and not g_gui:getIsGuiVisible() then |
238 | g_currentMission:onLeaveVehicle(); |
239 | end |
240 | |
241 | if InputBinding.hasEvent(InputBinding.RESET_HEAD_TRACKING) then |
242 | centerHeadTracking(); |
243 | end |
244 | |
245 | if self.isClient then |
246 | if self.steering ~= nil then |
247 | local rotTime = self.rotatedTime; |
248 | local baseRotation = self.steeringSpeed; |
249 | |
250 | if g_currentMission.controlledVehicle ~= self or not self.activeCamera.isInside then |
251 | baseRotation = Utils.getNoNil(self.steeringOutdoorRotation, self.steeringSpeed); |
252 | else |
253 | baseRotation = Utils.getNoNil(self.steeringIndoorRotation, self.steeringSpeed); |
254 | end |
255 | |
256 | if self.steeringOutdoorRotation ~= nil or self.steeringIndoorRotation ~= nil then |
257 | if self.rotatedTime > 0 then |
258 | rotTime = self.rotatedTime/math.abs(self.maxRotTime); |
259 | else |
260 | rotTime = self.rotatedTime/math.abs(self.minRotTime); |
261 | end |
262 | end |
263 | |
264 | local rotation = baseRotation * rotTime; |
265 | |
266 | if self.steeringLastRotation ~= rotation then |
267 | self.steeringLastRotation = rotation; |
268 | setRotation(self.steering, 0, rotation*Utils.getNoNil(self.reverserDirection, 1), 0); |
269 | |
270 | if self.vehicleCharacter ~= nil then |
271 | self.vehicleCharacter:setDirty(); |
272 | end |
273 | end |
274 | end |
275 | |
276 | if self.isEntered and self.vehicleCharacter ~= nil then |
277 | if self.vehicleCharacter.characterSpineNode ~= nil and self.vehicleCharacter.characterSpineSpeedDepended then |
278 | self.vehicleCharacter:setSpineDirty(self.lastSpeedAcceleration); |
279 | end |
280 | end |
281 | end |
282 | end |
283 | |
284 | if self.isEntered and self.isClient then |
285 | if not g_currentMission.hasSpecialCamera then |
286 | setCamera(self.activeCamera.cameraNode); |
287 | end |
288 | self.activeCamera:update(dt); |
289 | |
290 | -- do all the input handling |
291 | local activeForInput = true; |
292 | if g_gui:getIsGuiVisible() or g_currentMission.isPlayerFrozen then |
293 | activeForInput = false; |
294 | end |
295 | |
296 | if activeForInput then |
297 | |
298 | if InputBinding.hasEvent(InputBinding.CAMERA_SWITCH) then |
299 | self:setActiveCameraIndex(self.camIndex + 1); |
300 | end |
301 | |
302 | -- camera zoom is handled here, otherwise the controls are laggy or don't work at all |
303 | if self.activeCamera.allowTranslation then |
304 | if InputBinding.isPressed(InputBinding.CAMERA_ZOOM_IN) then |
305 | if InputBinding.getInputTypeOfDigitalAction(InputBinding.CAMERA_ZOOM_IN) == InputBinding.INPUTTYPE_MOUSE_WHEEL then |
306 | self.activeCamera:zoomSmoothly(-0.6); |
307 | else |
308 | self.activeCamera:zoomSmoothly(-0.005*dt); |
309 | end |
310 | elseif InputBinding.isPressed(InputBinding.CAMERA_ZOOM_OUT) then |
311 | if InputBinding.getInputTypeOfDigitalAction(InputBinding.CAMERA_ZOOM_OUT) == InputBinding.INPUTTYPE_MOUSE_WHEEL then |
312 | self.activeCamera:zoomSmoothly(0.6); |
313 | else |
314 | self.activeCamera:zoomSmoothly(0.005*dt); |
315 | end |
316 | end |
317 | end |
318 | |
319 | if self:canToggleLight() then |
320 | |
321 | local playSound = false; |
322 | |
323 | if self.numLightTypes >= 1 and InputBinding.hasEvent(InputBinding.TOGGLE_LIGHT_FRONT) then |
324 | playSound = true; |
325 | local lightsTypesMask = bitXOR(self.lightsTypesMask, 2^0); |
326 | self:setLightsTypesMask(lightsTypesMask); |
327 | elseif InputBinding.hasEvent(InputBinding.TOGGLE_LIGHTS) then |
328 | Steerable.setNextLightsState(self); |
329 | end |
330 | |
331 | if self.numLightTypes >= 2 then |
332 | -- work light back has light type 1 |
333 | if InputBinding.hasEvent(InputBinding.TOGGLE_WORK_LIGHT_BACK) then |
334 | playSound = true; |
335 | local lightsTypesMask = bitXOR(self.lightsTypesMask, 2^1); |
336 | self:setLightsTypesMask(lightsTypesMask); |
337 | end |
338 | |
339 | if self.numLightTypes >= 3 then |
340 | -- work light front has light type 2 |
341 | if InputBinding.hasEvent(InputBinding.TOGGLE_WORK_LIGHT_FRONT) then |
342 | playSound = true; |
343 | local lightsTypesMask = bitXOR(self.lightsTypesMask, 2^2); |
344 | self:setLightsTypesMask(lightsTypesMask); |
345 | end |
346 | |
347 | if self.numLightTypes >= 4 then |
348 | -- work light has light type 3 |
349 | if InputBinding.hasEvent(InputBinding.TOGGLE_HIGH_BEAM_LIGHT) then |
350 | playSound = true; |
351 | local lightsTypesMask = bitXOR(self.lightsTypesMask, 2^3); |
352 | self:setLightsTypesMask(lightsTypesMask); |
353 | end |
354 | end |
355 | end |
356 | end |
357 | |
358 | if playSound then |
359 | if self.sampleToggleLights ~= nil then |
360 | SoundUtil.playSample(self.sampleToggleLights, 1, 0, 1); |
361 | else |
362 | SoundUtil.playSample(g_currentMission.sampleToggleLights, 1, 0, 1); |
363 | end |
364 | end |
365 | |
366 | end |
367 | |
368 | end |
369 | |
370 | -- update character visiblity |
371 | if self.vehicleCharacter ~= nil then |
372 | self.vehicleCharacter:updateVisibility(); |
373 | end |
374 | end |
375 | end |
422 | function Steerable:enterVehicle(isControlling, playerIndex, playerColorIndex) |
423 | self.isControlled = true; |
424 | if isControlling then |
425 | self.isEntered = true; |
426 | |
427 | -- if head tracking is available we want to use the first indoor camera |
428 | if g_gameSettings:getValue("isHeadTrackingEnabled") and isHeadTrackingAvailable() then |
429 | for i,camera in pairs(self.cameras) do |
430 | if camera.isInside then |
431 | self.camIndex = i; |
432 | break; |
433 | end |
434 | end |
435 | end |
436 | |
437 | if g_gameSettings:getValue("resetCamera") then |
438 | self.camIndex = 1 |
439 | end |
440 | self:setActiveCameraIndex(self.camIndex); |
441 | end |
442 | |
443 | if self.vehicleCharacter ~= nil and not self:getIsHired() then |
444 | self.vehicleCharacter:loadCharacter(PlayerUtil.playerIndexToDesc[playerIndex].xmlFilename, playerColorIndex) |
445 | end |
446 | |
447 | if self.enterAnimation ~= nil and self.playAnimation ~= nil then |
448 | self:playAnimation(self.enterAnimation, 1, nil, true); |
449 | end |
450 | |
451 | self.playerIndex = playerIndex |
452 | self.playerColorIndex = playerColorIndex |
453 | |
454 | g_currentMission.controlledVehicles[self] = self; |
455 | self:onEnter(isControlling) |
456 | |
457 | if self.isServer and not isControlling and g_currentMission.trafficSystem ~= nil and g_currentMission.trafficSystem.trafficSystemId ~= 0 then |
458 | addTrafficSystemPlayer(g_currentMission.trafficSystem.trafficSystemId, self.components[1].node); |
459 | end |
460 | end |
464 | function Steerable:leaveVehicle() |
465 | if self.isServer and not self.isEntered and g_currentMission.trafficSystem ~= nil and g_currentMission.trafficSystem.trafficSystemId ~= 0 then |
466 | removeTrafficSystemPlayer(g_currentMission.trafficSystem.trafficSystemId, self.components[1].node); |
467 | end |
468 | |
469 | g_currentMission.interactionBlockTimer = 500 |
470 | self:onLeave() |
471 | |
472 | self.isControlled = false; |
473 | if self.activeCamera ~= nil and self.isEntered then |
474 | self.activeCamera:onDeactivate(); |
475 | SoundUtil.onCameraSwitched(false); |
476 | end |
477 | if self.vehicleCharacter ~= nil and self.disableCharacterOnLeave then |
478 | self.vehicleCharacter:delete() |
479 | end |
480 | |
481 | if self:getDeactivateOnLeave() then |
482 | self:onDeactivate(); |
483 | else |
484 | self:onDeactivateSounds(); |
485 | end |
486 | |
487 | if self.enterAnimation ~= nil and self.playAnimation ~= nil then |
488 | self:playAnimation(self.enterAnimation, -1, nil, true); |
489 | end |
490 | |
491 | self.currentLightState = 0 |
492 | |
493 | self.isEntered = false; |
494 | g_currentMission.controlledVehicles[self] = nil; |
495 | end |
524 | function Steerable.setNextLightsState(self) |
525 | if self.lightStates ~= nil and #self.lightStates > 0 then |
526 | if self.sampleToggleLights ~= nil then |
527 | SoundUtil.playSample(self.sampleToggleLights, 1, 0, 1); |
528 | else |
529 | SoundUtil.playSample(g_currentMission.sampleToggleLights, 1, 0, 1); |
530 | end |
531 | |
532 | local currentLightState = self.currentLightState + 1; |
533 | if currentLightState > #self.lightStates or (self.currentLightState == 0 and self.lightsTypesMask > 0) then |
534 | currentLightState = 0; |
535 | end |
536 | |
537 | local lightsTypesMask = 0; |
538 | if currentLightState > 0 then |
539 | for _, lightType in pairs(self.lightStates[currentLightState]) do |
540 | lightsTypesMask = bitOR(lightsTypesMask, 2^lightType); |
541 | end |
542 | end |
543 | |
544 | self:setLightsTypesMask(lightsTypesMask); |
545 | self.currentLightState = currentLightState; |
546 | end |
547 | end |