Sprache Deutsch Language English

Script Dokumentation LS 2015 - Train (Patch 1.3)

Script Dokumentation Übersicht

scripts/objects/Train.lua

Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
1-- Train class
2--
3-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.
4
5Train = {}
6
7local Train_mt = Class(Train, Object);
8
9InitStaticObjectClass(Train, "Train", ObjectIds.OBJECT_TRAIN);
10
11function Train:onCreate(id)
12 local train = Train:new(g_server ~= nil, g_client ~= nil);
13 if train:load(id) then
14 g_currentMission:addOnCreateLoadedObject(train);
15 train:register(true);
16 else
17 train:delete();
18 end;
19end;
20
21function Train:new(isServer, isClient, mt)
22 local mt = customMt;
23 if mt == nil then
24 mt = Train_mt;
25 end;
26
27 local self = Object:new(isServer, isClient, mt);
28
29 return self;
30end;
31
32function Train:load(id)
33 self.nodeId = id;
34 self.splineId = id;
35
36 local xmlFileName = getUserAttribute(id, "xmlFilename");
37 if xmlFileName == nil then
38 print("Warning: No Train 'xmlFilename' specified!");
39 return nil;
40 end;
41
42 xmlFileName = Utils.getFilename(xmlFileName, g_currentMission.loadingMapBaseDirectory);
43 local xmlFile = loadXMLFile("TrainConfigFile", xmlFileName);
44 if xmlFile == nil then
45 print("Warning: Could not open Train config file!");
46 return nil;
47 end;
48
49 local trainType = Utils.getNoNil(getUserAttribute(id, "trainType"), "grainTrain");
50 local i=0;
51 local trainTypeKey = nil;
52 while true do
53 local key = string.format("trainSystem.train(%d)", i);
54 if (not hasXMLProperty(xmlFile, key)) then
55 break;
56 end;
57
58 local currentTrainType = getXMLString(xmlFile, key .. "#type");
59 if currentTrainType == trainType then
60 trainTypeKey = key;
61 break;
62 end;
63 i = i + 1;
64 end;
65
66 if trainTypeKey == nil then
67 print("Warning: Traintype '"..trainType.."' not found in '"..xmlFileName.."'!");
68 return nil;
69 end;
70
71 self.isTrainStarted = false;
72
73 self.fruitToVehicle = {};
74 self.trainLength = 0;
75 self.train = {};
76 self.trainTransform = createTransformGroup("trainRoot");
77 link(getRootNode(), self.trainTransform);
78 local i = 0;
79 while true do
80 local key = string.format(trainTypeKey..".asset(%d)", i);
81 if (not hasXMLProperty(xmlFile, key)) then
82 break;
83 end;
84 local filename = getXMLString(xmlFile, key .. "#filename");
85
86 local root = Utils.loadSharedI3DFile(filename, g_currentMission.loadingMapBaseDirectory, false, true, false);
87 local vehicle = {};
88 vehicle.filename = filename;
89 vehicle.id = getChildAt(root, 0);
90 link(self.trainTransform, vehicle.id);
91 delete(root);
92 vehicle.dir = Utils.getNoNil(getXMLInt(xmlFile, key.."#direction"), 1);
93 vehicle.length = Utils.getNoNil(getXMLFloat(xmlFile, key.."#length"), 2);
94 vehicle.wheels = {};
95 vehicle.radius = getXMLFloat(xmlFile, key.."#wheelRadius");
96 local numWheels = Utils.getNoNil(getXMLInt(xmlFile, key.."#numWheels"), 0);
97 for i=0, numWheels-1 do
98 if vehicle.radius == nil then
99 local _,y,_ = getTranslation(getChildAt(vehicle.id, i));
100 vehicle.radius = y;
101 end;
102 table.insert(vehicle.wheels, getChildAt(vehicle.id, i));
103 end;
104
105 -- wood sell trigger directly on the train
106 local trainWoodSellTriggerIndex = getXMLString(xmlFile, key.."#woodSellTriggerIndex");
107 if trainWoodSellTriggerIndex ~= nil then
108 local trainWoodSellTriggerNode = Utils.indexToObject(vehicle.id, trainWoodSellTriggerIndex);
109 self.trainWoodSellTrigger = WoodSellTrigger:new(trainWoodSellTriggerNode);
110 g_currentMission:addNonUpdateable(self.trainWoodSellTrigger);
111 self.trainWoodSellTrigger:addUpdateEventListener(self);
112 end;
113
114 vehicle.fillLevels = {};
115 local fillTypes = getXMLString(xmlFile, key.."#fillTypes");
116 if fillTypes ~= nil then
117 local types = Utils.splitString(" ", fillTypes);
118 for _,fillTypeName in pairs(types) do
119 local desc = Fillable.fillTypeNameToDesc[fillTypeName];
120 if desc ~= nil then
121 self.fruitToVehicle[desc.index] = vehicle;
122 vehicle.fillLevels[desc.index] = 0;
123 end;
124 end;
125 end;
126
127 self.woodFillLevel = 0; -- for lumber trains which don't have a tip trigger
128
129 setTranslation(vehicle.id, 0, 0, self.trainLength + (vehicle.length / 2));
130 if vehicle.dir == -1 then
131 setRotation(vehicle.id, 0, math.pi, 0);
132 end;
133 self.trainLength = self.trainLength + vehicle.length;
134 table.insert(self.train, vehicle);
135
136 i = i + 1;
137 end;
138
139 -- attach sound to the last train wagon
140 self.trainSoundId = createAudioSource("train", "data/maps/sounds/train.wav", 160, 10, 1, 0);
141 local soundAttacherNode = self.trainTransform;
142 if getNumOfChildren(self.trainTransform) > 0 then
143 soundAttacherNode = getChildAt(self.trainTransform, getNumOfChildren(self.trainTransform) - 1);
144 end;
145 link(soundAttacherNode, self.trainSoundId);
146 setVisibility(self.trainSoundId, false);
147
148 self.maxSpeed = Utils.getNoNil(getXMLFloat(xmlFile, trainTypeKey .. "#maxSpeed"), 20) / 3600;
149 self.maxAcceleration = Utils.getNoNil(getXMLFloat(xmlFile, trainTypeKey .. "#maxAcc"), 5) * 1000;
150
151 self.endTime = 0;
152 self.endTimeOffset = Utils.getNoNil(getUserAttribute(id, "unloadingWaitTime"), 10) * 1000;
153 self.lastSpeed = 0;
154 self.startTimeOffset = 2000;
155 self.startTime = 0;
156
157 self.doBrake = false;
158 self.direction = 1;
159
160 self.currentTime = 0;
161 self.splineLength = getSplineLength(self.splineId);
162 self.startOffsetTime = self.trainLength/self.splineLength;
163 self.brakeTime = self.startOffsetTime / table.getn(self.train);
164 self:updateTrainPosition(self.startOffsetTime);
165
166 local tipTriggerIndex = getUserAttribute(id, "tipTriggerIndex");
167 if tipTriggerIndex ~= nil then
168 local tipTriggerNode = Utils.indexToObject(id, tipTriggerIndex);
169 self.tipTrigger = TipTrigger:new(self.isServer, self.isClient);
170 self.tipTrigger:load(tipTriggerNode);
171 g_currentMission:addOnCreateLoadedObject(self.tipTrigger);
172 self.tipTrigger:register(true);
173 self.tipTrigger:addUpdateEventListener(self);
174 end;
175
176 -- wood sell trigger on the ground
177 local woodSellTriggerIndex = getUserAttribute(id, "woodSellTriggerIndex");
178 if woodSellTriggerIndex ~= nil then
179 local woodSellTriggerNode = Utils.indexToObject(id, woodSellTriggerIndex);
180 self.woodSellTrigger = WoodSellTrigger:new(woodSellTriggerNode);
181 g_currentMission:addNonUpdateable(self.woodSellTrigger);
182 self.woodSellTrigger:addUpdateEventListener(self);
183 end;
184
185 local triggerIndex = getUserAttribute(id, "triggerIndex");
186 if triggerIndex ~= nil then
187 self.triggerId = Utils.indexToObject(id, triggerIndex);
188 if g_currentMission:getIsClient() then
189 addTrigger(self.triggerId, "triggerCallback", self);
190 end;
191 self.leverId = getChildAt(getChildAt(self.triggerId, 0), 0);
192
193 self.leverSoundId = createAudioSource("lever", "data/maps/sounds/lever.wav", 8, 1, 1, 1);
194 link(self.leverId, self.leverSoundId);
195 setVisibility(self.leverSoundId, false);
196
197 self.activateText = g_i18n:getText("SendOffTrain");
198 self.isEnabled = true;
199 self.objectActivated = false;
200 end;
201 self.leverStarted = false;
202 self.leverTimer = 0;
203
204 self.leverRotCurve = AnimCurve:new(linearInterpolator1);
205 self.leverRotCurve:addKeyframe({v=Utils.degToRad(0), time = 0});
206 self.leverRotCurve:addKeyframe({v=Utils.degToRad(12), time = 250});
207 self.leverRotCurve:addKeyframe({v=Utils.degToRad(20), time = 500});
208 self.leverRotCurve:addKeyframe({v=Utils.degToRad(26), time = 750});
209 self.leverRotCurve:addKeyframe({v=Utils.degToRad(30), time = 1000});
210 self.leverRotCurve:addKeyframe({v=Utils.degToRad(10), time = 1500});
211 self.leverRotCurve:addKeyframe({v=Utils.degToRad(0), time = 2000});
212
213 g_currentMission:addNodeObject(self.nodeId, self);
214
215 g_currentMission:addOnCreateLoadedObjectToSave(self);
216
217 return true;
218end;
219
220function Train:delete()
221 g_currentMission:removeOnCreateLoadedObjectToSave(self);
222
223 if g_currentMission:getIsClient() then
224 for _, vehicle in pairs(self.train) do
225 Utils.deleteParticleSystem(vehicle.exhaustParticleSystems);
226 Utils.releaseSharedI3DFile(vehicle.filename, g_currentMission.loadingMapBaseDirectory, true);
227 end;
228 if self.triggerId ~= nil then
229 removeTrigger(self.triggerId);
230 end;
231 if self.trainWoodSellTrigger ~= nil then
232 self.trainWoodSellTrigger:delete();
233 end;
234 end;
235
236 if (self.leverSoundId) then
237 stopSample(self.leverSoundId);
238 delete(self.leverSoundId);
239 self.leverSoundId = nil;
240 end;
241
242 if (self.trainSoundId) then
243 stopSample(self.trainSoundId);
244 delete(self.trainSoundId);
245 self.trainSoundId = nil;
246 end;
247
248 if self.trainTransform ~= nil then
249 delete(self.trainTransform);
250 end
251
252 if self.nodeId ~= 0 then
253 g_currentMission:removeNodeObject(self.nodeId);
254 end;
255 g_currentMission:removeActivatableObject(self);
256 Train:superClass().delete(self);
257end;
258
259function Train:readStream(streamId, connection)
260 Train:superClass().readStream(self, streamId, connection);
261 if connection:getIsServer() then
262 local isTrainStarted = streamReadBool(streamId);
263 if isTrainStarted then
264 self:startTrain(true);
265 self.lastSpeed = Utils.getNoNil(streamReadFloat32(streamId), 0);
266 self.currentTime = Utils.getNoNil(streamReadFloat32(streamId), self.currentTime);
267 self:updateTrainPosition(self.currentTime);
268 self.startTime = self.startTimeOffset + 1; -- make sure that train is driving
269 end;
270 end;
271end;
272
273function Train:writeStream(streamId, connection)
274 Train:superClass().writeStream(self, streamId, connection);
275 if not connection:getIsServer() then
276 streamWriteBool(streamId, self.isTrainStarted)
277 if self.isTrainStarted then
278 streamWriteFloat32(streamId, self.lastSpeed);
279 streamWriteFloat32(streamId, self.currentTime);
280 end;
281 end;
282end;
283
284function Train:update(dt)
285 if g_currentMission ~= nil and (self.startTime > self.startTimeOffset or self.endTime > self.endTimeOffset) then
286 -- handle braking and acceleration
287 if not self.doBrake then
288 self.lastSpeed = math.min(math.max(self.lastSpeed - dt/self.maxAcceleration, -1), 1);
289 else
290 if self.direction == 1 then
291 self.lastSpeed = self.startBrakeSpeed * math.max(0.1, (((1 - self.currentTime)/self.brakeTime)*0.8));
292 else
293 self.lastSpeed = self.startBrakeSpeed * math.max(0.1, (((self.currentTime-self.startOffsetTime)/self.brakeTime)*0.8));
294 end;
295 end;
296
297 -- update train
298 local lastTime = Utils.clamp(self.currentTime + (self.maxSpeed/self.splineLength)*-self.lastSpeed*self.direction*dt, self.startOffsetTime, 1);
299 if lastTime <= 1 then
300 if lastTime ~= self.currentTime then
301 self:updateTrainPosition(lastTime);
302 end;
303 end;
304 else
305 if self.isTrainStarted then
306 if self.direction == 1 then
307 self.startTime = self.startTime + dt;
308 else
309 self.endTime = self.endTime + dt;
310 end;
311 end;
312 end;
313
314 if g_currentMission ~= nil and self.leverStarted and self.leverId ~= nil then
315 local rx = self.leverRotCurve:get(self.leverTimer);
316 setRotation(self.leverId, rx, 0, 0);
317 self.leverTimer = self.leverTimer + dt;
318 if self.leverTimer > 2000 then
319 self.leverTimer = 0;
320 self.leverStarted = false;
321 setVisibility(self.leverSoundId, false);
322 end;
323 end;
324
325 if g_currentMission ~= nil and self.endTime > self.endTimeOffset and self.isTrainStarted then
326 if not getVisibility(self.trainSoundId) then
327 setVisibility(self.trainSoundId, true);
328 end;
329 end;
330
331end;
332
333function Train:startTrain(noEventSend)
334 if not self.isTrainStarted then
335 TrainStartEvent.sendEvent(self, noEventSend);
336 self.startTime = 0;
337 self.doBrake = false;
338 self.isTrainStarted = true;
339 self.leverStarted = true;
340 setVisibility(self.leverSoundId, true);
341 setVisibility(self.trainSoundId, true);
342 self.leverTimer = 0;
343 self:setTriggersEnabled(false);
344 end;
345end;
346
347function Train:updateTrainPosition(t)
348 local dt = self.currentTime - t;
349 local x, y, z = getSplinePosition(self.splineId, t);
350 local rx, ry, rz = getSplineOrientation(self.splineId, t, 0, -1, 0);
351 setTranslation(self.trainTransform, x, y, z);
352 setRotation(self.trainTransform, rx, ry, rz);
353
354 local distance = dt * self.splineLength;
355 for _, vehicle in pairs(self.train) do
356 for _, wheel in pairs(vehicle.wheels) do
357 rotate(wheel, (1/vehicle.radius) * distance * vehicle.dir, 0, 0);
358 end;
359 end;
360
361 self.currentTime = t;
362
363 if self.direction == 1 then
364 if self.currentTime >= (1-self.brakeTime) then
365 if not self.doBrake then
366 self.startBrakeSpeed = self.lastSpeed;
367 end;
368 self.doBrake = true;
369 end;
370 if self.currentTime == 1 then
371 self.doBrake = false;
372 self.endTime = 0;
373 self.startTime = 0;
374 self.direction = self.direction * -1;
375 self:unloadTrain();
376 setVisibility(self.trainSoundId, false);
377 end;
378 else
379 if self.currentTime <= (self.startOffsetTime+self.brakeTime) then
380 if not self.doBrake then
381 self.startBrakeSpeed = self.lastSpeed;
382 end;
383 self.doBrake = true;
384 end;
385 if self.currentTime == self.startOffsetTime then
386 if self.isTrainStarted then
387 self.doBrake = false;
388 self.endTime = 0;
389 self.startTime = 0;
390 self.direction = self.direction * -1;
391 setVisibility(self.trainSoundId, false);
392 end;
393 self.isTrainStarted = false;
394 self:setTriggersEnabled(true);
395 end;
396 end;
397end;
398
399function Train:onUpdateEvent(trigger, fillDelta, fillType, trailer)
400 local vehicle = self.fruitToVehicle[fillType];
401 if vehicle ~= nil then
402 if vehicle.fillLevels[fillType] ~= nil then
403 vehicle.fillLevels[fillType] = vehicle.fillLevels[fillType] - fillDelta;
404 end;
405 end;
406end;
407
408function Train:onWoodSellingUpdateEvent(trigger, sellValue)
409 self.woodFillLevel = self.woodFillLevel + sellValue;
410end;
411
412function Train:setTriggersEnabled(isEnabled)
413 if self.tipTrigger ~= nil then
414 self.tipTrigger.isEnabled = isEnabled;
415 end;
416 if self.woodSellTrigger ~= nil then
417 self.woodSellTrigger.isEnabled = isEnabled;
418 end;
419 if self.trainWoodSellTrigger ~= nil then
420 self.trainWoodSellTrigger.isEnabled = isEnabled;
421 end;
422end;
423
424function Train:unloadTrain()
425
426 local soldSomething = false;
427
428 for _, vehicle in pairs(self.train) do
429 if vehicle.fillLevels ~= nil then
430 for fillType, fillLevel in pairs(vehicle.fillLevels) do
431 -- update total amount of this fill type
432 local desc = Fillable.fillTypeIndexToDesc[fillType];
433 desc.totalAmount = desc.totalAmount + fillLevel;
434
435 -- increase money according to price of current fill type
436 local priceMultiplier = 1;
437 local greatDemandMultiplier = 1;
438 local difficultyMultiplier = math.max(2 * (3 - g_currentMission.missionStats.difficulty), 1); -- 1 2 4
439
440 if vehicle.fillTrigger ~= nil then
441 priceMultiplier = vehicle.fillTrigger.priceMultipliers[fillType];
442 -- check if a great demand pertaining to this fill type / station is currently running
443 local greatDemand = g_currentMission.economyManager:getCurrentGreatDemand(vehicle.fillTrigger.stationName, fillType);
444 if greatDemand ~= nil then
445 greatDemandMultiplier = greatDemand.demandMultiplier;
446 end;
447 end;
448
449 local money = Fillable.fillTypeIndexToDesc[fillType].pricePerLiter * priceMultiplier * difficultyMultiplier * greatDemandMultiplier * fillLevel;
450 if self.isServer then
451 g_currentMission:addSharedMoney(money, "harvestIncome");
452 g_currentMission:addMoneyChange(money, FSBaseMission.MONEY_TYPE_SINGLE, true);
453 end;
454
455 vehicle.fillLevels[fillType] = 0;
456
457 if money > 0 then soldSomething = true; end;
458
459 end;
460 end;
461 end;
462
463 if self.woodFillLevel > 0 then
464 if self.isServer then
465 g_currentMission:addSharedMoney(self.woodFillLevel, "harvestIncome");
466 g_currentMission:addMoneyChange(self.woodFillLevel, FSBaseMission.MONEY_TYPE_SINGLE, true);
467 end;
468 self.woodFillLevel = 0;
469 soldSomething = true;
470 end;
471
472 if soldSomething then
473 playSample(g_currentMission.cashRegistrySound, 1, 1, 0);
474 end;
475
476end;
477
478function Train:getIsActivatable()
479 return self.isEnabled and g_currentMission.controlPlayer and self.currentTime == self.startOffsetTime and not self.isTrainStarted;
480end;
481
482function Train:drawActivate()
483 return;
484end;
485
486function Train:onActivateObject()
487 self.objectActivated = true;
488 g_currentMission:addActivatableObject(self);
489 self:startTrain();
490end;
491
492function Train:loadFromAttributesAndNodes(xmlFile, key)
493 local isTrainStarted = Utils.getNoNil(getXMLBool(xmlFile, key .."#isTrainStarted"), false);
494 if isTrainStarted then
495 self:startTrain(true);
496 self.lastSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#lastSpeed"), 0);
497 self.currentTime = Utils.getNoNil(getXMLFloat(xmlFile, key.."#currentTime"), self.currentTime);
498 self.direction = Utils.getNoNil(getXMLInt(xmlFile, key.."#direction"), self.direction);
499 self:updateTrainPosition(self.currentTime);
500 self.startTime = self.startTimeOffset + 1; -- make sure that train is driving
501 end;
502
503 for i, vehicle in pairs(self.train) do
504 if vehicle.fillLevels ~= nil then
505 local vehicleKey = key..string.format(".vehicle%d", i);
506 local fillLevels = getXMLString(xmlFile, vehicleKey .. "#fillLevels");
507 if fillLevels ~= nil then
508 local levels = Utils.splitString(" ", fillLevels);
509 local i = 1;
510 for fillType, level in pairs(vehicle.fillLevels) do
511 vehicle.fillLevels[fillType] = levels[i];
512 i = i + 1;
513 end;
514 end;
515 end;
516 end;
517
518 self.woodFillLevel = Utils.getNoNil(getXMLFloat(xmlFile, key .. ".wood#fillLevel"), 0);
519
520 return true;
521end;
522
523function Train:getSaveAttributesAndNodes(nodeIdent)
524 local attributes = 'isTrainStarted="'..tostring(self.isTrainStarted)..'" currentTime="'..self.currentTime..'" direction="'..self.direction..'" lastSpeed="'..self.lastSpeed..'"';
525 local nodes = "";
526 for i, vehicle in pairs(self.train) do
527 if i>1 then
528 nodes = nodes.."\n";
529 end;
530 local fillLevels = "";
531 if vehicle.fillLevels ~= nil then
532 local j = 0;
533 for _, level in pairs(vehicle.fillLevels) do
534 if j > 0 then
535 fillLevels = fillLevels .. " ";
536 end;
537 fillLevels = fillLevels .. level;
538 j = j + 1;
539 end;
540 fillLevels = 'fillLevels="'..fillLevels..'"';
541 end;
542 nodes = nodes.. nodeIdent..'<vehicle'..i..' '..fillLevels..' />';
543 end;
544
545 nodes = nodes .. '\n' .. nodeIdent .. '<wood fillLevel="' .. self.woodFillLevel .. '" />';
546
547 return attributes, nodes;
548end;
549
550function Train:triggerCallback(triggerId, otherId, onEnter, onLeave, onStay)
551 if self.isEnabled then
552 if onEnter or onLeave then
553 if g_currentMission.player ~= nil and otherId == g_currentMission.player.rootNode then
554 if onEnter then
555 if not self.objectActivated then
556 g_currentMission:addActivatableObject(self);
557 self.objectActivated = true;
558 end
559 else
560 if self.objectActivated then
561 g_currentMission:removeActivatableObject(self);
562 self.objectActivated = false;
563 end
564 end;
565 end;
566 end;
567 end;
568end;
Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
Script Dokumentation Übersicht