Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
1 | -- |
2 | -- Motorized |
3 | -- Desc |
4 | -- |
5 | -- @author Stefan Geiger |
6 | -- @date 30/11/08 |
7 | -- |
8 | -- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved. |
9 | |
10 | Motorized = {}; |
11 | source("dataS/scripts/vehicles/specializations/SteerableToggleRefuelEvent.lua"); |
12 | source("dataS/scripts/vehicles/specializations/SetMotorTurnedOnEvent.lua"); |
13 | |
14 | |
15 | function Motorized.prerequisitesPresent(specializations) |
16 | return true; |
17 | end; |
18 | |
19 | function Motorized:load(xmlFile) |
20 | |
21 | self.startMotor = SpecializationUtil.callSpecializationsFunction("startMotor"); |
22 | self.stopMotor = SpecializationUtil.callSpecializationsFunction("stopMotor"); |
23 | self.setIsFuelFilling = SpecializationUtil.callSpecializationsFunction("setIsFuelFilling"); |
24 | self.setFuelFillLevel = SpecializationUtil.callSpecializationsFunction("setFuelFillLevel"); |
25 | self.addFuelFillTrigger = Motorized.addFuelFillTrigger; |
26 | self.removeFuelFillTrigger = Motorized.removeFuelFillTrigger; |
27 | |
28 | self.fuelCapacity = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.fuelCapacity"), 500); |
29 | local fuelUsage = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.fuelUsage"), 1); |
30 | if fuelUsage < 1 then |
31 | print("Warning: New unit for fuel usage is l/h at max. rpm"); |
32 | end; |
33 | self.fuelUsage = fuelUsage / (60*60*1000); -- from l/h to l/ms |
34 | |
35 | self.motorizedFillActivatable = MotorizedRefuelActivatable:new(self); |
36 | |
37 | self.fuelFillTriggers = {}; |
38 | self.isFuelFilling = false; |
39 | self.fuelFillLitersPerSecond = 10; |
40 | self:setFuelFillLevel(self.fuelCapacity); |
41 | self.sentFuelFillLevel = self.fuelFillLevel; |
42 | |
43 | self.stopMotorOnLeave = true; |
44 | |
45 | self.motorizedNode = nil; |
46 | for _, component in pairs(self.components) do |
47 | if component.motorized then |
48 | self.motorizedNode = component.node; |
49 | break; |
50 | end |
51 | end |
52 | |
53 | Motorized.loadDifferentials(self, xmlFile); |
54 | Motorized.loadMotor(self, xmlFile); |
55 | |
56 | if self.isClient then |
57 | self.sampleRefuel = Utils.loadSample(xmlFile, {}, "vehicle.refuelSound", "$data/maps/sounds/refuel.wav", self.baseDirectory, self.components[1].node); |
58 | self.sampleMotorStart = Utils.loadSample(xmlFile, {}, "vehicle.motorStartSound", nil, self.baseDirectory); |
59 | self.sampleMotorStop = Utils.loadSample(xmlFile, {}, "vehicle.motorStopSound", nil, self.baseDirectory); |
60 | self.sampleMotor = Utils.loadSample(xmlFile, {}, "vehicle.motorSound", nil, self.baseDirectory, self.components[1].node); |
61 | self.sampleMotorRun = Utils.loadSample(xmlFile, {}, "vehicle.motorSoundRun", nil, self.baseDirectory, self.components[1].node); |
62 | self.sampleMotorRun2 = Utils.loadSample(xmlFile, {}, "vehicle.motorSoundRun2", nil, self.baseDirectory, self.components[1].node); |
63 | self.sampleReverseDrive = Utils.loadSample(xmlFile, {}, "vehicle.reverseDriveSound", nil, self.baseDirectory); |
64 | self.sampleCompressedAir = Utils.loadSample(xmlFile, {}, "vehicle.compressedAirSound", nil, self.baseDirectory); |
65 | self.sampleCompression = Utils.loadSample(xmlFile, {}, "vehicle.compressionSound", nil, self.baseDirectory); |
66 | |
67 | self.motorRun2PitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorSoundRun2#pitchMax"), 2.0); |
68 | self.motorRun2VolumeMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorSoundRun2#volumeMax"), 2.0); |
69 | |
70 | self.motorSoundPitchScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorSound#pitchScale"), 0.05); |
71 | self.motorSoundPitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorSound#pitchMax"), 2.0); |
72 | self.motorSoundRunPitchScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorSoundRun#pitchScale"), 0.05); |
73 | self.motorSoundRunPitchMax = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorSoundRun#pitchMax"), 2.0); |
74 | self.compressedAirSoundEnabled = false; |
75 | self.compressionSoundTime = 0; |
76 | |
77 | self.exhaustParticleSystems = {}; |
78 | local exhaustParticleSystemCount = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.exhaustParticleSystems#count"), 0); |
79 | for i=1, exhaustParticleSystemCount do |
80 | local namei = string.format("vehicle.exhaustParticleSystems.exhaustParticleSystem%d", i); |
81 | |
82 | Utils.loadParticleSystem(xmlFile, self.exhaustParticleSystems, namei, self.components, false, nil, self.baseDirectory) |
83 | self.exhaustParticleSystems.minScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.exhaustParticleSystems#minScale"), 0.5); |
84 | self.exhaustParticleSystems.maxScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.exhaustParticleSystems#maxScale"), 1); |
85 | for _, v in ipairs(self.exhaustParticleSystems) do |
86 | v.originalLifespan = getParticleSystemLifespan(v.geometry); |
87 | end; |
88 | end; |
89 | |
90 | local exhaustFlapIndex = getXMLString(xmlFile, "vehicle.exhaustFlap#index"); |
91 | if exhaustFlapIndex ~= nil then |
92 | self.exhaustFlap = {}; |
93 | self.exhaustFlap.node = Utils.indexToObject(self.components, exhaustFlapIndex); |
94 | self.exhaustFlap.maxRot = Utils.degToRad(Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.exhaustFlap#maxRot"),0)); |
95 | end; |
96 | |
97 | self.exhaustEffects = {}; |
98 | Motorized.loadExhaustEffects(self, xmlFile, self.exhaustEffects); |
99 | if table.getn(self.exhaustEffects) == 0 then |
100 | self.exhaustEffects = nil; |
101 | end; |
102 | end; |
103 | |
104 | self.motorStartDuration = 0; |
105 | if self.sampleMotorStart ~= nil then |
106 | self.motorStartDuration = self.sampleMotorStart.duration; |
107 | end; |
108 | self.motorStartDuration = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motorStartDuration"), self.motorStartDuration); |
109 | self.motorStartTime = 0; |
110 | self.isMotorStarted = false; |
111 | self.lastRoundPerMinute = 0; |
112 | self.motorizedDirtyFlag = self:getNextDirtyFlag(); |
113 | end; |
114 | |
115 | function Motorized:loadExhaustEffects(xmlFile, exhaustEffects) |
116 | local i = 0; |
117 | while true do |
118 | local key = string.format("vehicle.exhaustEffects.exhaustEffect(%d)", i); |
119 | if not hasXMLProperty(xmlFile, key) then |
120 | break; |
121 | end |
122 | local filename = getXMLString(xmlFile, key .. "#filename"); |
123 | if filename ~= nil then |
124 | local effect = {}; |
125 | if exhaustEffects ~= nil and exhaustEffects[i+1] ~= nil then |
126 | effect = exhaustEffects[i+1]; |
127 | else |
128 | effect.node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
129 | effect.filename = filename; |
130 | local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false); |
131 | if i3dNode ~= 0 then |
132 | effect.effectNode = getChildAt(i3dNode, 0); |
133 | link(effect.node, effect.effectNode); |
134 | setVisibility(effect.effectNode, false); |
135 | delete(i3dNode); |
136 | table.insert(exhaustEffects, effect); |
137 | end; |
138 | end; |
139 | |
140 | effect.minRpmColor = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#minRpmColor"), "0 0 0 1"), 4); |
141 | effect.maxRpmColor = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#maxRpmColor"), "0.0384 0.0359 0.0627 2.0"), 4); |
142 | effect.minRpmScale = Utils.getNoNil(getXMLFloat(xmlFile, key.."#minRpmScale"), 0.25); |
143 | effect.maxRpmScale = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxRpmScale"), 0.95); |
144 | effect.maxForwardSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxForwardSpeed"), math.ceil(self.motor:getMaximumForwardSpeed()*3.6)); |
145 | effect.maxBackwardSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxBackwardSpeed"), math.ceil(self.motor:getMaximumBackwardSpeed()*3.6)); |
146 | effect.forwardXRotations = Utils.getRadiansFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#forwardXRotations"), "-250 0 250"), 3); |
147 | effect.forwardZRotations = Utils.getRadiansFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#forwardZRotations"), "0 0 0"), 3); |
148 | effect.steerXRotations = Utils.getRadiansFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#steerXRotations"), "0 0 0"), 3); |
149 | effect.steerZRotations = Utils.getRadiansFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#steerZRotations"), "120 0 -120"), 3); |
150 | effect.xRot = 0; |
151 | effect.zRot = 0; |
152 | end; |
153 | i = i + 1; |
154 | end; |
155 | self.exhaustEffectMaxSteeringSpeed = 0.001; |
156 | end; |
157 | |
158 | function Motorized:loadDifferentials(xmlFile) |
159 | self.differentials = {}; |
160 | if self.isServer and self.motorizedNode ~= nil then |
161 | local i = 0; |
162 | while true do |
163 | local key = string.format("vehicle.differentials.differential(%d)", i); |
164 | if not hasXMLProperty(xmlFile, key) then |
165 | break; |
166 | end; |
167 | local torqueRatio = Utils.getNoNil(getXMLFloat(xmlFile, key.."#torqueRatio"), 0.5); |
168 | local maxSpeedRatio = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxSpeedRatio"), 1.3); |
169 | |
170 | local diffIndex1, diffIndex1IsWheel; |
171 | local diffIndex2, diffIndex2IsWheel; |
172 | |
173 | local wheelIndex1 = getXMLInt(xmlFile, key.."#wheelIndex1"); |
174 | if wheelIndex1 ~= nil then |
175 | local wheel = self.wheels[wheelIndex1+1]; |
176 | if wheel ~= nil then |
177 | diffIndex1IsWheel = true; |
178 | diffIndex1 = wheel.wheelShape; |
179 | end |
180 | else |
181 | diffIndex1IsWheel = false; |
182 | diffIndex1 = getXMLInt(xmlFile, key.."#differentialIndex1"); |
183 | end |
184 | local wheelIndex2 = getXMLInt(xmlFile, key.."#wheelIndex2"); |
185 | if wheelIndex2 ~= nil then |
186 | local wheel = self.wheels[wheelIndex2+1]; |
187 | if wheel ~= nil then |
188 | diffIndex2IsWheel = true; |
189 | diffIndex2 = wheel.wheelShape; |
190 | end |
191 | else |
192 | diffIndex2IsWheel = false; |
193 | diffIndex2 = getXMLInt(xmlFile, key.."#differentialIndex2"); |
194 | end |
195 | if diffIndex1 ~= nil and diffIndex2 ~= nil then |
196 | table.insert(self.differentials, {torqueRatio=torqueRatio, maxSpeedRatio=maxSpeedRatio, diffIndex1=diffIndex1, diffIndex1IsWheel=diffIndex1IsWheel, diffIndex2=diffIndex2, diffIndex2IsWheel=diffIndex2IsWheel}); |
197 | addDifferential(self.motorizedNode, diffIndex1, diffIndex1IsWheel, diffIndex2, diffIndex2IsWheel, torqueRatio, maxSpeedRatio); |
198 | else |
199 | print("Error: Invalid differential indices in '"..self.configFileName.."'"); |
200 | end |
201 | |
202 | i = i + 1; |
203 | end |
204 | end |
205 | end |
206 | |
207 | function Motorized:loadMotor(xmlFile) |
208 | |
209 | local motorMinRpm = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#minRpm"), 1000); |
210 | local motorMaxRpm = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#maxRpm"), 1800); |
211 | local maxForwardSpeed = getXMLFloat(xmlFile, "vehicle.motor#maxForwardSpeed"); |
212 | local maxBackwardSpeed = getXMLFloat(xmlFile, "vehicle.motor#maxBackwardSpeed"); |
213 | if maxForwardSpeed ~= nil then |
214 | maxForwardSpeed = maxForwardSpeed/3.6; |
215 | end |
216 | if maxBackwardSpeed ~= nil then |
217 | maxBackwardSpeed = maxBackwardSpeed/3.6; |
218 | end |
219 | local brakeForce = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#brakeForce"), 10)*2; |
220 | local lowBrakeForceScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#lowBrakeForceScale"), 0.5); |
221 | local lowBrakeForceSpeedLimit = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#lowBrakeForceSpeedLimit"), 20)/3600; |
222 | local forwardGearRatio = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#forwardGearRatio"), 2); |
223 | local backwardGearRatio = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#backwardGearRatio"), 1.5); |
224 | local maxForwardGearRatio = getXMLFloat(xmlFile, "vehicle.motor#maxForwardGearRatio"); |
225 | local minForwardGearRatio = getXMLFloat(xmlFile, "vehicle.motor#minForwardGearRatio"); |
226 | local maxBackwardGearRatio = getXMLFloat(xmlFile, "vehicle.motor#maxBackwardGearRatio"); |
227 | local minBackwardGearRatio = getXMLFloat(xmlFile, "vehicle.motor#minBackwardGearRatio"); |
228 | local rpmFadeOutRange = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#rpmFadeOutRange"), 20); |
229 | local torqueScale = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#torqueScale"), 1); |
230 | local ptoMotorRpmRatio = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.motor#ptoMotorRpmRatio"), 4); |
231 | local maxTorque = 0; |
232 | local torqueCurve = AnimCurve:new(linearInterpolator1); |
233 | local torqueI = 0; |
234 | while true do |
235 | local key = string.format("vehicle.motor.torque(%d)", torqueI); |
236 | local normRpm = getXMLFloat(xmlFile, key.."#normRpm"); |
237 | local rpm; |
238 | if normRpm == nil then |
239 | rpm = getXMLFloat(xmlFile, key.."#rpm"); |
240 | else |
241 | rpm = normRpm * motorMaxRpm; |
242 | end |
243 | local torque = getXMLFloat(xmlFile, key.."#torque"); |
244 | if torque == nil or rpm == nil then |
245 | break; |
246 | end; |
247 | torqueCurve:addKeyframe({v=torque*torqueScale, time = rpm}); |
248 | torqueI = torqueI +1; |
249 | if torque*torqueScale > maxTorque then |
250 | maxTorque = torque*torqueScale; |
251 | end; |
252 | end; |
253 | self.motor = VehicleMotor:new(self, motorMinRpm, motorMaxRpm, maxForwardSpeed, maxBackwardSpeed, torqueCurve, brakeForce, forwardGearRatio, backwardGearRatio, minForwardGearRatio, maxForwardGearRatio, minBackwardGearRatio, maxBackwardGearRatio, ptoMotorRpmRatio, rpmFadeOutRange, maxTorque); |
254 | self.motor:setLowBrakeForce(lowBrakeForceScale, lowBrakeForceSpeedLimit); |
255 | end; |
256 | |
257 | function Motorized:delete() |
258 | |
259 | for _, trigger in pairs(self.fuelFillTriggers) do |
260 | trigger:onVehicleDeleted(self); |
261 | end |
262 | |
263 | g_currentMission:removeActivatableObject(self.motorizedFillActivatable); |
264 | |
265 | if self.isClient then |
266 | if self.exhaustEffects ~= nil then |
267 | for _, effect in pairs(self.exhaustEffects) do |
268 | Utils.releaseSharedI3DFile(effect.filename, self.baseDirectory, true); |
269 | end; |
270 | end; |
271 | |
272 | Utils.deleteParticleSystem(self.exhaustParticleSystems); |
273 | Utils.deleteSample(self.sampleRefuel); |
274 | Utils.deleteSample(self.sampleCompressedAir); |
275 | Utils.deleteSample(self.sampleCompression); |
276 | Utils.deleteSample(self.sampleMotor); |
277 | Utils.deleteSample(self.sampleMotorRun); |
278 | Utils.deleteSample(self.sampleMotorRun2); |
279 | Utils.deleteSample(self.sampleMotorStart); |
280 | Utils.deleteSample(self.sampleMotorStop); |
281 | Utils.deleteSample(self.sampleReverseDrive); |
282 | end; |
283 | end; |
284 | |
285 | function Motorized:readStream(streamId, connection) |
286 | local isMotorStarted = streamReadBool(streamId); |
287 | if isMotorStarted then |
288 | self:startMotor(true); |
289 | else |
290 | self:stopMotor(true); |
291 | end; |
292 | local isFuelFilling = streamReadBool(streamId); |
293 | self:setIsFuelFilling(isFuelFilling, true); |
294 | |
295 | local newFuelFillLevel=streamReadFloat32(streamId); |
296 | self:setFuelFillLevel(newFuelFillLevel); |
297 | end; |
298 | |
299 | function Motorized:writeStream(streamId, connection) |
300 | streamWriteBool(streamId, self.isMotorStarted); |
301 | streamWriteBool(streamId, self.isFuelFilling); |
302 | streamWriteFloat32(streamId, self.fuelFillLevel); |
303 | end; |
304 | |
305 | function Motorized:readUpdateStream(streamId, timestamp, connection) |
306 | if connection.isServer then |
307 | --self.motor.lastMotorRpm = streamReadFloat32(streamId); |
308 | local rpm = streamReadUIntN(streamId, 11); |
309 | rpm = rpm / 2047; |
310 | local rpmRange = self.motor.maxRpm - self.motor.minRpm; |
311 | self.motor.lastMotorRpm = (rpm * rpmRange) + self.motor.minRpm ; |
312 | |
313 | if streamReadBool(streamId) then |
314 | local fuelFillLevel = streamReadUIntN(streamId, 15)/32767*self.fuelCapacity; |
315 | self:setFuelFillLevel(fuelFillLevel); |
316 | end; |
317 | end; |
318 | end; |
319 | |
320 | function Motorized:writeUpdateStream(streamId, connection, dirtyMask) |
321 | if not connection.isServer then |
322 | --streamWriteFloat32(streamId, self.motor.lastMotorRpm); |
323 | local rpmRange = self.motor.maxRpm - self.motor.minRpm; |
324 | local rpm = (self.motor.lastMotorRpm - self.motor.minRpm) / rpmRange; |
325 | rpm = math.floor(rpm * 2047); |
326 | streamWriteUIntN(streamId, rpm, 11); |
327 | |
328 | if streamWriteBool(streamId, bitAND(dirtyMask, self.motorizedDirtyFlag) ~= 0) then |
329 | local percent = 0; |
330 | if self.fuelCapacity ~= 0 then |
331 | percent = Utils.clamp(self.fuelFillLevel / self.fuelCapacity, 0, 1); |
332 | end; |
333 | streamWriteUIntN(streamId, math.floor(percent*32767), 15); |
334 | end; |
335 | end; |
336 | end; |
337 | |
338 | function Motorized:loadFromAttributesAndNodes(xmlFile, key, resetVehicles) |
339 | local fuelFillLevel = getXMLFloat(xmlFile, key.."#fuelFillLevel"); |
340 | if fuelFillLevel ~= nil then |
341 | if self.fuelCapacity ~= 0 then |
342 | local minFuelFillLevel = 0.1*self.fuelCapacity |
343 | local numToRefill = math.max(minFuelFillLevel - fuelFillLevel, 0); |
344 | if numToRefill > 0 then |
345 | fuelFillLevel = minFuelFillLevel; |
346 | local delta = numToRefill * g_fuelPricePerLiter; |
347 | g_currentMission.missionStats:updateStats("expenses", delta); |
348 | g_currentMission:addSharedMoney(-delta, "vehicleRunningCost"); |
349 | end; |
350 | end; |
351 | self:setFuelFillLevel(fuelFillLevel); |
352 | end; |
353 | return BaseMission.VEHICLE_LOAD_OK; |
354 | end; |
355 | |
356 | function Motorized:getSaveAttributesAndNodes(nodeIdent) |
357 | local attributes = 'fuelFillLevel="'..self.fuelFillLevel..'"'; |
358 | return attributes, nil; |
359 | end; |
360 | |
361 | function Motorized:mouseEvent(posX, posY, isDown, isUp, button) |
362 | end; |
363 | |
364 | function Motorized:keyEvent(unicode, sym, modifier, isDown) |
365 | end; |
366 | |
367 | function Motorized:update(dt) |
368 | |
369 | if self.isMotorStarted then |
370 | local accInput = 0; |
371 | if self.axisForward ~= nil then |
372 | accInput = -self.axisForward; |
373 | end; |
374 | if self.cruiseControl ~= nil and self.cruiseControl.state ~= Drivable.CRUISECONTROL_STATE_OFF then |
375 | accInput = 1; |
376 | end |
377 | if self.isClient then |
378 | if self:getIsActiveForSound() then |
379 | if not Utils.isSamplePlaying(self.sampleMotorStart, 1.5*dt) then |
380 | Utils.playSample(self.sampleMotor, 0, 0, nil); |
381 | Utils.playSample(self.sampleMotorRun, 0, 0, 0); |
382 | Utils.playSample(self.sampleMotorRun2, 0, 0, 0); |
383 | end; |
384 | |
385 | if self.compressionSoundTime <= g_currentMission.time then |
386 | Utils.playSample(self.sampleCompression, 1, 0, nil); |
387 | self.compressionSoundTime = g_currentMission.time + 180000; |
388 | end |
389 | |
390 | Utils.stop3DSample(self.sampleMotor); |
391 | Utils.stop3DSample(self.sampleMotorRun); |
392 | Utils.stop3DSample(self.sampleMotorRun2); |
393 | else |
394 | Utils.play3DSample(self.sampleMotor); |
395 | Utils.play3DSample(self.sampleMotorRun); |
396 | Utils.play3DSample(self.sampleMotorRun2); |
397 | end; |
398 | |
399 | if self.sampleReverseDrive.sample ~= nil then |
400 | if (accInput < 0 or accInput == 0) and (self:getLastSpeed() > 3 and self.movingDirection ~= self.reverserDirection) then |
401 | if self:getIsActiveForSound() then |
402 | Utils.playSample(self.sampleReverseDrive, 0, 0, nil); |
403 | end; |
404 | else |
405 | Utils.stopSample(self.sampleReverseDrive); |
406 | end; |
407 | end; |
408 | end; |
409 | |
410 | if table.getn(self.wheels) > 0 then |
411 | local minRpm = self.motor.minRpm; |
412 | local maxRpm = self.motor.maxRpm; |
413 | |
414 | local maxSpeed; |
415 | if self.movingDirection >= 0 then |
416 | maxSpeed = self.motor.maxForwardSpeed*0.001; |
417 | else |
418 | maxSpeed = self.motor.maxBackwardSpeed*0.001; |
419 | end |
420 | |
421 | -- The actual rpm offset is 80% from the motor and 20% from the speed |
422 | local targetRpmOffset = (self.motor.lastMotorRpm-minRpm)*0.8 + math.min(self.lastSpeed/maxSpeed, 1)*(maxRpm-minRpm)*0.2; |
423 | |
424 | local alpha = math.pow(0.01, dt*0.001); |
425 | local roundPerMinute = targetRpmOffset + alpha*(self.lastRoundPerMinute-targetRpmOffset); |
426 | self.lastRoundPerMinute = roundPerMinute; |
427 | local roundPerSecond = roundPerMinute / 60; |
428 | if self.isClient then |
429 | local motorSoundPitch = math.min(self.sampleMotor.pitchOffset + self.motorSoundPitchScale*math.abs(roundPerSecond), self.motorSoundPitchMax) |
430 | Utils.setSamplePitch(self.sampleMotor, motorSoundPitch); |
431 | local motorSoundRunPitch = math.min(self.sampleMotorRun.pitchOffset + self.motorSoundRunPitchScale*math.abs(roundPerSecond), self.motorSoundRunPitchMax) |
432 | Utils.setSamplePitch(self.sampleMotorRun, motorSoundRunPitch); |
433 | |
434 | local rpmRunVolume = math.abs(roundPerMinute)/(maxRpm - minRpm); |
435 | if math.abs(accInput) < 0.01 then |
436 | rpmRunVolume = rpmRunVolume * 0.666; |
437 | end; |
438 | rpmRunVolume = Utils.clamp(rpmRunVolume, 0.0, 1.0); |
439 | Utils.setSampleVolume(self.sampleMotorRun, rpmRunVolume*self.sampleMotorRun.volume); |
440 | |
441 | local speedFactor = Utils.clamp(self:getLastSpeed() / math.ceil(self.motor:getMaximumForwardSpeed()*3.6), 0, 1); |
442 | local pitchRun2 = Utils.lerp(self.sampleMotorRun2.pitchOffset, self.motorRun2PitchMax, speedFactor); |
443 | local volumeRun2 = Utils.lerp(self.sampleMotorRun2.volume, self.motorRun2VolumeMax, speedFactor); |
444 | Utils.setSamplePitch(self.sampleMotorRun2, pitchRun2) |
445 | Utils.setSampleVolume(self.sampleMotorRun2, volumeRun2); |
446 | |
447 | if self.sampleCompressedAir.sample ~= nil then |
448 | |
449 | if self.movingDirection > 0 and self.lastSpeed > self.motor.maxForwardSpeed*0.0005 then -- faster than 50% of max speed |
450 | if accInput > 0.5 then |
451 | -- if we drive fast enough, we need to run the compressor the next time we brake |
452 | self.compressedAirSoundEnabled = false; |
453 | elseif accInput < -0.7 then |
454 | -- play the compressor sound if we drive fast enough and brake |
455 | if not self.compressedAirSoundEnabled then |
456 | if self:getIsActiveForSound() then |
457 | Utils.playSample(self.sampleCompressedAir, 1, 0, nil); |
458 | end; |
459 | self.compressedAirSoundEnabled = true; |
460 | end |
461 | end; |
462 | end |
463 | end |
464 | end; |
465 | end; |
466 | |
467 | if self.isServer then |
468 | if not self:getIsHired() then |
469 | if self.lastMovedDistance > 0 then |
470 | g_currentMission.missionStats:updateStats("traveledDistance", self.lastMovedDistance*0.001); |
471 | end; |
472 | |
473 | local rpmFactor = math.max(0.02, (self.motor.lastMotorRpm-self.motor.minRpm)/(self.motor.maxRpm-self.motor.minRpm)); |
474 | local fuelUsed = rpmFactor * (self.fuelUsage * dt); |
475 | self:setFuelFillLevel(self.fuelFillLevel-fuelUsed); |
476 | if fuelUsed > 0 then |
477 | g_currentMission.missionStats:updateStats("fuelUsage", fuelUsed); |
478 | end; |
479 | end; |
480 | end; |
481 | end; |
482 | end; |
483 | |
484 | function Motorized:updateTick(dt) |
485 | if self.isServer then |
486 | if math.abs(self.fuelFillLevel-self.sentFuelFillLevel) > 0.001 then |
487 | self:raiseDirtyFlags(self.motorizedDirtyFlag); |
488 | self.sentFuelFillLevel = self.fuelFillLevel; |
489 | end; |
490 | end; |
491 | |
492 | if self.isClient then |
493 | if self.isMotorStarted then |
494 | if self.exhaustParticleSystems.minScale ~= nil and self.exhaustParticleSystems.maxScale ~= nil then |
495 | local scale = Utils.lerp(self.exhaustParticleSystems.minScale, self.exhaustParticleSystems.maxScale, self.motor.lastMotorRpm / self.motor.maxRpm); |
496 | Utils.setEmitCountScale(self.exhaustParticleSystems, scale); |
497 | for _, ps in ipairs(self.exhaustParticleSystems) do |
498 | setParticleSystemLifespan(ps.geometry, ps.originalLifespan * scale, true); |
499 | end; |
500 | end; |
501 | |
502 | if self.exhaustFlap ~= nil then |
503 | local minRandom = -0.1; |
504 | local maxRandom = 0.1; |
505 | local angle = Utils.lerp(minRandom, maxRandom, math.random()) + self.exhaustFlap.maxRot * (self.motor.lastMotorRpm / self.motor.maxRpm); |
506 | angle = Utils.clamp(angle, 0, self.exhaustFlap.maxRot); |
507 | setRotation(self.exhaustFlap.node, angle, 0, 0); |
508 | end; |
509 | |
510 | if self.exhaustEffects ~= nil then |
511 | local lastSpeed = self:getLastSpeed(); |
512 | |
513 | self.currentDirection = {localDirectionToWorld(self.rootNode, 0, 0, 1)}; |
514 | if self.lastDirection == nil then |
515 | self.lastDirection = self.currentDirection; |
516 | end; |
517 | |
518 | local x,y,z = worldDirectionToLocal(self.rootNode, self.lastDirection[1], self.lastDirection[2], self.lastDirection[3]); |
519 | local dot = z; |
520 | dot = dot / Utils.vector2Length(x,z); |
521 | local angle = math.acos(dot); |
522 | if x < 0 then |
523 | angle = -angle; |
524 | end; |
525 | local steeringPercent = math.abs((angle / dt) / self.exhaustEffectMaxSteeringSpeed); |
526 | self.lastDirection = self.currentDirection; |
527 | |
528 | for _, effect in pairs(self.exhaustEffects) do |
529 | local rpmScale = self.motor.lastMotorRpm / self.motor.maxRpm; |
530 | local scale = Utils.lerp(effect.minRpmScale, effect.maxRpmScale, rpmScale); |
531 | local forwardXRot = effect.forwardXRotations[2]; |
532 | local forwardZRot = effect.forwardZRotations[2]; |
533 | local steerXRot = effect.steerXRotations[2]; |
534 | local steerZRot = effect.steerZRotations[2]; |
535 | |
536 | local r = Utils.lerp(effect.minRpmColor[1], effect.maxRpmColor[1], rpmScale); |
537 | local g = Utils.lerp(effect.minRpmColor[2], effect.maxRpmColor[2], rpmScale); |
538 | local b = Utils.lerp(effect.minRpmColor[3], effect.maxRpmColor[3], rpmScale); |
539 | local a = Utils.lerp(effect.minRpmColor[4], effect.maxRpmColor[4], rpmScale); |
540 | setShaderParameter(effect.effectNode, "exhaustColor", r, g, b, a, false); |
541 | |
542 | -- speed rotation |
543 | if self.movingDirection == 1 then |
544 | local percent = Utils.clamp(lastSpeed/effect.maxForwardSpeed, 0, 1); |
545 | forwardXRot = effect.forwardXRotations[1] * percent; |
546 | forwardZRot = effect.forwardZRotations[1] * percent; |
547 | elseif self.movingDirection == -1 then |
548 | local percent = Utils.clamp(lastSpeed/effect.maxBackwardSpeed, 0, 1); |
549 | forwardXRot = effect.forwardXRotations[3] * percent; |
550 | forwardZRot = effect.forwardZRotations[3] * percent; |
551 | end; |
552 | |
553 | -- steering rotation |
554 | if angle > 0 then |
555 | steerXRot = effect.steerXRotations[1] * steeringPercent; |
556 | steerZRot = effect.steerZRotations[1] * steeringPercent; |
557 | elseif angle < 0 then |
558 | steerXRot = effect.steerXRotations[3] * steeringPercent; |
559 | steerZRot = effect.steerZRotations[3] * steeringPercent; |
560 | end; |
561 | -- target rotations |
562 | local targetXRot = forwardXRot + steerXRot; |
563 | local targetZRot = forwardZRot + steerZRot; |
564 | |
565 | -- damping |
566 | if targetXRot > effect.xRot then |
567 | effect.xRot = math.min(effect.xRot + 0.003*dt, targetXRot); |
568 | else |
569 | effect.xRot = math.max(effect.xRot - 0.003*dt, targetXRot); |
570 | end; |
571 | if targetZRot > effect.xRot then |
572 | effect.zRot = math.min(effect.zRot + 0.003*dt, targetZRot); |
573 | else |
574 | effect.zRot = math.max(effect.zRot - 0.003*dt, targetZRot); |
575 | end; |
576 | setShaderParameter(effect.effectNode, "param", effect.xRot, effect.zRot, 0, scale, false); |
577 | end; |
578 | end; |
579 | end; |
580 | end; |
581 | |
582 | if self.isFuelFilling then |
583 | if self.isServer then |
584 | local delta = 0; |
585 | if self.fuelFillTrigger ~= nil then |
586 | delta = self.fuelFillLitersPerSecond*dt*0.001; |
587 | delta = self.fuelFillTrigger:fillFuel(self, delta); |
588 | end |
589 | if delta <= 0.001 then |
590 | self:setIsFuelFilling(false); |
591 | end |
592 | end |
593 | end; |
594 | end; |
595 | |
596 | function Motorized:draw() |
597 | end; |
598 | |
599 | function Motorized:startMotor(noEventSend) |
600 | if noEventSend == nil or noEventSend == false then |
601 | if g_server ~= nil then |
602 | g_server:broadcastEvent(SetMotorTurnedOnEvent:new(self, true), nil, nil, self); |
603 | else |
604 | g_client:getServerConnection():sendEvent(SetMotorTurnedOnEvent:new(self, true)); |
605 | end; |
606 | end; |
607 | if not self.isMotorStarted then |
608 | self.isMotorStarted = true; |
609 | |
610 | if self.isClient then |
611 | Utils.setEmittingState(self.exhaustParticleSystems, true) |
612 | if self:getIsActiveForSound() then |
613 | Utils.playSample(self.sampleMotorStart, 1, 0, nil); |
614 | end; |
615 | if self.exhaustEffects ~= nil then |
616 | for _, effect in pairs(self.exhaustEffects) do |
617 | setVisibility(effect.effectNode, true); |
618 | effect.xRot = effect.forwardXRotations[2] + effect.steerXRotations[2]; |
619 | effect.zRot = effect.forwardZRotations[2] + effect.steerZRotations[2]; |
620 | setShaderParameter(effect.effectNode, "param", effect.xRot, effect.zRot, 0, 1, false); |
621 | end; |
622 | end; |
623 | end; |
624 | |
625 | self.motorStartTime = g_currentMission.time + self.motorStartDuration; |
626 | self.compressionSoundTime = g_currentMission.time + 180000; |
627 | self.lastRoundPerMinute=0; |
628 | end; |
629 | end; |
630 | |
631 | function Motorized:stopMotor(noEventSend) |
632 | if noEventSend == nil or noEventSend == false then |
633 | if g_server ~= nil then |
634 | g_server:broadcastEvent(SetMotorTurnedOnEvent:new(self, false), nil, nil, self); |
635 | else |
636 | g_client:getServerConnection():sendEvent(SetMotorTurnedOnEvent:new(self, false)); |
637 | end; |
638 | end; |
639 | |
640 | self.isMotorStarted = false; |
641 | |
642 | Motorized.stopSounds(self); |
643 | |
644 | if self.isClient then |
645 | Utils.setEmittingState(self.exhaustParticleSystems, false); |
646 | if self:getIsActiveForSound() then |
647 | Utils.playSample(self.sampleMotorStop, 1, 0, nil); |
648 | end; |
649 | if self.exhaustEffects ~= nil then |
650 | for _, effect in pairs(self.exhaustEffects) do |
651 | setVisibility(effect.effectNode, false); |
652 | end; |
653 | end; |
654 | if self.exhaustFlap ~= nil then |
655 | setRotation(self.exhaustFlap.node, 0, 0, 0); |
656 | end; |
657 | end; |
658 | end; |
659 | |
660 | function Motorized:stopSounds() |
661 | if self.isClient then |
662 | Utils.stopSample(self.sampleMotor, true); |
663 | Utils.stopSample(self.sampleMotorRun, true); |
664 | Utils.stopSample(self.sampleMotorRun2, true); |
665 | Utils.stopSample(self.sampleMotorStart, true); |
666 | Utils.stopSample(self.sampleCompression, true); |
667 | Utils.stopSample(self.sampleReverseDrive, true); |
668 | Utils.stop3DSample(self.sampleMotor, true); |
669 | Utils.stop3DSample(self.sampleMotorRun, true); |
670 | Utils.stop3DSample(self.sampleMotorRun2, true); |
671 | --Utils.stopSample(self.sampleRefuel, true); |
672 | end; |
673 | end; |
674 | |
675 | function Motorized:onLeave() |
676 | if self.stopMotorOnLeave then |
677 | self:stopMotor(true); |
678 | end; |
679 | Motorized.stopSounds(self); |
680 | end |
681 | |
682 | function Motorized:setFuelFillLevel(newFillLevel) |
683 | self.fuelFillLevel = math.max(math.min(newFillLevel, self.fuelCapacity), 0); |
684 | end; |
685 | |
686 | function Motorized:setIsFuelFilling(isFilling, noEventSend) |
687 | if isFilling ~= self.isFuelFilling then |
688 | if noEventSend == nil or noEventSend == false then |
689 | if g_server ~= nil then |
690 | g_server:broadcastEvent(SteerableToggleRefuelEvent:new(self, isFilling), nil, nil, self); |
691 | else |
692 | g_client:getServerConnection():sendEvent(SteerableToggleRefuelEvent:new(self, isFilling)); |
693 | end; |
694 | end; |
695 | self.isFuelFilling = isFilling; |
696 | if isFilling then |
697 | -- find the first trigger which is activable |
698 | self.fuelFillTrigger = nil; |
699 | for i=1, table.getn(self.fuelFillTriggers) do |
700 | local trigger = self.fuelFillTriggers[i]; |
701 | if trigger:getIsActivatable(self) then |
702 | self.fuelFillTrigger = trigger; |
703 | break; |
704 | end; |
705 | end; |
706 | end |
707 | if self.isClient and self.sampleRefuel ~= nil then |
708 | if isFilling then |
709 | Utils.play3DSample(self.sampleRefuel); |
710 | else |
711 | Utils.stop3DSample(self.sampleRefuel); |
712 | end; |
713 | end; |
714 | end |
715 | end |
716 | |
717 | function Motorized:addFuelFillTrigger(trigger) |
718 | if table.getn(self.fuelFillTriggers) == 0 then |
719 | g_currentMission:addActivatableObject(self.motorizedFillActivatable); |
720 | end; |
721 | table.insert(self.fuelFillTriggers, trigger); |
722 | end; |
723 | |
724 | function Motorized:removeFuelFillTrigger(trigger) |
725 | for i=1, table.getn(self.fuelFillTriggers) do |
726 | if self.fuelFillTriggers[i] == trigger then |
727 | table.remove(self.fuelFillTriggers, i); |
728 | break; |
729 | end; |
730 | end; |
731 | if table.getn(self.fuelFillTriggers) == 0 or trigger == self.fuelFillTrigger then |
732 | if self.isServer then |
733 | self:setIsFuelFilling(false); |
734 | end; |
735 | if table.getn(self.fuelFillTriggers) == 0 then |
736 | g_currentMission:removeActivatableObject(self.motorizedFillActivatable); |
737 | end |
738 | end; |
739 | end; |
740 | |
741 | function Motorized:developmentReloadFromXML(xmlFile) |
742 | if self.motorizedNode ~= nil then |
743 | removeAllDifferentials(self.motorizedNode); |
744 | end |
745 | Motorized.loadDifferentials(self, xmlFile); |
746 | Motorized.loadMotor(self, xmlFile); |
747 | |
748 | if self.isClient then |
749 | Motorized.loadExhaustEffects(self, xmlFile, self.exhaustEffects); |
750 | end; |
751 | end; |
752 | |
753 | MotorizedRefuelActivatable = {} |
754 | local MotorizedRefuelActivatable_mt = Class(MotorizedRefuelActivatable); |
755 | |
756 | function MotorizedRefuelActivatable:new(motorized) |
757 | local self = {}; |
758 | setmetatable(self, MotorizedRefuelActivatable_mt); |
759 | |
760 | self.motorized = motorized; |
761 | self.activateText = "unknown"; |
762 | |
763 | return self; |
764 | end; |
765 | |
766 | |
767 | function MotorizedRefuelActivatable:getIsActivatable() |
768 | if self.motorized:getIsActiveForInput(false) and self.motorized.fuelFillLevel-1 < self.motorized.fuelCapacity then |
769 | -- find the first trigger which is activable |
770 | for i=1, table.getn(self.motorized.fuelFillTriggers) do |
771 | local trigger = self.motorized.fuelFillTriggers[i]; |
772 | if trigger:getIsActivatable(self.motorized) then |
773 | self:updateActivateText(); |
774 | return true; |
775 | end |
776 | end |
777 | end |
778 | return false; |
779 | end; |
780 | |
781 | function MotorizedRefuelActivatable:onActivateObject() |
782 | self.motorized:setIsFuelFilling(not self.motorized.isFuelFilling); |
783 | self:updateActivateText(); |
784 | g_currentMission:addActivatableObject(self); |
785 | end; |
786 | |
787 | function MotorizedRefuelActivatable:drawActivate() |
788 | g_currentMission:enableHudIcon("refuel", 5); |
789 | end; |
790 | |
791 | function MotorizedRefuelActivatable:updateActivateText() |
792 | if self.motorized.isFuelFilling then |
793 | self.activateText = string.format(g_i18n:getText("stop_Refuel"), self.motorized.typeDesc); |
794 | else |
795 | self.activateText = string.format(g_i18n:getText("Refuel"), self.motorized.typeDesc); |
796 | end; |
797 | end;
|
Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de