1 /******************************************************************************
2 * irrlamb - https://github.com/jazztickets/irrlamb
3 * Copyright (C) 2019 Alan Witkowski
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *******************************************************************************/
18 #include <level.h>
19 #include <globals.h>
20 #include <framework.h>
21 #include <objectmanager.h>
22 #include <scripting.h>
23 #include <graphics.h>
24 #include <save.h>
25 #include <replay.h>
26 #include <log.h>
27 #include <physics.h>
28 #include <input.h>
29 #include <audio.h>
30 #include <config.h>
31 #include <objects/template.h>
32 #include <objects/player.h>
33 #include <objects/plane.h>
34 #include <objects/sphere.h>
35 #include <objects/box.h>
36 #include <objects/orb.h>
37 #include <objects/cylinder.h>
38 #include <objects/terrain.h>
39 #include <objects/zone.h>
40 #include <objects/trimesh.h>
41 #include <objects/constraint.h>
42 #include <tinyxml2.h>
43 #include <ISceneManager.h>
44 #include <IFileSystem.h>
45
46 _Level Level;
47
48 using namespace irr;
49 using namespace tinyxml2;
50
51 // Handle user data from .irr file
OnReadUserData(irr::scene::ISceneNode * ForSceneNode,irr::io::IAttributes * UserData)52 void _UserDataLoader::OnReadUserData(irr::scene::ISceneNode *ForSceneNode, irr::io::IAttributes *UserData) {
53
54 for(uint32_t i = 0; i < UserData->getAttributeCount(); i++) {
55 core::stringc Name(UserData->getAttributeName(i));
56
57 if(Name == "BackgroundColor") {
58 video::SColorf FloatColor(UserData->getAttributeAsColorf(i));
59 Level.ClearColor.set(255, (uint32_t)(255 * FloatColor.r), (uint32_t)(255 * FloatColor.g), (uint32_t)(255 * FloatColor.b));
60 }
61 }
62
63 return;
64 }
65
66 // Loads a level file
Init(const std::string & LevelName,bool HeaderOnly)67 int _Level::Init(const std::string &LevelName, bool HeaderOnly) {
68 if(!HeaderOnly)
69 Log.Write("Loading level: %s", LevelName.c_str());
70
71 // Get fastest time
72 FastestTime = 0.0f;
73 const _LevelStat &Stats = Save.LevelStats[LevelName];
74 if(Stats.HighScores.size() > 0)
75 FastestTime = Stats.HighScores[0].Time;
76
77 // Get paths
78 this->LevelName = LevelName;
79 LevelNiceName = "";
80 std::string LevelFile = LevelName + "/" + LevelName + ".xml";
81 std::string FilePath = Framework.GetWorkingPath() + std::string("levels/") + LevelFile;
82 std::string CustomFilePath = Save.CustomLevelsPath + LevelFile;
83 CustomDataPath = Framework.GetWorkingPath() + std::string("levels/") + LevelName + "/";
84
85 // See if custom level exists first
86 IsCustomLevel = false;
87 std::ifstream CustomLevelExists(CustomFilePath.c_str());
88 if(CustomLevelExists) {
89 IsCustomLevel = true;
90 FilePath = CustomFilePath;
91 CustomDataPath = Save.CustomLevelsPath + LevelName + "/";
92 }
93 CustomLevelExists.close();
94
95 // Open the XML file
96 XMLDocument Document;
97 if(Document.LoadFile(FilePath.c_str()) != XML_SUCCESS) {
98 Log.Write("Error loading level file with error id = %d", Document.ErrorID());
99 Log.Write("Error string: %s", Document.ErrorStr());
100 Close();
101 return 0;
102 }
103
104 // Check for level tag
105 XMLElement *LevelElement = Document.FirstChildElement("level");
106 if(!LevelElement) {
107 Log.Write("Could not find level tag");
108 Close();
109 return 0;
110 }
111
112 // Level version
113 if(LevelElement->QueryIntAttribute("version", &LevelVersion) == XML_NO_ATTRIBUTE) {
114 Log.Write("Could not find level version");
115 Close();
116 return 0;
117 }
118
119 // Check required game version
120 GameVersion = LevelElement->Attribute("gameversion");
121 if(GameVersion == "") {
122 Log.Write("Could not find game version attribute");
123 Close();
124 return 0;
125 }
126
127 // Load level info
128 XMLElement *InfoElement = LevelElement->FirstChildElement("info");
129 if(InfoElement) {
130 XMLElement *NiceNameElement = InfoElement->FirstChildElement("name");
131 if(NiceNameElement) {
132 LevelNiceName = NiceNameElement->GetText();
133 }
134 }
135
136 // Return after header is read
137 if(HeaderOnly) {
138 Close();
139 return true;
140 }
141
142 // Load default lua script
143 Scripts.clear();
144 Scripts.push_back(Framework.GetWorkingPath() + "scripts/default.lua");
145
146 // Options
147 bool Fog = false;
148 bool EmitLight = false;
149 Level.ClearColor.set(255, 0, 0, 0);
150 irrScene->setAmbientLight(video::SColorf(0.3, 0.3, 0.3, 1));
151 irrDriver->setFog(video::SColor(0), irr::video::EFT_FOG_EXP, 0, 0, 0);
152
153 // Load options
154 XMLElement *OptionsElement = LevelElement->FirstChildElement("options");
155 if(OptionsElement) {
156
157 // Use lights from world/player
158 XMLElement *EmitLightElement = OptionsElement->FirstChildElement("emitlight");
159 if(EmitLightElement) {
160 EmitLightElement->QueryBoolAttribute("enabled", &EmitLight);
161 }
162 }
163
164 // Load world
165 XMLElement *ResourcesElement = LevelElement->FirstChildElement("resources");
166 if(ResourcesElement) {
167
168 // Load scenes
169 for(XMLElement *SceneElement = ResourcesElement->FirstChildElement("scene"); SceneElement != 0; SceneElement = SceneElement->NextSiblingElement("scene")) {
170
171 // Get file
172 std::string File = SceneElement->Attribute("file");
173 if(File == "") {
174 Log.Write("Could not find file attribute on scene");
175 Close();
176 return 0;
177 }
178
179 // Reset fog
180 irrDriver->setFog(video::SColor(0, 0, 0, 0), video::EFT_FOG_EXP, 0, 0, 0.0f);
181
182 // Load scene
183 if(IsCustomLevel) {
184 irrFile->changeWorkingDirectoryTo(CustomDataPath.c_str());
185 irrScene->loadScene(File.c_str(), &UserDataLoader);
186 irrFile->changeWorkingDirectoryTo(Framework.GetWorkingPath().c_str());
187 }
188 else {
189 irrScene->loadScene((CustomDataPath + File).c_str(), &UserDataLoader);
190 }
191
192 // Set texture filters on meshes in the scene
193 core::array<irr::scene::ISceneNode *> MeshNodes;
194 irrScene->getSceneNodesFromType(scene::ESNT_MESH, MeshNodes);
195 for(uint32_t i = 0; i < MeshNodes.size(); i++) {
196 if(EmitLight && Config.Shaders) {
197 video::SMaterial &Material = MeshNodes[i]->getMaterial(0);
198 int ShaderType = 0;
199 if(Material.MaterialType == video::EMT_TRANSPARENT_ALPHA_CHANNEL) {
200 ShaderType = 1;
201 }
202 MeshNodes[i]->setMaterialType((video::E_MATERIAL_TYPE)Graphics.GetCustomMaterial(ShaderType));
203 }
204
205 //MeshNodes[i]->setMaterialFlag(video::EMF_WIREFRAME, true);
206 MeshNodes[i]->setMaterialFlag(video::EMF_TRILINEAR_FILTER, Config.TrilinearFiltering);
207 for(uint32_t j = 0; j < MeshNodes[i]->getMaterialCount(); j++) {
208 for(int k = 0; k < 4; k++) {
209 MeshNodes[i]->getMaterial(j).TextureLayer[k].AnisotropicFilter = Config.AnisotropicFiltering;
210 if(MeshNodes[i]->getMaterial(j).FogEnable)
211 Fog = true;
212 }
213 }
214 }
215 }
216
217 // Load collision
218 for(XMLElement *CollisionElement = ResourcesElement->FirstChildElement("collision"); CollisionElement != 0; CollisionElement = CollisionElement->NextSiblingElement("collision")) {
219
220 // Get file
221 std::string File = CollisionElement->Attribute("file");
222 if(File == "") {
223 Log.Write("Could not find file attribute on collision");
224 Close();
225 return 0;
226 }
227
228 // Create template
229 _Template *Template = new _Template;
230 Template->CollisionFile = CustomDataPath + File;
231 Template->Type = _Object::COLLISION;
232 Template->CollisionGroup = _Physics::FILTER_STATIC | _Physics::FILTER_CAMERA;
233 Template->CollisionMask = _Physics::FILTER_RIGIDBODY;
234 Template->Mass = 0.0f;
235 Templates.push_back(Template);
236
237 // Get collision friction
238 CollisionElement->QueryFloatAttribute("friction", &Template->Friction);
239
240 // Create spawn
241 _ObjectSpawn *ObjectSpawn = new _ObjectSpawn;
242 ObjectSpawn->Template = Template;
243 const char *AttributeName;
244 if((AttributeName = CollisionElement->Attribute("name")))
245 ObjectSpawn->Name = AttributeName;
246 ObjectSpawns.push_back(ObjectSpawn);
247 }
248
249 // Load scripts
250 for(XMLElement *ScriptElement = ResourcesElement->FirstChildElement("script"); ScriptElement != 0; ScriptElement = ScriptElement->NextSiblingElement("script")) {
251
252 // Get file
253 std::string File = ScriptElement->Attribute("file");
254 if(File == "") {
255 Log.Write("Could not find file attribute on script");
256 Close();
257 return 0;
258 }
259
260 Scripts.push_back(CustomDataPath + File);
261 }
262
263 // Load sounds
264 Sounds.clear();
265 for(XMLElement *SoundElement = ResourcesElement->FirstChildElement("sound"); SoundElement != 0; SoundElement = SoundElement->NextSiblingElement("sound")) {
266
267 // Get file
268 std::string File = SoundElement->Attribute("file");
269 if(File == "") {
270 Log.Write("Could not find file attribute on sound");
271 Close();
272 return 0;
273 }
274
275 // Attempt to load sound
276 if(Audio.LoadBuffer(File))
277 Sounds.push_back(File);
278 }
279 }
280
281 // Load templates
282 XMLElement *TemplatesElement = LevelElement->FirstChildElement("templates");
283 int TemplateID = 0;
284 if(TemplatesElement) {
285 for(XMLElement *TemplateElement = TemplatesElement->FirstChildElement(); TemplateElement != 0; TemplateElement = TemplateElement->NextSiblingElement()) {
286
287 // Create a template
288 _Template *Template = new _Template;
289 Template->Fog = Fog;
290
291 // Get the template properties
292 if(!GetTemplateProperties(TemplateElement, *Template))
293 return 0;
294
295 // Assign options
296 Template->TemplateID = TemplateID;
297 if(EmitLight) {
298
299 // Use shaders on materials that receive light
300 if(Config.Shaders)
301 Template->CustomMaterial = Graphics.GetCustomMaterial(0);
302
303 // Set the player to emit light
304 if(Template->Type == _Object::PLAYER || Template->Type == _Object::ORB)
305 Template->EmitLight = true;
306 }
307 TemplateID++;
308
309 // Store for later
310 Templates.push_back(Template);
311 }
312 }
313
314 // Create zones from 'empty' node types
315 core::array<irr::scene::ISceneNode *> MeshNodes;
316 irrScene->getSceneNodesFromType(scene::ESNT_EMPTY, MeshNodes);
317 for(uint32_t i = 0; i < MeshNodes.size(); i++) {
318
319 // Create zone template
320 _Template *Template = new _Template;
321 Template->Name = MeshNodes[i]->getName();
322 Template->TemplateID = TemplateID++;
323 Template->Type = _Object::ZONE;
324 Template->Mass = 0.0f;
325 Template->CollisionGroup = _Physics::FILTER_ZONE;
326 Template->CollisionMask = _Physics::FILTER_RIGIDBODY;
327 Template->Shape[0] = std::abs(MeshNodes[i]->getScale().X * 2);
328 Template->Shape[1] = std::abs(MeshNodes[i]->getScale().Y * 2);
329 Template->Shape[2] = std::abs(MeshNodes[i]->getScale().Z * 2);
330 Templates.push_back(Template);
331
332 // Create object spawn
333 _ObjectSpawn *ObjectSpawn = new _ObjectSpawn;
334 ObjectSpawn->Name = MeshNodes[i]->getName();
335 ObjectSpawn->Position[0] = MeshNodes[i]->getPosition().X;
336 ObjectSpawn->Position[1] = MeshNodes[i]->getPosition().Y;
337 ObjectSpawn->Position[2] = MeshNodes[i]->getPosition().Z;
338 ObjectSpawn->Template = Template;
339 ObjectSpawns.push_back(ObjectSpawn);
340 }
341
342 // Load object spawns
343 XMLElement *ObjectsElement = LevelElement->FirstChildElement("objects");
344 if(ObjectsElement) {
345 for(XMLElement *ObjectElement = ObjectsElement->FirstChildElement(); ObjectElement != 0; ObjectElement = ObjectElement->NextSiblingElement()) {
346
347 // Create an object spawn
348 _ObjectSpawn *ObjectSpawn = new _ObjectSpawn;
349
350 // Get the object properties
351 if(!GetObjectSpawnProperties(ObjectElement, *ObjectSpawn))
352 return 0;
353
354 // Store for later
355 ObjectSpawns.push_back(ObjectSpawn);
356 }
357 }
358
359 // Load constraint spawns
360 XMLElement *ConstraintsElement = LevelElement->FirstChildElement("constraints");
361 if(ConstraintsElement) {
362 for(XMLElement *ConstraintElement = ConstraintsElement->FirstChildElement(); ConstraintElement != 0; ConstraintElement = ConstraintElement->NextSiblingElement()) {
363
364 // Create a constraint spawn
365 _ConstraintSpawn *ConstraintSpawn = new _ConstraintSpawn;
366
367 // Get the constraint properties
368 if(!GetConstraintSpawnProperties(ConstraintElement, *ConstraintSpawn))
369 return 0;
370
371 // Store for later
372 ConstraintSpawns.push_back(ConstraintSpawn);
373 }
374 }
375
376 return 1;
377 }
378
379 // Closes the level
Close()380 int _Level::Close() {
381
382 // Clear scripts
383 Scripts.clear();
384
385 // Clear sounds
386 for(size_t i = 0; i < Sounds.size(); i++)
387 Audio.CloseBuffer(Sounds[i]);
388
389 Sounds.clear();
390
391 // Delete templates
392 for(size_t i = 0; i < Templates.size(); i++)
393 delete Templates[i];
394
395 Templates.clear();
396
397 // Delete object spawn data
398 for(size_t i = 0; i < ObjectSpawns.size(); i++)
399 delete ObjectSpawns[i];
400
401 ObjectSpawns.clear();
402
403 // Delete constraint spawn data
404 for(size_t i = 0; i < ConstraintSpawns.size(); i++)
405 delete ConstraintSpawns[i];
406
407 ConstraintSpawns.clear();
408
409 return 1;
410 }
411
412 // Processes a template tag
GetTemplateProperties(XMLElement * TemplateElement,_Template & Template)413 int _Level::GetTemplateProperties(XMLElement *TemplateElement, _Template &Template) {
414 XMLElement *Element;
415 const char *String;
416
417 // Get type
418 std::string ObjectType = TemplateElement->Value();
419
420 // Object defaults
421 if(ObjectType == "orb") {
422 Template.Sleep = true;
423 }
424
425 // Get name
426 String = TemplateElement->Attribute("name");
427 if(!String) {
428 Log.Write("Template is missing name");
429 return 0;
430 }
431 Template.Name = String;
432
433 // Get attributes
434 TemplateElement->QueryFloatAttribute("lifetime", &Template.Lifetime);
435 TemplateElement->QueryIntAttribute("smooth", &Template.Smooth);
436 TemplateElement->QueryIntAttribute("detail", &Template.Detail);
437
438 // Get scale
439 Element = TemplateElement->FirstChildElement("shape");
440 if(Element) {
441 Element->QueryFloatAttribute("w", &Template.Shape[0]);
442 Element->QueryFloatAttribute("h", &Template.Shape[1]);
443 Element->QueryFloatAttribute("l", &Template.Shape[2]);
444 Element->QueryFloatAttribute("r", &Template.Radius);
445 }
446
447 // Get mesh properties
448 Element = TemplateElement->FirstChildElement("mesh");
449 if(Element) {
450 String = Element->Attribute("file");
451 if(String) {
452 Template.Mesh = String;
453
454 // Try custom path first
455 Template.Mesh = CustomDataPath + std::string("meshes/") + String;
456 if(!irrFile->existFile(Template.Mesh.c_str())) {
457
458 // Try normal path
459 Template.Mesh = Framework.GetWorkingPath() + std::string("meshes/") + String;
460 if(!irrFile->existFile(Template.Mesh.c_str())) {
461 Log.Write("Mesh file does not exist: %s", String);
462 return 0;
463 }
464 }
465 }
466
467 // Get component scale
468 Element->QueryFloatAttribute("w", &Template.Scale[0]);
469 Element->QueryFloatAttribute("h", &Template.Scale[1]);
470 Element->QueryFloatAttribute("l", &Template.Scale[2]);
471
472 // Get scale
473 float Scale = 1.0f;
474 Element->QueryFloatAttribute("scale", &Scale);
475 for(int i = 0; i < 3; i++)
476 Template.Scale[i] *= Scale;
477 }
478
479 // Get heightmap properties
480 Element = TemplateElement->FirstChildElement("heightmap");
481 if(Element) {
482 String = Element->Attribute("file");
483 if(String) {
484 Template.HeightMap = String;
485
486 // Try custom path first
487 Template.HeightMap = CustomDataPath + std::string("textures/") + String;
488 if(!irrFile->existFile(Template.HeightMap.c_str())) {
489
490 // Try normal path
491 Template.HeightMap = Framework.GetWorkingPath() + std::string("textures/") + String;
492 if(!irrFile->existFile(Template.HeightMap.c_str())) {
493 Log.Write("Heightmap file does not exist: %s", String);
494 return 0;
495 }
496 }
497 }
498 }
499
500 // Get physical attributes
501 Element = TemplateElement->FirstChildElement("physics");
502 if(Element) {
503 Element->QueryIntAttribute("kinematic", &Template.Kinematic);
504 Element->QueryIntAttribute("sleep", &Template.Sleep);
505 Element->QueryFloatAttribute("mass", &Template.Mass);
506 Element->QueryFloatAttribute("friction", &Template.Friction);
507 Element->QueryFloatAttribute("rolling_friction", &Template.RollingFriction);
508 Element->QueryFloatAttribute("restitution", &Template.Restitution);
509 Element->QueryFloatAttribute("erp", &Template.ERP);
510 Element->QueryFloatAttribute("cfm", &Template.CFM);
511 }
512
513 // Get collision attributes
514 Element = TemplateElement->FirstChildElement("collision");
515 if(Element) {
516 Element->QueryIntAttribute("group", &Template.CollisionGroup);
517 Element->QueryIntAttribute("mask", &Template.CollisionMask);
518 String = Element->Attribute("callback");
519 if(String)
520 Template.CollisionCallback = String;
521 }
522
523 // Get damping
524 Element = TemplateElement->FirstChildElement("damping");
525 if(Element) {
526 Element->QueryFloatAttribute("linear", &Template.LinearDamping);
527 Element->QueryFloatAttribute("angular", &Template.AngularDamping);
528 }
529
530 // Get axis for constraints
531 Element = TemplateElement->FirstChildElement("axis");
532 if(Element) {
533 Element->QueryFloatAttribute("x", &Template.ConstraintAxis[0]);
534 Element->QueryFloatAttribute("y", &Template.ConstraintAxis[1]);
535 Element->QueryFloatAttribute("z", &Template.ConstraintAxis[2]);
536 }
537
538 // Get textures
539 for(XMLElement *Element = TemplateElement->FirstChildElement("texture"); Element != 0; Element = Element->NextSiblingElement("texture")) {
540
541 // Get texture index
542 int Index = 0;
543 Element->QueryIntAttribute("index", &Index);
544 if(Index > 3) {
545 Log.Write("Texture index out of bounds! %d > 3", Index);
546 return 0;
547 }
548
549 // Get texture scale
550 Element->QueryFloatAttribute("scale", &Template.TextureScale[Index]);
551
552 // Get filename
553 const char *Filename = Element->Attribute("file");
554 if(Filename) {
555
556 // Try custom path first
557 Template.Textures[Index] = CustomDataPath + std::string("textures/") + Filename;
558 if(!irrFile->existFile(Template.Textures[Index].c_str())) {
559
560 // Try normal path
561 Template.Textures[Index] = Framework.GetWorkingPath() + std::string("textures/") + Filename;
562 if(!irrFile->existFile(Template.Textures[Index].c_str())) {
563 Log.Write("Texture file does not exist: %s", Filename);
564 return 0;
565 }
566 }
567 }
568 }
569
570 // Validate objects
571 if(ObjectType == "player") {
572 Template.Type = _Object::PLAYER;
573 Template.RollingFriction = 0.001f;
574 Template.CollisionGroup &= ~_Physics::FILTER_CAMERA;
575 Template.Fog = false;
576 }
577 else if(ObjectType == "orb") {
578 Template.Type = _Object::ORB;
579 }
580 else if(ObjectType == "plane") {
581 Template.Type = _Object::PLANE;
582 Template.Mass = 0.0f;
583 }
584 else if(ObjectType == "sphere") {
585 Template.Type = _Object::SPHERE;
586 }
587 else if(ObjectType == "box") {
588 Template.Type = _Object::BOX;
589 }
590 else if(ObjectType == "cylinder") {
591 Template.Type = _Object::CYLINDER;
592 }
593 else if(ObjectType == "terrain") {
594 Template.Type = _Object::TERRAIN;
595 Template.Mass = 0.0f;
596 }
597 else if(ObjectType == "zone") {
598 Template.Type = _Object::ZONE;
599 Template.Mass = 0.0f;
600 Template.CollisionGroup = _Physics::FILTER_ZONE;
601 Template.CollisionMask = _Physics::FILTER_RIGIDBODY;
602 }
603 else if(ObjectType == "d6") {
604 Template.Type = _Object::CONSTRAINT_D6;
605 }
606 else if(ObjectType == "fixed") {
607 Template.Type = _Object::CONSTRAINT_FIXED;
608 }
609 else if(ObjectType == "hinge") {
610 Template.Type = _Object::CONSTRAINT_HINGE;
611 }
612
613 // If a body's mass is zero, set group to static
614 if(Template.Mass == 0.0f && (Template.CollisionGroup & _Physics::FILTER_RIGIDBODY)) {
615 Template.CollisionGroup = _Physics::FILTER_STATIC | _Physics::FILTER_CAMERA;
616 Template.CollisionMask = 0;
617 }
618
619 return 1;
620 }
621
622 // Processes an object tag
GetObjectSpawnProperties(XMLElement * ObjectElement,_ObjectSpawn & ObjectSpawn)623 int _Level::GetObjectSpawnProperties(XMLElement *ObjectElement, _ObjectSpawn &ObjectSpawn) {
624 XMLElement *Element;
625
626 // Get name
627 ObjectSpawn.Name = ObjectElement->Attribute("name");
628 if(ObjectSpawn.Name == "") {
629 Log.Write("Object is missing name");
630 return 0;
631 }
632
633 // Get template name
634 std::string TemplateName = ObjectElement->Attribute("template");
635 if(TemplateName == "") {
636 Log.Write("Object is missing template name");
637 return 0;
638 }
639
640 // Get template data
641 ObjectSpawn.Template = GetTemplate(TemplateName);
642 if(ObjectSpawn.Template == nullptr) {
643 Log.Write("Cannot find object template %s", TemplateName.c_str());
644 return 0;
645 }
646
647 // Get position
648 Element = ObjectElement->FirstChildElement("position");
649 if(Element) {
650 Element->QueryFloatAttribute("x", &ObjectSpawn.Position[0]);
651 Element->QueryFloatAttribute("y", &ObjectSpawn.Position[1]);
652 Element->QueryFloatAttribute("z", &ObjectSpawn.Position[2]);
653 }
654
655 // Get euler rotation
656 Element = ObjectElement->FirstChildElement("rotation");
657 if(Element) {
658 Element->QueryFloatAttribute("x", &ObjectSpawn.Rotation[0]);
659 Element->QueryFloatAttribute("y", &ObjectSpawn.Rotation[1]);
660 Element->QueryFloatAttribute("z", &ObjectSpawn.Rotation[2]);
661 }
662
663 // Get quaternion rotation
664 Element = ObjectElement->FirstChildElement("quaternion");
665 if(Element) {
666 Element->QueryFloatAttribute("x", &ObjectSpawn.Quaternion.x);
667 Element->QueryFloatAttribute("y", &ObjectSpawn.Quaternion.y);
668 Element->QueryFloatAttribute("z", &ObjectSpawn.Quaternion.z);
669 Element->QueryFloatAttribute("w", &ObjectSpawn.Quaternion.w);
670 ObjectSpawn.HasQuaternion = true;
671 ObjectSpawn.CalculateRotation();
672 }
673
674 // Get plane
675 Element = ObjectElement->FirstChildElement("plane");
676 if(Element) {
677
678 // Get normal
679 glm::vec3 Normal;
680 Element->QueryFloatAttribute("x", &Normal[0]);
681 Element->QueryFloatAttribute("y", &Normal[1]);
682 Element->QueryFloatAttribute("z", &Normal[2]);
683 Element->QueryFloatAttribute("d", &ObjectSpawn.Plane[3]);
684
685 // Normalize
686 if(Normal != glm::vec3(0))
687 Normal = glm::normalize(Normal);
688
689 // Set plane
690 ObjectSpawn.Plane = glm::vec4(Normal, ObjectSpawn.Plane[3]);
691 }
692
693 // Get linear velocity
694 Element = ObjectElement->FirstChildElement("linear_velocity");
695 if(Element) {
696 Element->QueryFloatAttribute("x", &ObjectSpawn.LinearVelocity[0]);
697 Element->QueryFloatAttribute("y", &ObjectSpawn.LinearVelocity[1]);
698 Element->QueryFloatAttribute("z", &ObjectSpawn.LinearVelocity[2]);
699 }
700
701 // Get angular velocity
702 Element = ObjectElement->FirstChildElement("angular_velocity");
703 if(Element) {
704 Element->QueryFloatAttribute("x", &ObjectSpawn.AngularVelocity[0]);
705 Element->QueryFloatAttribute("y", &ObjectSpawn.AngularVelocity[1]);
706 Element->QueryFloatAttribute("z", &ObjectSpawn.AngularVelocity[2]);
707 }
708
709 return 1;
710 }
711
712 // Processes a constraint tag
GetConstraintSpawnProperties(XMLElement * ConstraintElement,_ConstraintSpawn & ConstraintSpawn)713 int _Level::GetConstraintSpawnProperties(XMLElement *ConstraintElement, _ConstraintSpawn &ConstraintSpawn) {
714 XMLElement *Element;
715 const char *String = nullptr;
716
717 // Get name
718 ConstraintSpawn.Name = ConstraintElement->Attribute("name");
719 if(ConstraintSpawn.Name == "") {
720 Log.Write("Constraint is missing name");
721 return 0;
722 }
723
724 // Get template name
725 std::string TemplateName = ConstraintElement->Attribute("template");
726 if(TemplateName == "") {
727 Log.Write("Constraint is missing template name");
728 return 0;
729 }
730
731 // Get template data
732 ConstraintSpawn.Template = GetTemplate(TemplateName);
733 if(ConstraintSpawn.Template == nullptr) {
734 Log.Write("Cannot find constraint template %s", TemplateName.c_str());
735 return 0;
736 }
737
738 // Get first object
739 String = ConstraintElement->Attribute("object1");
740 if(String)
741 ConstraintSpawn.MainObjectName = String;
742
743 // Requirements
744 if(ConstraintSpawn.MainObjectName == "") {
745 Log.Write("Constraint is missing object1's name");
746 return 0;
747 }
748
749 // Get second object
750 String = ConstraintElement->Attribute("object2");
751 if(String)
752 ConstraintSpawn.OtherObjectName = String;
753
754 // Get position
755 Element = ConstraintElement->FirstChildElement("anchor_position");
756 if(Element) {
757 Element->QueryFloatAttribute("x", &ConstraintSpawn.AnchorPosition[0]);
758 Element->QueryFloatAttribute("y", &ConstraintSpawn.AnchorPosition[1]);
759 Element->QueryFloatAttribute("z", &ConstraintSpawn.AnchorPosition[2]);
760 ConstraintSpawn.HasAnchorPosition = true;
761 }
762
763 return 1;
764 }
765
766 // Spawns all of the objects and constraints in the level
SpawnEntities()767 void _Level::SpawnEntities() {
768
769 // Create objects
770 for(size_t i = 0; i < ObjectSpawns.size(); i++) {
771 _ObjectSpawn *Spawn = ObjectSpawns[i];
772 CreateObject(*Spawn);
773 }
774
775 // Create constraints
776 for(size_t i = 0; i < ConstraintSpawns.size(); i++) {
777 _ConstraintSpawn *Spawn = ConstraintSpawns[i];
778 Spawn->MainObject = ObjectManager.GetObjectByName(Spawn->MainObjectName);
779 Spawn->OtherObject = ObjectManager.GetObjectByName(Spawn->OtherObjectName);
780 CreateConstraint(*Spawn);
781 }
782 }
783
784 // Creates an object from a spawn struct
CreateObject(const _ObjectSpawn & Object)785 _Object *_Level::CreateObject(const _ObjectSpawn &Object) {
786
787 // Add object
788 _Object *NewObject = nullptr;
789 switch(Object.Template->Type) {
790 case _Object::PLAYER:
791 NewObject = ObjectManager.AddObject(new _Player(Object));
792 break;
793 case _Object::ORB:
794 NewObject = ObjectManager.AddObject(new _Orb(Object));
795 break;
796 case _Object::COLLISION:
797 NewObject = ObjectManager.AddObject(new _Trimesh(Object));
798 break;
799 case _Object::PLANE:
800 NewObject = ObjectManager.AddObject(new _Plane(Object));
801 break;
802 case _Object::SPHERE:
803 NewObject = ObjectManager.AddObject(new _Sphere(Object));
804 break;
805 case _Object::BOX:
806 NewObject = ObjectManager.AddObject(new _Box(Object));
807 break;
808 case _Object::CYLINDER:
809 NewObject = ObjectManager.AddObject(new _Cylinder(Object));
810 break;
811 case _Object::TERRAIN:
812 NewObject = ObjectManager.AddObject(new _Terrain(Object));
813 break;
814 case _Object::ZONE:
815 NewObject = ObjectManager.AddObject(new _Zone(Object));
816 break;
817 }
818
819 // Record replay event
820 if(Replay.IsRecording() && Object.Template->TemplateID != -1) {
821
822 // Write replay information
823 std::fstream &ReplayFile = Replay.GetFile();
824 Replay.WriteEvent(_Replay::PACKET_CREATE);
825 ReplayFile.write((char *)&Object.Template->TemplateID, sizeof(Object.Template->TemplateID));
826 ReplayFile.write((char *)&NewObject->GetID(), sizeof(NewObject->GetID()));
827 if(Object.Template->Type == _Object::PLANE) {
828 ReplayFile.put(1);
829 ReplayFile.write((char *)&Object.Plane, sizeof(float) * 4);
830 }
831 else {
832 ReplayFile.put(0);
833 ReplayFile.write((char *)&Object.Position, sizeof(float) * 3);
834 }
835 ReplayFile.write((char *)&Object.Rotation, sizeof(float) * 3);
836 }
837
838 return NewObject;
839 }
840
841 // Creates a constraint from a template
CreateConstraint(const _ConstraintSpawn & Constraint)842 _Object *_Level::CreateConstraint(const _ConstraintSpawn &Constraint) {
843
844 // Add object
845 _Object *NewObject = ObjectManager.AddObject(new _Constraint(Constraint));
846
847 return NewObject;
848 }
849
850 // Gets a template by name
GetTemplate(const std::string & Name)851 _Template *_Level::GetTemplate(const std::string &Name) {
852
853 // Search templates by name
854 for(size_t i = 0; i < Templates.size(); i++) {
855 if(Templates[i]->Name == Name) {
856 return Templates[i];
857 }
858 }
859
860 return nullptr;
861 }
862
863 // Gets a template by name
GetTemplateFromID(int ID)864 _Template *_Level::GetTemplateFromID(int ID) {
865
866 // Search templates by id
867 for(size_t i = 0; i < Templates.size(); i++) {
868 if(Templates[i]->TemplateID == ID) {
869 return Templates[i];
870 }
871 }
872
873 return nullptr;
874 }
875
876 // Runs the level's scripts
RunScripts()877 void _Level::RunScripts() {
878
879 // Reset Lua state
880 Scripting.Reset();
881
882 // Add key names to lua scope
883 Scripting.DefineLuaVariable("KEY_FORWARD", Input.GetKeyName(Actions.GetInputForAction(_Input::KEYBOARD, _Actions::MOVE_FORWARD)));
884 Scripting.DefineLuaVariable("KEY_BACK", Input.GetKeyName(Actions.GetInputForAction(_Input::KEYBOARD, _Actions::MOVE_BACK)));
885 Scripting.DefineLuaVariable("KEY_LEFT", Input.GetKeyName(Actions.GetInputForAction(_Input::KEYBOARD, _Actions::MOVE_LEFT)));
886 Scripting.DefineLuaVariable("KEY_RIGHT", Input.GetKeyName(Actions.GetInputForAction(_Input::KEYBOARD, _Actions::MOVE_RIGHT)));
887 Scripting.DefineLuaVariable("KEY_RESET", Input.GetKeyName(Actions.GetInputForAction(_Input::KEYBOARD, _Actions::RESET)));
888 Scripting.DefineLuaVariable("KEY_JUMP", Input.GetKeyName(Actions.GetInputForAction(_Input::KEYBOARD, _Actions::JUMP)));
889
890 // Run scripts
891 for(uint32_t i = 0; i < Scripts.size(); i++) {
892
893 // Load a level
894 Scripting.LoadFile(Scripts[i]);
895 }
896 }
897