Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/WolfireGames/overgrowth.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'Source/XML/level_loader.cpp')
-rw-r--r--Source/XML/level_loader.cpp815
1 files changed, 815 insertions, 0 deletions
diff --git a/Source/XML/level_loader.cpp b/Source/XML/level_loader.cpp
new file mode 100644
index 00000000..5464a46e
--- /dev/null
+++ b/Source/XML/level_loader.cpp
@@ -0,0 +1,815 @@
+//-----------------------------------------------------------------------------
+// Name: level_loader.cpp
+// Developer: Wolfire Games LLC
+// Description:
+// License: Read below
+//-----------------------------------------------------------------------------
+//
+// Copyright 2022 Wolfire Games LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//-----------------------------------------------------------------------------
+
+#include <Internal/profiler.h>
+#include <Internal/common.h>
+#include <Internal/filesystem.h>
+#include <Internal/dialogues.h>
+#include <Internal/error.h>
+#include <Internal/datemodified.h>
+#include <Internal/locale.h>
+#include <Internal/memwrite.h>
+#include <Internal/levelxml.h>
+#include <Internal/config.h>
+
+#include <Objects/terrainobject.h>
+#include <Objects/cameraobject.h>
+#include <Objects/editorcameraobject.h>
+#include <Objects/envobject.h>
+#include <Objects/movementobject.h>
+#include <Objects/group.h>
+#include <Objects/hotspot.h>
+#include <Objects/decalobject.h>
+#include <Objects/hotspot.h>
+
+#include <XML/xml_helper.h>
+#include <XML/level_loader.h>
+
+#include <Graphics/models.h>
+#include <Graphics/camera.h>
+#include <Graphics/sky.h>
+#include <Graphics/graphics.h>
+#include <Graphics/shaders.h>
+#include <Graphics/font_renderer.h>
+#include <Graphics/particles.h>
+#include <Graphics/text.h>
+
+#include <Asset/Asset/ambientsounds.h>
+#include <Asset/Asset/skeletonasset.h>
+#include <Asset/Asset/syncedanimation.h>
+#include <Asset/Asset/voicefile.h>
+
+#include <Sound/sound.h>
+#include <Sound/threaded_sound_wrapper.h>
+
+#include <Main/scenegraph.h>
+#include <Math/vec4.h>
+#include <Editors/map_editor.h>
+#include <Game/level.h>
+#include <Logging/logdata.h>
+#include <GUI/widgetframework.h>
+#include <Online/online.h>
+
+#include <tinyxml.h>
+
+extern std::string script_dir_path;
+extern bool g_level_shadows;
+
+void LoadTerrain(SceneGraph& s, const TerrainInfo& ti) {
+ AddLoadingText("Loading terrain object...");
+
+ TerrainObject* terrain_object = NULL;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Creating terrain object");
+ terrain_object = new TerrainObject(ti);
+ }
+ terrain_object->SetID(0);
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Adding terrain object to scenegraph");
+ s.addObject(terrain_object);
+ }
+ s.terrain_object_ = terrain_object;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Preparing terrain physics");
+ terrain_object->PreparePhysicsMesh();
+ }
+}
+
+void AddCamera(SceneGraph &s) {
+ EditorCameraObject* new_object = new EditorCameraObject();
+ new_object->SetTranslation(vec3(0,0,0));
+ new_object->controlled = true;
+ new_object->has_position_initialized = false;
+ s.addObject(new_object);
+ ActiveCameras::Get()->SetCameraObject((CameraObject*)new_object);
+ ActiveCameras::Get()->SetFlags(Camera::kEditorCamera);
+}
+
+static void LoadAll(const PathSet &path_set, ThreadedSound* sound) {
+ std::list<std::string> skeleton_paths;
+ for(PathSet::const_iterator iter = path_set.begin(); iter != path_set.end(); ++iter) {
+ const std::string &entry = (*iter);
+ int space_pos = entry.find(' ');
+ const std::string &type = entry.substr(0,space_pos);
+ const std::string &path = entry.substr(space_pos+1, entry.size()-(space_pos+1));
+ static const int kBufSize = 1024;
+ char buf[kBufSize];
+ FormatString(buf, kBufSize, "Loading %s from %s", type.c_str(), path.c_str());
+ PROFILER_ZONE_DYNAMIC_STRING(g_profiler_ctx, buf);
+ switch(type[0]){
+ case 'a':
+ switch(type[1]){
+ case 'm':
+ if(type == "ambient_sound"){Engine::Instance()->GetAssetManager()->LoadSync<AmbientSound>(path);}
+ break;
+ case 'n':
+ if(type == "animation"){Engine::Instance()->GetAssetManager()->LoadSync<Animation>(path);}
+ break;
+ case 't':
+ if(type == "attack"){Engine::Instance()->GetAssetManager()->LoadSync<Attack>(path);}
+ break;
+ }
+ break;
+ case 'c':
+ if(type == "character"){Engine::Instance()->GetAssetManager()->LoadSync<Character>(path);}
+ break;
+ case 'd':
+ if(type == "decal"){Engine::Instance()->GetAssetManager()->LoadSync<DecalFile>(path);}
+ break;
+ case 'f':
+ if(type == "font"){FontRenderer::Instance()->PreLoadFont(path);}
+ break;
+ case 'h'://heightmap
+ break;
+ case 'i':
+ if(type == "image_sample"){Engine::Instance()->GetAssetManager()->LoadSync<ImageSampler>(path);}
+ break;
+ case 'l'://level
+ break;
+ case 'm':
+ switch(type[1]){
+ case 'a':
+ if(type == "material"){Engine::Instance()->GetAssetManager()->LoadSync<Material>(path);}
+ break;
+ case 'o'://model
+ break;
+ }
+ break;
+ case 'o': //object
+ break;
+ case 'p'://particle
+ if(type == "particle"){Engine::Instance()->GetAssetManager()->LoadSync<ParticleType>(path);}
+ break;
+ case 's':
+ switch(type[1]){
+ case 'c'://script
+ break;
+ case 'h'://shader
+ break;
+ case 'k':
+ if(type == "skeleton"){
+ //SkeletonAssets::Instance()->ReturnRef(path);
+ Engine::Instance()->GetAssetManager()->LoadSync<SkeletonAsset>(path);
+ skeleton_paths.push_back(path);
+ }
+ break;
+ case 'o':
+ if(type == "sound"){
+ sound->LoadSoundFile(path);
+ } else if(type == "soundgroup"){
+ //SoundGroupInfoCollection::Instance()->ReturnRef(path);
+ Engine::Instance()->GetAssetManager()->LoadSync<SoundGroupInfo>(path);
+ }
+ break;
+ case 'y':
+ if(type == "synced_animation"){Engine::Instance()->GetAssetManager()->LoadSync<SyncedAnimationGroup>(path);}
+ break;
+ }
+ break;
+ case 't':
+ //Textures::Instance()->returnTextureAssetRef(path);
+ break;
+ case 'v':
+ if(type == "voice"){Engine::Instance()->GetAssetManager()->LoadSync<VoiceFile>(path);}
+ break;
+ }
+ }
+ LOGI << "Finished loading path set" << std::endl;
+}
+
+struct IDAndType {
+ int id;
+ EntityType type;
+ EntityDescription *desc;
+ uint32_t counter; //we use this counter to guarantee the same order on all systems when two objects are otherwise identical.
+};
+
+static int IDAndType_CompareID(const void *a, const void *b) {
+ IDAndType *a_ptr = (IDAndType*)a;
+ IDAndType *b_ptr = (IDAndType*)b;
+ int cmp = a_ptr->id - b_ptr->id;
+ if(cmp == 0) {
+ //We add to the type value, so that we have space to prioritize special types
+ int a_val = a_ptr->type + 3;
+ int b_val = b_ptr->type + 3;
+
+ //We override group and prefab to be prioritized first, unknown why.
+ if(a_ptr->type == _group) {
+ a_val = 0;
+ }
+ if(b_ptr->type == _group) {
+ b_val = 0;
+ }
+
+ if(a_ptr->type == _prefab) {
+ a_val = 1;
+ }
+ if(b_ptr->type == _prefab) {
+ b_val = 1;
+ }
+ //We prioritize movement_object, because there have been cases where it loses its id, breaking dialogues
+ if(a_ptr->type == _movement_object) {
+ a_val = 2;
+ }
+ if(b_ptr->type == _movement_object) {
+ b_val = 2;
+ }
+
+ cmp = a_val - b_val;
+
+ if( cmp == 0 ) {
+ return a_ptr->counter - b_ptr->counter;
+ }
+ }
+ return cmp;
+}
+
+// Finds entity IDs that are used more than once, and replaces repeats with -1 so they will be automatically reassigned
+static void ExtractFlatList(EntityDescriptionList *desc_list, std::vector<IDAndType> &id_used){
+ uint32_t counter = 1; //Zero is assigned to the terrain
+ //bool print_results = false;
+ for(EntityDescriptionList::iterator it = desc_list->begin();
+ it != desc_list->end();
+ ++it)
+ {
+ id_used.resize(id_used.size()+1);
+ IDAndType *id_and_type = &id_used.back();
+ id_and_type->desc = &(*it);
+ id_and_type->desc->GetEditableField(EDF_ENTITY_TYPE)->ReadInt((int*)&id_and_type->type);
+ id_and_type->desc->GetEditableField(EDF_ID)->ReadInt(&id_and_type->id);
+ id_and_type->counter = counter++;
+ if(!id_and_type->desc->children.empty()){
+ ExtractFlatList(&id_and_type->desc->children, id_used);
+ }
+ }
+}
+
+static void SetDescID(EntityDescription *desc, int id){
+ EntityDescriptionField* edf = desc->GetEditableField(EDF_ID);
+ if( edf )
+ {
+ edf->data.clear();
+ edf->WriteInt(id);
+ }
+ else
+ {
+ LOGE << "EDF_ID object returned is NULL, unexpected behaviour" << std::endl;
+ }
+}
+
+static void FixDuplicateIDs(EntityDescriptionList *desc_list, bool has_terrain){
+ std::vector<IDAndType> id_used;
+ ExtractFlatList(desc_list, id_used);
+ if(!id_used.empty()){
+ EntityDescription terrain_desc;
+ if(has_terrain){
+ IDAndType terrain_id_and_type;
+ terrain_id_and_type.id = 0;
+ terrain_id_and_type.counter = 0;
+ terrain_id_and_type.type = _terrain_type;
+ terrain_id_and_type.desc = &terrain_desc;
+ id_used.push_back(terrain_id_and_type);
+ }
+ qsort(&id_used[0], id_used.size(), sizeof(id_used[0]), IDAndType_CompareID);
+ int free_index = 1;
+ int free_id = -1;
+ for(int i=1, len=id_used.size(); i<len; ++i){
+ if(id_used[i-1].id == id_used[i].id){
+ while(free_index != len && (id_used[free_index].id < id_used[free_index-1].id+2 || id_used[free_index].id < free_id+2)){
+ free_id = id_used[free_index].id;
+ ++free_index;
+ }
+ ++free_id;
+ if(id_used[i-1].type == _group || id_used[i-1].type == _prefab){
+ LOGW << "Object loaded of type " << id_used[i-1].type << " is changing id from " << id_used[i-1].id << " to " << free_id << " due to collision." << std::endl;
+ SetDescID(id_used[i-1].desc, free_id);
+ } else {
+ LOGW << "Object loaded of type " << id_used[i].type << " is changing id from " << id_used[i].id << " to " << free_id << " due to collision." << std::endl;
+ SetDescID(id_used[i].desc, free_id);
+ }
+ }
+ }
+ }
+}
+
+extern TextAtlasRenderer g_text_atlas_renderer;
+extern TextAtlas g_text_atlas[kNumTextAtlas];
+extern ASTextContext g_as_text_context;
+extern const char* font_path;
+
+void AnalyzeForLineBreaks(char* str, int len){
+ FontRenderer* font_renderer = FontRenderer::Instance();
+ int font_size = int(max(18, min(Graphics::Instance()->window_dims[1] / 30, Graphics::Instance()->window_dims[0] / 50)));
+ TextMetrics metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, str);
+ float threshold = (float) std::min(font_size*40, Graphics::Instance()->window_dims[0]-100);
+ std::string final;
+ std::string first_line = str;
+ std::string second_line;
+ while(first_line.length() > 0){
+ while(metrics.bounds[2] > threshold){
+ int last_space = first_line.find_last_of(" ");
+ second_line.insert(0, first_line.substr(last_space));
+ first_line.resize(last_space);
+ metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, first_line.c_str());
+ }
+ final += first_line + "\n";
+ if(!second_line.empty()){
+ first_line = second_line.substr(1);
+ second_line = "";
+ } else {
+ first_line.clear();
+ }
+ metrics = g_as_text_context.ASGetTextAtlasMetrics( font_path, font_size, 0, first_line.c_str());
+ }
+ FormatString(str, len, "%s", final.c_str());
+}
+
+bool LevelLoader::LoadLevel(const Path& level_path, SceneGraph& s) {
+ Graphics* gi = Graphics::Instance();
+
+ if(level_path.isValid() == false) {
+ FatalError("Error", "Could not find level file: %s", level_path.GetFullPath());
+ }
+
+ LevelInfo li;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Parsing level xml");
+ ParseLevelXML(level_path.GetFullPath(), li);
+ g_level_shadows = li.shadows_;
+ const char* localized_load_tip = GetLevelTip(config["language"].str().c_str(), FindShortestPath(level_path.GetFullPath()).c_str());
+ char temp_load_screen_tip[kPathSize] = {'\0'};
+ if(localized_load_tip) {
+ FormatString(temp_load_screen_tip, kPathSize, "%s", localized_load_tip);
+ } else {
+ // Simple fallback to en_us in case there is one available
+ const char* localized_load_tip = GetLevelTip("en_us", FindShortestPath(level_path.GetFullPath()).c_str());
+ if(localized_load_tip) {
+ FormatString(temp_load_screen_tip, kPathSize, "%s", localized_load_tip);
+ } else if (li.spm_.find("Load Tip") != li.spm_.end()){
+ FormatString(temp_load_screen_tip, kPathSize, li.spm_["Load Tip"].GetString().c_str());
+ }
+ }
+ AnalyzeForLineBreaks(temp_load_screen_tip, kPathSize);
+ FormatString(Engine::Instance()->load_screen_tip, kPathSize, "%s", temp_load_screen_tip);
+ if(!li.script_.empty()){
+ std::string path = li.script_.substr(0, li.script_.size()-3) + "_paths.xml";
+ if(!FileExists(path.c_str(), kAnyPath)){
+ path = script_dir_path + path;
+ }
+ Path level_script_path;
+ level_script_path = FindFilePath(path.c_str(), kAnyPath, false);
+ if(level_script_path.isValid()){
+ TiXmlDocument doc;
+ if (!doc.LoadFile(level_script_path.GetFullPath())) {
+ FatalError("Error", "Bad xml data in level script path file: %s", path.c_str());
+ }
+ const TiXmlElement* root = doc.RootElement();
+
+ LevelInfo::StrPair script_path;
+ for(const TiXmlElement* field = root; field; field = field->NextSiblingElement()){
+ const char* val = field->Value();
+ if(strcmp(val, "path") == 0) {
+ script_path.first.clear();
+ script_path.second.clear();
+ const TiXmlAttribute* attrib = field->FirstAttribute();
+ while(attrib){
+ const char* name = attrib->Name();
+ if(strcmp(name, "path") == 0){
+ script_path.second = attrib->Value();
+ } else if(strcmp(name, "key") == 0){
+ script_path.first = attrib->Value();
+ }
+ attrib = attrib->Next();
+ }
+ li.script_paths_.push_back(script_path);
+ }
+ }
+ }
+ }
+ }
+
+ s.level_path_ = FindShortestPath2(level_path.GetFullPath());
+ s.level_has_been_previously_saved_ = false;
+ s.level_name_ = li.level_name_;
+ s.level_visible_name_ = li.visible_name_;
+ s.level_visible_description_ = li.visible_description_;
+ gi->post_shader_name = li.shader_;
+
+ bool has_terrain = !li.terrain_info_.colormap.empty();
+ if(has_terrain){
+ PROFILER_ZONE(g_profiler_ctx, "Loading terrain");
+ LoadTerrain(s, li.terrain_info_);
+ }
+
+ FixDuplicateIDs(&li.desc_list_, has_terrain);
+ AddLoadingText("Adding loaded objects to scene...");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Adding objects");
+
+ static const int kBufSize = 256;
+ char buf[kBufSize];
+ for(unsigned i=0, len=li.desc_list_.size(); i<len; ++i){
+ buf[0] = '\0';
+ const EntityDescriptionField* path_field = li.desc_list_[i].GetField(EDF_FILE_PATH);
+ if(path_field && !path_field->data.empty()){
+ FormatString(buf, kBufSize, "Adding object: %s", std::string(path_field->data.begin(), path_field->data.end()).c_str());
+ PROFILER_ENTER_DYNAMIC_STRING(g_profiler_ctx, buf);
+ } else {
+ PROFILER_ENTER(g_profiler_ctx, "Adding object: unknown");
+ }
+ Object * obj = MapEditor::AddEntityFromDesc(&s, li.desc_list_[i], true);
+ if( obj == NULL ) {
+ LOGE << "Failed to construct object \"" << buf << "\" for level load" << std::endl;
+ }
+ PROFILER_LEAVE(g_profiler_ctx);
+ if( Engine::Instance()->RequestedInterruptLoading() ) {
+ return false;
+ }
+ }
+ }
+ s.map_editor->SetUpSky(li.sky_info_);
+
+ if(ActiveCameras::Get()->m_camera_object == NULL) {
+ AddCamera(s);
+ }
+
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Setting up level info");
+ s.level->SetFromLevelInfo(li);
+ }
+ {
+ gi->nav_mesh_out_of_date = li.out_of_date_info_.nav_mesh;
+ gi->nav_mesh_out_of_date_chunk = -1;
+ }
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Loading nav mesh");
+ AddLoadingText("Loading nav mesh...");
+ if( s.LoadNavMesh() )
+ {
+ AddLoadingText("Nav mesh loaded!");
+ }
+ else
+ {
+ if(li.nav_mesh_parameters_.generate && config["no_auto_nav_mesh"].toBool() == false){
+ AddLoadingText("Navmesh needs to be rebuilt for some reason");
+ AddLoadingText("Generating new nav mesh...");
+ s.CreateNavMesh();
+ s.SaveNavMesh();
+ }
+ }
+ }
+ s.SendMessageToAllObjects(OBJECT_MSG::FINALIZE_LOADED_CONNECTIONS);
+
+ AddLoadingText("Loading ambient sounds and music...");
+ for(unsigned i=0; i<li.ambient_sounds_.size(); ++i){
+ Engine::Instance()->GetSound()->AddAmbientTriangle(li.ambient_sounds_[i]);
+ }
+ AddLoadingText("Getting path set...");
+ PathSet path_set;
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Returning paths to path set");
+ li.ReturnPaths(path_set);
+ }
+ AddLoadingText("Loading all data in advance...");
+ {
+ PROFILER_ZONE(g_profiler_ctx, "Preloading all paths");
+ LoadAll(path_set, Engine::Instance()->GetSound());
+ }
+
+ PROFILER_ZONE(g_profiler_ctx, "Applying level script params");
+ // Add defaults if needed
+ ScriptParams sp;
+ sp.SetParameterMap(li.spm_);
+ sp.ASAddString("Objectives", "destroy_all");
+ sp.ASAddString("Achievements", "flawless, no_injuries, no_kills");
+ sp.ASAddInt("Level Boundaries", 1);
+ sp.ASAddInt("Shared Camera", 0);
+ sp.ASAddFloat("HDR White point", 0.7f);
+ sp.ASAddFloat("HDR Black point", 0.005f);
+ sp.ASAddFloat("HDR Bloom multiplier", 1.0f);
+ SceneGraph::ApplyScriptParams(&s, sp.GetParameterMap());
+
+ return true;
+}
+
+void LevelLoader::SaveTerrain(TiXmlNode* root, SceneGraph* s) {
+ TerrainObject *to = s->terrain_object_;
+ if(to){
+ const TerrainInfo &ti = to->terrain_info();
+
+ TiXmlElement* terrain_el = new TiXmlElement("Terrain");
+ root->LinkEndChild(terrain_el);
+
+ TiXmlElement* terrain_sub_el;
+ terrain_sub_el = new TiXmlElement("Heightmap");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.heightmap.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ terrain_sub_el = new TiXmlElement("ShaderExtra");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.shader_extra.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ terrain_sub_el = new TiXmlElement("DetailMap");
+ terrain_sub_el->LinkEndChild( new TiXmlText("") );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ terrain_sub_el = new TiXmlElement("ColorMap");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.colormap.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ if(!ti.weightmap.empty()){
+ terrain_sub_el = new TiXmlElement("WeightMap");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.weightmap.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ }
+ if(!ti.model_override.empty()){
+ terrain_sub_el = new TiXmlElement("ModelOverride");
+ terrain_sub_el->LinkEndChild( new TiXmlText(ti.model_override.c_str()) );
+ terrain_el->LinkEndChild(terrain_sub_el);
+ }
+
+ terrain_sub_el = new TiXmlElement("DetailMaps");
+ for(unsigned i=0; i<4; ++i){
+ TiXmlElement* detail_map_el = new TiXmlElement("DetailMap");
+ detail_map_el->SetAttribute("colorpath",ti.detail_map_info[i].colorpath.c_str());
+ detail_map_el->SetAttribute("normalpath",ti.detail_map_info[i].normalpath.c_str());
+ detail_map_el->SetAttribute("materialpath",ti.detail_map_info[i].materialpath.c_str());
+ terrain_sub_el->LinkEndChild(detail_map_el);
+ }
+ terrain_el->LinkEndChild(terrain_sub_el);
+
+ TiXmlElement *does = new TiXmlElement("DetailObjects");
+ for(unsigned i=0; i<ti.detail_object_info.size(); ++i){
+ const DetailObjectLayer &dol = ti.detail_object_info[i];
+ TiXmlElement *doe = new TiXmlElement("DetailObject");
+ doe->SetAttribute("obj_path", dol.obj_path.c_str());
+ doe->SetAttribute("weight_path", dol.weight_path.c_str());
+ doe->SetDoubleAttribute("normal_conform", (double)dol.normal_conform);
+ doe->SetDoubleAttribute("density", (double)dol.density);
+ doe->SetDoubleAttribute("min_embed", (double)dol.min_embed);
+ doe->SetDoubleAttribute("max_embed", (double)dol.max_embed);
+ doe->SetDoubleAttribute("min_scale", (double)dol.min_scale);
+ doe->SetDoubleAttribute("max_scale", (double)dol.max_scale);
+ doe->SetDoubleAttribute("view_distance", (double)dol.view_dist);
+ doe->SetDoubleAttribute("jitter_degrees", (double)dol.jitter_degrees);
+ doe->SetDoubleAttribute("overbright", (double)dol.overbright);
+ doe->SetDoubleAttribute("tint_weight", (double)dol.tint_weight);
+ if(dol.collision_type == _static){
+ doe->SetAttribute("collision_type", "static");
+ } else if(dol.collision_type == _plant){
+ doe->SetAttribute("collision_type", "plant");
+ }
+ does->LinkEndChild(doe);
+ }
+ terrain_el->LinkEndChild(does);
+ }
+}
+
+void LevelLoader::SaveLevel(SceneGraph &s, SaveLevelType type) {
+ /*if(s.VerifySanity() == false ) {
+ SDL_MessageBoxData message_box_data;
+ message_box_data.title = "Overgrowth";
+ message_box_data.message = "Your level has some sanity warnings that should be fixed, are you sure you wish to save?";
+ message_box_data.colorScheme = NULL;
+ message_box_data.window = NULL;
+ message_box_data.flags = SDL_MESSAGEBOX_WARNING;
+ message_box_data.numbuttons = 2;
+ SDL_MessageBoxButtonData buttons[3];
+ buttons[0].text = "Yes";
+ buttons[0].flags = SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
+ buttons[0].buttonid = 0;
+ buttons[1].text = "No";
+ buttons[1].flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
+ buttons[1].buttonid = 1;
+ message_box_data.buttons = buttons;
+ int button_id;
+ SDL_ShowMessageBox(&message_box_data, &button_id);
+ if(button_id == 1){
+ return;
+ }
+ }*/
+
+ std::string save_path;
+ std::string old_path;
+ if(s.level_path_.isValid()) {
+ save_path = s.level_path_.GetFullPath();
+ old_path = save_path;
+ }
+
+ if(type == kSaveAs || s.level_path_.isValid() == false || (((s.level_path_.source & kModPaths) || (s.level_path_.source & kDataPaths)) && (!s.level_has_been_previously_saved_ && config["allow_game_dir_save"].toBool() == false))) {
+ std::string start_dir;
+ if( config["allow_game_dir_save"].toBool() ) {
+ if( save_path.empty() ) {
+ start_dir = std::string(GetDataPath(0)) + "/Data/";
+ } else {
+ start_dir = SplitPathFileName( save_path ).first;
+ }
+ } else {
+ start_dir = std::string(GetWritePath(CoreGameModID).c_str()) + "/Data/";
+ }
+
+ bool valid = false;
+
+ while(!valid) {
+ const int BUF_SIZE = 512;
+ char buf[BUF_SIZE];
+ Dialog::DialogErr err = Dialog::writeFile("xml",1,start_dir.c_str(),buf,BUF_SIZE);
+ if(err){
+ LOGE << "Cancelling level save due to dialog close" << std::endl;
+ return;
+ } else {
+ save_path = buf;
+ old_path = save_path;
+
+ std::string temp = save_path.substr(0, save_path.find_last_of("\\/"));
+ if(CheckWritePermissions(temp.c_str()) != 0) {
+ LOGW << "Couldn't write to directory at " << temp << std::endl;
+ } else {
+ valid = true;
+ }
+ }
+ }
+ }
+
+ if(fileexists(old_path.c_str()) == 0) {
+ CreateBackup(old_path.c_str(), config["level_backup_count"].toNumber<int>());
+ }
+ createfile(save_path.c_str());
+
+ int slash_position = save_path.find_last_of("\\/")+1;
+ int dot_position = save_path.rfind('.');
+
+ s.level_path_ = FindShortestPath2(save_path);
+ s.level_has_been_previously_saved_ = true;
+ s.level_name_ = save_path.substr(slash_position, dot_position-slash_position);
+ s.level_visible_name_ = s.level_name_; // This isn't really used
+
+ Graphics* graphics = Graphics::Instance();
+ LOGI << "Saving level: " << s.level_name_ << std::endl;
+
+ TiXmlDocument doc;
+
+ TiXmlDeclaration* decl = new TiXmlDeclaration( "2.0", "", "" );
+ doc.LinkEndChild( decl );
+
+ //
+ //
+ TiXmlNode* eRoot = static_cast<TiXmlNode*>(&doc);
+ /* This should be activated in the future like alpha 213 or 214
+ TiXmlElement* eRoot = new TiXmlElement("Level");
+ doc.LinkEndChild( eRoot );
+ */
+
+
+ // write file type
+ TiXmlElement* version = new TiXmlElement("Type");
+ eRoot->LinkEndChild(version);
+ version->LinkEndChild(new TiXmlText("saved"));
+
+ TiXmlElement* name_el = new TiXmlElement("Name");
+ eRoot->LinkEndChild(name_el);
+ name_el->LinkEndChild(new TiXmlText(s.level_visible_name_.c_str()));
+
+ TiXmlElement* desc_el = new TiXmlElement("Description");
+ eRoot->LinkEndChild(desc_el);
+ desc_el->LinkEndChild(new TiXmlText(s.level_visible_description_.c_str()));
+
+ TiXmlElement* shader_el;
+ shader_el = new TiXmlElement("Shader");
+ shader_el->LinkEndChild( new TiXmlText(graphics->post_shader_name.c_str()) );
+ eRoot->LinkEndChild(shader_el);
+
+ TiXmlElement* shadow_el;
+ shadow_el = new TiXmlElement("Shadows");
+ shadow_el->LinkEndChild( new TiXmlText(g_level_shadows ? "true" : "false") );
+ eRoot->LinkEndChild(shadow_el);
+
+ TiXmlElement* loading_screen_el;
+ loading_screen_el = new TiXmlElement("LoadingScreen");
+ TiXmlElement* loading_screen_image_el = new TiXmlElement("Image");
+ loading_screen_image_el->LinkEndChild(new TiXmlText(s.level->loading_screen_.image.c_str()));
+ loading_screen_el->LinkEndChild( loading_screen_image_el );
+ eRoot->LinkEndChild(loading_screen_el);
+
+ LOGI << "Saving terrain and sky..." << std::endl;
+ SaveTerrain(eRoot, &s);
+
+ {
+ TiXmlElement* element = new TiXmlElement("OutOfDate");
+ eRoot->LinkEndChild(element);
+ element->SetAttribute("NavMesh",graphics->nav_mesh_out_of_date?"true":"false");
+ element->SetAttribute("NavMeshParamHash", (int)HashNavMeshParameters(s.level->nav_mesh_parameters_));
+ }
+
+ LOGI << "Saving script and spawn points..." << std::endl;
+ {
+ TiXmlElement* ambient_sound_element = new TiXmlElement("AmbientSounds");
+ eRoot->LinkEndChild(ambient_sound_element);
+
+ const std::vector<AmbientTriangle>& ambient_triangles =
+ Engine::Instance()->GetSound()->GetAmbientTriangles();
+ for(unsigned i=0; i<ambient_triangles.size(); ++i){
+ TiXmlElement* ambient;
+ ambient = new TiXmlElement("Ambient");
+ ambient_sound_element->LinkEndChild(ambient);
+ ambient->SetAttribute("path", ambient_triangles[i].path.c_str());
+ }
+ }
+
+ {
+ TiXmlElement* element = new TiXmlElement("Script");
+ element->LinkEndChild(new TiXmlText(s.level->GetLevelSpecificScript().c_str()));
+ eRoot->LinkEndChild(element);
+ }
+ {
+ if(s.level->GetPCScript(NULL) != Level::DEFAULT_PLAYER_SCRIPT) {
+ TiXmlElement* element = new TiXmlElement("PCScript");
+ element->LinkEndChild(new TiXmlText(s.level->GetPCScript(NULL).c_str()));
+ eRoot->LinkEndChild(element);
+ }
+ }
+ {
+ if(s.level->GetNPCScript(NULL) != Level::DEFAULT_ENEMY_SCRIPT) {
+ TiXmlElement* element = new TiXmlElement("NPCScript");
+ element->LinkEndChild(new TiXmlText(s.level->GetNPCScript(NULL).c_str()));
+ eRoot->LinkEndChild(element);
+ }
+ }
+ {
+ TiXmlElement* element = new TiXmlElement("LevelScriptParameters");
+ eRoot->LinkEndChild(element);
+ WriteScriptParamsToXML(s.level->script_params().GetParameterMap(), element);
+ }
+ {
+ TiXmlElement* element = new TiXmlElement("NavMeshParameters");
+ eRoot->LinkEndChild(element);
+ WriteNavMeshParametersToXML(s.level->nav_mesh_parameters_, element);
+ }
+
+ LOGI << "Saving entities..." << std::endl;
+ s.map_editor->SaveEntities(eRoot);
+
+ {
+ LOGI << "Saving recent spawner item list" << std::endl;
+ TiXmlElement* elements = new TiXmlElement("RecentItems");
+
+ std::vector<SpawnerItem> recent_items = s.level->GetRecentlyCreatedItems();
+
+ for( unsigned i = 0; i < recent_items.size(); i++ ) {
+ SpawnerItem* si = &recent_items[i];
+
+ TiXmlElement* element = new TiXmlElement("SpawnerItem");
+ element->SetAttribute("display_name", si->display_name.c_str());
+ element->SetAttribute("path", si->path.c_str());
+ element->SetAttribute("thumbnail_path", si->thumbnail_path.c_str());
+
+ elements->LinkEndChild(element);
+ }
+ eRoot->LinkEndChild(elements);
+ }
+
+ LOGI << "Full Level Save Path: " << save_path << std::endl;
+ doc.SaveFile(save_path);
+ LOGI << "Short Level Save Path: " << s.level_path_ << std::endl;
+}
+
+EntityType CheckGenericType(TiXmlDocument& doc) {
+ TiXmlHandle hDoc(&doc);
+ TiXmlNode* pNode;
+
+ pNode = hDoc.FirstChild("Object").ToNode();
+ if (pNode != NULL) return _env_object;
+
+ pNode = hDoc.FirstChild("Actor").ToNode();
+ if (pNode != NULL) return _movement_object;
+
+ pNode = hDoc.FirstChild("Sound").ToNode();
+ if (pNode != NULL) return _ambient_sound_object;
+
+ pNode = hDoc.FirstChild("item").ToNode();
+ if (pNode != NULL) return _item_object;
+
+ pNode = hDoc.FirstChild("DecalObject").ToNode();
+ if (pNode != NULL) return _decal_object;
+
+ pNode = hDoc.FirstChild("Hotspot").ToNode();
+ if (pNode != NULL) return _hotspot_object;
+
+ return _no_type;
+}