#if !defined(NO_CUSTOM_LEVELS) #define CL_DEFINITION #include "CustomLevels.h" #include #include #include #include #include "Alloc.h" #include "Constants.h" #include "Editor.h" #include "Enums.h" #include "FileSystemUtils.h" #include "Font.h" #include "Game.h" #include "Graphics.h" #include "GraphicsUtil.h" #include "KeyPoll.h" #include "Localization.h" #include "LocalizationStorage.h" #include "Map.h" #include "Screen.h" #include "Script.h" #include "UTF8.h" #include "UtilityClass.h" #include "Vlogging.h" #include "XMLUtils.h" #ifdef _WIN32 #define SCNx32 "x" #define SCNu32 "u" #else #ifndef __STDC_FORMAT_MACROS #define __STDC_FORMAT_MACROS #endif #ifndef _POSIX_SOURCE #define _POSIX_SOURCE #endif #include #endif #define VMULT(y) (y * SCREEN_WIDTH_TILES * maxwidth) #define Y_INBOUNDS(y) (y >= 0 && y < SCREEN_HEIGHT_TILES * maxheight) RoomProperty::RoomProperty(void) { tileset=0; tilecol=0; warpdir=0; platx1=0; platy1=0; platx2=320; platy2=240; platv=4; enemyx1=0; enemyy1=0; enemyx2=320; enemyy2=240; enemytype=0; directmode=0; } customlevelclass::customlevelclass(void) { reset(); } // comparison, not case sensitive. static bool compare_nocase (std::string first, std::string second) { unsigned int i=0; while ( (iSDL_tolower(second[i])) return false; ++i; } if (first.length()"); //Encode general XML entities size_t start_pos = 0; while ((start_pos = value.find("&#", start_pos)) != std::string::npos) { if (start_pos + 2 >= value.length()) { return ""; } bool hex = value[start_pos + 2] == 'x'; size_t end = value.find(';', start_pos); if (end == std::string::npos) { return ""; } size_t real_start = start_pos + 2 + ((int) hex); std::string number(value.substr(real_start, end - real_start)); if (!is_positive_num(number.c_str(), hex)) { return ""; } uint32_t character = 0; if (hex) { SDL_sscanf(number.c_str(), "%" SCNx32, &character); } else { SDL_sscanf(number.c_str(), "%" SCNu32, &character); } value.replace(start_pos, end - start_pos + 1, UTF8_encode(character).bytes); } return value; } #define TAG_FINDER(NAME, TAG) \ static std::string NAME(const std::string& buf) \ { \ return find_tag(buf, "<" TAG ">", ""); \ } TAG_FINDER(find_metadata, "MetaData") //only for checking that it exists TAG_FINDER(find_creator, "Creator") TAG_FINDER(find_title, "Title") TAG_FINDER(find_desc1, "Desc1") TAG_FINDER(find_desc2, "Desc2") TAG_FINDER(find_desc3, "Desc3") TAG_FINDER(find_website, "website") TAG_FINDER(find_font, "font") /* For CliPlaytestArgs */ TAG_FINDER(find_playtest, "Playtest") TAG_FINDER(find_playx, "playx") TAG_FINDER(find_playy, "playy") TAG_FINDER(find_playrx, "playrx") TAG_FINDER(find_playry, "playry") TAG_FINDER(find_playgc, "playgc") TAG_FINDER(find_playmusic, "playmusic") #undef TAG_FINDER static void levelMetaDataCallback(const char* filename) { extern customlevelclass cl; LevelMetaData temp; std::string filename_ = filename; if (!endsWith(filename, ".vvvvvv") || !FILESYSTEM_isFile(filename) || FILESYSTEM_isMounted(filename)) { return; } if (cl.getLevelMetaData(filename_, temp)) { cl.ListOfMetaData.push_back(temp); } } void customlevelclass::getDirectoryData(void) { ListOfMetaData.clear(); FILESYSTEM_clearLevelDirError(); loadZips(); FILESYSTEM_enumerateLevelDirFileNames(levelMetaDataCallback); for(size_t i = 0; i < ListOfMetaData.size(); i++) { for(size_t k = 0; k < ListOfMetaData.size(); k++) { if(compare_nocase(ListOfMetaData[i].title, ListOfMetaData[k].title )) { std::swap(ListOfMetaData[i] , ListOfMetaData[k]); } } } } bool customlevelclass::getLevelMetaDataAndPlaytestArgs(const std::string& _path, LevelMetaData& _data, CliPlaytestArgs* pt_args) { unsigned char *uMem; FILESYSTEM_loadFileToMemory(_path.c_str(), &uMem, NULL); if (uMem == NULL) { vlog_warn("Level %s not found :(", _path.c_str()); return false; } std::string buf((char*) uMem); VVV_free(uMem); if (find_metadata(buf) == "") { vlog_warn("Couldn't load metadata for %s", _path.c_str()); return false; } _data.creator = translate_creator(find_creator(buf), &_data.creator_is_gettext); _data.title = translate_title(find_title(buf), &_data.title_is_gettext); _data.Desc1 = find_desc1(buf); _data.Desc2 = find_desc2(buf); _data.Desc3 = find_desc3(buf); _data.website = find_website(buf); if (!font::find_main_font_by_name(find_font(buf).c_str(), &_data.level_main_font_idx)) { _data.level_main_font_idx = font::get_font_idx_8x8(); } if (pt_args != NULL) { const std::string playtest = find_playtest(buf); if (playtest == "") { pt_args->valid = false; } else { pt_args->valid = true; pt_args->x = help.Int(find_playx(playtest).c_str()); pt_args->y = help.Int(find_playy(playtest).c_str()); pt_args->rx = help.Int(find_playrx(playtest).c_str()); pt_args->ry = help.Int(find_playry(playtest).c_str()); pt_args->gc = help.Int(find_playgc(playtest).c_str()); pt_args->music = help.Int(find_playmusic(playtest).c_str()); } } _data.filename = _path; return true; } bool customlevelclass::getLevelMetaData(const std::string& _path, LevelMetaData& _data) { return getLevelMetaDataAndPlaytestArgs(_path, _data, NULL); } void customlevelclass::reset(void) { version=2; //New smaller format change is 2 mapwidth=5; mapheight=5; title="Untitled Level"; // Already translatable creator="Unknown"; levmusic=0; customentities.clear(); levmusic=0; for (int j = 0; j < maxheight; j++) { for (int i = 0; i < maxwidth; i++) { roomproperties[i+(j*maxwidth)].tileset=0; roomproperties[i+(j*maxwidth)].tilecol=(i+j)%32; roomproperties[i+(j*maxwidth)].roomname=""; roomproperties[i+(j*maxwidth)].warpdir=0; roomproperties[i+(j*maxwidth)].platx1=0; roomproperties[i+(j*maxwidth)].platy1=0; roomproperties[i+(j*maxwidth)].platx2=320; roomproperties[i+(j*maxwidth)].platy2=240; roomproperties[i+(j*maxwidth)].platv=4; roomproperties[i+(j*maxwidth)].enemyx1=0; roomproperties[i+(j*maxwidth)].enemyy1=0; roomproperties[i+(j*maxwidth)].enemyx2=320; roomproperties[i+(j*maxwidth)].enemyy2=240; roomproperties[i+(j*maxwidth)].enemytype=0; roomproperties[i+(j*maxwidth)].directmode=0; } } SDL_zeroa(contents); script.clearcustom(); onewaycol_override = false; script.textbox_colours.clear(); script.add_default_colours(); map.specialroomnames.clear(); } const int* customlevelclass::loadlevel( int rxi, int ryi ) { //Set up our buffer array to be picked up by mapclass rxi -= 100; ryi -= 100; if(rxi<0)rxi+=mapwidth; if(ryi<0)ryi+=mapheight; if(rxi>=mapwidth)rxi-=mapwidth; if(ryi>=mapheight)ryi-=mapheight; static int result[1200]; for (int j = 0; j < 30; j++) { for (int i = 0; i < 40; i++) { result[i + j*40] = gettile(rxi, ryi, i, j); } } return result; } int customlevelclass::getlevelcol(const int tileset, const int tilecol) { if(tileset==0) //Space Station { return tilecol; } else if(tileset==1) //Outside { return 32+tilecol; } else if(tileset==2) //Lab { return 40+tilecol; } else if(tileset==3) //Warp Zone { return 46+tilecol; } else if(tileset==4) //Ship { return 52+tilecol; } return 0; } int customlevelclass::getenemycol(int t) { switch(t) { //RED case 3: case 7: case 12: case 23: case 28: case 34: case 42: case 48: case 58: return 6; break; //GREEN case 5: case 9: case 22: case 25: case 29: case 31: case 38: case 46: case 52: case 53: return 7; break; //BLUE case 1: case 6: case 14: case 27: case 33: case 44: case 50: case 57: return 12; break; //YELLOW case 4: case 17: case 24: case 30: case 37: case 45: case 51: case 55: return 9; break; //PURPLE case 2: case 11: case 15: case 19: case 32: case 36: case 49: return 20; break; //CYAN case 8: case 10: case 13: case 18: case 26: case 35: case 41: case 47: case 54: return 11; break; //PINK case 16: case 20: case 39: case 43: case 56: return 8; break; //ORANGE case 21: case 40: return 17; break; default: return 6; break; } } int customlevelclass::getwarpbackground(int rx, int ry) { const RoomProperty* const room = getroomprop(rx, ry); switch(room->tileset) { case 0: //Space Station switch(room->tilecol) { case 0: return 3; break; case 1: return 2; break; case 2: return 1; break; case 3: return 4; break; case 4: return 5; break; case 5: return 3; break; case 6: return 1; break; case 7: return 0; break; case 8: return 5; break; case 9: return 0; break; case 10: return 2; break; case 11: return 1; break; case 12: return 5; break; case 13: return 0; break; case 14: return 3; break; case 15: return 2; break; case 16: return 4; break; case 17: return 0; break; case 18: return 3; break; case 19: return 1; break; case 20: return 4; break; case 21: return 5; break; case 22: return 1; break; case 23: return 4; break; case 24: return 5; break; case 25: return 0; break; case 26: return 3; break; case 27: return 1; break; case 28: return 5; break; case 29: return 4; break; case 30: return 5; break; case 31: return 2; break; default: return 6; break; } break; case 1: //Outside switch(room->tilecol) { case 0: return 3; break; case 1: return 1; break; case 2: return 0; break; case 3: return 2; break; case 4: return 4; break; case 5: return 5; break; case 6: return 2; break; case 7: return 4; break; default: return 6; break; } break; case 2: //Lab switch(room->tilecol) { case 0: return 0; break; case 1: return 1; break; case 2: return 2; break; case 3: return 3; break; case 4: return 4; break; case 5: return 5; break; case 6: return 6; break; default: return 6; break; } break; case 3: //Warp Zone switch(room->tilecol) { case 0: return 0; break; case 1: return 1; break; case 2: return 2; break; case 3: return 3; break; case 4: return 4; break; case 5: return 5; break; case 6: return 6; break; default: return 6; break; } break; case 4: //Ship switch(room->tilecol) { case 0: return 5; break; case 1: return 0; break; case 2: return 4; break; case 3: return 2; break; case 4: return 3; break; case 5: return 1; break; case 6: return 6; break; default: return 6; break; } break; case 5: //Tower return 6; break; default: return 6; break; } } int customlevelclass::gettileidx( const int rx, const int ry, const int x, const int y ) { const int yoff = y + ry*30; int mult; int idx; if (Y_INBOUNDS(yoff)) { mult = VMULT(yoff); } else { mult = 0; } idx = x + rx*40 + mult; return idx; } void customlevelclass::settile( const int rx, const int ry, const int x, const int y, const int t ) { const int idx = gettileidx(rx, ry, x, y); if (!INBOUNDS_ARR(idx, contents)) { return; } contents[idx] = t; } int customlevelclass::gettile( const int rx, const int ry, const int x, const int y ) { const int idx = gettileidx(rx, ry, x, y); if (!INBOUNDS_ARR(idx, contents)) { return 0; } return contents[idx]; } int customlevelclass::getabstile(const int x, const int y) { int idx; int yoff; if (Y_INBOUNDS(y)) { yoff = VMULT(y); } else { yoff = 0; } idx = x + yoff; if (!INBOUNDS_ARR(idx, contents)) { return 0; } return contents[idx]; } int customlevelclass::getroompropidx(const int rx, const int ry) { return rx + ry*maxwidth; } const RoomProperty* customlevelclass::getroomprop(const int rx, const int ry) { const int idx = getroompropidx(rx, ry); if (INBOUNDS_ARR(idx, roomproperties)) { return &roomproperties[idx]; } static RoomProperty blank; blank.tileset = 1; blank.directmode = 1; blank.roomname.clear(); return ␣ } #define FOREACH_PROP(NAME, TYPE) \ void customlevelclass::setroom##NAME(const int rx, const int ry, const TYPE NAME) \ { \ const int idx = getroompropidx(rx, ry); \ \ if (!INBOUNDS_ARR(idx, roomproperties)) \ { \ return; \ } \ \ roomproperties[idx].NAME = NAME; \ } ROOM_PROPERTIES #undef FOREACH_PROP int customlevelclass::absfree( int x, int y ) { //Returns 0 if tile is not a block, 1 otherwise, abs on grid if(x>=0 && y>=0 && x=2 && getabstile(x, y)<80) { return 0; } if(getabstile(x, y)>=680) { return 0; } } } return 1; } void customlevelclass::findstartpoint(void) { //Ok! Scan the room for the closest checkpoint int testeditor=-1; //First up; is there a start point on this screen? for(size_t i=0; iNextSiblingElement()) { const char* pKey = pElem->Value(); const char* pText = pElem->GetText(); if(pText == NULL) { pText = ""; } if (SDL_strcmp(pKey, "MetaData") == 0) { for( tinyxml2::XMLElement* subElem = pElem->FirstChildElement(); subElem; subElem= subElem->NextSiblingElement()) { const char* pKey_ = subElem->Value(); const char* pText_ = subElem->GetText() ; if(pText_ == NULL) { pText_ = ""; } if(SDL_strcmp(pKey_, "Creator") == 0) { creator = pText_; } if(SDL_strcmp(pKey_, "Title") == 0) { title = pText_; } if(SDL_strcmp(pKey_, "Desc1") == 0) { Desc1 = pText_; } if(SDL_strcmp(pKey_, "Desc2") == 0) { Desc2 = pText_; } if(SDL_strcmp(pKey_, "Desc3") == 0) { Desc3 = pText_; } if(SDL_strcmp(pKey_, "website") == 0) { website = pText_; } if(SDL_strcmp(pKey_, "onewaycol_override") == 0) { onewaycol_override = help.Int(pText_); } if(SDL_strcmp(pKey_, "font") == 0) { level_font_name = pText_; } } } if (SDL_strcmp(pKey, "mapwidth") == 0) { mapwidth = help.Int(pText); } if (SDL_strcmp(pKey, "mapheight") == 0) { mapheight = help.Int(pText); } if (SDL_strcmp(pKey, "levmusic") == 0) { levmusic = help.Int(pText); } if (SDL_strcmp(pKey, "contents") == 0 && pText[0] != '\0') { int x = 0; int y = 0; char buffer[16]; size_t start = 0; while (next_split_s(buffer, sizeof(buffer), &start, pText, ',')) { const int idx = x + maxwidth*40*y; if (INBOUNDS_ARR(idx, contents)) { contents[idx] = help.Int(buffer); } ++x; if (x == mapwidth*40) { x = 0; ++y; } } } if (SDL_strcmp(pKey, "edEntities") == 0) { for( tinyxml2::XMLElement* edEntityEl = pElem->FirstChildElement(); edEntityEl; edEntityEl=edEntityEl->NextSiblingElement()) { CustomEntity entity = CustomEntity(); const char* text = edEntityEl->GetText(); int global_x = 0; int global_y = 0; if (text != NULL) { size_t len = SDL_strlen(text); // And now we come to the part where we have to deal with // the terrible decisions of the past. // // For some reason, the closing tag of edentities generated // by 2.2 and below has not only been put on a separate // line, but also indented to match with the opening tag as // well. Like this: // // contents // // // Instead of doing contents. // // This is COMPLETELY terrible. This requires the XML to be // parsed in an extremely specific and quirky way, which // TinyXML-1 just happened to do. // // TinyXML-2 by default interprets the newline and the next // indentation of whitespace literally, so you end up with // tag contents that has a linefeed plus a bunch of extra // spaces. You can't fix this by setting the whitespace // mode to COLLAPSE_WHITESPACE, that does way more than // TinyXML-1 ever did - it removes the leading whitespace // from things like this, and // collapses XML-encoded whitespace like // this, which TinyXML-1 never did. // // Best solution here is to specifically hardcode removing // the linefeed + the extremely specific amount of // whitespace at the end of the contents. if (endsWith(text, "\n ")) // linefeed + exactly 12 spaces { // 12 spaces + 1 linefeed = 13 chars len -= 13; } entity.scriptname = std::string(text, len); } edEntityEl->QueryIntAttribute("x", &global_x); edEntityEl->QueryIntAttribute("y", &global_y); entity.rx = global_x / SCREEN_WIDTH_TILES; entity.x = global_x % SCREEN_WIDTH_TILES; entity.ry = global_y / SCREEN_HEIGHT_TILES; entity.y = global_y % SCREEN_HEIGHT_TILES; edEntityEl->QueryIntAttribute("t", &entity.t); edEntityEl->QueryIntAttribute("p1", &entity.p1); edEntityEl->QueryIntAttribute("p2", &entity.p2); edEntityEl->QueryIntAttribute("p3", &entity.p3); edEntityEl->QueryIntAttribute("p4", &entity.p4); edEntityEl->QueryIntAttribute("p5", &entity.p5); edEntityEl->QueryIntAttribute("p6", &entity.p6); customentities.push_back(entity); } } if (SDL_strcmp(pKey, "levelMetaData") == 0) { int i = 0; for( tinyxml2::XMLElement* edLevelClassElement = pElem->FirstChildElement(); edLevelClassElement; edLevelClassElement=edLevelClassElement->NextSiblingElement()) { if (!INBOUNDS_ARR(i, roomproperties)) { continue; } if(edLevelClassElement->GetText() != NULL) { roomproperties[i].roomname = std::string(edLevelClassElement->GetText()) ; } edLevelClassElement->QueryIntAttribute("tileset", &roomproperties[i].tileset); edLevelClassElement->QueryIntAttribute("tilecol", &roomproperties[i].tilecol); edLevelClassElement->QueryIntAttribute("platx1", &roomproperties[i].platx1); edLevelClassElement->QueryIntAttribute("platy1", &roomproperties[i].platy1); edLevelClassElement->QueryIntAttribute("platx2", &roomproperties[i].platx2); edLevelClassElement->QueryIntAttribute("platy2", &roomproperties[i].platy2); edLevelClassElement->QueryIntAttribute("platv", &roomproperties[i].platv); edLevelClassElement->QueryIntAttribute("enemyx1", &roomproperties[i].enemyx1); edLevelClassElement->QueryIntAttribute("enemyy1", &roomproperties[i].enemyy1); edLevelClassElement->QueryIntAttribute("enemyx2", &roomproperties[i].enemyx2); edLevelClassElement->QueryIntAttribute("enemyy2", &roomproperties[i].enemyy2); edLevelClassElement->QueryIntAttribute("enemytype", &roomproperties[i].enemytype); edLevelClassElement->QueryIntAttribute("directmode", &roomproperties[i].directmode); edLevelClassElement->QueryIntAttribute("warpdir", &roomproperties[i].warpdir); i++; } } if (SDL_strcmp(pKey, "script") == 0 && pText[0] != '\0') { Script script_; bool headerfound = false; size_t start = 0; size_t len = 0; size_t prev_start = 0; while (next_split(&start, &len, &pText[start], '|')) { if (len > 0 && pText[prev_start + len - 1] == ':') { if (headerfound) { script.customscripts.push_back(script_); } script_.name = std::string(&pText[prev_start], len - 1); script_.contents.clear(); headerfound = true; goto next; } if (headerfound) { script_.contents.push_back(std::string(&pText[prev_start], len)); } next: prev_start = start; } /* Add the last script */ if (headerfound) { script.customscripts.push_back(script_); } } if (SDL_strcmp(pKey, "TextboxColours") == 0) { for (tinyxml2::XMLElement* textColourElement = pElem->FirstChildElement(); textColourElement; textColourElement = textColourElement->NextSiblingElement()) { if (SDL_strcmp(textColourElement->Value(), "colour") == 0) { int r = 255; int g = 255; int b = 255; textColourElement->QueryIntAttribute("r", &r); textColourElement->QueryIntAttribute("g", &g); textColourElement->QueryIntAttribute("b", &b); const char* name = textColourElement->Attribute("name"); if (name != NULL) { SDL_Colour colour; colour.r = r; colour.g = g; colour.b = b; script.textbox_colours[name] = colour; } } } } if (SDL_strcmp(pKey, "SpecialRoomnames") == 0) { for (tinyxml2::XMLElement* roomnameElement = pElem->FirstChildElement(); roomnameElement; roomnameElement = roomnameElement->NextSiblingElement()) { const char* roomnameType = roomnameElement->Value(); Roomname name; name.x = 0; name.y = 0; name.flag = -1; name.loop = false; name.type = RoomnameType_STATIC; name.progress = 0; name.delay = 0; if (SDL_strcmp(roomnameType, "transform") == 0) { name.type = RoomnameType_TRANSFORM; name.delay = 2; } else if (SDL_strcmp(roomnameType, "glitch") == 0) { name.type = RoomnameType_GLITCH; name.progress = 1; name.delay = -1; } name.text.clear(); roomnameElement->QueryIntAttribute("x", &name.x); roomnameElement->QueryIntAttribute("y", &name.y); roomnameElement->QueryIntAttribute("flag", &name.flag); roomnameElement->QueryBoolAttribute("loop", &name.loop); // Rooms start at (100, 100) instead of (0, 0), so offset the coordinates name.x += 100; name.y += 100; if (name.type == RoomnameType_STATIC) { const char* text = roomnameElement->GetText(); if (text != NULL) { name.text.push_back(std::string(text)); } } else { // Does it have children? if (roomnameElement->FirstChildElement() == NULL) { continue; } for (tinyxml2::XMLElement* textElement = roomnameElement->FirstChildElement(); textElement; textElement = textElement->NextSiblingElement()) { if (SDL_strcmp(textElement->Value(), "text") == 0) { const char* text = textElement->GetText(); if (text != NULL) { name.text.push_back(std::string(text)); } } } } map.specialroomnames.push_back(name); } } } if (mapwidth < maxwidth) { /* Unscramble platv, since it was stored incorrectly * in 2.2 and previous... */ size_t i; int x = 0; int y = 0; int temp_platv[numrooms]; for (i = 0; i < numrooms; ++i) { temp_platv[i] = roomproperties[i].platv; } for (i = 0; i < numrooms; ++i) { if (x < mapwidth) { const int platv_idx = x + y * mapwidth; if (INBOUNDS_ARR(platv_idx, temp_platv)) { roomproperties[i].platv = temp_platv[platv_idx]; } } else { roomproperties[i].platv = 4; /* default */ } ++x; if (x >= maxwidth) { x = 0; ++y; } } } loc::loadtext_custom(_path.c_str()); font::load_custom(level_font_name.c_str()); version=2; return true; fail: return false; } #ifndef NO_EDITOR bool customlevelclass::save(const std::string& _path) { tinyxml2::XMLDocument doc; std::string newpath("levels/" + _path); // Try to preserve the XML of the currently-loaded one bool already_exists = !ed.loaded_filepath.empty() && FILESYSTEM_loadTiXml2Document(ed.loaded_filepath.c_str(), doc); if (!already_exists && !ed.loaded_filepath.empty()) { vlog_error("Currently-loaded %s not found", ed.loaded_filepath.c_str()); } ed.loaded_filepath = newpath; tinyxml2::XMLElement* msg; xml::update_declaration(doc); tinyxml2::XMLElement * root = xml::update_element(doc, "MapData"); root->SetAttribute("version",version); xml::update_comment(root, " Save file "); tinyxml2::XMLElement * data = xml::update_element(root, "Data"); msg = xml::update_element(data, "MetaData"); //getUser xml::update_tag(msg, "Creator", creator.c_str()); xml::update_tag(msg, "Title", title.c_str()); xml::update_tag(msg, "Created", version); xml::update_tag(msg, "Modified", modifier.c_str()); xml::update_tag(msg, "Modifiers", version); xml::update_tag(msg, "Desc1", Desc1.c_str()); xml::update_tag(msg, "Desc2", Desc2.c_str()); xml::update_tag(msg, "Desc3", Desc3.c_str()); xml::update_tag(msg, "website", website.c_str()); if (onewaycol_override) { xml::update_tag(msg, "onewaycol_override", onewaycol_override); } else { // Delete the element. I could just delete one, but just to be sure, // I will delete all of them if there are more than one tinyxml2::XMLElement* element; while ((element = msg->FirstChildElement("onewaycol_override")) != NULL) { doc.DeleteNode(element); } } if (level_font_name != "" && level_font_name != "font") { xml::update_tag(msg, "font", level_font_name.c_str()); } else { // Get rid of it completely, same as tinyxml2::XMLElement* element; while ((element = msg->FirstChildElement("font")) != NULL) { doc.DeleteNode(element); } } xml::update_tag(data, "mapwidth", mapwidth); xml::update_tag(data, "mapheight", mapheight); xml::update_tag(data, "levmusic", levmusic); //New save format std::string contentsString=""; for(int y = 0; y < mapheight*30; y++ ) { for(int x = 0; x < mapwidth*40; x++ ) { contentsString += help.String(getabstile(x, y)) + ","; } } xml::update_tag(data, "contents", contentsString.c_str()); msg = xml::update_element_delete_contents(data, "edEntities"); for(size_t i = 0; i < customentities.size(); i++) { tinyxml2::XMLElement *edentityElement = doc.NewElement( "edentity" ); const int global_x = customentities[i].rx * SCREEN_WIDTH_TILES + customentities[i].x; const int global_y = customentities[i].ry * SCREEN_HEIGHT_TILES + customentities[i].y; edentityElement->SetAttribute("x", global_x); edentityElement->SetAttribute("y", global_y); edentityElement->SetAttribute( "t", customentities[i].t); edentityElement->SetAttribute( "p1", customentities[i].p1); edentityElement->SetAttribute( "p2", customentities[i].p2); edentityElement->SetAttribute( "p3", customentities[i].p3); edentityElement->SetAttribute( "p4", customentities[i].p4); edentityElement->SetAttribute( "p5", customentities[i].p5); edentityElement->SetAttribute( "p6", customentities[i].p6); edentityElement->LinkEndChild( doc.NewText( customentities[i].scriptname.c_str() )) ; msg->LinkEndChild( edentityElement ); } msg = xml::update_element_delete_contents(data, "levelMetaData"); int temp_platv[numrooms]; for (size_t i = 0; i < SDL_arraysize(temp_platv); ++i) { temp_platv[i] = 4; /* default */ } if (mapwidth < maxwidth) { /* Re-scramble platv, since it was stored incorrectly * in 2.2 and previous... */ size_t i; int x = 0; int y = 0; for (i = 0; i < numrooms; ++i) { if (x < mapwidth) { const int platv_idx = x + y * mapwidth; if (INBOUNDS_ARR(platv_idx, temp_platv)) { temp_platv[platv_idx] = roomproperties[i].platv; } } ++x; if (x >= mapwidth) { /* Skip to next actual row. */ i += maxwidth - mapwidth; x = 0; ++y; } } } for(size_t i = 0; i < SDL_arraysize(roomproperties); i++) { tinyxml2::XMLElement *roompropertyElement = doc.NewElement( "edLevelClass" ); roompropertyElement->SetAttribute( "tileset", roomproperties[i].tileset); roompropertyElement->SetAttribute( "tilecol", roomproperties[i].tilecol); roompropertyElement->SetAttribute( "platx1", roomproperties[i].platx1); roompropertyElement->SetAttribute( "platy1", roomproperties[i].platy1); roompropertyElement->SetAttribute( "platx2", roomproperties[i].platx2); roompropertyElement->SetAttribute( "platy2", roomproperties[i].platy2); roompropertyElement->SetAttribute( "platv", temp_platv[i]); roompropertyElement->SetAttribute( "enemyx1", roomproperties[i].enemyx1); roompropertyElement->SetAttribute( "enemyy1", roomproperties[i].enemyy1); roompropertyElement->SetAttribute( "enemyx2", roomproperties[i].enemyx2); roompropertyElement->SetAttribute( "enemyy2", roomproperties[i].enemyy2); roompropertyElement->SetAttribute( "enemytype", roomproperties[i].enemytype); roompropertyElement->SetAttribute( "directmode", roomproperties[i].directmode); roompropertyElement->SetAttribute( "warpdir", roomproperties[i].warpdir); roompropertyElement->LinkEndChild( doc.NewText( roomproperties[i].roomname.c_str() )) ; msg->LinkEndChild( roompropertyElement ); } std::string scriptString; for(size_t i = 0; i < script.customscripts.size(); i++) { Script& script_ = script.customscripts[i]; scriptString += script_.name + ":|"; for (size_t ii = 0; ii < script_.contents.size(); ++ii) { scriptString += script_.contents[ii]; // Inserts a space if the line ends with a : if (script_.contents[ii].length() && *script_.contents[ii].rbegin() == ':') { scriptString += " "; } scriptString += "|"; } } xml::update_tag(data, "script", scriptString.c_str()); return FILESYSTEM_saveTiXml2Document(newpath.c_str(), doc); } #endif /* NO_EDITOR */ void customlevelclass::generatecustomminimap(void) { map.customzoom = 1; if (mapwidth <= 10 && mapheight <= 10) { map.customzoom = 2; } if (mapwidth <= 5 && mapheight <= 5) { map.customzoom = 4; } // Set minimap offsets switch (map.customzoom) { case 4: map.custommmxoff = 24 * (5 - mapwidth); map.custommmyoff = 18 * (5 - mapheight); break; case 2: map.custommmxoff = 12 * (10 - mapwidth); map.custommmyoff = 9 * (10 - mapheight); break; default: map.custommmxoff = 6 * (20 - mapwidth); map.custommmyoff = int(4.5 * (20 - mapheight)); break; } map.custommmxsize = 240 - (map.custommmxoff * 2); map.custommmysize = 180 - (map.custommmyoff * 2); // Start drawing the minimap SDL_Texture* target = SDL_GetRenderTarget(gameScreen.m_renderer); graphics.set_render_target(graphics.images[IMAGE_CUSTOMMINIMAP]); graphics.clear(); // Scan over the map size for (int j2 = 0; j2 < mapheight; j2++) { for (int i2 = 0; i2 < mapwidth; i2++) { int tm; if (getroomprop(i2, j2)->tileset == 1) { tm = 96; } else { tm = 196; } // Ok, now scan over each square for (int j = 0; j < 9 * map.customzoom; j++) { for (int i = 0; i < 12 * map.customzoom; i++) { int tile; switch (map.customzoom) { case 4: tile = absfree( int(i * 0.83) + (i2 * 40), int(j * 0.83) + (j2 * 30) ); break; case 2: tile = absfree( int(i * 1.6) + (i2 * 40), int(j * 1.6) + (j2 * 30) ); break; default: tile = absfree( 3 + (i * 3) + (i2 * 40), (j * 3) + (j2 * 30) ); break; } if (tile >= 1) { // Fill in this pixel graphics.fill_rect( (i2 * 12 * map.customzoom) + i, (j2 * 9 * map.customzoom) + j, 1, 1, graphics.getRGB(tm, tm, tm) ); } } } } } graphics.set_render_target(target); } // Return a graphics-ready color based off of the given tileset and tilecol // Much kudos to Dav999 for saving me a lot of work, because I stole these colors from const.lua in Ved! -Info Teddy SDL_Color customlevelclass::getonewaycol(const int rx, const int ry) { const RoomProperty* const room = getroomprop(rx, ry); switch (room->tileset) { case 0: // Space Station switch (room->tilecol) { case -1: return graphics.getRGB(109, 109, 109); case 0: return graphics.getRGB(131, 141, 235); case 1: return graphics.getRGB(227, 140, 227); case 2: return graphics.getRGB(242, 126, 151); case 3: return graphics.getRGB(229, 235, 133); case 4: return graphics.getRGB(148, 238, 130); case 5: return graphics.getRGB(140, 165, 227); case 6: return graphics.getRGB(227, 140, 148); case 7: return graphics.getRGB(140, 173, 228); case 8: return graphics.getRGB(142, 235, 137); case 9: return graphics.getRGB(137, 235, 206); case 10: return graphics.getRGB(235, 139, 223); case 11: return graphics.getRGB(238, 130, 138); case 12: return graphics.getRGB(137, 235, 178); case 13: return graphics.getRGB(125, 205, 247); case 14: return graphics.getRGB(190, 137, 235); case 15: return graphics.getRGB(235, 137, 206); case 16: return graphics.getRGB(229, 247, 127); case 17: return graphics.getRGB(127, 200, 247); case 18: return graphics.getRGB(197, 137, 235); case 19: return graphics.getRGB(235, 131, 175); case 20: return graphics.getRGB(242, 210, 123); case 21: return graphics.getRGB(131, 235, 158); case 22: return graphics.getRGB(242, 126, 151); case 23: return graphics.getRGB(219, 243, 123); case 24: return graphics.getRGB(131, 234, 145); case 25: return graphics.getRGB(131, 199, 234); case 26: return graphics.getRGB(141, 131, 234); case 27: return graphics.getRGB(226, 140, 144); case 28: return graphics.getRGB(129, 236, 144); case 29: return graphics.getRGB(235, 231, 131); case 30: return graphics.getRGB(153, 235, 131); case 31: return graphics.getRGB(207, 131, 235); } break; case 1: // Outside switch (room->tilecol) { case 0: return graphics.getRGB(57, 86, 140); case 1: return graphics.getRGB(156, 42, 42); case 2: return graphics.getRGB(42, 156, 155); case 3: return graphics.getRGB(125, 36, 162); case 4: return graphics.getRGB(191, 198, 0); case 5: return graphics.getRGB(0, 198, 126); case 6: return graphics.getRGB(224, 110, 177); case 7: return graphics.getRGB(255, 142, 87); } break; case 2: // Lab switch (room->tilecol) { case 0: return graphics.getRGB(0, 165, 206); case 1: return graphics.getRGB(206, 5, 0); case 2: return graphics.getRGB(222, 0, 173); case 3: return graphics.getRGB(27, 67, 255); case 4: return graphics.getRGB(194, 206, 0); case 5: return graphics.getRGB(0, 206, 39); case 6: return graphics.getRGB(0, 165, 206); } break; case 3: // Warp Zone switch (room->tilecol) { case 0: return graphics.getRGB(113, 178, 197); case 1: return graphics.getRGB(197, 113, 119); case 2: return graphics.getRGB(196, 113, 197); case 3: return graphics.getRGB(149, 113, 197); case 4: return graphics.getRGB(197, 182, 113); case 5: return graphics.getRGB(141, 197, 113); case 6: return graphics.getRGB(109, 109, 109); } break; case 4: // Ship switch (room->tilecol) { case 0: return graphics.getRGB(0, 206, 39); case 1: return graphics.getRGB(0, 165, 206); case 2: return graphics.getRGB(194, 206, 0); case 3: return graphics.getRGB(206, 0, 160); case 4: return graphics.getRGB(27, 67, 255); case 5: return graphics.getRGB(206, 5, 0); } break; } // Uh, I guess return solid white return graphics.getRGB(255, 255, 255); } // This version detects the room automatically SDL_Color customlevelclass::getonewaycol(void) { #ifndef NO_EDITOR if (game.gamestate == EDITORMODE) { return getonewaycol(ed.levx, ed.levy); } else #endif if (map.custommode) { return getonewaycol(game.roomx - 100, game.roomy - 100); } // Uh, I guess return solid white return graphics.getRGB(255, 255, 255); } static SDL_INLINE bool inbounds(const CustomEntity* entity) { extern customlevelclass cl; return entity->x >= 0 && entity->y >= 0 && entity->x < cl.mapwidth * SCREEN_WIDTH_TILES && entity->y < cl.mapheight * SCREEN_HEIGHT_TILES; } int customlevelclass::numtrinkets(void) { int temp = 0; for (size_t i = 0; i < customentities.size(); i++) { if (customentities[i].t == 9 && inbounds(&customentities[i])) { temp++; } } return temp; } int customlevelclass::numcrewmates(void) { int temp = 0; for (size_t i = 0; i < customentities.size(); i++) { if (customentities[i].t == 15 && inbounds(&customentities[i])) { temp++; } } return temp; } #endif /* NO_CUSTOM_LEVELS */